Skip to content
PYD ENTERPRISES
Mainframe8 min read

Extracting interest calculation rules from a COBOL loan servicing core

Interest accrual looks like one formula. In a servicing core it is a slice of a dozen programs, three copybooks and a batch schedule, and every piece changes the number.

Venkatesh IyerPrincipal Modernization Architect, PYD ENTERPRISES
On this page · 6 sections
  1. 01Start from the field, not the program
  2. 02The core paragraph
  3. 03Following the slice outward
  4. 04The 30/360 detail that is not 30/360
  5. 05Tests that prove the rewrite
  6. 06Why the slice is the deliverable

Start from the field, not the program

The question a lending product owner asks is simple: how does the system calculate the interest on this loan? Most discovery teams answer it by finding the program with ACCRUE in its name and reading it. That finds the formula. It misses the eligibility rules, the day-count basis selection, the rounding step in a separate posting program, and the month-end adjustment that runs in a different JCL stream.

A better starting point is the field that holds the answer. In the core we will use as an example, a commercial lending system of about 1.8 million lines, that field is LN-ACCRUED-INT in the loan master record. A backward program slice from every write to that field, across programs, gives you every statement that can influence its value. Everything outside the slice is irrelevant to the question, however interesting it looks.

The core paragraph

The slice starts here. The daily accrual is computed with a day-count basis chosen by an 88-level condition, zeroed for loans in non-accrual status, and added to the running accrued balance.

Three things in these fifteen lines are business rules: the choice of day-count basis, the non-accrual override, and the precision of WS-DAILY-INT. That field is declared PIC S9(7)V9(6) COMP-3, carrying six decimal places so that daily accruals do not lose sub-cent amounts. Rounding to cents happens later, in the posting program, at month end. Collapse those two steps into one and the interest on a large balance drifts by a few cents a month, which is enough to fail reconciliation.

LNACCR02.cblCOBOL
1 3200-ACCRUE-DAILY.
2 IF LN-DAYCOUNT-ACT365
3 COMPUTE WS-DAILY-INT ROUNDED =
4 LN-PRIN-BAL * LN-RATE / 365
5 ELSE
6 COMPUTE WS-DAILY-INT ROUNDED =
7 LN-PRIN-BAL * LN-RATE / 360
8 END-IF
9 IF LN-STATUS-NONACCRUAL
10 MOVE ZERO TO WS-DAILY-INT
11 END-IF
12 ADD WS-DAILY-INT TO LN-ACCRUED-INT
13 ON SIZE ERROR PERFORM 9200-ACCRUAL-OVERFLOW
14 END-ADD.
15 3200-EXIT.
16 EXIT.

Following the slice outward

The interesting rules are not in that paragraph. They are in the places that set its inputs.

  • LN-DAYCOUNT-ACT365 is an 88-level on LN-DAYCOUNT-CD. The slice shows that code is set at loan boarding from the product table, and overwritten in one place: a maintenance transaction that converts certain participations to 30/360. That second write is a rule nobody listed.
  • LN-STATUS-NONACCRUAL is set by a delinquency program when days past due exceed a threshold read from a parameter file, unless a manual hold flag is set. The threshold is data; the hold exception is a rule.
  • LN-RATE for variable loans is recomputed nightly from an index and a margin, with a floor and a cap. The floor is applied before the cap. If both are breached because of a data error, the cap wins.
  • The month-end posting program rounds LN-ACCRUED-INT to cents half away from zero and carries the remainder forward in a separate field.

Each of those becomes a decision table row or a formula with its own source reference. The accrual calculation is not one rule. It is a small decision model with seven decisions, and only one of them is in the program everyone pointed to.

The 30/360 detail that is not 30/360

Read the code closely and you notice the ELSE branch divides by 360, but the daily paragraph runs once per calendar day. That is actual/360, not 30/360. True 30/360 would require adjusting day counts at month boundaries, and this program does not. The product documentation calls the basis 30/360. The code implements actual/360. For a year with 365 days that is about 1.4% more interest than the documented basis would produce.

Whether that is a defect or the policy is not a question static analysis can answer. It is exactly the question the sign-off view is for. The extracted rule shows the behavior, the code, and a note that the documented name and the computed behavior differ. The lending product owner decides which one the new system implements, and if they choose the documented basis, the change is a conscious product decision with a customer communication plan, not a migration accident.

Tests that prove the rewrite

The equivalence suite for this decision model is generated from the branch predicates in the slice. Each fixture names the rule it exercises.

accrual.fixtures.json (excerpt)JSON
1[
2 { "rule": "ACCR-BASIS-ACT365", "given": { "principal": "250000.00", "rate": "0.0725", "dayCount": "A" },
3 "expect": { "dailyInterest": "49.657534" } },
4 { "rule": "ACCR-BASIS-ACT360", "given": { "principal": "250000.00", "rate": "0.0725", "dayCount": "3" },
5 "expect": { "dailyInterest": "50.347222" } },
6 { "rule": "ACCR-NONACCRUAL", "given": { "principal": "250000.00", "rate": "0.0725", "status": "N" },
7 "expect": { "dailyInterest": "0.000000" } }
8]

Why the slice is the deliverable

Check the arithmetic: 250,000 times 0.0725 is 18,125; divided by 365 that is 49.6575342..., rounded to six places 49.657534; divided by 360 it is 50.3472222..., rounded to 50.347222. A new Java or Go accrual service has to reproduce those to the sixth decimal, day after day, and then post the same cents at month end.

A slice-based inventory turns an open question, what does this system do with interest, into a closed list of decisions with code behind each. That is what lets a banking modernization program commit to a build estimate. The migration guide for COBOL to Java shows how the same slice maps to Spring services.

Keep reading

All posts

Legacy anti-patterns catalog

25 legacy anti-patterns, mapped to modern designs.

From COBOL ALTER and GO TO DEPENDING ON to EJB entity beans and PL/SQL cursors: why each is dangerous during migration, how it is detected, and the before and after code.

Explore the catalog

See your own code as a rule inventory.

Run PYD ENTERPRISES on up to 100,000 lines of COBOL, Java or .NET in your private VPC and review the extracted rules beside their source lines.