ENGINE 27
SFS-E27-INT · DEBT SERVICE · REV 2026-07-24 · PRODUCTION
PRODUCTION = runnable end-to-end, CI-backed, full test suite passing. All data fictional and seeded.
Interest Accrual & Loan Amortization
Does the interest a note reports equal the interest its own terms produce?
A lender carries a portfolio of promissory notes, and every period somebody accrues interest on each one, rolls its balance forward, and posts a journal entry that is supposed to tie to both sides. This engine proves, note by note, that the interest a schedule reports is the interest its own terms produce. It reads the seven artifacts a debt-service record carries -- the note register, the roll-forward schedule, the interest journal, the trial balance, the interest summary, the maturity watchlist and the debt-service report -- and runs twenty-eight deterministic controls over them. Is the loan file complete and the review date legible, are the note ids unique and each note's rate, day-count and principal well-formed with two distinct sides, does every schedule row carry its key fields and tie its day count to its own span, does each period's accrued interest re-derive from beginning balance times rate times days over the day-count year, does every ending balance rebuild from beginning plus advance plus accrued minus payment and continue as the next period's beginning, does the schedule open at the note's principal, does each period accrue at the stated or stepped default rate, does accrual stop at maturity, is a prepayment-barred note left alone and a subordinate kept behind its senior, no payment beyond the balance owed and a payoff settled to zero, do the reciprocal receivable and payable and the interest income and expense each tie the schedule, does the journal prove to zero and tie the accrual and cover every note, and do the settled counts and the maturity watchlist recompute from the schedule beneath them. It rebuilds every figure through one shared accrual kernel, compares with exact ==, measures every maturity and gate test to the as_of review date rather than the system clock, and never writes to a source artifact.
An amortization schedule is the most trusted column on the debt-service worksheet: a beginning balance, a rate, an accrual, an ending, repeated down the page. That is exactly why it drifts. None of the ways it goes wrong look wrong in a column of balances. A period accrues at the wrong rate, on the wrong balance, or over the wrong day count, and the booked interest is off by an amount too small to notice against a six-figure balance -- until it compounds. One period's ending is not the next period's beginning, an advance is added but never lands, a payment relieves more than was owed, or the payoff leaves a residual cent instead of settling to zero. A gate is missed -- interest keeps accruing past maturity, the default rate never steps after an event of default, a note barred from prepayment is paid down early, a subordinated note is paid ahead of the senior it sits behind. And the two sides quietly stop reconciling -- the receivable and the payable drift apart, interest income no longer equals interest expense, the monthly journal no longer ties the accrual it was meant to record. The engine rebuilds every accrual and every balance from the note's own terms through one shared kernel and compares with exact ==, enforces each gate as a date or a threshold rather than a judgement, ships a planted-defect file for every registered control, and states its benchmark as a command rather than a claim. A cent of drift is invisible until someone recomputes the interest the note already booked.
Architecture
functional block stack · static overviewSelect a layer
Nine control families run in registry order over each loan file. The first proves the others had something to read and that the review date is legible, because a control that passes on absent evidence reports assurance it never performed.
Key specifications
at a glanceWhat it does for you
plain termsA lender carries notes receivable and interest income; the reciprocal borrower carries notes payable and interest expense; and every period somebody accrues interest, rolls the balance forward, and posts a journal entry that is supposed to tie to both sides. The question is narrower and entirely arithmetic: rebuild each note's accrual from its stated rate and day-count, roll the balance forward row by row, and prove the schedule, the reciprocal GL and the journal all agree. Four failures hide inside that, and none of them look wrong in a column of numbers. The accrual drifts from the terms -- a period accrues at the wrong rate, on the wrong balance, or over the wrong day count, and the booked interest is off by an amount too small to notice against a six-figure balance until it compounds. The roll-forward breaks -- one period's ending is not the next period's beginning, an advance is added but never lands, a payment relieves more than was owed, the payoff leaves a residual cent instead of settling to zero. A gate is missed -- interest keeps accruing past maturity, the default rate never steps after an event of default, a note barred from prepayment is paid down early, a subordinated note is paid ahead of the senior it sits behind. And the two sides stop reconciling -- the receivable and the payable drift apart, interest income no longer equals interest expense, the monthly journal no longer ties the accrual it was meant to record. None of these are judgment calls. They are equalities and date gates -- an accrual against beginning times rate times days with exact ==, an ending against its re-derivation, a period start against a maturity date, a payment against the period's accrued interest -- which a deterministic control settles better than a schedule inherited by whoever tracks the portfolio this year.
The seeded demo runs every control over every loan file7. The CLI generates thirty fictional loan files -- one clean baseline and one carrying each of the twenty-nine planted defects -- then runs all twenty-eight controls over each. The clean file returns PASS with no flags; every defect file returns REVIEW or FAIL and names the control it tripped along with the reason that control exists.
The day-count and the accrual are independent controls8. A schedule can accrue interest that re-derives perfectly and still be wrong, because it re-derives against the very day count that is wrong. One defect file stretches a period's end date without touching its stated day count: acc_interest_rederives still passes -- the accrual matches the stated day count -- while acc_day_count_ties fails, because the stated day count no longer equals the span between the period's own two dates. The two controls catch different failures at the same boundary.
The accrual is recomputed, not read back9. A defect file adds ten dollars to one period's stated accrued figure without changing the note's rate, balance or day count. acc_interest_rederives rebuilds the interest from beginning times rate times days through the shared kernel and fails on the cent, proving the accrued figure is recomputed from the terms beneath it rather than trusted as stated.
Functional block diagram
engineering · each block links to its sourcePlain terms
- Seeded Loan Files. fictional loan files enter the control registry
- Structural Precondition. is the loan file complete and the review date legible
- Note Register. is the register sound and each note's terms well-formed
- Accrual Re-Derivation. does each period's interest re-derive from the terms
- Roll-Forward. does the balance roll forward, begin to end
- Rate, Maturity & Prepayment Gates. do the rate, maturity and prepayment gates hold
- Payment Waterfall & Subordination. does the payment waterfall and subordination order hold
- Reciprocal Reconciliation. do the two sides of each note reconcile
- Journal & Trial-Balance. does the journal balance, tie the accrual, and cover every note
- Rollup Recompute. do the rollups recompute from the schedule
- Register, Schedule & Trial Balance. the seven artifacts and the accrual kernel every rule resolves against
- Verdict and Findings. every finding, with the reason it exists, ends at a person
Engineering
- Seeded Loan Files. generate_corpus writes one clean baseline plus one planted-defect file for every named control, thirty files in total. Only each note's terms and the schedule events -- the period dates, advances and payment intent -- are stated; every accrual and ending balance, each note's settled flag and accrued total, the reciprocal trial balance, the interest journal, the maturity watchlist and the portfolio counts are re-derived through the same accrual kernel the engine later recomputes with, so the relationships the engine tests are the same relationships that produced the data.
- Structural Precondition. One control. FAILs a file missing any of the seven artifact types or carrying a duplicate, or an unreadable as_of review date, so no downstream rule holds vacuously on absent evidence.
- Note Register. Four controls: unique note ids, the required term and GL-account fields present, a well-formed rate / day-count / principal, and a lender and borrower that are two distinct entities.
- Accrual Re-Derivation. Three controls: every row carries its key fields, its day count ties its own span, and its accrued interest rebuilds as beginning times rate times days over the day-count year, truncated to the cent and compared with exact ==.
- Roll-Forward. Four controls: every row names a real note, each ending rebuilds from beginning + advance + accrued - payment, each ending is the next beginning, and the first period opens at the note's principal.
- Rate, Maturity & Prepayment Gates. Four controls: each period accrues at the stated or stepped default rate, no interest accrues on or after maturity, a prepayment-barred note is not paid down early, and an unsettled note maturing inside the lead-time window is flagged.
- Payment Waterfall & Subordination. Three controls: no payment exceeds the balance owed in its period, a payoff-expected note settles to exactly zero, and a subordinate does not reduce principal while its senior is unrepaid.
- Reciprocal Reconciliation. Two controls: the note's receivable equals its payable equals its schedule ending, and its interest income equals its expense equals its re-derived accrual.
- Journal & Trial-Balance. Four controls: the interest journal proves to zero, its booked interest ties the re-derived accrual, every note's GL accounts appear on the trial balance, and every trial-balance account maps back to a note.
- Rollup Recompute. Three controls: each note's settled flag and accrued total recompute from its schedule, the portfolio settled / outstanding counts tie the summary, and the maturity watchlist ties the note evidence.
- Register, Schedule & Trial Balance. The note register carries each note's id, sides, principal, rate, day-count, maturity, default-event, prepayment and subordination terms and its four GL account references; the roll-forward schedule carries each period's begin / advance / accrued / pay / end row; the trial balance carries the reciprocal receivable, payable, income and expense balances; and the interest summary, maturity watchlist, interest journal and debt-service report carry the derived determinations and counts. Every accrual, roll-forward, gate, waterfall, reciprocal, journal and rollup control resolves against these through the shared accrual kernel.
- Verdict and Findings. Findings roll up per loan file: any FAIL is FAIL, FLAGs without FAILs are REVIEW, clean is PASS. The CLI exit code is the verdict, so a pipeline can gate on it. Reports carry no timestamps or absolute paths, which is what makes the committed report diffable.
Instruction set
every public command| Command | Operation | Output | Exit | Artifacts |
|---|---|---|---|---|
python run.py | regenerate the seeded fictional corpus, run all twenty-eight controls, write both reports | per-file verdicts and every actionable finding, then the overall verdict | 2 for the bundled corpus, which carries a planted defect for every control by design | interest_report.json, interest_report.md, samples/ |
python -m interest_engine samples | analyze an existing folder of loan files without regenerating it | per-file verdicts and findings | 0 PASS / 1 REVIEW / 2 FAIL / 3 usage | none unless --json or --md is passed |
python -m interest_engine samples --generate --quiet | regenerate the corpus and print only the overall verdict | a single verdict line | 0 PASS / 1 REVIEW / 2 FAIL / 3 usage | samples/ |
python -m pytest interest_engine/tests -q | run the full test suite for this engine | 10402 passed | 0 when every test passes | none |
Benchmarks
measured demo results| Measure | Result |
|---|---|
| Engine tests10 | 10,402 tests collected live collection from the engine directory |
| Registered controls11 | 28 controls counted from the registry, not from documentation |
| Planted defects12 | 29 defect files every registered control has at least one file that trips it |
| Control coverage13 | 100 percent of controls with a planted defect no control ships without a file demonstrating it firing |
Control characteristics
engineeringPlain terms
The engine has no write path and therefore no autonomy to gate. It produces findings; a person decides whether a note is carried into the close and whether a maturing note is refinanced or repaid.
Engineering
Deterministic envelope. seeded fictional inputs, read-only operation, offline default mode.
Demo gate. FAIL on the bundled corpus, by design -- it carries a planted defect for every registered control
| Severity | Verdict | Action |
|---|---|---|
| No findings above PASS | PASS | carry the mechanically clean loan file into the documented sign-off and debt-service review |
| One or more FLAG, no FAIL | REVIEW | a human resolves each flag; a note maturing inside the lead-time window and a maturity watchlist that does not tie the evidence both land here |
| One or more FAIL | FAIL | the note or figure is not carried into the close until the failing control is cleared -- an accrual that does not re-derive, a broken roll-forward, a missed gate, a reciprocity break, or a rollup that does not recompute |
- Read-only: loan files are parsed and never written back, so the engine cannot introduce the break it reports.
- Integer cents throughout, compared with exact ==; there is no tolerance band on an accrual or a balance, and a cent off the re-derivation is reported as a break.
- A balance or accrual figure that should be integer cents but is not produces an AMOUNT_INVALID finding contained to the one row it was read on, rather than being coerced into the number the engine is meant to audit.
- One accrual kernel: the controls and the generator share the arithmetic in interest_engine.accrual, so a re-derivation cannot disagree with the data that produced it.
- Every maturity, rate-step and gate test is measured to the as_of review date carried in the file, never the system clock, so the same file yields the same findings on any day.
- Deterministic and byte-stable: same inputs produce the same findings in the same order, with no timestamps or absolute paths in any output.
- Absent evidence is never a passing control; a missing artifact fails completeness rather than letting the controls that read it pass unread.
Operating limits
what it refuses to do- The engine never posts a journal entry, books an accrual, files a debt-service report, contacts a lender or borrower, or writes to a source artifact. It reads the loan file and stops.14
- It reads the seven artifacts the debt-service record emits. It does not connect to a loan-servicing system, a general ledger, a bank or the underlying note agreements.15
- It re-derives each accrual at the stated rate, day-count and terms the note carries; it does not decide whether that rate, that day-count convention or that maturity is the correct one for the note, or whether an event of default genuinely occurred.16
- It proves the schedule ties out -- the accrual re-derives, the balance rolls forward, the gates hold and the two sides reconcile -- not that a closing or advance actually occurred or that the note is collectible. That judgment is a credit officer's and a reviewer's.17
- It takes the note terms and schedule events at face value and recomputes everything above them. All shipped data is fictional and the accrual period is set in a fictional future.18
See it run
control architecture
Integration
how to run itDistribution: public repository, MIT license.
- 1python -m pytest --collect-only -q -o addopts= (interest-accrual-automation/)
- 2len(interest_engine.engine.REGISTRY)
- 3distinct rule-id prefixes in interest_engine.engine.REGISTRY
- 4samples/*.json after python run.py
- 5len(interest_engine.model.DOC_TYPES)
- 6interest_engine/tests boundary tests on the cent and the maturity / rate-step date
- 7python run.py (interest-accrual-automation/); interest_engine/tests planted-defect suite
- 8interest_engine/generate.py DEFECTS day_count_mismatch / accrual_wrong
- 9interest_engine/generate.py DEFECTS accrual_wrong -> acc_interest_rederives
- 10python -m pytest --collect-only -q -o addopts= (interest-accrual-automation/)
- 11len(interest_engine.engine.REGISTRY)
- 12len(interest_engine.generate.DEFECTS)
- 13interest_engine/tests: every registered rule has a planted defect
- 14interest-accrual-automation/README.md and interest_engine read-only / determinism tests
- 15interest_engine/model.py DOC_TYPES
- 16interest_engine/engine.py note_ and gate_ families
- 17interest_engine/engine.py rf_ and recip_ families
- 18interest_engine/generate.py
Show us where the hours go.
One conversation: you describe the work that consumes your team's month; we tell you plainly what this engine can take over, what it can't, and what a scoped first phase would cost. Your people keep approval authority.
Book a free consultation