Business Rule Mining
| Field | Value |
|---|---|
| Type | Skill Resource |
| Source | ~/.copilot/skills/architecture/references/discovery/business-rule-mining.md |
| Description | Not specified |
Source Content
Business Rule Mining
How to pull business logic out of legacy RPG, COBOL, CL, and CICS code — the eligibility calculations, status transitions, edit routines, and thresholds that the system runs on but nobody wrote down. This is phase 3 of the discovery playbook. It comes after the data model is extracted, because you read the code against the record layout, not cold.
Contents
- Two analysts, two answers
- What counts as a business rule
- The stored value under-tells the story
- Where rules hide, per platform
- The extraction method
- The rule-catalog template
- Confidence and open-questions discipline
Two analysts, two answers
Jack had a data dictionary and a hunch. The CLAIMS table has an ELIG_FLAG column set to Y or N, so he wrote “eligibility is stored in ELIG_FLAG” and moved on — the rebuild would just copy the column. Three weeks into the rebuild, a tester found claims marked ELIG_FLAG = Y that the legacy system had actually denied. The flag was set by a data-entry screen; the real denial happened later, in an edit routine that ran before the payment write and never touched the flag.
Jill traced the code instead. She followed every program that wrote to CLAIMS, found the edit routine CHKELIG, and read it: it recomputed eligibility from wage records, base-period earnings, and a disqualification table at payment time, ignoring ELIG_FLAG entirely. She wrote the rule, not the column — inputs, logic, output, and the line range it lived on. Her catalog entry is the one the rebuild trusted, because it described what the system does, not what one table happens to hold.
What counts as a business rule
A business rule is a decision or calculation the organization would defend in an audit — something a policy analyst, not a programmer, owns. “A claim is denied if base-period wages are below the threshold” is a rule. “The program opens the file with DISP=SHR” is plumbing. Mine the first; note the second only where it changes what the rule sees.
Use this split to sort what you read:
| Signal | Business rule | Plumbing |
|---|---|---|
| A calculation on money, time, or eligibility | Yes — capture it | - |
A named condition (88 CLAIM-APPROVED, an indicator set on “denied”) | Yes — the name is the rule | - |
| A threshold, rate, or magic number in WORKING-STORAGE or a data area | Yes — capture the value and where it is used | - |
A status transition ('A' → 'P' → 'C') | Yes — capture the trigger and guard | - |
| File open/close, cursor positioning, error-message formatting | - | Yes — skip unless it gates a rule |
| A validation that rejects input before a write | Yes — it encodes what “valid” means | - |
| Screen field positioning, print spacing, commitment control | - | Yes — plumbing |
How to tell them apart in practice: ask “would a policy change force an edit here?” If yes, it is a rule; if only a platform migration would touch it, it is plumbing.
The stored value under-tells the story
The single most expensive mistake in rule mining is trusting the database column. Legacy code routinely transforms data before it is stored and re-interprets it after it is read — so the value sitting in the table is a waypoint, not the whole rule. Read the code on both sides of every read and write, or you will document the waypoint and miss the logic.
Three shapes the stored value takes
Transform-before-store
A benefit amount is computed, capped at a weekly maximum, rounded, and then written. The stored BENEFIT_AMT is the post-cap, post-rounding number — the cap and the rounding rule are invisible in the data. A rebuild that reads only stored amounts will reproduce the outputs for existing rows but compute new ones wrong, because it never learned the cap.
Re-interpret-after-read
A status code 'H' is stored plainly, but on read the program checks a second field — a hold-reason date — and treats 'H' as “expired hold, proceed” when the date is older than 10 days. The column says “on hold”; the system behaves as “released.” The rule lives in the read path, not the value.
Encoded-meaning
A packed-decimal field stores 00000 to mean “not yet rated” and any positive value to mean “rated at this amount” — so zero is a state, not an amount. Elsewhere a signed-overpunch field packs a sign into the last digit (see the data-model reference for the decode). The stored bytes are meaningless until the code that reads them tells you the convention. Resolve the convention, or every downstream rule inherits the ambiguity.
The discipline: for every field a rule depends on, find the write that produced it and the read that consumes it, and record any transform on either side as part of the rule.
Where rules hide, per platform
Rules do not sit in one place. Each platform has a handful of constructs where logic clusters — learn them and you know where to point your reading. The syntax for each construct lives in the platform decoders; this section says which constructs carry rules and why.
The rule-bearing constructs
IBM i RPG
Four constructs carry the logic. See the IBM i decoder for EXSR, indicator, and data-area syntax.
- Subroutines — a
BEGSR/ENDSRblock reached byEXSR, often named for what it does (CHKELIG,CALCBEN); read every name as a candidate rule. - Edit and validation routines — run before a
WRITE/UPDATEand encode what “valid” means; this is the transform-before-store path. - Indicators (
*IN01–*IN99) — carry meaning by convention, so one set on “record not found” or “over limit” is a rule branch; trace each to where it is set and tested. - Data areas (
*DTAARA) — gate logic: a single data area can hold the processing date or a “batch is running” flag that turns a rule on or off.
COBOL
Four constructs carry the logic. See the COBOL decoder for 88, EVALUATE, COMPUTE, and WORKING-STORAGE syntax.
88-level condition names —88 CLAIM-APPROVED VALUE 'A'names a business state in plain English; harvest every one straight into the catalog.EVALUATE— usually encodes a decision table: eachWHENis a rule branch, and the whole block is one rule with several outcomes.COMPUTE(andADD/SUBTRACT/MULTIPLY/DIVIDE) — where money and eligibility math live; read the formula, not just the target field.- WORKING-STORAGE constants and thresholds — the magic numbers a rule tests against (a weekly maximum, a disqualification count); capture the value and every place it is used.
CICS
Online rules split into two zones. Because the program is pseudo-conversational, a rule may span two tasks — the check on entry and the write on the next keystroke — so read the COMMAREA to follow the thread. See the CICS decoder for EIBAID, RECEIVE MAP, and the pseudo-conversational model.
EIBAIDdispatch (EVALUATE EIBAID) — the screen’s keyboard contract: which key does what, and each branch can trigger a different rule path. Capture it as the entry logic.- Validation before
WRITE/REWRITE— the heavier rules: a program checks the received map fields, cross-reads other files, and rejects or transforms before committing.
CL
CL is glue, but two constructs carry real rules. See the IBM i decoder for MONMSG and data-area commands.
MONMSG— the try/catch of CL: a monitored message and its handler encode “what to do when this fails,” often a business decision (skip the claim, halt the run, retry).- Data-area gates (
RTVDTAARAthen a test) — turn whole steps on or off; a CL program that only submits the nightly job when a flag is set is enforcing an operational rule.
The extraction method
Follow this loop for each rule. It is deliberately data-first, because logic clusters around the reads and writes — the call stack tells you how the program is organized, not what it decides.
flowchart TD subgraph find["Find the rule"] READS["fas:fa-magnifying-glass Follow reads and writes to a file"] CONSTRUCT["fas:fa-code-branch Land on a rule construct<br/>(subroutine, 88, EVALUATE, edit routine)"] end subgraph capture["Capture the rule"] INPUTS["fas:fa-arrow-right-to-bracket Trace inputs"] LOGIC["fas:fa-gears Read the logic"] OUTPUT["fas:fa-arrow-right-from-bracket Trace output and effect"] RESOLVE["fas:fa-key Resolve coded values"] NAME["fas:fa-pen Name it in plain English"] end READS --> CONSTRUCT --> INPUTS --> LOGIC --> OUTPUT --> RESOLVE --> NAME classDef find fill:#e8eef7,stroke:#5b7aa8,color:#1a2a3a classDef capture fill:#e5efe6,stroke:#5a8a5f,color:#1e2e1f class READS,CONSTRUCT find class INPUTS,LOGIC,OUTPUT,RESOLVE,NAME captureWhat to notice: finding a rule is a data hunt (follow the file I/O), but capturing it is a five-step transcription — and resolving the coded values comes before naming, because you cannot name a rule you cannot read.
The five capture steps in words:
- Trace each rule’s inputs. Which fields, constants, and data areas feed the decision? Follow each back to where it is set — a threshold in WORKING-STORAGE, a value read from another file, a data area gate.
- Read the logic. The calculation or the branch. Write it as a formula or an if/then a policy analyst would recognize, not as code.
- Trace the output and effect. What field is written, what status is set, what record is created or skipped. Note any transform-before-store on the write path.
- Resolve coded values. A status of
'A'means nothing until you find the88level, the comment, the message file, or the screen that defines it.'A'= “Approved”,'H'= “Hold”, indicator*IN60= “over the weekly cap.” An unresolved code is an open question, not a finished rule. - Name the rule in plain English. “Weekly benefit is base-period high-quarter wages divided by 26, capped at the state maximum.” The name is what a non-programmer reads; the source citation is what a programmer verifies.
The rule-catalog template
Every mined rule becomes one row. The catalog is the phase-3 deliverable and the raw material for phase 5 test generation — each rule with clear inputs and outputs becomes a golden-master test case. Keep it as a table; one rule per row.
| Rule ID | Name | Trigger | Inputs | Logic (plain English) | Output / Effect | Source (program:lines) | Confidence | Open questions |
|---|---|---|---|---|---|---|---|---|
| BR-014 | Weekly benefit amount | Claim reaches payment calc in nightly run | High-quarter wages (WAGE.HQ_AMT), state max (data area MAXWBA), divisor constant 26 | Weekly amount = high-quarter wages ÷ 26, rounded down to whole dollars, then capped at the state maximum | Sets CLAIMS.BENEFIT_AMT (post-cap, post-round); if wages missing, skips and flags for review | CALCBEN.RPGLE:210-268 | Confirmed | Is the divisor always 26, or does a data area override it for some programs? |
| BR-021 | Base-period wage eligibility | Claim edit before payment write | Base-period wages (WAGE reads), minimum-earnings threshold (WS-MIN-EARN = 1600), disqualification count | Deny if total base-period wages < 1600 or disqualification count ≥ 3 | Sets denial status 'D'; writes reason code to CLAIMS.DENY_RSN | CHKELIG.RPGLE:88-141 | Inferred | Is 1600 hard-coded or read from config elsewhere? [VERIFY] |
What to notice: every row cites a program and line range, resolves its coded values in the Logic column, and carries its own confidence and open questions — a reader can act on a Confirmed row and knows to double-check an Inferred one. The [VERIFY] marker in BR-021 flags a fact the analyst could not confirm from the source alone.
Confidence and open-questions discipline
A rule catalog that hides its own uncertainty is worse than none — it reads as authoritative and gets built into the rebuild unchecked. Mark every rule with its confidence, and keep the open questions visible. Two levels are enough:
The two confidence levels
Confirmed
You read the code, resolved every coded value, and the inputs and output are unambiguous. A confirmed rule can seed a test case directly.
Inferred
The logic is probably right but rests on an assumption — an unresolved code, a threshold that might be overridden elsewhere, a REDEFINES whose selecting condition you have not found. Mark the specific uncertainty in the open-questions column and flag the unverified fact [VERIFY] inline, so a later pass (or an engineer with production access) can close it.
Calibrate [VERIFY] — do not under-claim
[VERIFY] is not free. A fact wrongly marked [VERIFY] sends a reviewer to re-check a finding the source already proves, so over-marking wastes exactly the attention the marker is meant to direct. The failure to guard against is under-claiming, not only over-claiming.
The rule: if the confirming source file is in hand, read it and mark Confirmed. Reserve [VERIFY] for a claim that genuinely depends on a file you did not open — an unread config member, a data area you cannot see, a copybook not in the export.
- The routing literals
'A'/'U'are in the program you are reading → read the branch, mark Confirmed, cite the line. - The reject codes
100–103are defined in the same source → resolve them, mark Confirmed. - A threshold “might be overridden in a config file not in this export” → genuinely unread →
[VERIFY].
Before you write [VERIFY], ask: is the confirming source open in front of me? If yes, delete the marker and cite the line instead.
The rule: never silently promote an inference to a fact. An Inferred rule with a clear open question is honest and useful; an Inferred rule dressed as Confirmed is a production incident waiting for its trigger. When you finish a program, the count of Inferred rules and open questions is your honest measure of how much is left to understand.