FA-58521 / Loan amortization schedules / Open access
Late fee after grace period: days late origin · case 01
Days late are understated by the grace period.
ROOT CAUSE
Lateness is counted from the end of grace instead of the due date.
VERIFIED REPAIR
Count days late from the due date.
Unsuccessful approach: Adding one counts the due date itself as a late day.
Case contract
x = {'due_day', 'grace', 'paid_day' or None, 'payment_due', 'paid_amount', 'tolerance', 'fee_bp', 'fee_min', 'fee_max', 'as_of'}. A payment is on time when paid_day is not None, paid_day <= due_day + grace and payment_due - paid_amount <= tolerance. Otherwise fee = clamp(round_half_up(payment_due*fee_bp/10000), fee_min, fee_max) and days_late = max(0, (paid_day or as_of when unpaid) - due_day). Return {'late', 'fee', 'days_late'}.
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)
deadline = x['due_day'] + x['grace']
paid = x['paid_day']
short = x['payment_due'] - x['paid_amount']
on_time = paid is not None and paid <= deadline and short <= x['tolerance']
if on_time:
return {'late': False, 'fee': 0, 'days_late': 0}
end = paid if paid is not None else x['as_of']
fee = rnd(x['payment_due'] * x['fee_bp'], 10000)
fee = max(x['fee_min'], min(fee, x['fee_max']))
return {'late': True, 'fee': fee, 'days_late': max(0, end - deadline)}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: days late origin', {'due_day': 1, 'grace': 10, 'paid_day': 12, 'payment_due': 20000, 'paid_amount': 0, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 800, 'days_late': 11}], ['control 1', {'due_day': 5, 'grace': 15, 'paid_day': 5, 'payment_due': 150000, 'paid_amount': 99999, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 5000, 'as_of': 25}, {'late': True, 'fee': 5000, 'days_late': 0}], ['control 2', {'due_day': 1, 'grace': 10, 'paid_day': 0, 'payment_due': 100000, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 41}, {'late': True, 'fee': 5000, 'days_late': 0}], ['control 3', {'due_day': 5, 'grace': 0, 'paid_day': 5, 'payment_due': 20000, 'paid_amount': 2500, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 1500, 'days_late': 0}], ['control 4', {'due_day': 15, 'grace': 0, 'paid_day': 18, 'payment_due': 100000, 'paid_amount': 2500, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 55}, {'late': True, 'fee': 4000, 'days_late': 3}], ['control 5', {'due_day': 5, 'grace': 0, 'paid_day': 4, 'payment_due': 20000, 'paid_amount': 149990, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 45}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 6', {'due_day': 5, 'grace': 0, 'paid_day': 8, 'payment_due': 150000, 'paid_amount': 20000, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 7500, 'days_late': 3}]], [['regression: days late origin', {'due_day': 15, 'grace': 15, 'paid_day': 30, 'payment_due': 20000, 'paid_amount': 2500, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 55}, {'late': True, 'fee': 2500, 'days_late': 15}], ['control 1', {'due_day': 15, 'grace': 15, 'paid_day': 18, 'payment_due': 20000, 'paid_amount': 100000, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 35}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 2', {'due_day': 5, 'grace': 0, 'paid_day': 4, 'payment_due': 20000, 'paid_amount': 20000, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 25}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 3', {'due_day': 1, 'grace': 0, 'paid_day': 2, 'payment_due': 2500, 'paid_amount': 20000, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 2500, 'days_late': 1}], ['control 4', {'due_day': 15, 'grace': 0, 'paid_day': 16, 'payment_due': 150000, 'paid_amount': 99999, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 55}, {'late': True, 'fee': 5000, 'days_late': 1}], ['control 5', {'due_day': 5, 'grace': 15, 'paid_day': 5, 'payment_due': 20000, 'paid_amount': 149990, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 25}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 6', {'due_day': 5, 'grace': 0, 'paid_day': 8, 'payment_due': 100000, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 25}, {'late': True, 'fee': 5000, 'days_late': 3}]], [['regression: days late origin', {'due_day': 1, 'grace': 10, 'paid_day': 12, 'payment_due': 150000, 'paid_amount': 150000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 5000, 'days_late': 11}], ['control 1', {'due_day': 5, 'grace': 0, 'paid_day': 5, 'payment_due': 2500, 'paid_amount': 150000, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 45}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 2', {'due_day': 15, 'grace': 0, 'paid_day': 14, 'payment_due': 100000, 'paid_amount': 100000, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 35}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 3', {'due_day': 5, 'grace': 0, 'paid_day': None, 'payment_due': 2500, 'paid_amount': 99999, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 45}, {'late': True, 'fee': 1500, 'days_late': 40}], ['control 4', {'due_day': 1, 'grace': 15, 'paid_day': 16, 'payment_due': 2500, 'paid_amount': 150000, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 41}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 5', {'due_day': 5, 'grace': 10, 'paid_day': 5, 'payment_due': 100000, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 25}, {'late': True, 'fee': 4000, 'days_late': 0}], ['control 6', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 2500, 'paid_amount': 149990, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 21}, {'late': False, 'fee': 0, 'days_late': 0}]], [['regression: days late origin', {'due_day': 5, 'grace': 15, 'paid_day': 21, 'payment_due': 20000, 'paid_amount': 20000, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 1000, 'days_late': 16}], ['control 1', {'due_day': 5, 'grace': 0, 'paid_day': 6, 'payment_due': 2500, 'paid_amount': 0, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 45}, {'late': True, 'fee': 100, 'days_late': 1}], ['control 2', {'due_day': 1, 'grace': 0, 'paid_day': 4, 'payment_due': 2500, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 21}, {'late': True, 'fee': 125, 'days_late': 3}], ['control 3', {'due_day': 1, 'grace': 10, 'paid_day': 1, 'payment_due': 20000, 'paid_amount': 149990, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 21}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 4', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 20000, 'paid_amount': 20000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 21}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 5', {'due_day': 5, 'grace': 15, 'paid_day': 5, 'payment_due': 20000, 'paid_amount': 99999, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 45}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 6', {'due_day': 5, 'grace': 0, 'paid_day': 8, 'payment_due': 150000, 'paid_amount': 100000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 25}, {'late': True, 'fee': 5000, 'days_late': 3}]], [['regression: days late origin', {'due_day': 5, 'grace': 10, 'paid_day': None, 'payment_due': 100000, 'paid_amount': 149990, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 5000, 'days_late': 40}], ['control 1', {'due_day': 1, 'grace': 0, 'paid_day': 2, 'payment_due': 100000, 'paid_amount': 149990, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 21}, {'late': True, 'fee': 4000, 'days_late': 1}], ['control 2', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 100000, 'paid_amount': 149990, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 5000, 'as_of': 41}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 3', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 150000, 'paid_amount': 100000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 5000, 'days_late': 0}], ['control 4', {'due_day': 15, 'grace': 10, 'paid_day': 14, 'payment_due': 150000, 'paid_amount': 150000, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 5000, 'as_of': 35}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 5', {'due_day': 5, 'grace': 0, 'paid_day': 5, 'payment_due': 2500, 'paid_amount': 0, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 2500, 'days_late': 0}], ['control 6', {'due_day': 1, 'grace': 10, 'paid_day': 1, 'payment_due': 100000, 'paid_amount': 2500, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 21}, {'late': True, 'fee': 4000, 'days_late': 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: days late origin | {'days_late': 1, 'fee': 800, 'late': True} | {'days_late': 11, 'fee': 800, 'late': True} | Failed |
| control 1 | {'days_late': 0, 'fee': 5000, 'late': True} | {'days_late': 0, 'fee': 5000, 'late': True} | Passed |
| control 2 | {'days_late': 0, 'fee': 5000, 'late': True} | {'days_late': 0, 'fee': 5000, 'late': True} | Passed |
| control 3 | {'days_late': 0, 'fee': 1500, 'late': True} | {'days_late': 0, 'fee': 1500, 'late': True} | Passed |
| control 4 | {'days_late': 3, 'fee': 4000, 'late': True} | {'days_late': 3, 'fee': 4000, 'late': True} | Passed |
| control 5 | {'days_late': 0, 'fee': 0, 'late': False} | {'days_late': 0, 'fee': 0, 'late': False} | Passed |
| control 6 | {'days_late': 3, 'fee': 7500, 'late': True} | {'days_late': 3, 'fee': 7500, 'late': True} | Passed |
SHA-256 / 2a8116c6e200b0bda5ebf991e8e3f94a7414d07737a14561fb067120235dcf71
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)
deadline = x['due_day'] + x['grace']
paid = x['paid_day']
short = x['payment_due'] - x['paid_amount']
on_time = paid is not None and paid <= deadline and short <= x['tolerance']
if on_time:
return {'late': False, 'fee': 0, 'days_late': 0}
end = paid if paid is not None else x['as_of']
fee = rnd(x['payment_due'] * x['fee_bp'], 10000)
fee = max(x['fee_min'], min(fee, x['fee_max']))
return {'late': True, 'fee': fee, 'days_late': max(0, end - x['due_day'] + 1)}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: days late origin', {'due_day': 1, 'grace': 10, 'paid_day': 12, 'payment_due': 20000, 'paid_amount': 0, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 800, 'days_late': 11}], ['control 1', {'due_day': 5, 'grace': 15, 'paid_day': 5, 'payment_due': 150000, 'paid_amount': 99999, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 5000, 'as_of': 25}, {'late': True, 'fee': 5000, 'days_late': 0}], ['control 2', {'due_day': 1, 'grace': 10, 'paid_day': 0, 'payment_due': 100000, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 41}, {'late': True, 'fee': 5000, 'days_late': 0}], ['control 3', {'due_day': 5, 'grace': 0, 'paid_day': 5, 'payment_due': 20000, 'paid_amount': 2500, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 1500, 'days_late': 0}], ['control 4', {'due_day': 15, 'grace': 0, 'paid_day': 18, 'payment_due': 100000, 'paid_amount': 2500, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 55}, {'late': True, 'fee': 4000, 'days_late': 3}], ['control 5', {'due_day': 5, 'grace': 0, 'paid_day': 4, 'payment_due': 20000, 'paid_amount': 149990, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 45}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 6', {'due_day': 5, 'grace': 0, 'paid_day': 8, 'payment_due': 150000, 'paid_amount': 20000, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 7500, 'days_late': 3}]], [['regression: days late origin', {'due_day': 15, 'grace': 15, 'paid_day': 30, 'payment_due': 20000, 'paid_amount': 2500, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 55}, {'late': True, 'fee': 2500, 'days_late': 15}], ['control 1', {'due_day': 15, 'grace': 15, 'paid_day': 18, 'payment_due': 20000, 'paid_amount': 100000, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 35}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 2', {'due_day': 5, 'grace': 0, 'paid_day': 4, 'payment_due': 20000, 'paid_amount': 20000, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 25}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 3', {'due_day': 1, 'grace': 0, 'paid_day': 2, 'payment_due': 2500, 'paid_amount': 20000, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 2500, 'days_late': 1}], ['control 4', {'due_day': 15, 'grace': 0, 'paid_day': 16, 'payment_due': 150000, 'paid_amount': 99999, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 55}, {'late': True, 'fee': 5000, 'days_late': 1}], ['control 5', {'due_day': 5, 'grace': 15, 'paid_day': 5, 'payment_due': 20000, 'paid_amount': 149990, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 25}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 6', {'due_day': 5, 'grace': 0, 'paid_day': 8, 'payment_due': 100000, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 25}, {'late': True, 'fee': 5000, 'days_late': 3}]], [['regression: days late origin', {'due_day': 1, 'grace': 10, 'paid_day': 12, 'payment_due': 150000, 'paid_amount': 150000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 5000, 'days_late': 11}], ['control 1', {'due_day': 5, 'grace': 0, 'paid_day': 5, 'payment_due': 2500, 'paid_amount': 150000, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 45}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 2', {'due_day': 15, 'grace': 0, 'paid_day': 14, 'payment_due': 100000, 'paid_amount': 100000, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 35}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 3', {'due_day': 5, 'grace': 0, 'paid_day': None, 'payment_due': 2500, 'paid_amount': 99999, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 45}, {'late': True, 'fee': 1500, 'days_late': 40}], ['control 4', {'due_day': 1, 'grace': 15, 'paid_day': 16, 'payment_due': 2500, 'paid_amount': 150000, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 41}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 5', {'due_day': 5, 'grace': 10, 'paid_day': 5, 'payment_due': 100000, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 25}, {'late': True, 'fee': 4000, 'days_late': 0}], ['control 6', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 2500, 'paid_amount': 149990, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 21}, {'late': False, 'fee': 0, 'days_late': 0}]], [['regression: days late origin', {'due_day': 5, 'grace': 15, 'paid_day': 21, 'payment_due': 20000, 'paid_amount': 20000, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 1000, 'days_late': 16}], ['control 1', {'due_day': 5, 'grace': 0, 'paid_day': 6, 'payment_due': 2500, 'paid_amount': 0, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 45}, {'late': True, 'fee': 100, 'days_late': 1}], ['control 2', {'due_day': 1, 'grace': 0, 'paid_day': 4, 'payment_due': 2500, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 21}, {'late': True, 'fee': 125, 'days_late': 3}], ['control 3', {'due_day': 1, 'grace': 10, 'paid_day': 1, 'payment_due': 20000, 'paid_amount': 149990, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 21}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 4', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 20000, 'paid_amount': 20000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 21}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 5', {'due_day': 5, 'grace': 15, 'paid_day': 5, 'payment_due': 20000, 'paid_amount': 99999, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 45}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 6', {'due_day': 5, 'grace': 0, 'paid_day': 8, 'payment_due': 150000, 'paid_amount': 100000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 25}, {'late': True, 'fee': 5000, 'days_late': 3}]], [['regression: days late origin', {'due_day': 5, 'grace': 10, 'paid_day': None, 'payment_due': 100000, 'paid_amount': 149990, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 5000, 'days_late': 40}], ['control 1', {'due_day': 1, 'grace': 0, 'paid_day': 2, 'payment_due': 100000, 'paid_amount': 149990, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 21}, {'late': True, 'fee': 4000, 'days_late': 1}], ['control 2', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 100000, 'paid_amount': 149990, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 5000, 'as_of': 41}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 3', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 150000, 'paid_amount': 100000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 5000, 'days_late': 0}], ['control 4', {'due_day': 15, 'grace': 10, 'paid_day': 14, 'payment_due': 150000, 'paid_amount': 150000, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 5000, 'as_of': 35}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 5', {'due_day': 5, 'grace': 0, 'paid_day': 5, 'payment_due': 2500, 'paid_amount': 0, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 2500, 'days_late': 0}], ['control 6', {'due_day': 1, 'grace': 10, 'paid_day': 1, 'payment_due': 100000, 'paid_amount': 2500, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 21}, {'late': True, 'fee': 4000, 'days_late': 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: days late origin | {'days_late': 12, 'fee': 800, 'late': True} | {'days_late': 11, 'fee': 800, 'late': True} | Failed |
| control 1 | {'days_late': 1, 'fee': 5000, 'late': True} | {'days_late': 0, 'fee': 5000, 'late': True} | Failed |
| control 2 | {'days_late': 0, 'fee': 5000, 'late': True} | {'days_late': 0, 'fee': 5000, 'late': True} | Passed |
| control 3 | {'days_late': 1, 'fee': 1500, 'late': True} | {'days_late': 0, 'fee': 1500, 'late': True} | Failed |
| control 4 | {'days_late': 4, 'fee': 4000, 'late': True} | {'days_late': 3, 'fee': 4000, 'late': True} | Failed |
| control 5 | {'days_late': 0, 'fee': 0, 'late': False} | {'days_late': 0, 'fee': 0, 'late': False} | Passed |
| control 6 | {'days_late': 4, 'fee': 7500, 'late': True} | {'days_late': 3, 'fee': 7500, 'late': True} | Failed |
SHA-256 / 3e80e90087896d6db78774d08406eade1bbe08e5346bcdc3c15bbbcd36d9bddc
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)
deadline = x['due_day'] + x['grace']
paid = x['paid_day']
short = x['payment_due'] - x['paid_amount']
on_time = paid is not None and paid <= deadline and short <= x['tolerance']
if on_time:
return {'late': False, 'fee': 0, 'days_late': 0}
end = paid if paid is not None else x['as_of']
fee = rnd(x['payment_due'] * x['fee_bp'], 10000)
fee = max(x['fee_min'], min(fee, x['fee_max']))
return {'late': True, 'fee': fee, 'days_late': max(0, end - x['due_day'])}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: days late origin', {'due_day': 1, 'grace': 10, 'paid_day': 12, 'payment_due': 20000, 'paid_amount': 0, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 800, 'days_late': 11}], ['control 1', {'due_day': 5, 'grace': 15, 'paid_day': 5, 'payment_due': 150000, 'paid_amount': 99999, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 5000, 'as_of': 25}, {'late': True, 'fee': 5000, 'days_late': 0}], ['control 2', {'due_day': 1, 'grace': 10, 'paid_day': 0, 'payment_due': 100000, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 41}, {'late': True, 'fee': 5000, 'days_late': 0}], ['control 3', {'due_day': 5, 'grace': 0, 'paid_day': 5, 'payment_due': 20000, 'paid_amount': 2500, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 1500, 'days_late': 0}], ['control 4', {'due_day': 15, 'grace': 0, 'paid_day': 18, 'payment_due': 100000, 'paid_amount': 2500, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 55}, {'late': True, 'fee': 4000, 'days_late': 3}], ['control 5', {'due_day': 5, 'grace': 0, 'paid_day': 4, 'payment_due': 20000, 'paid_amount': 149990, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 45}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 6', {'due_day': 5, 'grace': 0, 'paid_day': 8, 'payment_due': 150000, 'paid_amount': 20000, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 7500, 'days_late': 3}]], [['regression: days late origin', {'due_day': 15, 'grace': 15, 'paid_day': 30, 'payment_due': 20000, 'paid_amount': 2500, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 55}, {'late': True, 'fee': 2500, 'days_late': 15}], ['control 1', {'due_day': 15, 'grace': 15, 'paid_day': 18, 'payment_due': 20000, 'paid_amount': 100000, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 35}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 2', {'due_day': 5, 'grace': 0, 'paid_day': 4, 'payment_due': 20000, 'paid_amount': 20000, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 25}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 3', {'due_day': 1, 'grace': 0, 'paid_day': 2, 'payment_due': 2500, 'paid_amount': 20000, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 2500, 'days_late': 1}], ['control 4', {'due_day': 15, 'grace': 0, 'paid_day': 16, 'payment_due': 150000, 'paid_amount': 99999, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 55}, {'late': True, 'fee': 5000, 'days_late': 1}], ['control 5', {'due_day': 5, 'grace': 15, 'paid_day': 5, 'payment_due': 20000, 'paid_amount': 149990, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 25}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 6', {'due_day': 5, 'grace': 0, 'paid_day': 8, 'payment_due': 100000, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 25}, {'late': True, 'fee': 5000, 'days_late': 3}]], [['regression: days late origin', {'due_day': 1, 'grace': 10, 'paid_day': 12, 'payment_due': 150000, 'paid_amount': 150000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 5000, 'days_late': 11}], ['control 1', {'due_day': 5, 'grace': 0, 'paid_day': 5, 'payment_due': 2500, 'paid_amount': 150000, 'tolerance': 100, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 45}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 2', {'due_day': 15, 'grace': 0, 'paid_day': 14, 'payment_due': 100000, 'paid_amount': 100000, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 35}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 3', {'due_day': 5, 'grace': 0, 'paid_day': None, 'payment_due': 2500, 'paid_amount': 99999, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 45}, {'late': True, 'fee': 1500, 'days_late': 40}], ['control 4', {'due_day': 1, 'grace': 15, 'paid_day': 16, 'payment_due': 2500, 'paid_amount': 150000, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 41}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 5', {'due_day': 5, 'grace': 10, 'paid_day': 5, 'payment_due': 100000, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 25}, {'late': True, 'fee': 4000, 'days_late': 0}], ['control 6', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 2500, 'paid_amount': 149990, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 21}, {'late': False, 'fee': 0, 'days_late': 0}]], [['regression: days late origin', {'due_day': 5, 'grace': 15, 'paid_day': 21, 'payment_due': 20000, 'paid_amount': 20000, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 1000, 'days_late': 16}], ['control 1', {'due_day': 5, 'grace': 0, 'paid_day': 6, 'payment_due': 2500, 'paid_amount': 0, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 45}, {'late': True, 'fee': 100, 'days_late': 1}], ['control 2', {'due_day': 1, 'grace': 0, 'paid_day': 4, 'payment_due': 2500, 'paid_amount': 99999, 'tolerance': 0, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 10000, 'as_of': 21}, {'late': True, 'fee': 125, 'days_late': 3}], ['control 3', {'due_day': 1, 'grace': 10, 'paid_day': 1, 'payment_due': 20000, 'paid_amount': 149990, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 21}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 4', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 20000, 'paid_amount': 20000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 21}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 5', {'due_day': 5, 'grace': 15, 'paid_day': 5, 'payment_due': 20000, 'paid_amount': 99999, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 45}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 6', {'due_day': 5, 'grace': 0, 'paid_day': 8, 'payment_due': 150000, 'paid_amount': 100000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 25}, {'late': True, 'fee': 5000, 'days_late': 3}]], [['regression: days late origin', {'due_day': 5, 'grace': 10, 'paid_day': None, 'payment_due': 100000, 'paid_amount': 149990, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 5000, 'days_late': 40}], ['control 1', {'due_day': 1, 'grace': 0, 'paid_day': 2, 'payment_due': 100000, 'paid_amount': 149990, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 0, 'fee_max': 5000, 'as_of': 21}, {'late': True, 'fee': 4000, 'days_late': 1}], ['control 2', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 100000, 'paid_amount': 149990, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 5000, 'as_of': 41}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 3', {'due_day': 1, 'grace': 0, 'paid_day': 1, 'payment_due': 150000, 'paid_amount': 100000, 'tolerance': 100, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 5000, 'as_of': 41}, {'late': True, 'fee': 5000, 'days_late': 0}], ['control 4', {'due_day': 15, 'grace': 10, 'paid_day': 14, 'payment_due': 150000, 'paid_amount': 150000, 'tolerance': 10, 'fee_bp': 500, 'fee_min': 0, 'fee_max': 5000, 'as_of': 35}, {'late': False, 'fee': 0, 'days_late': 0}], ['control 5', {'due_day': 5, 'grace': 0, 'paid_day': 5, 'payment_due': 2500, 'paid_amount': 0, 'tolerance': 0, 'fee_bp': 400, 'fee_min': 2500, 'fee_max': 10000, 'as_of': 45}, {'late': True, 'fee': 2500, 'days_late': 0}], ['control 6', {'due_day': 1, 'grace': 10, 'paid_day': 1, 'payment_due': 100000, 'paid_amount': 2500, 'tolerance': 10, 'fee_bp': 400, 'fee_min': 1500, 'fee_max': 10000, 'as_of': 21}, {'late': True, 'fee': 4000, 'days_late': 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: days late origin | {'days_late': 11, 'fee': 800, 'late': True} | {'days_late': 11, 'fee': 800, 'late': True} | Passed |
| control 1 | {'days_late': 0, 'fee': 5000, 'late': True} | {'days_late': 0, 'fee': 5000, 'late': True} | Passed |
| control 2 | {'days_late': 0, 'fee': 5000, 'late': True} | {'days_late': 0, 'fee': 5000, 'late': True} | Passed |
| control 3 | {'days_late': 0, 'fee': 1500, 'late': True} | {'days_late': 0, 'fee': 1500, 'late': True} | Passed |
| control 4 | {'days_late': 3, 'fee': 4000, 'late': True} | {'days_late': 3, 'fee': 4000, 'late': True} | Passed |
| control 5 | {'days_late': 0, 'fee': 0, 'late': False} | {'days_late': 0, 'fee': 0, 'late': False} | Passed |
| control 6 | {'days_late': 3, 'fee': 7500, 'late': True} | {'days_late': 3, 'fee': 7500, 'late': True} | Passed |
SHA-256 / 1c0a915ad1f84be735b80437f771d75c45300218392db0d199ab24d8fac5b014
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:27.495837+00:00.
Case digest / 63e4582018013597cf80772c2d9052f407d293743c46f06ca02da3ca6bedb4c4