Loading...
Loading...
Use when analyzing FileMaker DDR to extract calculations, custom functions, and business logic for PostgreSQL import processes or maintenance scripts - focuses on understanding and adapting FileMaker logic rather than direct schema migration
npx skill4agent add ajbcoding/claude-skill-eval extracting-filemaker-business-logic{ProjectName}_ddr/{DatabaseName}.html/Users/anthonybyrnes/PycharmProjects/Python419/AugustServer_ddr/AugustServer.htmlField Name: wtu_calculation
Type: Calculation
Result Type: Number
Formula:
contacthours * units * csfactorfrom lxml import etree
tree = etree.parse("AugustServer_ddr/AugustServer.html", etree.HTMLParser())
# Find calculation fields
calc_fields = tree.xpath('//td[contains(text(), "Calculation")]/parent::tr')
for field_row in calc_fields:
field_name = field_row.xpath('./td[1]/text()')[0]
formula = field_row.xpath('./following-sibling::tr//text()')Script: 419F - Loop WTU Contact Hours - c courses
Steps:
If [Units ≠ "-"]
Set Field [ClassInstance::wtu; ACCU * Workload_Weight_Factor]
Set Field [ClassInstance::contactHours; ACCU * contact_hours_per_unit]
End If#valCustomFunctionsSectionAnchor_Function Name: GenerateUUID
Parameters: none
Formula:
Upper(Get(UUID))CF_Calc_Validate_Format_Calculate_| FileMaker | PostgreSQL | Notes |
|---|---|---|
| | FileMaker UUIDs are uppercase |
| | Direct equivalent |
| | Direct equivalent |
| | Direct equivalent |
| | PostgreSQL simpler, use SUBSTRING for start/occurrence |
| | Direct equivalent |
| | FileMaker's scoped variables |
| | Similar structure |
| | Or use |
| Dynamic SQL or CASE statement | FileMaker allows dynamic field references |
| | Relationship counts become subqueries |
| | Aggregate from related table |
&||≠!=<>!=andornotANDORNOT¶E'\n'**Field**: ClassInstance.wtu
**Purpose**: Calculate weighted teaching units for workload reporting
**Formula**: `contacthours * units * csfactor`
**Dependencies**: contacthours, units, csfactor fields
**Used by**: WTU reports, faculty workload calculations
**Implementation**: PostgreSQL VIEW or calculated during import// FileMaker calculation field
Case(
enrollment_total = 0; "Empty";
enrollment_total < class_capacity * 0.5; "Low";
enrollment_total >= class_capacity; "Full";
"Adequate"
)CREATE OR REPLACE VIEW class_status AS
SELECT
id,
class_nbr,
CASE
WHEN enrollment_total = 0 THEN 'Empty'
WHEN enrollment_total < class_capacity * 0.5 THEN 'Low'
WHEN enrollment_total >= class_capacity THEN 'Full'
ELSE 'Adequate'
END AS status
FROM classinstance;def calculate_class_status(enrollment_total, class_capacity):
"""Adapted from FileMaker ClassInstance.status calculation"""
if enrollment_total == 0:
return 'Empty'
elif enrollment_total < class_capacity * 0.5:
return 'Low'
elif enrollment_total >= class_capacity:
return 'Full'
else:
return 'Adequate'
# Use during import
cursor.execute("""
UPDATE classinstance
SET enrollment_status = %s
WHERE id = %s
""", (calculate_class_status(row['enrollment_total'], row['class_capacity']), row['id']))Function: GenerateUUID
Parameters: none
Code:
Upper(Get(UUID))def generate_uuid():
"""Generate uppercase UUID matching FileMaker format"""
return str(uuid.uuid4()).upper()program_catalog_parser.py:116-120// Count related records
Count(ClassAssign::id)
// Sum from related table
Sum(Enrollment::units)# Calculate during import
cursor.execute("""
SELECT COUNT(*)
FROM classassign
WHERE id_classinstance = %s
""", (classinstance_id,))
assign_count = cursor.fetchone()[0]
# Or as a VIEW
CREATE VIEW classinstance_summary AS
SELECT
ci.id,
COUNT(ca.id) as assignment_count,
SUM(e.units) as total_enrollment_units
FROM classinstance ci
LEFT JOIN classassign ca ON ca.id_classinstance = ci.id
LEFT JOIN enrollment e ON e.id_classinstance = ci.id
GROUP BY ci.id;Let([
base = contacthours * units;
factor = Case(
component = "LAB"; 1.5;
component = "LEC"; 1.0;
1.0
)
];
base * factor
)CREATE OR REPLACE FUNCTION calculate_wtu(
contact_hours NUMERIC,
units NUMERIC,
component_type TEXT
) RETURNS NUMERIC AS $$
DECLARE
base NUMERIC;
factor NUMERIC;
BEGIN
base := contact_hours * units;
factor := CASE component_type
WHEN 'LAB' THEN 1.5
WHEN 'LEC' THEN 1.0
ELSE 1.0
END;
RETURN base * factor;
END;
$$ LANGUAGE plpgsql IMMUTABLE;// Extract course code from title
Let([
spacePos = Position(" "; course_title; 1; 1)
];
Left(course_title; spacePos - 1)
)def extract_course_code(course_title):
"""Extract course code from title (FileMaker logic)"""
space_pos = course_title.find(' ')
if space_pos > 0:
return course_title[:space_pos]
return course_title
# Or SQL function
CREATE OR REPLACE FUNCTION extract_course_code(course_title TEXT)
RETURNS TEXT AS $$
BEGIN
RETURN SPLIT_PART(course_title, ' ', 1);
END;
$$ LANGUAGE plpgsql IMMUTABLE;# Reference: program_catalog_parser.py patterns
def process_catalog_entry(row, catalog_year):
"""
Process catalog entry with business logic adapted from FileMaker.
FileMaker calculation: catalog_year format "2024-2025"
FileMaker custom function: ConvertToAY("2024-2025") → "24-25"
"""
# Adapt FileMaker's year conversion logic
ay_short = catalog_year[2:4] + '-' + catalog_year[7:9]
# Get AY record (FileMaker relationship equivalent)
cursor.execute("SELECT id FROM ay WHERE ay = %s", (ay_short,))
id_ay = cursor.fetchone()[0]
# Apply business rule (from FileMaker calculation)
if row['total_units'] is None:
# FileMaker: If(IsEmpty(total_units); Calculate_Default_Units; total_units)
total_units = calculate_default_units(row)
else:
total_units = row['total_units']
return {
'id_ay': id_ay,
'total_units': total_units,
# ... other fields
}| Rationalization | Reality |
|---|---|
| "The field is type Number, so it's not calculated" | FileMaker scripts often calculate and set Number/Text fields. Check scripts that reference the field. |
| "I'll just look at calculation fields" | Most complex logic is in scripts (SetField steps), not calculation field types. Scripts are the PRIMARY source. |
| "This is too complex to extract, I'll rebuild from scratch" | You'll miss critical business rules. Extract the logic first, then refactor for PostgreSQL. |
| "I can translate this literally to PostgreSQL" | FileMaker idioms differ from PostgreSQL. Understand the business rule, then implement idiomatically. |
| "I don't need to document this, the code is self-explanatory" | Business context gets lost. Document WHY the calculation exists, not just WHAT it does. |
| "I'll skip the custom functions for now" | Custom functions contain reusable business logic. Extract them early; they'll be referenced throughout. |
| "This global field must be in PostgreSQL" | Global fields are UI/session state, not database state. Handle in application layer, not schema. |
| "I can figure out relationships from field names alone" | FileMaker relationships include conditions. Check DDR relationship definitions for filtering rules. |
# program_catalog_parser.py:41-70
def get_ay_id(conn, catalog_year: str) -> Optional[str]:
"""
Get AY id from catalog_year string.
Converts full format "2025-2026" to short format "25-26"
and looks up corresponding AY record.
This logic was adapted from FileMaker calculation that
performed similar year format conversion in catalog imports.
"""
# Convert "2025-2026" to "25-26" (FileMaker custom function logic)
try:
ay_short = catalog_year[2:4] + '-' + catalog_year[7:9]
except IndexError:
logger.error(f"Invalid catalog_year format: {catalog_year}")
return None
with conn.cursor() as cur:
cur.execute("SELECT id FROM ay WHERE ay = %s", (ay_short,))
result = cur.fetchone()
if not result:
logger.warning(f"AY not found for catalog_year: {catalog_year}")
return None
return result[0]