extracting-filemaker-business-logic

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Extracting FileMaker Business Logic

提取FileMaker业务逻辑

Overview

概述

FileMaker DDR reports contain calculation fields, custom functions, and business logic embedded in scripts. This skill helps you extract, understand, and adapt that logic for PostgreSQL implementations—typically for import processes, data transformations, and maintenance scripts.
Key principle: PostgreSQL implementations will often be more efficient than direct translations. Use FileMaker logic to understand what the business rules are, then implement them idiomatically in PostgreSQL.
FileMaker DDR报告包含计算字段、自定义函数以及嵌入在脚本中的业务逻辑。本技能可帮助您提取、理解并适配这些逻辑,用于PostgreSQL实现——通常用于导入流程、数据转换和维护脚本。
核心原则:PostgreSQL实现通常比直接翻译更高效。借助FileMaker逻辑理解业务规则的本质,然后以符合PostgreSQL风格的方式实现这些规则。

When to Use

适用场景

Use this skill when:
  • Extracting calculation logic from FileMaker DDR for import scripts
  • Understanding business rules embedded in FileMaker calculations
  • Adapting FileMaker custom functions to PostgreSQL functions
  • Creating PostgreSQL maintenance scripts based on FileMaker script logic
  • Documenting business logic that exists only in FileMaker
Do NOT use for:
  • Direct 1:1 database migration (most PostgreSQL designs will differ)
  • FileMaker UI/layout logic (not relevant to PostgreSQL)
  • Simple field mappings (use standard ETL tools)
本技能适用于:
  • 从FileMaker DDR中提取计算逻辑用于导入脚本
  • 理解嵌入在FileMaker计算中的业务规则
  • 将FileMaker自定义函数适配为PostgreSQL函数
  • 基于FileMaker脚本逻辑创建PostgreSQL维护脚本
  • 记录仅存在于FileMaker中的业务逻辑
不适用于:
  • 直接1:1数据库迁移(大多数PostgreSQL设计会有所不同)
  • FileMaker UI/布局逻辑(与PostgreSQL无关)
  • 简单字段映射(使用标准ETL工具即可)

DDR Files and Structure

DDR文件与结构

Detailed DDR:
{ProjectName}_ddr/{DatabaseName}.html
(5-20MB file)
  • Contains complete calculation formulas
  • Lists all custom functions with code
  • Documents scripts step-by-step
  • Shows field definitions and relationships
Example from user's project:
  • File:
    /Users/anthonybyrnes/PycharmProjects/Python419/AugustServer_ddr/AugustServer.html
  • Size: 9.7MB
  • Contains: 148 custom functions, 362 scripts, calculation fields
详细DDR文件
{ProjectName}_ddr/{DatabaseName}.html
(5-20MB大小)
  • 包含完整的计算公式
  • 列出所有带代码的自定义函数
  • 分步记录脚本内容
  • 显示字段定义和关系
用户项目示例
  • 文件路径:
    /Users/anthonybyrnes/PycharmProjects/Python419/AugustServer_ddr/AugustServer.html
  • 大小:9.7MB
  • 包含:148个自定义函数、362个脚本、计算字段

Finding Calculations in DDR

在DDR中查找计算逻辑

Calculation Fields

计算字段

In DDR HTML, calculation fields appear in field definitions:
Field Name: wtu_calculation
Type: Calculation
Result Type: Number
Formula:
  contacthours * units * csfactor
XPath pattern (adjust based on actual DDR structure):
python
from lxml import etree

tree = etree.parse("AugustServer_ddr/AugustServer.html", etree.HTMLParser())
在DDR HTML中,计算字段出现在字段定义中:
Field Name: wtu_calculation
Type: Calculation
Result Type: Number
Formula:
  contacthours * units * csfactor
XPath模式(根据实际DDR结构调整):
python
from lxml import etree

tree = etree.parse("AugustServer_ddr/AugustServer.html", etree.HTMLParser())

Find calculation fields

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()')
undefined
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()')
undefined

Scripts (Critical Source of Business Logic)

脚本(业务逻辑的关键来源)

Important: Many "calculated" values are actually set by FileMaker scripts, not calculation fields. Scripts often contain the most complex business logic.
Look for scripts that:
  • SetField steps (e.g., "Set Field [ClassInstance::wtu; ...]")
  • Loop through records performing calculations
  • Names containing "Calculate", "Update", "Process", "Compute"
  • Triggered by imports or scheduled tasks
Example: The WTU field may appear as a Number field (not Calculation), but scripts like "419F - Loop WTU Contact Hours" contain the actual calculation logic.
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
Extraction tip: Search DDR for field names (e.g., "wtu") to find all scripts that reference them.
重要提示:许多“计算”值实际上是由FileMaker脚本设置的,而非计算字段。脚本通常包含最复杂的业务逻辑。
需要关注的脚本类型
  • SetField步骤(例如:"Set Field [ClassInstance::wtu; ...]")
  • 循环遍历记录执行计算的脚本
  • 名称包含“Calculate”、“Update”、“Process”、“Compute”的脚本
  • 由导入或计划任务触发的脚本
示例:WTU字段可能显示为Number类型(非Calculation类型),但类似“419F - Loop WTU Contact Hours”的脚本包含实际的计算逻辑。
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
提取技巧:在DDR中搜索字段名(例如“wtu”),找到所有引用该字段的脚本。

Custom Functions

自定义函数

Custom functions section (anchor:
#valCustomFunctionsSectionAnchor_
):
Function Name: GenerateUUID
Parameters: none
Formula:
  Upper(Get(UUID))
Look for patterns in custom function names:
  • Prefix conventions (e.g.,
    CF_
    ,
    Calc_
    )
  • Purpose indicators (
    Validate_
    ,
    Format_
    ,
    Calculate_
    )
自定义函数部分(锚点:
#valCustomFunctionsSectionAnchor_
):
Function Name: GenerateUUID
Parameters: none
Formula:
  Upper(Get(UUID))
关注自定义函数名称的模式:
  • 前缀约定(例如
    CF_
    Calc_
  • 用途标识(
    Validate_
    Format_
    Calculate_

Understanding FileMaker Calculation Syntax

理解FileMaker计算语法

Common FileMaker Functions → PostgreSQL Equivalents

常见FileMaker函数 → PostgreSQL等效函数

FileMakerPostgreSQLNotes
Get(UUID)
gen_random_uuid()
or
uuid_generate_v4()
FileMaker UUIDs are uppercase
Upper(text)
UPPER(text)
Direct equivalent
Left(text, n)
LEFT(text, n)
Direct equivalent
Right(text, n)
RIGHT(text, n)
Direct equivalent
Position(search, text, start, occurrence)
POSITION(search IN text)
PostgreSQL simpler, use SUBSTRING for start/occurrence
Substitute(text, search, replace)
REPLACE(text, search, replace)
Direct equivalent
Let([var1 = value; var2 = value]; expression)
WITH vars AS (...)
or function variables
FileMaker's scoped variables
Case(test1; result1; test2; result2; default)
CASE WHEN test1 THEN result1 WHEN test2 THEN result2 ELSE default END
Similar structure
If(test; trueResult; falseResult)
CASE WHEN test THEN trueResult ELSE falseResult END
Or use
IF
in PL/pgSQL
GetField(fieldName)
Dynamic SQL or CASE statementFileMaker allows dynamic field references
Count(relationship::field)
SELECT COUNT(*) FROM related_table WHERE...
Relationship counts become subqueries
Sum(relationship::field)
SELECT SUM(field) FROM related_table WHERE...
Aggregate from related table
FileMakerPostgreSQL说明
Get(UUID)
gen_random_uuid()
uuid_generate_v4()
FileMaker UUID为大写
Upper(text)
UPPER(text)
直接等效
Left(text, n)
LEFT(text, n)
直接等效
Right(text, n)
RIGHT(text, n)
直接等效
Position(search, text, start, occurrence)
POSITION(search IN text)
PostgreSQL语法更简洁,如需指定起始位置或出现次数可使用SUBSTRING
Substitute(text, search, replace)
REPLACE(text, search, replace)
直接等效
Let([var1 = value; var2 = value]; expression)
WITH vars AS (...)
或函数变量
FileMaker的作用域变量
Case(test1; result1; test2; result2; default)
CASE WHEN test1 THEN result1 WHEN test2 THEN result2 ELSE default END
结构类似
If(test; trueResult; falseResult)
CASE WHEN test THEN trueResult ELSE falseResult END
或在PL/pgSQL中使用
IF
GetField(fieldName)
动态SQL或CASE语句FileMaker允许动态字段引用
Count(relationship::field)
SELECT COUNT(*) FROM related_table WHERE...
关系计数变为子查询
Sum(relationship::field)
SELECT SUM(field) FROM related_table WHERE...
从关联表聚合数据

FileMaker Operators

FileMaker运算符

  • &
    (concatenation) →
    ||
    in PostgreSQL
  • or
    !=
    <>
    or
    !=
    in PostgreSQL
  • and
    ,
    or
    ,
    not
    AND
    ,
    OR
    ,
    NOT
    in PostgreSQL
  • (paragraph return) →
    E'\n'
    in PostgreSQL
  • &
    (连接)→ PostgreSQL中使用
    ||
  • !=
    → PostgreSQL中使用
    <>
    !=
  • and
    ,
    or
    ,
    not
    → PostgreSQL中使用
    AND
    ,
    OR
    ,
    NOT
  • (段落换行)→ PostgreSQL中使用
    E'\n'

Extraction Workflow

提取工作流

Step 1: Identify Business Logic Locations

步骤1:确定业务逻辑位置

Scan DDR for (in priority order):
  • Scripts that manipulate data (SetField, Loop, calculations) - often the PRIMARY source
  • Calculation fields in tables you're importing
  • Custom functions referenced by calculations/scripts
  • Auto-enter calculations (default values with logic)
  • Validation calculations (field constraints)
Critical: Check scripts FIRST. Many fields appear as "Number" or "Text" but are actually calculated by scripts.
按优先级顺序扫描DDR:
  • 操作数据的脚本(SetField、Loop、计算逻辑)——通常是主要来源
  • 您要导入的表中的计算字段
  • 计算/脚本引用的自定义函数
  • 自动输入计算(带逻辑的默认值)
  • 验证计算(字段约束)
关键提示:先检查脚本。许多字段显示为“Number”或“Text”类型,但实际上是由脚本计算得出的。

Step 2: Document Calculation Purpose

步骤2:记录计算用途

For each calculation field:
markdown
**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
针对每个计算字段:
markdown
**字段**:ClassInstance.wtu
**用途**:计算用于工作量报告的加权教学单元
**公式**`contacthours * units * csfactor`
**依赖项**:contacthours、units、csfactor字段
**使用者**:WTU报告、教师工作量计算
**实现方式**:PostgreSQL VIEW或在导入时计算

Step 3: Adapt to PostgreSQL Idioms

步骤3:适配PostgreSQL风格

FileMaker approach (calculation field):
// FileMaker calculation field
Case(
  enrollment_total = 0; "Empty";
  enrollment_total < class_capacity * 0.5; "Low";
  enrollment_total >= class_capacity; "Full";
  "Adequate"
)
PostgreSQL approach (CASE expression in VIEW):
sql
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;
Or as import script logic (Python):
python
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'
FileMaker方式(计算字段):
// FileMaker calculation field
Case(
  enrollment_total = 0; "Empty";
  enrollment_total < class_capacity * 0.5; "Low";
  enrollment_total >= class_capacity; "Full";
  "Adequate"
)
PostgreSQL方式(VIEW中的CASE表达式):
sql
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;
或作为导入脚本逻辑(Python):
python
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

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']))
undefined
cursor.execute(""" UPDATE classinstance SET enrollment_status = %s WHERE id = %s """, (calculate_class_status(row['enrollment_total'], row['class_capacity']), row['id']))
undefined

Step 4: Handle Custom Functions

步骤4:处理自定义函数

FileMaker custom function:
Function: GenerateUUID
Parameters: none
Code:
  Upper(Get(UUID))
PostgreSQL equivalent (in existing codebase pattern):
python
def generate_uuid():
    """Generate uppercase UUID matching FileMaker format"""
    return str(uuid.uuid4()).upper()
Reference:
program_catalog_parser.py:116-120
FileMaker自定义函数
Function: GenerateUUID
Parameters: none
Code:
  Upper(Get(UUID))
PostgreSQL等效实现(符合现有代码库模式):
python
def generate_uuid():
    """Generate uppercase UUID matching FileMaker format"""
    return str(uuid.uuid4()).upper()
Reference:
program_catalog_parser.py:116-120

Common Patterns

常见模式

Pattern 1: Aggregates from Relationships

模式1:关联表聚合

FileMaker:
// Count related records
Count(ClassAssign::id)

// Sum from related table
Sum(Enrollment::units)
PostgreSQL (import script):
python
undefined
FileMaker
// Count related records
Count(ClassAssign::id)

// Sum from related table
Sum(Enrollment::units)
PostgreSQL(导入脚本):
python
undefined

Calculate during import

Calculate during import

cursor.execute(""" SELECT COUNT(*) FROM classassign WHERE id_classinstance = %s """, (classinstance_id,)) assign_count = cursor.fetchone()[0]
cursor.execute(""" SELECT COUNT(*) FROM classassign WHERE id_classinstance = %s """, (classinstance_id,)) assign_count = cursor.fetchone()[0]

Or as a VIEW

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;
undefined
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;
undefined

Pattern 2: Conditional Logic

模式2:条件逻辑

FileMaker:
Let([
  base = contacthours * units;
  factor = Case(
    component = "LAB"; 1.5;
    component = "LEC"; 1.0;
    1.0
  )
];
  base * factor
)
PostgreSQL function:
sql
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;
FileMaker
Let([
  base = contacthours * units;
  factor = Case(
    component = "LAB"; 1.5;
    component = "LEC"; 1.0;
    1.0
  )
];
  base * factor
)
PostgreSQL函数
sql
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;

Pattern 3: Text Parsing/Formatting

模式3:文本解析/格式化

FileMaker:
// Extract course code from title
Let([
  spacePos = Position(" "; course_title; 1; 1)
];
  Left(course_title; spacePos - 1)
)
PostgreSQL (import script):
python
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
FileMaker
// Extract course code from title
Let([
  spacePos = Position(" "; course_title; 1; 1)
];
  Left(course_title; spacePos - 1)
)
PostgreSQL(导入脚本):
python
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

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;
undefined
CREATE OR REPLACE FUNCTION extract_course_code(course_title TEXT) RETURNS TEXT AS $$ BEGIN RETURN SPLIT_PART(course_title, ' ', 1); END; $$ LANGUAGE plpgsql IMMUTABLE;
undefined

Script Logic Extraction

脚本逻辑提取

FileMaker scripts often contain:
  • Data transformation logic → PostgreSQL functions or Python import scripts
  • Validation rules → CHECK constraints or application validation
  • Business workflows → Application layer logic
  • UI automation → Ignore for PostgreSQL
Focus on extracting:
  • SetField steps (data updates)
  • If/Else logic (conditional rules)
  • Loop structures (batch processing)
  • Calculation expressions used in scripts
FileMaker脚本通常包含:
  • 数据转换逻辑 → PostgreSQL函数或Python导入脚本
  • 验证规则 → CHECK约束或应用层验证
  • 业务工作流 → 应用层逻辑
  • UI自动化 → 针对PostgreSQL可忽略
重点提取内容
  • SetField步骤(数据更新)
  • If/Else逻辑(条件规则)
  • 循环结构(批量处理)
  • 脚本中使用的计算表达式

Integration with Import Processes

与导入流程的集成

When building import scripts:
python
undefined
构建导入脚本时:
python
undefined

Reference: program_catalog_parser.py patterns

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
}
undefined
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
}
undefined

Common Mistakes

常见错误

Mistake 1: Literal Translation
  • Don't translate FileMaker calculations character-by-character
  • Understand the business rule, then implement idiomatically in PostgreSQL
Mistake 2: Ignoring Context
  • Calculation fields may reference global fields or UI state
  • Determine if logic is data-based (extract) or UI-based (ignore)
Mistake 3: Missing Dependencies
  • Custom functions may call other custom functions
  • Extract the full dependency chain
Mistake 4: Over-Engineering
  • Simple calculations don't need PostgreSQL functions
  • Calculate during import if logic is only used once
Mistake 5: Skipping Documentation
  • Document the business purpose, not just the formula
  • Future maintainers need to understand why, not just what
Mistake 6: Only Checking Calculation Fields
  • Scripts often contain the primary business logic
  • Check SetField steps in scripts for complex calculations
  • A "Number" field type doesn't mean it's not calculated
错误1:直译
  • 不要逐字符翻译FileMaker计算逻辑
  • 理解业务规则后,以符合PostgreSQL风格的方式实现
错误2:忽略上下文
  • 计算字段可能引用全局字段或UI状态
  • 判断逻辑是基于数据的(提取)还是基于UI的(忽略)
错误3:遗漏依赖项
  • 自定义函数可能调用其他自定义函数
  • 提取完整的依赖链
错误4:过度设计
  • 简单计算不需要PostgreSQL函数
  • 如果逻辑仅使用一次,可在导入时计算
错误5:跳过文档记录
  • 记录业务用途,而不仅仅是公式
  • 未来维护人员需要理解“为什么”,而不只是“是什么”
错误6:仅检查计算字段
  • 脚本通常包含主要的业务逻辑
  • 检查脚本中的SetField步骤以获取复杂计算
  • “Number”字段类型不代表它不是计算得出的

Common Rationalizations to Avoid

需避免的常见自我合理化

RationalizationReality
"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.
自我合理化实际情况
“该字段是Number类型,所以它不是计算得出的”FileMaker脚本经常计算并设置Number/Text字段。检查引用该字段的脚本。
“我只需要看计算字段”大多数复杂逻辑存在于脚本(SetField步骤)中,而非计算字段类型。脚本是主要来源。
“这太复杂了,无法提取,我将从头重建”您会遗漏关键业务规则。先提取逻辑,再针对PostgreSQL重构。
“我可以直接将其直译到PostgreSQL”FileMaker的惯用写法与PostgreSQL不同。理解业务规则后,以符合PostgreSQL风格的方式实现。
“不需要记录这个,代码本身就很清楚”业务上下文会丢失。记录计算存在的原因,而不只是计算内容。
“我现在先跳过自定义函数”自定义函数包含可复用的业务逻辑。尽早提取它们,因为它们会被广泛引用。
“这个全局字段必须在PostgreSQL中存在”全局字段是UI/会话状态,而非数据库状态。在应用层处理,而非数据库模式中。
“我仅通过字段名就能弄清楚关系”FileMaker关系包含条件。检查DDR关系定义中的过滤规则。

Checklist for Logic Extraction

逻辑提取检查清单

When extracting FileMaker business logic:
  • Locate DDR detailed HTML file
  • Identify calculation fields in relevant tables
  • Document purpose and dependencies for each calculation
  • List custom functions used by calculations
  • Extract custom function code and dependencies
  • Map FileMaker functions to PostgreSQL equivalents
  • Decide: PostgreSQL function, VIEW, or import script logic?
  • Implement with idiomatic PostgreSQL patterns
  • Test with sample data from FileMaker
  • Document business rules separately from code
提取FileMaker业务逻辑时:
  • 找到DDR详细HTML文件
  • 确定相关表中的计算字段
  • 记录每个计算的用途和依赖项
  • 列出计算使用的自定义函数
  • 提取自定义函数代码和依赖项
  • 映射FileMaker函数到PostgreSQL等效函数
  • 决定:使用PostgreSQL函数、VIEW还是导入脚本逻辑?
  • 以符合PostgreSQL风格的方式实现
  • 使用FileMaker中的示例数据进行测试
  • 单独记录业务规则(与代码分开)

Real-World Example

实际案例

From user's existing codebase:
python
undefined
来自用户现有代码库:
python
undefined

program_catalog_parser.py:41-70

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]

This function adapted FileMaker's year conversion logic for use in PostgreSQL import scripts.
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]

此函数适配了FileMaker的年份转换逻辑,用于PostgreSQL导入脚本。