LLM-Assisted Regulation Development
AI-generated payroll regulations and tests with human review
Large language models can generate Payroll Engine regulations, test cases and examples from natural language requirements. The PE's declarative JSON/YAML architecture is structurally well-suited for this: regulations are well-constrained documents with no logic outside expressions and scripting hooks — exactly the format LLMs produce reliably.
The automated test framework closes the loop: generated output is verified by the test runner without manual case-by-case review. This concentrates human effort on what requires actual payroll domain knowledge.
The Development Model
| Phase | Who | What |
|---|---|---|
| 1. Define requirements | Human | Natural language description of the regulation |
| 2. Generate regulation | LLM | Cases, WageTypes, Collectors, Lookups in JSON/YAML |
| 3. Generate tests | LLM | Expected results for test employees |
| 4. Review & complete | Human | Apply domain knowledge, close gaps |
| 5. Verify | PE Test Runner | Automated verification of all test results |
Phases 2 and 3 typically cover 90–95% of the work. Phase 4 takes 30–60 minutes for a regulation of moderate complexity — compared to 1–2 days for a full manual build.
Practical Example: GlobalTech Payroll
The GlobalPayroll example was designed as a reference for this workflow. Prompt.md in that folder contains the natural language requirements used as input, with the human corrections documented alongside.
Phase 1 — Requirements (Human → LLM)
"Create a Payroll Engine regulation for GlobalTech, a fictitious technology company.
The following rules apply: - Base salary = gross salary × employment level - Employees aged 50 or older receive a senior supplement of 5% on the base salary - A one-time performance bonus is paid in the month it is recorded - Progressive income tax: up to 3 000 → 10%, up to 6 000 → 18%, up to 10 000 → 25%, above → 32% - Social security: 10% (national), health insurance: 5% (company)
Two test employees: - Sarah Chen, born 1985, full-time, salary 6 000, January bonus 1 000 - Carlos Rodriguez, born 1970, 60% part-time, salary 10 000 (full-time basis)"*
Phase 2 — Generate Regulation (LLM)
The LLM generates the complete Payroll.json. Key elements:
Cases:
"cases": [
{ "name": "SocialSecurity", "caseType": "National",
"fields": [{ "name": "SocialSecurityRate", "valueType": "Percent", "timeType": "Period" }] },
{ "name": "HealthInsurance", "caseType": "Company",
"fields": [{ "name": "HealthInsuranceRate", "valueType": "Percent", "timeType": "CalendarPeriod" }] },
{ "name": "Salary", "caseType": "Employee",
"fields": [{ "name": "Salary", "valueType": "Money", "timeType": "CalendarPeriod" }] },
{ "name": "EmploymentLevel", "caseType": "Employee",
"fields": [{ "name": "EmploymentLevel", "valueType": "Percent", "timeType": "Period" }] },
{ "name": "BirthDate", "caseType": "Employee",
"fields": [{ "name": "BirthDate", "valueType": "Date", "timeType": "Timeless" }] },
{ "name": "PerformanceBonus", "caseType": "Employee",
"fields": [{ "name": "PerformanceBonus", "valueType": "Money", "timeType": "Moment" }] }
]
Lookup: IncomeTax (progressive bracket):
"lookups": [
{ "name": "IncomeTax", "rangeSize": 0.01,
"values": [
{ "key": "Bracket1", "rangeValue": 1, "value": "\"0.10\"" },
{ "key": "Bracket2", "rangeValue": 3000, "value": "\"0.18\"" },
{ "key": "Bracket3", "rangeValue": 6000, "value": "\"0.25\"" },
{ "key": "Bracket4", "rangeValue": 10000, "value": "\"0.32\"" }
]
}
]
Wage Types:
"wageTypes": [
{ "wageTypeNumber": 100, "name": "BaseSalary",
"valueExpression": "^^Salary * ^^EmploymentLevel",
"collectors": ["GrossIncome"] },
{ "wageTypeNumber": 110, "name": "SeniorSupplement",
"valueExpression": "Age(^^BirthDate) >= 50 ? WageType[100] * 0.05M : 0M",
"collectors": ["GrossIncome"] },
{ "wageTypeNumber": 120, "name": "PerformanceBonus",
"valueExpression": "^^PerformanceBonus",
"collectors": ["GrossIncome"] },
{ "wageTypeNumber": 200, "name": "IncomeTax",
"valueExpression": "var rate = GetRangeLookup<decimal>(\"IncomeTax\", Collector[\"GrossIncome\"]); return rate != null ? Collector[\"GrossIncome\"] * rate : 0M;",
"collectors": ["Deductions"] },
{ "wageTypeNumber": 210, "name": "SocialSecurity",
"valueExpression": "Collector[\"GrossIncome\"] * CaseValue[\"SocialSecurityRate\"]",
"collectors": ["Deductions"] },
{ "wageTypeNumber": 220, "name": "HealthInsurance",
"valueExpression": "Collector[\"GrossIncome\"] * CaseValue[\"HealthInsuranceRate\"]",
"collectors": ["Deductions"] }
]
Phase 3 — Generate Tests (LLM)
The LLM calculates expected results and generates Test.et.json. Two periods are covered:
January 2024 — Sarah Chen (full-time, bonus 1 000):
| Wage Type | Calculation | Result |
|---|---|---|
| 100 BaseSalary | 6 000 × 1.0 | 6 000.00 |
| 110 SeniorSupplement | age 38 < 50 | 0.00 |
| 120 PerformanceBonus | Moment Jan 2024 | 1 000.00 |
| GrossIncome | 7 000.00 | |
| 200 IncomeTax | 7 000 × 25 % (Bracket3) | 1 750.00 |
| 210 SocialSecurity | 7 000 × 10 % | 700.00 |
| 220 HealthInsurance | 7 000 × 5 % | 350.00 |
| Deductions | 2 800.00 |
February 2024 — Sarah Chen (no bonus — Moment validation):
| Wage Type | Calculation | Result |
|---|---|---|
| 120 PerformanceBonus | Moment not in Feb — must be 0 | 0.00 |
| GrossIncome | 6 000.00 | |
| Deductions | 2 400.00 |
The February result is the critical verification: it confirms that the Moment time type correctly scopes the bonus to a single period.
Phase 4 — Review & Complete (Human)
| Check | Typical LLM issue | Human correction |
|---|---|---|
| Tax bracket | Boundary logic (≤ vs. <) | Verify rangeSize and bracket values |
| Senior supplement | Age reference date | Confirm Age() is evaluated at period start |
| Moment bonus | Aggregation behaviour | Clarify PeriodAggregation for multiple entries |
| Retroactive behaviour | Usually not generated | Add retro test case for employment level change |
| Naming conventions | Spaces, special characters | Ensure PascalCase for all object names |
Phase 5 — Verify (PE Test Runner)
Test.pecmd
The test runner executes the payrun and compares all wageTypeResults and collectorResults against expected values. Every deviation is reported as a failure — no manual review required.
What LLMs Do Well
| Task | Quality |
|---|---|
| Derive cases from requirements | ✅ Very good |
| WageType expressions (No-Code) | ✅ Very good |
| Structure lookup tables | ✅ Very good |
| Calculate expected test results | ✅ Good (simple arithmetic) |
| Collector assignments | ✅ Good |
| Progressive tax / bracket logic | ⚠️ Review boundary cases |
| Retroactive scenarios | ⚠️ Rarely correct without guidance |
| Time overlaps and splitting | ❌ Domain knowledge required |
| Compliance-specific rules | ❌ Human verification mandatory |
See Also
- GlobalPayroll Example — reference implementation with
Prompt.md - Test-Driven Development — PE test framework overview
- No-Code / Low-Code — regulation authoring without .NET