FA-58811 / Loan amortization schedules / Open access
Credit line average daily balance: average daily balance divisor · case 01
Average daily balance is wrong in 28 and 31 day cycles.
ROOT CAUSE
The average divides by a fixed 30 days.
VERIFIED REPAIR
Divide by the actual cycle length.
Unsuccessful approach: Rounding half-up instead of flooring disagrees with the statement definition.
Case contract
x = {'rate_bp', 'basis', 'start_balance', 'cycle_days', 'events': [[day 1..cycle_days, signed amount]], 'min_bp', 'min_floor'}. Events take effect at the start of their day. Each day adds max(balance, 0) to the accumulator; interest = round_half_up(acc*bp/(10000*basis)) once per cycle. owed = end balance + interest. Minimum = 0 if owed <= 0 else min(owed, max(min_floor, interest + round_half_up(max(end balance, 0)*min_bp/10000))). Return {'interest', 'balance': owed, 'minimum', 'adb': acc // cycle_days}.
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
N = 1
observations = []
def solve(x):
def rnd(n, d):
q, r = divmod(n, d)
return q + (1 if 2 * r >= d else 0)
bal = x['start_balance']
ev = sorted(x['events'])
i = 0
acc = 0
for day in range(1, x['cycle_days'] + 1):
while i < len(ev) and ev[i][0] == day:
bal += ev[i][1]
i += 1
acc += max(bal, 0)
interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
owed = bal + interest
if owed <= 0:
minimum = 0
else:
minimum = min(owed, max(x['min_floor'], interest + rnd(max(bal, 0) * x['min_bp'], 10000)))
return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': acc // 30}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[23, -20000], [29, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': -10000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[7, 50000], [15, 50000], [24, -20000], [23, -80000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 1387, 'balance': 101387, 'minimum': 10000, 'adb': 140666}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[15, 5000], [18, -80000], [8, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 38, 'balance': -64962, 'minimum': 0, 'adb': 3833}]], [['regression: average daily balance divisor', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[11, -80000], [21, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 99, 'balance': -44901, 'minimum': 0, 'adb': 10000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[4, 50000], [27, 10000], [8, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 7578, 'balance': 1117578, 'minimum': 24228, 'adb': 1084666}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[23, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 1118, 'balance': 151118, 'minimum': 2618, 'adb': 113333}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[28, -80000], [7, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 1302, 'balance': 71302, 'minimum': 2500, 'adb': 132000}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -80000], [1, -80000], [8, -20000], [6, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -230000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[29, 5000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 9866, 'balance': 1014866, 'minimum': 24941, 'adb': 1000333}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -80000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[10, -20000], [3, 50000], [2, -80000], [29, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 275, 'balance': 55275, 'minimum': 2500, 'adb': 55666}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[30, 10000], [22, -20000], [23, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -5000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 296, 'balance': 30296, 'minimum': 10000, 'adb': 30000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[7, -80000], [3, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 6409, 'balance': 906409, 'minimum': 15409, 'adb': 917333}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[2, -20000], [14, 10000], [20, -20000], [27, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -20000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[5, -20000], [14, -20000], [1, 5000], [20, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 7, 'balance': -24993, 'minimum': 0, 'adb': 666}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[28, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9912, 'balance': 1059912, 'minimum': 25662, 'adb': 1005000}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000], [22, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -75000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[4, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 276, 'balance': 20276, 'minimum': 2500, 'adb': 28000}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -20000], [3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 494, 'balance': 60494, 'minimum': 10000, 'adb': 70666}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[30, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 2, 'balance': 10002, 'minimum': 10000, 'adb': 333}]]]
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: average daily balance divisor | {'adb': 1040000, 'balance': 1000129, 'interest': 5129, 'minimum': 15079} | {'adb': 1006451, 'balance': 1000129, 'interest': 5129, 'minimum': 15079} | Failed |
| control 1 | {'adb': 146666, 'balance': 151447, 'interest': 1447, 'minimum': 10000} | {'adb': 146666, 'balance': 151447, 'interest': 1447, 'minimum': 10000} | Passed |
| control 2 | {'adb': 1000, 'balance': -149990, 'interest': 10, 'minimum': 0} | {'adb': 1000, 'balance': -149990, 'interest': 10, 'minimum': 0} | Passed |
| control 3 | {'adb': 0, 'balance': 0, 'interest': 0, 'minimum': 0} | {'adb': 0, 'balance': 0, 'interest': 0, 'minimum': 0} | Passed |
| control 4 | {'adb': 0, 'balance': -10000, 'interest': 0, 'minimum': 0} | {'adb': 0, 'balance': -10000, 'interest': 0, 'minimum': 0} | Passed |
| control 5 | {'adb': 140666, 'balance': 101387, 'interest': 1387, 'minimum': 10000} | {'adb': 140666, 'balance': 101387, 'interest': 1387, 'minimum': 10000} | Passed |
| control 6 | {'adb': 3833, 'balance': -64962, 'interest': 38, 'minimum': 0} | {'adb': 3833, 'balance': -64962, 'interest': 38, 'minimum': 0} | Passed |
SHA-256 / 74bf00fd2649f5c9f4b1efc1fadf471a0b974e08bc15e145c5d3840e4f2b0ed1
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
def rnd(n, d):
q, r = divmod(n, d)
return q + (1 if 2 * r >= d else 0)
bal = x['start_balance']
ev = sorted(x['events'])
i = 0
acc = 0
for day in range(1, x['cycle_days'] + 1):
while i < len(ev) and ev[i][0] == day:
bal += ev[i][1]
i += 1
acc += max(bal, 0)
interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
owed = bal + interest
if owed <= 0:
minimum = 0
else:
minimum = min(owed, max(x['min_floor'], interest + rnd(max(bal, 0) * x['min_bp'], 10000)))
return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': rnd(acc, x['cycle_days'])}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[23, -20000], [29, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': -10000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[7, 50000], [15, 50000], [24, -20000], [23, -80000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 1387, 'balance': 101387, 'minimum': 10000, 'adb': 140666}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[15, 5000], [18, -80000], [8, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 38, 'balance': -64962, 'minimum': 0, 'adb': 3833}]], [['regression: average daily balance divisor', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[11, -80000], [21, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 99, 'balance': -44901, 'minimum': 0, 'adb': 10000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[4, 50000], [27, 10000], [8, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 7578, 'balance': 1117578, 'minimum': 24228, 'adb': 1084666}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[23, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 1118, 'balance': 151118, 'minimum': 2618, 'adb': 113333}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[28, -80000], [7, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 1302, 'balance': 71302, 'minimum': 2500, 'adb': 132000}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -80000], [1, -80000], [8, -20000], [6, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -230000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[29, 5000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 9866, 'balance': 1014866, 'minimum': 24941, 'adb': 1000333}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -80000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[10, -20000], [3, 50000], [2, -80000], [29, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 275, 'balance': 55275, 'minimum': 2500, 'adb': 55666}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[30, 10000], [22, -20000], [23, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -5000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 296, 'balance': 30296, 'minimum': 10000, 'adb': 30000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[7, -80000], [3, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 6409, 'balance': 906409, 'minimum': 15409, 'adb': 917333}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[2, -20000], [14, 10000], [20, -20000], [27, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -20000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[5, -20000], [14, -20000], [1, 5000], [20, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 7, 'balance': -24993, 'minimum': 0, 'adb': 666}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[28, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9912, 'balance': 1059912, 'minimum': 25662, 'adb': 1005000}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000], [22, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -75000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[4, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 276, 'balance': 20276, 'minimum': 2500, 'adb': 28000}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -20000], [3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 494, 'balance': 60494, 'minimum': 10000, 'adb': 70666}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[30, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 2, 'balance': 10002, 'minimum': 10000, 'adb': 333}]]]
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: average daily balance divisor | {'adb': 1006452, 'balance': 1000129, 'interest': 5129, 'minimum': 15079} | {'adb': 1006451, 'balance': 1000129, 'interest': 5129, 'minimum': 15079} | Failed |
| control 1 | {'adb': 146667, 'balance': 151447, 'interest': 1447, 'minimum': 10000} | {'adb': 146666, 'balance': 151447, 'interest': 1447, 'minimum': 10000} | Failed |
| control 2 | {'adb': 1000, 'balance': -149990, 'interest': 10, 'minimum': 0} | {'adb': 1000, 'balance': -149990, 'interest': 10, 'minimum': 0} | Passed |
| control 3 | {'adb': 0, 'balance': 0, 'interest': 0, 'minimum': 0} | {'adb': 0, 'balance': 0, 'interest': 0, 'minimum': 0} | Passed |
| control 4 | {'adb': 0, 'balance': -10000, 'interest': 0, 'minimum': 0} | {'adb': 0, 'balance': -10000, 'interest': 0, 'minimum': 0} | Passed |
| control 5 | {'adb': 140667, 'balance': 101387, 'interest': 1387, 'minimum': 10000} | {'adb': 140666, 'balance': 101387, 'interest': 1387, 'minimum': 10000} | Failed |
| control 6 | {'adb': 3833, 'balance': -64962, 'interest': 38, 'minimum': 0} | {'adb': 3833, 'balance': -64962, 'interest': 38, 'minimum': 0} | Passed |
SHA-256 / 060646bc4fb239abb7f127eb504b842cdbe4f29648b69917cdff99a4f852e86e
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
def rnd(n, d):
q, r = divmod(n, d)
return q + (1 if 2 * r >= d else 0)
bal = x['start_balance']
ev = sorted(x['events'])
i = 0
acc = 0
for day in range(1, x['cycle_days'] + 1):
while i < len(ev) and ev[i][0] == day:
bal += ev[i][1]
i += 1
acc += max(bal, 0)
interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
owed = bal + interest
if owed <= 0:
minimum = 0
else:
minimum = min(owed, max(x['min_floor'], interest + rnd(max(bal, 0) * x['min_bp'], 10000)))
return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': acc // x['cycle_days']}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[23, -20000], [29, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': -10000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[7, 50000], [15, 50000], [24, -20000], [23, -80000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 1387, 'balance': 101387, 'minimum': 10000, 'adb': 140666}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[15, 5000], [18, -80000], [8, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 38, 'balance': -64962, 'minimum': 0, 'adb': 3833}]], [['regression: average daily balance divisor', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[11, -80000], [21, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 99, 'balance': -44901, 'minimum': 0, 'adb': 10000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[4, 50000], [27, 10000], [8, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 7578, 'balance': 1117578, 'minimum': 24228, 'adb': 1084666}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[23, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 1118, 'balance': 151118, 'minimum': 2618, 'adb': 113333}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[28, -80000], [7, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 1302, 'balance': 71302, 'minimum': 2500, 'adb': 132000}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -80000], [1, -80000], [8, -20000], [6, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -230000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[29, 5000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 9866, 'balance': 1014866, 'minimum': 24941, 'adb': 1000333}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -80000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[10, -20000], [3, 50000], [2, -80000], [29, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 275, 'balance': 55275, 'minimum': 2500, 'adb': 55666}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[30, 10000], [22, -20000], [23, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -5000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 296, 'balance': 30296, 'minimum': 10000, 'adb': 30000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[7, -80000], [3, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 6409, 'balance': 906409, 'minimum': 15409, 'adb': 917333}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[2, -20000], [14, 10000], [20, -20000], [27, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -20000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[5, -20000], [14, -20000], [1, 5000], [20, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 7, 'balance': -24993, 'minimum': 0, 'adb': 666}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[28, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9912, 'balance': 1059912, 'minimum': 25662, 'adb': 1005000}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000], [22, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -75000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[4, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 276, 'balance': 20276, 'minimum': 2500, 'adb': 28000}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -20000], [3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 494, 'balance': 60494, 'minimum': 10000, 'adb': 70666}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[30, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 2, 'balance': 10002, 'minimum': 10000, 'adb': 333}]]]
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: average daily balance divisor | {'adb': 1006451, 'balance': 1000129, 'interest': 5129, 'minimum': 15079} | {'adb': 1006451, 'balance': 1000129, 'interest': 5129, 'minimum': 15079} | Passed |
| control 1 | {'adb': 146666, 'balance': 151447, 'interest': 1447, 'minimum': 10000} | {'adb': 146666, 'balance': 151447, 'interest': 1447, 'minimum': 10000} | Passed |
| control 2 | {'adb': 1000, 'balance': -149990, 'interest': 10, 'minimum': 0} | {'adb': 1000, 'balance': -149990, 'interest': 10, 'minimum': 0} | Passed |
| control 3 | {'adb': 0, 'balance': 0, 'interest': 0, 'minimum': 0} | {'adb': 0, 'balance': 0, 'interest': 0, 'minimum': 0} | Passed |
| control 4 | {'adb': 0, 'balance': -10000, 'interest': 0, 'minimum': 0} | {'adb': 0, 'balance': -10000, 'interest': 0, 'minimum': 0} | Passed |
| control 5 | {'adb': 140666, 'balance': 101387, 'interest': 1387, 'minimum': 10000} | {'adb': 140666, 'balance': 101387, 'interest': 1387, 'minimum': 10000} | Passed |
| control 6 | {'adb': 3833, 'balance': -64962, 'interest': 38, 'minimum': 0} | {'adb': 3833, 'balance': -64962, 'interest': 38, 'minimum': 0} | Passed |
SHA-256 / bbcf7e4551983340fa72c800c43d58b75c1e2a5059a21933d582c585c38b7eef
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:30.209624+00:00.
Case digest / 6d5fba21cd3a237e36961c8baf2118f0a2fa8e9b007c34f305d1bf419c5e56f6