FAILURE MAP
← Case archive

FA-58636 / Loan amortization schedules / Open access

Rule of 78s early payoff rebate: remaining installment count · case 01

Payoff quotes omit one remaining installment and its rebate.

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

ROOT CAUSE

The current installment is excluded from the remaining count.

VERIFIED REPAIR

Remaining installments are n - k.

Unsuccessful approach: Adding one counts an installment already paid.

Case contract

x = {'term' n, 'paid' k, 'finance_charge', 'acq_fee' (fully earned up front), 'payment', 'min_rebate'}. Rebatable charge = finance_charge - acq_fee; remaining r = n - k; rebate = round_half_up(rebatable * r(r+1) / (n(n+1))), set to 0 when below min_rebate. Payoff = payment*r - rebate; earned = finance_charge - rebate. Return {'rebate', 'payoff', 'earned'}.

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)
    n = x['term']
    k = x['paid']
    fc = x['finance_charge'] - x['acq_fee']
    rem = n - k - 1
    rebate = rnd(fc * rem * (rem + 1), n * (n + 1))
    if rebate < x['min_rebate']:
        rebate = 0
    remaining = x['payment'] * rem
    return {'rebate': rebate, 'payoff': remaining - rebate, 'earned': x['finance_charge'] - rebate}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: remaining installment count', {'term': 36, 'paid': 35, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 100}, {'rebate': 180, 'payoff': 9820, 'earned': 119820}], ['sample 1', {'term': 36, 'paid': 35, 'finance_charge': 50000, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 25000, 'earned': 50000}], ['sample 2', {'term': 24, 'paid': 23, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 25000, 'earned': 100}], ['sample 3', {'term': 24, 'paid': 12, 'finance_charge': 50000, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 100}, {'rebate': 13000, 'payoff': 287000, 'earned': 37000}], ['sample 4', {'term': 12, 'paid': 3, 'finance_charge': 100, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 100}, {'rebate': 0, 'payoff': 38889, 'earned': 100}], ['sample 5', {'term': 6, 'paid': 3, 'finance_charge': 7777, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 500}, {'rebate': 2222, 'payoff': 27778, 'earned': 5555}], ['sample 6', {'term': 24, 'paid': 1, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 100}, {'rebate': 110400, 'payoff': -11017, 'earned': 9600}]], [['regression: remaining installment count', {'term': 36, 'paid': 3, 'finance_charge': 7777, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 500}, {'rebate': 4445, 'payoff': 325555, 'earned': 3332}], ['sample 1', {'term': 24, 'paid': 1, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 500}, {'rebate': 110400, 'payoff': 464600, 'earned': 9600}], ['sample 2', {'term': 12, 'paid': 1, 'finance_charge': 100, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 110000, 'earned': 100}], ['sample 3', {'term': 24, 'paid': 24, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 0, 'earned': 120000}], ['sample 4', {'term': 36, 'paid': 0, 'finance_charge': 100, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 900000, 'earned': 100}], ['sample 5', {'term': 24, 'paid': 12, 'finance_charge': 100, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 120000, 'earned': 100}], ['sample 6', {'term': 6, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 500}, {'rebate': 7777, 'payoff': 52223, 'earned': 0}]], [['regression: remaining installment count', {'term': 6, 'paid': 5, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 25000, 'earned': 100}], ['sample 1', {'term': 24, 'paid': 3, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 2000}, {'rebate': 92400, 'payoff': 117600, 'earned': 27600}], ['sample 2', {'term': 12, 'paid': 3, 'finance_charge': 50000, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 2000}, {'rebate': 28846, 'payoff': 10043, 'earned': 21154}], ['sample 3', {'term': 6, 'paid': 3, 'finance_charge': 7777, 'acq_fee': 7500, 'payment': 4321, 'min_rebate': 0}, {'rebate': 79, 'payoff': 12884, 'earned': 7698}], ['sample 4', {'term': 6, 'paid': 5, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 0}, {'rebate': 5714, 'payoff': 4286, 'earned': 114286}], ['sample 5', {'term': 12, 'paid': 12, 'finance_charge': 7777, 'acq_fee': 7500, 'payment': 10000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 0, 'earned': 7777}], ['sample 6', {'term': 24, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 0}, {'rebate': 5277, 'payoff': 234723, 'earned': 2500}]], [['regression: remaining installment count', {'term': 36, 'paid': 0, 'finance_charge': 100, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 900000, 'earned': 100}], ['sample 1', {'term': 12, 'paid': 12, 'finance_charge': 50000, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 0}, {'rebate': 0, 'payoff': 0, 'earned': 50000}], ['sample 2', {'term': 12, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 2000}, {'rebate': 7777, 'payoff': 44075, 'earned': 0}], ['sample 3', {'term': 36, 'paid': 0, 'finance_charge': 100, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 0}, {'rebate': 100, 'payoff': 359900, 'earned': 0}], ['sample 4', {'term': 6, 'paid': 3, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 75000, 'earned': 100}], ['sample 5', {'term': 24, 'paid': 12, 'finance_charge': 120000, 'acq_fee': 2500, 'payment': 4321, 'min_rebate': 100}, {'rebate': 30550, 'payoff': 21302, 'earned': 89450}], ['sample 6', {'term': 36, 'paid': 18, 'finance_charge': 50000, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 0}, {'rebate': 12838, 'payoff': 64940, 'earned': 37162}]], [['regression: remaining installment count', {'term': 24, 'paid': 12, 'finance_charge': 120000, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 500}, {'rebate': 29250, 'payoff': 270750, 'earned': 90750}], ['sample 1', {'term': 24, 'paid': 12, 'finance_charge': 100, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 500}, {'rebate': 0, 'payoff': 51852, 'earned': 100}], ['sample 2', {'term': 36, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 900000, 'earned': 7777}], ['sample 3', {'term': 6, 'paid': 5, 'finance_charge': 120000, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 100}, {'rebate': 5595, 'payoff': 4405, 'earned': 114405}], ['sample 4', {'term': 36, 'paid': 18, 'finance_charge': 7777, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 100}, {'rebate': 1355, 'payoff': 178645, 'earned': 6422}], ['sample 5', {'term': 36, 'paid': 36, 'finance_charge': 100, 'acq_fee': 7500, 'payment': 4321, 'min_rebate': 0}, {'rebate': 0, 'payoff': 0, 'earned': 100}], ['sample 6', {'term': 12, 'paid': 0, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 300000, 'earned': 100}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: remaining installment count{'earned': 120000, 'payoff': 0, 'rebate': 0}{'earned': 119820, 'payoff': 9820, 'rebate': 180}Failed
sample 1{'earned': 50000, 'payoff': 0, 'rebate': 0}{'earned': 50000, 'payoff': 25000, 'rebate': 0}Failed
sample 2{'earned': 100, 'payoff': 0, 'rebate': 0}{'earned': 100, 'payoff': 25000, 'rebate': 0}Failed
sample 3{'earned': 39000, 'payoff': 264000, 'rebate': 11000}{'earned': 37000, 'payoff': 287000, 'rebate': 13000}Failed
sample 4{'earned': 100, 'payoff': 34568, 'rebate': 0}{'earned': 100, 'payoff': 38889, 'rebate': 0}Failed
sample 5{'earned': 6666, 'payoff': 18889, 'rebate': 1111}{'earned': 5555, 'payoff': 27778, 'rebate': 2222}Failed
sample 6{'earned': 18800, 'payoff': -6138, 'rebate': 101200}{'earned': 9600, 'payoff': -11017, 'rebate': 110400}Failed

SHA-256 / 300a207f5150bf9a5d9ba73e9f90984effd18bb880a9de71da5902335aca4234

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)
    n = x['term']
    k = x['paid']
    fc = x['finance_charge'] - x['acq_fee']
    rem = n - k + 1
    rebate = rnd(fc * rem * (rem + 1), n * (n + 1))
    if rebate < x['min_rebate']:
        rebate = 0
    remaining = x['payment'] * rem
    return {'rebate': rebate, 'payoff': remaining - rebate, 'earned': x['finance_charge'] - rebate}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: remaining installment count', {'term': 36, 'paid': 35, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 100}, {'rebate': 180, 'payoff': 9820, 'earned': 119820}], ['sample 1', {'term': 36, 'paid': 35, 'finance_charge': 50000, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 25000, 'earned': 50000}], ['sample 2', {'term': 24, 'paid': 23, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 25000, 'earned': 100}], ['sample 3', {'term': 24, 'paid': 12, 'finance_charge': 50000, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 100}, {'rebate': 13000, 'payoff': 287000, 'earned': 37000}], ['sample 4', {'term': 12, 'paid': 3, 'finance_charge': 100, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 100}, {'rebate': 0, 'payoff': 38889, 'earned': 100}], ['sample 5', {'term': 6, 'paid': 3, 'finance_charge': 7777, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 500}, {'rebate': 2222, 'payoff': 27778, 'earned': 5555}], ['sample 6', {'term': 24, 'paid': 1, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 100}, {'rebate': 110400, 'payoff': -11017, 'earned': 9600}]], [['regression: remaining installment count', {'term': 36, 'paid': 3, 'finance_charge': 7777, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 500}, {'rebate': 4445, 'payoff': 325555, 'earned': 3332}], ['sample 1', {'term': 24, 'paid': 1, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 500}, {'rebate': 110400, 'payoff': 464600, 'earned': 9600}], ['sample 2', {'term': 12, 'paid': 1, 'finance_charge': 100, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 110000, 'earned': 100}], ['sample 3', {'term': 24, 'paid': 24, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 0, 'earned': 120000}], ['sample 4', {'term': 36, 'paid': 0, 'finance_charge': 100, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 900000, 'earned': 100}], ['sample 5', {'term': 24, 'paid': 12, 'finance_charge': 100, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 120000, 'earned': 100}], ['sample 6', {'term': 6, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 500}, {'rebate': 7777, 'payoff': 52223, 'earned': 0}]], [['regression: remaining installment count', {'term': 6, 'paid': 5, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 25000, 'earned': 100}], ['sample 1', {'term': 24, 'paid': 3, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 2000}, {'rebate': 92400, 'payoff': 117600, 'earned': 27600}], ['sample 2', {'term': 12, 'paid': 3, 'finance_charge': 50000, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 2000}, {'rebate': 28846, 'payoff': 10043, 'earned': 21154}], ['sample 3', {'term': 6, 'paid': 3, 'finance_charge': 7777, 'acq_fee': 7500, 'payment': 4321, 'min_rebate': 0}, {'rebate': 79, 'payoff': 12884, 'earned': 7698}], ['sample 4', {'term': 6, 'paid': 5, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 0}, {'rebate': 5714, 'payoff': 4286, 'earned': 114286}], ['sample 5', {'term': 12, 'paid': 12, 'finance_charge': 7777, 'acq_fee': 7500, 'payment': 10000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 0, 'earned': 7777}], ['sample 6', {'term': 24, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 0}, {'rebate': 5277, 'payoff': 234723, 'earned': 2500}]], [['regression: remaining installment count', {'term': 36, 'paid': 0, 'finance_charge': 100, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 900000, 'earned': 100}], ['sample 1', {'term': 12, 'paid': 12, 'finance_charge': 50000, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 0}, {'rebate': 0, 'payoff': 0, 'earned': 50000}], ['sample 2', {'term': 12, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 2000}, {'rebate': 7777, 'payoff': 44075, 'earned': 0}], ['sample 3', {'term': 36, 'paid': 0, 'finance_charge': 100, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 0}, {'rebate': 100, 'payoff': 359900, 'earned': 0}], ['sample 4', {'term': 6, 'paid': 3, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 75000, 'earned': 100}], ['sample 5', {'term': 24, 'paid': 12, 'finance_charge': 120000, 'acq_fee': 2500, 'payment': 4321, 'min_rebate': 100}, {'rebate': 30550, 'payoff': 21302, 'earned': 89450}], ['sample 6', {'term': 36, 'paid': 18, 'finance_charge': 50000, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 0}, {'rebate': 12838, 'payoff': 64940, 'earned': 37162}]], [['regression: remaining installment count', {'term': 24, 'paid': 12, 'finance_charge': 120000, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 500}, {'rebate': 29250, 'payoff': 270750, 'earned': 90750}], ['sample 1', {'term': 24, 'paid': 12, 'finance_charge': 100, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 500}, {'rebate': 0, 'payoff': 51852, 'earned': 100}], ['sample 2', {'term': 36, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 900000, 'earned': 7777}], ['sample 3', {'term': 6, 'paid': 5, 'finance_charge': 120000, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 100}, {'rebate': 5595, 'payoff': 4405, 'earned': 114405}], ['sample 4', {'term': 36, 'paid': 18, 'finance_charge': 7777, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 100}, {'rebate': 1355, 'payoff': 178645, 'earned': 6422}], ['sample 5', {'term': 36, 'paid': 36, 'finance_charge': 100, 'acq_fee': 7500, 'payment': 4321, 'min_rebate': 0}, {'rebate': 0, 'payoff': 0, 'earned': 100}], ['sample 6', {'term': 12, 'paid': 0, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 300000, 'earned': 100}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: remaining installment count{'earned': 119459, 'payoff': 19459, 'rebate': 541}{'earned': 119820, 'payoff': 9820, 'rebate': 180}Failed
sample 1{'earned': 49809, 'payoff': 49809, 'rebate': 191}{'earned': 50000, 'payoff': 25000, 'rebate': 0}Failed
sample 2{'earned': 100, 'payoff': 50000, 'rebate': 0}{'earned': 100, 'payoff': 25000, 'rebate': 0}Failed
sample 3{'earned': 34833, 'payoff': 309833, 'rebate': 15167}{'earned': 37000, 'payoff': 287000, 'rebate': 13000}Failed
sample 4{'earned': 100, 'payoff': 43210, 'rebate': 0}{'earned': 100, 'payoff': 38889, 'rebate': 0}Failed
sample 5{'earned': 4074, 'payoff': 36297, 'rebate': 3703}{'earned': 5555, 'payoff': 27778, 'rebate': 2222}Failed
sample 6{'earned': 0, 'payoff': -16296, 'rebate': 120000}{'earned': 9600, 'payoff': -11017, 'rebate': 110400}Failed

SHA-256 / 40e2dfde476059fbe327ed8ba6d23bcdcf4e4ade380bf2aaf5ae577d4290091f

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)
    n = x['term']
    k = x['paid']
    fc = x['finance_charge'] - x['acq_fee']
    rem = n - k
    rebate = rnd(fc * rem * (rem + 1), n * (n + 1))
    if rebate < x['min_rebate']:
        rebate = 0
    remaining = x['payment'] * rem
    return {'rebate': rebate, 'payoff': remaining - rebate, 'earned': x['finance_charge'] - rebate}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: remaining installment count', {'term': 36, 'paid': 35, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 100}, {'rebate': 180, 'payoff': 9820, 'earned': 119820}], ['sample 1', {'term': 36, 'paid': 35, 'finance_charge': 50000, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 25000, 'earned': 50000}], ['sample 2', {'term': 24, 'paid': 23, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 25000, 'earned': 100}], ['sample 3', {'term': 24, 'paid': 12, 'finance_charge': 50000, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 100}, {'rebate': 13000, 'payoff': 287000, 'earned': 37000}], ['sample 4', {'term': 12, 'paid': 3, 'finance_charge': 100, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 100}, {'rebate': 0, 'payoff': 38889, 'earned': 100}], ['sample 5', {'term': 6, 'paid': 3, 'finance_charge': 7777, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 500}, {'rebate': 2222, 'payoff': 27778, 'earned': 5555}], ['sample 6', {'term': 24, 'paid': 1, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 100}, {'rebate': 110400, 'payoff': -11017, 'earned': 9600}]], [['regression: remaining installment count', {'term': 36, 'paid': 3, 'finance_charge': 7777, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 500}, {'rebate': 4445, 'payoff': 325555, 'earned': 3332}], ['sample 1', {'term': 24, 'paid': 1, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 500}, {'rebate': 110400, 'payoff': 464600, 'earned': 9600}], ['sample 2', {'term': 12, 'paid': 1, 'finance_charge': 100, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 110000, 'earned': 100}], ['sample 3', {'term': 24, 'paid': 24, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 100}, {'rebate': 0, 'payoff': 0, 'earned': 120000}], ['sample 4', {'term': 36, 'paid': 0, 'finance_charge': 100, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 900000, 'earned': 100}], ['sample 5', {'term': 24, 'paid': 12, 'finance_charge': 100, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 120000, 'earned': 100}], ['sample 6', {'term': 6, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 500}, {'rebate': 7777, 'payoff': 52223, 'earned': 0}]], [['regression: remaining installment count', {'term': 6, 'paid': 5, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 25000, 'earned': 100}], ['sample 1', {'term': 24, 'paid': 3, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 2000}, {'rebate': 92400, 'payoff': 117600, 'earned': 27600}], ['sample 2', {'term': 12, 'paid': 3, 'finance_charge': 50000, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 2000}, {'rebate': 28846, 'payoff': 10043, 'earned': 21154}], ['sample 3', {'term': 6, 'paid': 3, 'finance_charge': 7777, 'acq_fee': 7500, 'payment': 4321, 'min_rebate': 0}, {'rebate': 79, 'payoff': 12884, 'earned': 7698}], ['sample 4', {'term': 6, 'paid': 5, 'finance_charge': 120000, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 0}, {'rebate': 5714, 'payoff': 4286, 'earned': 114286}], ['sample 5', {'term': 12, 'paid': 12, 'finance_charge': 7777, 'acq_fee': 7500, 'payment': 10000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 0, 'earned': 7777}], ['sample 6', {'term': 24, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 0}, {'rebate': 5277, 'payoff': 234723, 'earned': 2500}]], [['regression: remaining installment count', {'term': 36, 'paid': 0, 'finance_charge': 100, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 900000, 'earned': 100}], ['sample 1', {'term': 12, 'paid': 12, 'finance_charge': 50000, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 0}, {'rebate': 0, 'payoff': 0, 'earned': 50000}], ['sample 2', {'term': 12, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 2000}, {'rebate': 7777, 'payoff': 44075, 'earned': 0}], ['sample 3', {'term': 36, 'paid': 0, 'finance_charge': 100, 'acq_fee': 0, 'payment': 10000, 'min_rebate': 0}, {'rebate': 100, 'payoff': 359900, 'earned': 0}], ['sample 4', {'term': 6, 'paid': 3, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 75000, 'earned': 100}], ['sample 5', {'term': 24, 'paid': 12, 'finance_charge': 120000, 'acq_fee': 2500, 'payment': 4321, 'min_rebate': 100}, {'rebate': 30550, 'payoff': 21302, 'earned': 89450}], ['sample 6', {'term': 36, 'paid': 18, 'finance_charge': 50000, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 0}, {'rebate': 12838, 'payoff': 64940, 'earned': 37162}]], [['regression: remaining installment count', {'term': 24, 'paid': 12, 'finance_charge': 120000, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 500}, {'rebate': 29250, 'payoff': 270750, 'earned': 90750}], ['sample 1', {'term': 24, 'paid': 12, 'finance_charge': 100, 'acq_fee': 0, 'payment': 4321, 'min_rebate': 500}, {'rebate': 0, 'payoff': 51852, 'earned': 100}], ['sample 2', {'term': 36, 'paid': 0, 'finance_charge': 7777, 'acq_fee': 7500, 'payment': 25000, 'min_rebate': 2000}, {'rebate': 0, 'payoff': 900000, 'earned': 7777}], ['sample 3', {'term': 6, 'paid': 5, 'finance_charge': 120000, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 100}, {'rebate': 5595, 'payoff': 4405, 'earned': 114405}], ['sample 4', {'term': 36, 'paid': 18, 'finance_charge': 7777, 'acq_fee': 2500, 'payment': 10000, 'min_rebate': 100}, {'rebate': 1355, 'payoff': 178645, 'earned': 6422}], ['sample 5', {'term': 36, 'paid': 36, 'finance_charge': 100, 'acq_fee': 7500, 'payment': 4321, 'min_rebate': 0}, {'rebate': 0, 'payoff': 0, 'earned': 100}], ['sample 6', {'term': 12, 'paid': 0, 'finance_charge': 100, 'acq_fee': 0, 'payment': 25000, 'min_rebate': 500}, {'rebate': 0, 'payoff': 300000, 'earned': 100}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: remaining installment count{'earned': 119820, 'payoff': 9820, 'rebate': 180}{'earned': 119820, 'payoff': 9820, 'rebate': 180}Passed
sample 1{'earned': 50000, 'payoff': 25000, 'rebate': 0}{'earned': 50000, 'payoff': 25000, 'rebate': 0}Passed
sample 2{'earned': 100, 'payoff': 25000, 'rebate': 0}{'earned': 100, 'payoff': 25000, 'rebate': 0}Passed
sample 3{'earned': 37000, 'payoff': 287000, 'rebate': 13000}{'earned': 37000, 'payoff': 287000, 'rebate': 13000}Passed
sample 4{'earned': 100, 'payoff': 38889, 'rebate': 0}{'earned': 100, 'payoff': 38889, 'rebate': 0}Passed
sample 5{'earned': 5555, 'payoff': 27778, 'rebate': 2222}{'earned': 5555, 'payoff': 27778, 'rebate': 2222}Passed
sample 6{'earned': 9600, 'payoff': -11017, 'rebate': 110400}{'earned': 9600, 'payoff': -11017, 'rebate': 110400}Passed

SHA-256 / 518dcb65906dd75a949a59a9b08d05ae1f159cb0e95f37a02628d1904d481c2b

Verification & scope

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

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

Case digest / 2f25cf5dedee502ae9e1ec40094ffb7827af1037d221e68c7f234c5518276f4b