FAILURE MAP
← Case archive

FA-58611 / Loan amortization schedules / Open access

Negative amortization cap recast: balance roll-forward · case 01

Payments above interest never reduce the balance.

Verified by executionVariant 1 · 7 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Only shortfalls are added; surplus payments are not applied to principal.

VERIFIED REPAIR

Roll the balance forward by interest minus payment in every month.

Unsuccessful approach: Applying only surpluses stops capitalizing the deferred interest.

Case contract

x = {'principal', 'rate_bp', 'min_payment', 'cap_pct_bp' (e.g. 11000 = 110% of principal), 'months'}. Each month interest = round_half_up(balance*bp/120000); the shortfall interest - payment (if positive) is deferred; balance += interest - payment. The first time balance*10000 exceeds principal*cap_pct_bp the loan recasts once: payment = level(balance, months - k) (exact annuity, half-up). Return {'balance', 'recast_month' or None, 'deferred', 'payment'}.

Why this case matters

Amortization engines drive borrower statements, payoff quotes and investor remittances; a misplaced rounding step, boundary or ordering rule compounds across hundreds of periods.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    n = x['months']
    bal = P
    pay = x['min_payment']
    deferred = 0
    recast = None
    limit = P * x['cap_pct_bp']
    for k in range(1, n + 1):
        interest = rnd(bal * bp, 120000)
        if pay < interest:
            deferred += interest - pay
        bal += max(interest - pay, 0)
        if recast is None and bal * 10000 > limit:
            recast = k
            pay = level(bal, n - k)
    return {'balance': bal, 'recast_month': recast, 'deferred': deferred, 'payment': pay}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: balance roll-forward', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 52312, 'payment': 534061}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1063081, 'recast_month': None, 'deferred': 63081, 'payment': 10000}], ['control 2', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 5000}], ['control 5', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10500, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 500}]], [['regression: balance roll-forward', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 0, 'recast_month': 2, 'deferred': 4040, 'payment': 22715}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 500}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 3', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 10200, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 219024, 'recast_month': None, 'deferred': 19024, 'payment': 500}]], [['regression: balance roll-forward', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 4, 'recast_month': 2, 'deferred': 20200, 'payment': 113575}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['control 3', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 2500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1015189, 'recast_month': None, 'deferred': 15189, 'payment': 2500}], ['control 4', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10500, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 106341, 'recast_month': None, 'deferred': 6341, 'payment': 500}]], [['regression: balance roll-forward', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 187696, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['regression: balance roll-forward, partial-repair probe', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 222078, 'recast_month': 6, 'deferred': 22078, 'payment': 0}], ['control 1', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 2', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 3', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10100, 'months': 24}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 2000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 2000}]], [['regression: balance roll-forward', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 109498, 'payment': 571446}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 24}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 3', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['control 4', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10500, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 6', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: balance roll-forward{'balance': 1052312, 'deferred': 52312, 'payment': 534061, 'recast_month': 10}{'balance': 1, 'deferred': 52312, 'payment': 534061, 'recast_month': 10}Failed
control 1{'balance': 1063081, 'deferred': 63081, 'payment': 10000, 'recast_month': None}{'balance': 1063081, 'deferred': 63081, 'payment': 10000, 'recast_month': None}Passed
control 2{'balance': 1000000, 'deferred': 0, 'payment': 10000, 'recast_month': None}{'balance': 1000000, 'deferred': 0, 'payment': 10000, 'recast_month': None}Passed
control 3{'balance': 200000, 'deferred': 0, 'payment': 4000, 'recast_month': None}{'balance': 200000, 'deferred': 0, 'payment': 4000, 'recast_month': None}Passed
control 4{'balance': 1000000, 'deferred': 0, 'payment': 5000, 'recast_month': None}{'balance': 1000000, 'deferred': 0, 'payment': 5000, 'recast_month': None}Passed
control 5{'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None}{'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None}Passed
control 6{'balance': 100000, 'deferred': 0, 'payment': 500, 'recast_month': None}{'balance': 100000, 'deferred': 0, 'payment': 500, 'recast_month': None}Passed

SHA-256 / 4427b64b07197032b4fd364853b7cbfdb9b67daf07cdbde31320a1962dd3f687

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    n = x['months']
    bal = P
    pay = x['min_payment']
    deferred = 0
    recast = None
    limit = P * x['cap_pct_bp']
    for k in range(1, n + 1):
        interest = rnd(bal * bp, 120000)
        if pay < interest:
            deferred += interest - pay
        bal -= pay - interest if pay > interest else 0
        if recast is None and bal * 10000 > limit:
            recast = k
            pay = level(bal, n - k)
    return {'balance': bal, 'recast_month': recast, 'deferred': deferred, 'payment': pay}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: balance roll-forward', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 52312, 'payment': 534061}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1063081, 'recast_month': None, 'deferred': 63081, 'payment': 10000}], ['control 2', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 5000}], ['control 5', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10500, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 500}]], [['regression: balance roll-forward', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 0, 'recast_month': 2, 'deferred': 4040, 'payment': 22715}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 500}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 3', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 10200, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 219024, 'recast_month': None, 'deferred': 19024, 'payment': 500}]], [['regression: balance roll-forward', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 4, 'recast_month': 2, 'deferred': 20200, 'payment': 113575}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['control 3', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 2500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1015189, 'recast_month': None, 'deferred': 15189, 'payment': 2500}], ['control 4', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10500, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 106341, 'recast_month': None, 'deferred': 6341, 'payment': 500}]], [['regression: balance roll-forward', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 187696, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['regression: balance roll-forward, partial-repair probe', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 222078, 'recast_month': 6, 'deferred': 22078, 'payment': 0}], ['control 1', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 2', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 3', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10100, 'months': 24}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 2000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 2000}]], [['regression: balance roll-forward', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 109498, 'payment': 571446}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 24}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 3', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['control 4', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10500, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 6', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: balance roll-forward{'balance': 1000000, 'deferred': 60000, 'payment': 5000, 'recast_month': None}{'balance': 1, 'deferred': 52312, 'payment': 534061, 'recast_month': 10}Failed
control 1{'balance': 1000000, 'deferred': 60000, 'payment': 10000, 'recast_month': None}{'balance': 1063081, 'deferred': 63081, 'payment': 10000, 'recast_month': None}Failed
control 2{'balance': 1000000, 'deferred': 0, 'payment': 10000, 'recast_month': None}{'balance': 1000000, 'deferred': 0, 'payment': 10000, 'recast_month': None}Passed
control 3{'balance': 200000, 'deferred': 0, 'payment': 4000, 'recast_month': None}{'balance': 200000, 'deferred': 0, 'payment': 4000, 'recast_month': None}Passed
control 4{'balance': 1000000, 'deferred': 0, 'payment': 5000, 'recast_month': None}{'balance': 1000000, 'deferred': 0, 'payment': 5000, 'recast_month': None}Passed
control 5{'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None}{'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None}Passed
control 6{'balance': 100000, 'deferred': 0, 'payment': 500, 'recast_month': None}{'balance': 100000, 'deferred': 0, 'payment': 500, 'recast_month': None}Passed

SHA-256 / ae20a8755eecc190a357db9e17b407112724fc154e14ae91ca7687bb57970338

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    n = x['months']
    bal = P
    pay = x['min_payment']
    deferred = 0
    recast = None
    limit = P * x['cap_pct_bp']
    for k in range(1, n + 1):
        interest = rnd(bal * bp, 120000)
        if pay < interest:
            deferred += interest - pay
        bal += interest - pay
        if recast is None and bal * 10000 > limit:
            recast = k
            pay = level(bal, n - k)
    return {'balance': bal, 'recast_month': recast, 'deferred': deferred, 'payment': pay}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: balance roll-forward', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 52312, 'payment': 534061}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1063081, 'recast_month': None, 'deferred': 63081, 'payment': 10000}], ['control 2', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 5000}], ['control 5', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10500, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 500}]], [['regression: balance roll-forward', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 0, 'recast_month': 2, 'deferred': 4040, 'payment': 22715}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 500}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 3', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 10200, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 219024, 'recast_month': None, 'deferred': 19024, 'payment': 500}]], [['regression: balance roll-forward', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 4, 'recast_month': 2, 'deferred': 20200, 'payment': 113575}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['control 3', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 2500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1015189, 'recast_month': None, 'deferred': 15189, 'payment': 2500}], ['control 4', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10500, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 106341, 'recast_month': None, 'deferred': 6341, 'payment': 500}]], [['regression: balance roll-forward', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 187696, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['regression: balance roll-forward, partial-repair probe', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 222078, 'recast_month': 6, 'deferred': 22078, 'payment': 0}], ['control 1', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 2', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 3', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10100, 'months': 24}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 2000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 2000}]], [['regression: balance roll-forward', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 109498, 'payment': 571446}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 24}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 3', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['control 4', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10500, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 6', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: balance roll-forward{'balance': 1, 'deferred': 52312, 'payment': 534061, 'recast_month': 10}{'balance': 1, 'deferred': 52312, 'payment': 534061, 'recast_month': 10}Passed
control 1{'balance': 1063081, 'deferred': 63081, 'payment': 10000, 'recast_month': None}{'balance': 1063081, 'deferred': 63081, 'payment': 10000, 'recast_month': None}Passed
control 2{'balance': 1000000, 'deferred': 0, 'payment': 10000, 'recast_month': None}{'balance': 1000000, 'deferred': 0, 'payment': 10000, 'recast_month': None}Passed
control 3{'balance': 200000, 'deferred': 0, 'payment': 4000, 'recast_month': None}{'balance': 200000, 'deferred': 0, 'payment': 4000, 'recast_month': None}Passed
control 4{'balance': 1000000, 'deferred': 0, 'payment': 5000, 'recast_month': None}{'balance': 1000000, 'deferred': 0, 'payment': 5000, 'recast_month': None}Passed
control 5{'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None}{'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None}Passed
control 6{'balance': 100000, 'deferred': 0, 'payment': 500, 'recast_month': None}{'balance': 100000, 'deferred': 0, 'payment': 500, 'recast_month': None}Passed

SHA-256 / b1a7f05a56c151cb815e2d8324ce74d3b2826b29ecf707eb613e3f1b3d3d3840

Verification & scope

A deterministic bounded teaching model with stipulated toy lending rules stated in the contract; money is integer cents and rates are basis points; it makes no claim of conformance to any regulation, servicing standard or product. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:46:28.295899+00:00.

Case digest / 2f9b7ea7f1387ea1138bf52dac7d8cc5098946c3e3b289db268ba41125640514