FA-58366 / Loan amortization schedules / Open access
Level payment with final adjustment: monthly interest rounding · case 01
Total interest is a few cents low across the schedule.
ROOT CAUSE
Monthly interest is truncated instead of rounded half-up.
VERIFIED REPAIR
Round monthly interest half-up.
Unsuccessful approach: Rounding any remainder up overcharges interest in most months.
Case contract
x = {'principal', 'rate_bp' (annual, monthly rate = bp/120000), 'months', 'round': 'nearest'|'up'}. The exact annuity payment P*r/(1-(1+r)^-n) (P/n at zero rate) is rounded half-up ('nearest') or up to the cent. Monthly interest is round_half_up(balance*bp/120000); months 1..n-1 pay the level payment and month n pays balance + interest. Return {'payment', 'final_payment', 'total_interest'}.
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):
P = x['principal']
n = x['months']
bp = x['rate_bp']
if bp == 0:
exact = Fraction(P, n)
else:
r = Fraction(bp, 120000)
exact = P * r / (1 - (1 + r) ** -n)
if x['round'] == 'up':
pay = math.ceil(exact)
else:
pay = math.floor(exact + Fraction(1, 2))
bal = P
total_int = 0
last = 0
for k in range(1, n + 1):
q, rem = divmod(bal * bp, 120000)
interest = q
total_int += interest
if k == n:
last = bal + interest
else:
bal = bal + interest - pay
return {'payment': pay, 'final_payment': last, 'total_interest': total_int}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: monthly interest rounding', {'principal': 7, 'rate_bp': 725, 'months': 360, 'round': 'up'}, {'payment': 1, 'final_payment': -1123, 'total_interest': -771}], ['control 1', {'principal': 1000000, 'rate_bp': 13, 'months': 36, 'round': 'up'}, {'payment': 27834, 'final_payment': 27808, 'total_interest': 1998}], ['control 2', {'principal': 12345, 'rate_bp': 1200, 'months': 1, 'round': 'nearest'}, {'payment': 12468, 'final_payment': 12468, 'total_interest': 123}], ['control 3', {'principal': 999999, 'rate_bp': 0, 'months': 60, 'round': 'nearest'}, {'payment': 16667, 'final_payment': 16646, 'total_interest': 0}], ['control 4', {'principal': 30000000, 'rate_bp': 0, 'months': 36, 'round': 'up'}, {'payment': 833334, 'final_payment': 833310, 'total_interest': 0}], ['control 5', {'principal': 1000000, 'rate_bp': 0, 'months': 3, 'round': 'nearest'}, {'payment': 333333, 'final_payment': 333334, 'total_interest': 0}], ['control 6', {'principal': 100000, 'rate_bp': 0, 'months': 360, 'round': 'up'}, {'payment': 278, 'final_payment': 198, 'total_interest': 0}]], [['regression: monthly interest rounding', {'principal': 2500000, 'rate_bp': 1999, 'months': 3, 'round': 'nearest'}, {'payment': 861250, 'final_payment': 861251, 'total_interest': 83751}], ['regression: monthly interest rounding, partial-repair probe', {'principal': 500, 'rate_bp': 13, 'months': 3, 'round': 'up'}, {'payment': 167, 'final_payment': 166, 'total_interest': 0}], ['control 1', {'principal': 7, 'rate_bp': 0, 'months': 60, 'round': 'nearest'}, {'payment': 0, 'final_payment': 7, 'total_interest': 0}], ['control 2', {'principal': 999999, 'rate_bp': 0, 'months': 3, 'round': 'nearest'}, {'payment': 333333, 'final_payment': 333333, 'total_interest': 0}], ['control 3', {'principal': 999999, 'rate_bp': 0, 'months': 36, 'round': 'nearest'}, {'payment': 27778, 'final_payment': 27769, 'total_interest': 0}], ['control 4', {'principal': 500, 'rate_bp': 13, 'months': 12, 'round': 'nearest'}, {'payment': 42, 'final_payment': 38, 'total_interest': 0}], ['control 5', {'principal': 30000000, 'rate_bp': 0, 'months': 7, 'round': 'nearest'}, {'payment': 4285714, 'final_payment': 4285716, 'total_interest': 0}]], [['regression: monthly interest rounding', {'principal': 2500000, 'rate_bp': 13, 'months': 1, 'round': 'nearest'}, {'payment': 2500271, 'final_payment': 2500271, 'total_interest': 271}], ['regression: monthly interest rounding, partial-repair probe', {'principal': 100000, 'rate_bp': 1999, 'months': 3, 'round': 'nearest'}, {'payment': 34450, 'final_payment': 34450, 'total_interest': 3350}], ['control 1', {'principal': 999999, 'rate_bp': 0, 'months': 12, 'round': 'nearest'}, {'payment': 83333, 'final_payment': 83336, 'total_interest': 0}], ['control 2', {'principal': 12345, 'rate_bp': 0, 'months': 12, 'round': 'up'}, {'payment': 1029, 'final_payment': 1026, 'total_interest': 0}], ['control 3', {'principal': 1000000, 'rate_bp': 0, 'months': 360, 'round': 'up'}, {'payment': 2778, 'final_payment': 2698, 'total_interest': 0}], ['control 4', {'principal': 500, 'rate_bp': 0, 'months': 7, 'round': 'nearest'}, {'payment': 71, 'final_payment': 74, 'total_interest': 0}], ['control 5', {'principal': 999999, 'rate_bp': 0, 'months': 12, 'round': 'up'}, {'payment': 83334, 'final_payment': 83325, 'total_interest': 0}]], [['regression: monthly interest rounding', {'principal': 7, 'rate_bp': 13, 'months': 12, 'round': 'up'}, {'payment': 1, 'final_payment': -4, 'total_interest': 0}], ['control 1', {'principal': 7, 'rate_bp': 1999, 'months': 7, 'round': 'nearest'}, {'payment': 1, 'final_payment': 1, 'total_interest': 0}], ['control 2', {'principal': 30000000, 'rate_bp': 600, 'months': 2, 'round': 'up'}, {'payment': 15112594, 'final_payment': 15112593, 'total_interest': 225187}], ['control 3', {'principal': 30000000, 'rate_bp': 0, 'months': 360, 'round': 'nearest'}, {'payment': 83333, 'final_payment': 83453, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 600, 'months': 3, 'round': 'up'}, {'payment': 33668, 'final_payment': 33665, 'total_interest': 1001}], ['control 5', {'principal': 500, 'rate_bp': 13, 'months': 36, 'round': 'up'}, {'payment': 14, 'final_payment': 10, 'total_interest': 0}], ['control 6', {'principal': 999999, 'rate_bp': 13, 'months': 2, 'round': 'nearest'}, {'payment': 500081, 'final_payment': 500080, 'total_interest': 162}]], [['regression: monthly interest rounding', {'principal': 7, 'rate_bp': 600, 'months': 360, 'round': 'up'}, {'payment': 1, 'final_payment': -876, 'total_interest': -524}], ['control 1', {'principal': 30000000, 'rate_bp': 499, 'months': 2, 'round': 'nearest'}, {'payment': 15093627, 'final_payment': 15093627, 'total_interest': 187254}], ['control 2', {'principal': 999999, 'rate_bp': 0, 'months': 3, 'round': 'nearest'}, {'payment': 333333, 'final_payment': 333333, 'total_interest': 0}], ['control 3', {'principal': 500, 'rate_bp': 0, 'months': 2, 'round': 'up'}, {'payment': 250, 'final_payment': 250, 'total_interest': 0}], ['control 4', {'principal': 7, 'rate_bp': 600, 'months': 60, 'round': 'nearest'}, {'payment': 0, 'final_payment': 7, 'total_interest': 0}], ['control 5', {'principal': 500, 'rate_bp': 0, 'months': 12, 'round': 'up'}, {'payment': 42, 'final_payment': 38, 'total_interest': 0}], ['control 6', {'principal': 500, 'rate_bp': 0, 'months': 12, 'round': 'nearest'}, {'payment': 42, 'final_payment': 38, 'total_interest': 0}]]]
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: monthly interest rounding | {'final_payment': -1864, 'payment': 1, 'total_interest': -1512} | {'final_payment': -1123, 'payment': 1, 'total_interest': -771} | Failed |
| control 1 | {'final_payment': 27808, 'payment': 27834, 'total_interest': 1998} | {'final_payment': 27808, 'payment': 27834, 'total_interest': 1998} | Passed |
| control 2 | {'final_payment': 12468, 'payment': 12468, 'total_interest': 123} | {'final_payment': 12468, 'payment': 12468, 'total_interest': 123} | Passed |
| control 3 | {'final_payment': 16646, 'payment': 16667, 'total_interest': 0} | {'final_payment': 16646, 'payment': 16667, 'total_interest': 0} | Passed |
| control 4 | {'final_payment': 833310, 'payment': 833334, 'total_interest': 0} | {'final_payment': 833310, 'payment': 833334, 'total_interest': 0} | Passed |
| control 5 | {'final_payment': 333334, 'payment': 333333, 'total_interest': 0} | {'final_payment': 333334, 'payment': 333333, 'total_interest': 0} | Passed |
| control 6 | {'final_payment': 198, 'payment': 278, 'total_interest': 0} | {'final_payment': 198, 'payment': 278, 'total_interest': 0} | Passed |
SHA-256 / 13bbe5a6aa0ab60c9ab8e311763e2c834cb4be28724256236c46936dd2ad37b3
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):
P = x['principal']
n = x['months']
bp = x['rate_bp']
if bp == 0:
exact = Fraction(P, n)
else:
r = Fraction(bp, 120000)
exact = P * r / (1 - (1 + r) ** -n)
if x['round'] == 'up':
pay = math.ceil(exact)
else:
pay = math.floor(exact + Fraction(1, 2))
bal = P
total_int = 0
last = 0
for k in range(1, n + 1):
q, rem = divmod(bal * bp, 120000)
interest = q + (1 if rem else 0)
total_int += interest
if k == n:
last = bal + interest
else:
bal = bal + interest - pay
return {'payment': pay, 'final_payment': last, 'total_interest': total_int}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: monthly interest rounding', {'principal': 7, 'rate_bp': 725, 'months': 360, 'round': 'up'}, {'payment': 1, 'final_payment': -1123, 'total_interest': -771}], ['control 1', {'principal': 1000000, 'rate_bp': 13, 'months': 36, 'round': 'up'}, {'payment': 27834, 'final_payment': 27808, 'total_interest': 1998}], ['control 2', {'principal': 12345, 'rate_bp': 1200, 'months': 1, 'round': 'nearest'}, {'payment': 12468, 'final_payment': 12468, 'total_interest': 123}], ['control 3', {'principal': 999999, 'rate_bp': 0, 'months': 60, 'round': 'nearest'}, {'payment': 16667, 'final_payment': 16646, 'total_interest': 0}], ['control 4', {'principal': 30000000, 'rate_bp': 0, 'months': 36, 'round': 'up'}, {'payment': 833334, 'final_payment': 833310, 'total_interest': 0}], ['control 5', {'principal': 1000000, 'rate_bp': 0, 'months': 3, 'round': 'nearest'}, {'payment': 333333, 'final_payment': 333334, 'total_interest': 0}], ['control 6', {'principal': 100000, 'rate_bp': 0, 'months': 360, 'round': 'up'}, {'payment': 278, 'final_payment': 198, 'total_interest': 0}]], [['regression: monthly interest rounding', {'principal': 2500000, 'rate_bp': 1999, 'months': 3, 'round': 'nearest'}, {'payment': 861250, 'final_payment': 861251, 'total_interest': 83751}], ['regression: monthly interest rounding, partial-repair probe', {'principal': 500, 'rate_bp': 13, 'months': 3, 'round': 'up'}, {'payment': 167, 'final_payment': 166, 'total_interest': 0}], ['control 1', {'principal': 7, 'rate_bp': 0, 'months': 60, 'round': 'nearest'}, {'payment': 0, 'final_payment': 7, 'total_interest': 0}], ['control 2', {'principal': 999999, 'rate_bp': 0, 'months': 3, 'round': 'nearest'}, {'payment': 333333, 'final_payment': 333333, 'total_interest': 0}], ['control 3', {'principal': 999999, 'rate_bp': 0, 'months': 36, 'round': 'nearest'}, {'payment': 27778, 'final_payment': 27769, 'total_interest': 0}], ['control 4', {'principal': 500, 'rate_bp': 13, 'months': 12, 'round': 'nearest'}, {'payment': 42, 'final_payment': 38, 'total_interest': 0}], ['control 5', {'principal': 30000000, 'rate_bp': 0, 'months': 7, 'round': 'nearest'}, {'payment': 4285714, 'final_payment': 4285716, 'total_interest': 0}]], [['regression: monthly interest rounding', {'principal': 2500000, 'rate_bp': 13, 'months': 1, 'round': 'nearest'}, {'payment': 2500271, 'final_payment': 2500271, 'total_interest': 271}], ['regression: monthly interest rounding, partial-repair probe', {'principal': 100000, 'rate_bp': 1999, 'months': 3, 'round': 'nearest'}, {'payment': 34450, 'final_payment': 34450, 'total_interest': 3350}], ['control 1', {'principal': 999999, 'rate_bp': 0, 'months': 12, 'round': 'nearest'}, {'payment': 83333, 'final_payment': 83336, 'total_interest': 0}], ['control 2', {'principal': 12345, 'rate_bp': 0, 'months': 12, 'round': 'up'}, {'payment': 1029, 'final_payment': 1026, 'total_interest': 0}], ['control 3', {'principal': 1000000, 'rate_bp': 0, 'months': 360, 'round': 'up'}, {'payment': 2778, 'final_payment': 2698, 'total_interest': 0}], ['control 4', {'principal': 500, 'rate_bp': 0, 'months': 7, 'round': 'nearest'}, {'payment': 71, 'final_payment': 74, 'total_interest': 0}], ['control 5', {'principal': 999999, 'rate_bp': 0, 'months': 12, 'round': 'up'}, {'payment': 83334, 'final_payment': 83325, 'total_interest': 0}]], [['regression: monthly interest rounding', {'principal': 7, 'rate_bp': 13, 'months': 12, 'round': 'up'}, {'payment': 1, 'final_payment': -4, 'total_interest': 0}], ['control 1', {'principal': 7, 'rate_bp': 1999, 'months': 7, 'round': 'nearest'}, {'payment': 1, 'final_payment': 1, 'total_interest': 0}], ['control 2', {'principal': 30000000, 'rate_bp': 600, 'months': 2, 'round': 'up'}, {'payment': 15112594, 'final_payment': 15112593, 'total_interest': 225187}], ['control 3', {'principal': 30000000, 'rate_bp': 0, 'months': 360, 'round': 'nearest'}, {'payment': 83333, 'final_payment': 83453, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 600, 'months': 3, 'round': 'up'}, {'payment': 33668, 'final_payment': 33665, 'total_interest': 1001}], ['control 5', {'principal': 500, 'rate_bp': 13, 'months': 36, 'round': 'up'}, {'payment': 14, 'final_payment': 10, 'total_interest': 0}], ['control 6', {'principal': 999999, 'rate_bp': 13, 'months': 2, 'round': 'nearest'}, {'payment': 500081, 'final_payment': 500080, 'total_interest': 162}]], [['regression: monthly interest rounding', {'principal': 7, 'rate_bp': 600, 'months': 360, 'round': 'up'}, {'payment': 1, 'final_payment': -876, 'total_interest': -524}], ['control 1', {'principal': 30000000, 'rate_bp': 499, 'months': 2, 'round': 'nearest'}, {'payment': 15093627, 'final_payment': 15093627, 'total_interest': 187254}], ['control 2', {'principal': 999999, 'rate_bp': 0, 'months': 3, 'round': 'nearest'}, {'payment': 333333, 'final_payment': 333333, 'total_interest': 0}], ['control 3', {'principal': 500, 'rate_bp': 0, 'months': 2, 'round': 'up'}, {'payment': 250, 'final_payment': 250, 'total_interest': 0}], ['control 4', {'principal': 7, 'rate_bp': 600, 'months': 60, 'round': 'nearest'}, {'payment': 0, 'final_payment': 7, 'total_interest': 0}], ['control 5', {'principal': 500, 'rate_bp': 0, 'months': 12, 'round': 'up'}, {'payment': 42, 'final_payment': 38, 'total_interest': 0}], ['control 6', {'principal': 500, 'rate_bp': 0, 'months': 12, 'round': 'nearest'}, {'payment': 42, 'final_payment': 38, 'total_interest': 0}]]]
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: monthly interest rounding | {'final_payment': 8, 'payment': 1, 'total_interest': 360} | {'final_payment': -1123, 'payment': 1, 'total_interest': -771} | Failed |
| control 1 | {'final_payment': 27844, 'payment': 27834, 'total_interest': 2034} | {'final_payment': 27808, 'payment': 27834, 'total_interest': 1998} | Failed |
| control 2 | {'final_payment': 12469, 'payment': 12468, 'total_interest': 124} | {'final_payment': 12468, 'payment': 12468, 'total_interest': 123} | Failed |
| control 3 | {'final_payment': 16646, 'payment': 16667, 'total_interest': 0} | {'final_payment': 16646, 'payment': 16667, 'total_interest': 0} | Passed |
| control 4 | {'final_payment': 833310, 'payment': 833334, 'total_interest': 0} | {'final_payment': 833310, 'payment': 833334, 'total_interest': 0} | Passed |
| control 5 | {'final_payment': 333334, 'payment': 333333, 'total_interest': 0} | {'final_payment': 333334, 'payment': 333333, 'total_interest': 0} | Passed |
| control 6 | {'final_payment': 198, 'payment': 278, 'total_interest': 0} | {'final_payment': 198, 'payment': 278, 'total_interest': 0} | Passed |
SHA-256 / e278069a6ad0fa25180e796eeb8825af08d306d67c2c64e460720a930ecb8a98
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):
P = x['principal']
n = x['months']
bp = x['rate_bp']
if bp == 0:
exact = Fraction(P, n)
else:
r = Fraction(bp, 120000)
exact = P * r / (1 - (1 + r) ** -n)
if x['round'] == 'up':
pay = math.ceil(exact)
else:
pay = math.floor(exact + Fraction(1, 2))
bal = P
total_int = 0
last = 0
for k in range(1, n + 1):
q, rem = divmod(bal * bp, 120000)
interest = q + (1 if 2 * rem >= 120000 else 0)
total_int += interest
if k == n:
last = bal + interest
else:
bal = bal + interest - pay
return {'payment': pay, 'final_payment': last, 'total_interest': total_int}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: monthly interest rounding', {'principal': 7, 'rate_bp': 725, 'months': 360, 'round': 'up'}, {'payment': 1, 'final_payment': -1123, 'total_interest': -771}], ['control 1', {'principal': 1000000, 'rate_bp': 13, 'months': 36, 'round': 'up'}, {'payment': 27834, 'final_payment': 27808, 'total_interest': 1998}], ['control 2', {'principal': 12345, 'rate_bp': 1200, 'months': 1, 'round': 'nearest'}, {'payment': 12468, 'final_payment': 12468, 'total_interest': 123}], ['control 3', {'principal': 999999, 'rate_bp': 0, 'months': 60, 'round': 'nearest'}, {'payment': 16667, 'final_payment': 16646, 'total_interest': 0}], ['control 4', {'principal': 30000000, 'rate_bp': 0, 'months': 36, 'round': 'up'}, {'payment': 833334, 'final_payment': 833310, 'total_interest': 0}], ['control 5', {'principal': 1000000, 'rate_bp': 0, 'months': 3, 'round': 'nearest'}, {'payment': 333333, 'final_payment': 333334, 'total_interest': 0}], ['control 6', {'principal': 100000, 'rate_bp': 0, 'months': 360, 'round': 'up'}, {'payment': 278, 'final_payment': 198, 'total_interest': 0}]], [['regression: monthly interest rounding', {'principal': 2500000, 'rate_bp': 1999, 'months': 3, 'round': 'nearest'}, {'payment': 861250, 'final_payment': 861251, 'total_interest': 83751}], ['regression: monthly interest rounding, partial-repair probe', {'principal': 500, 'rate_bp': 13, 'months': 3, 'round': 'up'}, {'payment': 167, 'final_payment': 166, 'total_interest': 0}], ['control 1', {'principal': 7, 'rate_bp': 0, 'months': 60, 'round': 'nearest'}, {'payment': 0, 'final_payment': 7, 'total_interest': 0}], ['control 2', {'principal': 999999, 'rate_bp': 0, 'months': 3, 'round': 'nearest'}, {'payment': 333333, 'final_payment': 333333, 'total_interest': 0}], ['control 3', {'principal': 999999, 'rate_bp': 0, 'months': 36, 'round': 'nearest'}, {'payment': 27778, 'final_payment': 27769, 'total_interest': 0}], ['control 4', {'principal': 500, 'rate_bp': 13, 'months': 12, 'round': 'nearest'}, {'payment': 42, 'final_payment': 38, 'total_interest': 0}], ['control 5', {'principal': 30000000, 'rate_bp': 0, 'months': 7, 'round': 'nearest'}, {'payment': 4285714, 'final_payment': 4285716, 'total_interest': 0}]], [['regression: monthly interest rounding', {'principal': 2500000, 'rate_bp': 13, 'months': 1, 'round': 'nearest'}, {'payment': 2500271, 'final_payment': 2500271, 'total_interest': 271}], ['regression: monthly interest rounding, partial-repair probe', {'principal': 100000, 'rate_bp': 1999, 'months': 3, 'round': 'nearest'}, {'payment': 34450, 'final_payment': 34450, 'total_interest': 3350}], ['control 1', {'principal': 999999, 'rate_bp': 0, 'months': 12, 'round': 'nearest'}, {'payment': 83333, 'final_payment': 83336, 'total_interest': 0}], ['control 2', {'principal': 12345, 'rate_bp': 0, 'months': 12, 'round': 'up'}, {'payment': 1029, 'final_payment': 1026, 'total_interest': 0}], ['control 3', {'principal': 1000000, 'rate_bp': 0, 'months': 360, 'round': 'up'}, {'payment': 2778, 'final_payment': 2698, 'total_interest': 0}], ['control 4', {'principal': 500, 'rate_bp': 0, 'months': 7, 'round': 'nearest'}, {'payment': 71, 'final_payment': 74, 'total_interest': 0}], ['control 5', {'principal': 999999, 'rate_bp': 0, 'months': 12, 'round': 'up'}, {'payment': 83334, 'final_payment': 83325, 'total_interest': 0}]], [['regression: monthly interest rounding', {'principal': 7, 'rate_bp': 13, 'months': 12, 'round': 'up'}, {'payment': 1, 'final_payment': -4, 'total_interest': 0}], ['control 1', {'principal': 7, 'rate_bp': 1999, 'months': 7, 'round': 'nearest'}, {'payment': 1, 'final_payment': 1, 'total_interest': 0}], ['control 2', {'principal': 30000000, 'rate_bp': 600, 'months': 2, 'round': 'up'}, {'payment': 15112594, 'final_payment': 15112593, 'total_interest': 225187}], ['control 3', {'principal': 30000000, 'rate_bp': 0, 'months': 360, 'round': 'nearest'}, {'payment': 83333, 'final_payment': 83453, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 600, 'months': 3, 'round': 'up'}, {'payment': 33668, 'final_payment': 33665, 'total_interest': 1001}], ['control 5', {'principal': 500, 'rate_bp': 13, 'months': 36, 'round': 'up'}, {'payment': 14, 'final_payment': 10, 'total_interest': 0}], ['control 6', {'principal': 999999, 'rate_bp': 13, 'months': 2, 'round': 'nearest'}, {'payment': 500081, 'final_payment': 500080, 'total_interest': 162}]], [['regression: monthly interest rounding', {'principal': 7, 'rate_bp': 600, 'months': 360, 'round': 'up'}, {'payment': 1, 'final_payment': -876, 'total_interest': -524}], ['control 1', {'principal': 30000000, 'rate_bp': 499, 'months': 2, 'round': 'nearest'}, {'payment': 15093627, 'final_payment': 15093627, 'total_interest': 187254}], ['control 2', {'principal': 999999, 'rate_bp': 0, 'months': 3, 'round': 'nearest'}, {'payment': 333333, 'final_payment': 333333, 'total_interest': 0}], ['control 3', {'principal': 500, 'rate_bp': 0, 'months': 2, 'round': 'up'}, {'payment': 250, 'final_payment': 250, 'total_interest': 0}], ['control 4', {'principal': 7, 'rate_bp': 600, 'months': 60, 'round': 'nearest'}, {'payment': 0, 'final_payment': 7, 'total_interest': 0}], ['control 5', {'principal': 500, 'rate_bp': 0, 'months': 12, 'round': 'up'}, {'payment': 42, 'final_payment': 38, 'total_interest': 0}], ['control 6', {'principal': 500, 'rate_bp': 0, 'months': 12, 'round': 'nearest'}, {'payment': 42, 'final_payment': 38, 'total_interest': 0}]]]
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: monthly interest rounding | {'final_payment': -1123, 'payment': 1, 'total_interest': -771} | {'final_payment': -1123, 'payment': 1, 'total_interest': -771} | Passed |
| control 1 | {'final_payment': 27808, 'payment': 27834, 'total_interest': 1998} | {'final_payment': 27808, 'payment': 27834, 'total_interest': 1998} | Passed |
| control 2 | {'final_payment': 12468, 'payment': 12468, 'total_interest': 123} | {'final_payment': 12468, 'payment': 12468, 'total_interest': 123} | Passed |
| control 3 | {'final_payment': 16646, 'payment': 16667, 'total_interest': 0} | {'final_payment': 16646, 'payment': 16667, 'total_interest': 0} | Passed |
| control 4 | {'final_payment': 833310, 'payment': 833334, 'total_interest': 0} | {'final_payment': 833310, 'payment': 833334, 'total_interest': 0} | Passed |
| control 5 | {'final_payment': 333334, 'payment': 333333, 'total_interest': 0} | {'final_payment': 333334, 'payment': 333333, 'total_interest': 0} | Passed |
| control 6 | {'final_payment': 198, 'payment': 278, 'total_interest': 0} | {'final_payment': 198, 'payment': 278, 'total_interest': 0} | Passed |
SHA-256 / 5ca1df439180be1ac97b99189a4b3924301a6aceae404328be545bcb248e114c
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:26.008175+00:00.
Case digest / 1137e8442f131e56b512cdb5b34362723f47bba01d46c04ec7227dd6654290eb