FA-58596 / Loan amortization schedules / Open access
Negative amortization cap recast: recast principal · case 01
The recast payment leaves capitalized interest unamortized.
ROOT CAUSE
The recast levels the original principal instead of the grown balance.
VERIFIED REPAIR
Recast on the current balance including capitalized interest.
Unsuccessful approach: Excluding deferred interest still leaves part of the balance unamortized.
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 += interest - pay
if recast is None and bal * 10000 > limit:
recast = k
pay = level(P, 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: recast principal', {'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': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 6', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}]], [['regression: recast principal', {'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': 100000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 96962, 'recast_month': None, 'deferred': 0, 'payment': 1000}]], [['regression: recast principal', {'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': 1000000, 'rate_bp': 600, 'min_payment': 20000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 618521, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 5', {'principal': 100000, 'rate_bp': 600, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 90887, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 174636, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: recast principal', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 250, 'cap_pct_bp': 10100, 'months': 12}, {'balance': -3, 'recast_month': 1, 'deferred': 1750, 'payment': 10397}], ['control 1', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 187696, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 2', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 222078, 'recast_month': 6, 'deferred': 22078, 'payment': 0}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 5', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: recast principal', {'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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: recast principal | {'balance': 53364, 'deferred': 52312, 'payment': 507512, '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': 113849, 'deferred': 0, 'payment': 4000, 'recast_month': None} | {'balance': 113849, 'deferred': 0, 'payment': 4000, 'recast_month': None} | Passed |
| control 6 | {'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None} | {'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None} | Passed |
SHA-256 / 88443bb4d1c1d79c0c390a5719157434a507beb4b9a29554da452373061736ab
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 += interest - pay
if recast is None and bal * 10000 > limit:
recast = k
pay = level(bal - deferred, 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: recast principal', {'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': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 6', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}]], [['regression: recast principal', {'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': 100000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 96962, 'recast_month': None, 'deferred': 0, 'payment': 1000}]], [['regression: recast principal', {'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': 1000000, 'rate_bp': 600, 'min_payment': 20000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 618521, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 5', {'principal': 100000, 'rate_bp': 600, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 90887, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 174636, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: recast principal', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 250, 'cap_pct_bp': 10100, 'months': 12}, {'balance': -3, 'recast_month': 1, 'deferred': 1750, 'payment': 10397}], ['control 1', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 187696, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 2', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 222078, 'recast_month': 6, 'deferred': 22078, 'payment': 0}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 5', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: recast principal', {'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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: recast principal | {'balance': 53364, 'deferred': 52312, 'payment': 507512, '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': 113849, 'deferred': 0, 'payment': 4000, 'recast_month': None} | {'balance': 113849, 'deferred': 0, 'payment': 4000, 'recast_month': None} | Passed |
| control 6 | {'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None} | {'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None} | Passed |
SHA-256 / 43e61d0dc1e10db797b73068d07625e50f96f31bc9490462f9c817e9afe2f0ed
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: recast principal', {'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': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 6', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}]], [['regression: recast principal', {'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': 100000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 96962, 'recast_month': None, 'deferred': 0, 'payment': 1000}]], [['regression: recast principal', {'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': 1000000, 'rate_bp': 600, 'min_payment': 20000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 618521, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 5', {'principal': 100000, 'rate_bp': 600, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 90887, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 174636, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: recast principal', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 250, 'cap_pct_bp': 10100, 'months': 12}, {'balance': -3, 'recast_month': 1, 'deferred': 1750, 'payment': 10397}], ['control 1', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 187696, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 2', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 222078, 'recast_month': 6, 'deferred': 22078, 'payment': 0}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 5', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: recast principal', {'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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: recast principal | {'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': 113849, 'deferred': 0, 'payment': 4000, 'recast_month': None} | {'balance': 113849, 'deferred': 0, 'payment': 4000, 'recast_month': None} | Passed |
| control 6 | {'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None} | {'balance': 1000000, 'deferred': 0, 'payment': 20000, 'recast_month': None} | Passed |
SHA-256 / 74e6318f43cdde75b7daf1ec10f851a52d6952718f2746c9a17571e522d52da2
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.172151+00:00.
Case digest / 403d9c6bc5241da4d8e1fdb50d0019ba48123becdcc75af646e790a95f4207b7