Library · Data Practice OS
Extracting business rules
Reference guide · Published · Source reviewed
The answer: legacy code is the only complete record of how the organisation calculates fees, determines eligibility and validates data. Extract those Business rules as plain-language, testable specifications before conversion. The specifications become the acceptance tests, so the new platform is proven against the business intent rather than against a copy of old bugs.
Why bother when tools convert SQL?
- Converters translate syntax, not intent. A converted procedure that reproduces a 2009 workaround is still a 2009 workaround.
- Rules in CA Gen action blocks, COBOL, SSIS expressions and report formulas are invisible to SQL converters.
- The people who know why a rule exists are retiring. The specification captures that knowledge while it can still be checked with them.
- Specifications let the business confirm or change a rule deliberately during modernisation, instead of discovering the change in production.
The rule card
Rule REG-FEE-014: Concession on registration for pensioners
Source: proc usp_calc_rego_fee lines 212-260; SSIS pkg REGO_DAILY expr "ConcFlag"
Owner: Registration policy, Customer Services
Confidence: confirmed by SME (2026-10-21)
Priority: P0
Category: calculation
Suspected defect: card expiring on the renewal date is refused (decision: REG-DEC-007)
Scenario: Eligible pensioner, light vehicle
Given a registration renewal for vehicle class "LIGHT"
And the holder has a valid concession card of type "PENSIONER"
When the renewal fee is calculated
Then the registration fee component is reduced by the concession rate
And the CTP component is not reduced
Scenario: Concession card expired before renewal date
Given the holder's concession card expired before the renewal date
When the renewal fee is calculated
Then no concession applies
Each card has a stable ID, cites its source lines, names a business owner, and records a confidence (inferred, SME-confirmed or policy-confirmed). Three fields are optional: Priority (P0, P1 or P2; P0 is fees, eligibility, sanctions and decisions about people; proposed scale, agree it with the rule catalogue owner, see VERIFY.md), Category (validation, policy, lifecycle or calculation) and Suspected defect (free text, with the decision that settles it). Keep the domain-coded id (REG-FEE-014) as the single scheme. See Tracing rules from legacy code to modern code for how tools key on it.
Process
- Mine. Read code module by module and draft rule cards. LLMs such as Anthropic Claude are good at this when every card must cite file and line, and a second pass checks each citation exists and says what the card claims.
- Triage. Mark each rule keep, change or retire, and flag rules that touch decisions about people for Human in the loop review.
- Confirm. Subject-matter experts review the high-impact rules (fees, eligibility, sanctions). Unconfirmed rules stay labelled as inferred.
- Encode. Turn scenarios into tests: dbt unit tests for transformation logic, data tests for invariants, and Data contract rules for producer guarantees. For P0 rules, first run a pinning (Characterisation (pinning) test) case against the legacy, and feed its fixtures into the dbt unit test (proposed, Tracing rules from legacy code to modern code).
- Trace. Link each rule ID to the new model or function that implements it, so audit can go from rule to code and back. See Tracing rules from legacy code to modern code for the rule-as-hub model, evidence on every link and the gap queries (draft).
# dbt unit test generated from REG-FEE-014
unit_tests:
- name: reg_fee_014_pensioner_light_vehicle
model: int_registration_fee
given:
- input: ref('stg_renewals')
rows: [{ renewal_id: 1, vehicle_class: LIGHT, concession_type: PENSIONER,
concession_expiry: '2027-01-01', renewal_date: '2026-11-01' }]
expect:
rows: [{ renewal_id: 1, concession_applied: true, ctp_concession_applied: false }]
The repo's sibling tooling (the code-modernization rule-extraction workflow) automates mining and citation checking. Use it rather than hand-reading large estates.
Notes by source type
- CA Gen / mainframe. Rules are spread across action blocks and screens. Extract from the model or generated code, and expect many rules to be about the operational system rather than the data platform. Keep only those that affect derived data.
- EDW and ETL. Concentrate on stored procedures with branching logic, SSIS derived-column and conditional-split expressions, and Informatica expression transformations.
- Hadoop. Rules live in Spark or Hive scripts, often duplicated across jobs. De-duplicate during extraction.
Worked example
A licensing data mart has 140 procedures. Mining yields 410 candidate rules. After de-duplication there are 230, and SMEs confirm 95 high-impact rules over four workshops. Twelve rules are marked change because policy moved on and the code never caught up. Those 12 are signed off by the policy owner as intentional differences, so Reconciliation expects them to differ.
Pitfalls
- Rules without citations. These cannot be verified and erode trust. Reject them.
- Boiling the ocean. Extract deeply for high-value modules, and lightly for simple loads.
- Encoding bugs as rules. When code and policy disagree, the policy owner decides, and the decision is recorded. Record the suspected defect on the card, so open preserve-or-fix decisions can be listed (Tracing rules from legacy code to modern code, "undecided suspected defects").
- Specs that never become tests. An unexecuted spec is documentation that will rot.
Related: Migration assessment, Code conversion, Data contracts, Modernisation and migration.
After extraction: where rules live
The rule of record is a rule card in a governed repository, with effective dates. dbt tests, decision tables and engine code are projections of it. The platform team extracted 2,050 GEN rule cards. Even when an analytics migration keeps only the rules that affect derived data, send all of them to the rule repository: mine once, use twice. See Where business rules run after migration.