FAILURE MAP
← Case archive

FA-58571 / Loan amortization schedules / Open access

Balloon maturity payment: amortization basis · case 01

Balloon loans are quoted fully amortizing payments over the short term.

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

ROOT CAUSE

The payment is leveled over the balloon term instead of the amortization period.

VERIFIED REPAIR

Level the payment over the amortization period.

Unsuccessful approach: Switching to the term when it exceeds amortization stretches loans that should finish early.

Case contract

x = {'principal', 'rate_bp', 'amort_months', 'term_months'}. The level payment amortizes over amort_months (exact annuity, half-up). Payments run to the earlier of term and amortization end; the last one pays balance + interest (the balloon). Monthly interest is round_half_up(balance*bp/120000). Return {'payment', 'final_payment', 'balloon_excess': final - payment, 'total_interest'}.

Why this case matters

Amortization engines drive borrower statements, payoff quotes and investor remittances; a misplaced rounding step, boundary or ordering rule compounds across hundreds of periods.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    pay = level(P, x['term_months'])
    last_k = min(x['term_months'], x['amort_months'])
    bal = P
    total = 0
    final = 0
    for k in range(1, last_k + 1):
        interest = rnd(bal * bp, 120000)
        total += interest
        if k == last_k:
            final = bal + interest
        else:
            bal -= pay - interest
    return {'payment': pay, 'final_payment': final, 'balloon_excess': final - pay, 'total_interest': total}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: amortization basis', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 600, 'amort_months': 12, 'term_months': 84}, {'payment': 4781, 'final_payment': 4786, 'balloon_excess': 5, 'total_interest': 1822}], ['control 1', {'principal': 100000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 20364, 'final_payment': 20364, 'balloon_excess': 0, 'total_interest': 1820}], ['control 2', {'principal': 55555, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 11278, 'final_payment': 11280, 'balloon_excess': 2, 'total_interest': 837}], ['control 3', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 16667, 'final_payment': 16647, 'balloon_excess': -20, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 725, 'amort_months': 60, 'term_months': 60}, {'payment': 1992, 'final_payment': 1987, 'balloon_excess': -5, 'total_interest': 19515}], ['control 5', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 83333, 'final_payment': 83337, 'balloon_excess': 4, 'total_interest': 0}]], [['regression: amortization basis', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 8333, 'final_payment': 66668, 'balloon_excess': 58335, 'total_interest': 0}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 600, 'amort_months': 12, 'term_months': 60}, {'payment': 4781, 'final_payment': 4786, 'balloon_excess': 5, 'total_interest': 1822}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 20301, 'final_payment': 20300, 'balloon_excess': -1, 'total_interest': 1504}], ['control 2', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 60}, {'payment': 2224, 'final_payment': 2262, 'balloon_excess': 38, 'total_interest': 33478}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 1667, 'final_payment': 1647, 'balloon_excess': -20, 'total_interest': 0}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 60}, {'payment': 19333, 'final_payment': 19321, 'balloon_excess': -12, 'total_interest': 159968}], ['control 5', {'principal': 55555, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 11313, 'final_payment': 11314, 'balloon_excess': 1, 'total_interest': 1011}]], [['regression: amortization basis', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['regression: amortization basis, partial-repair probe', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 60}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['control 1', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}], ['control 2', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 8333, 'final_payment': 8337, 'balloon_excess': 4, 'total_interest': 0}], ['control 3', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 5}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['control 4', {'principal': 55555, 'rate_bp': 725, 'amort_months': 12, 'term_months': 12}, {'payment': 4813, 'final_payment': 4819, 'balloon_excess': 6, 'total_interest': 2207}], ['control 5', {'principal': 55555, 'rate_bp': 0, 'amort_months': 5, 'term_months': 5}, {'payment': 11111, 'final_payment': 11111, 'balloon_excess': 0, 'total_interest': 0}]], [['regression: amortization basis', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 12}, {'payment': 6822, 'final_payment': 997140, 'balloon_excess': 990318, 'total_interest': 72182}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 11111, 'final_payment': 11111, 'balloon_excess': 0, 'total_interest': 0}], ['control 1', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 203010, 'final_payment': 203010, 'balloon_excess': 0, 'total_interest': 15050}], ['control 2', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 5}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 1667, 'final_payment': 1647, 'balloon_excess': -20, 'total_interest': 0}], ['control 4', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 83333, 'final_payment': 83337, 'balloon_excess': 4, 'total_interest': 0}], ['control 5', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 60}, {'payment': 22244, 'final_payment': 22281, 'balloon_excess': 37, 'total_interest': 334677}]], [['regression: amortization basis', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 12}, {'payment': 66733, 'final_payment': 2600867, 'balloon_excess': 2534134, 'total_interest': 334930}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 60}, {'payment': 4936, 'final_payment': 4938, 'balloon_excess': 2, 'total_interest': 3679}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 60}, {'payment': 1933, 'final_payment': 1952, 'balloon_excess': 19, 'total_interest': 15999}], ['control 2', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}], ['control 3', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}], ['control 4', {'principal': 3000000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 610919, 'final_payment': 610918, 'balloon_excess': -1, 'total_interest': 54594}], ['control 5', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 203640, 'final_payment': 203639, 'balloon_excess': -1, 'total_interest': 18199}]]]
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: amortization basis{'balloon_excess': 1, 'final_payment': 33334, 'payment': 33333, 'total_interest': 0}{'balloon_excess': 40000, 'final_payment': 60000, 'payment': 20000, 'total_interest': 0}Failed
regression: amortization basis, partial-repair probe{'balloon_excess': 48965, 'final_payment': 49777, 'payment': 812, 'total_interest': 3154}{'balloon_excess': 5, 'final_payment': 4786, 'payment': 4781, 'total_interest': 1822}Failed
control 1{'balloon_excess': 0, 'final_payment': 20364, 'payment': 20364, 'total_interest': 1820}{'balloon_excess': 0, 'final_payment': 20364, 'payment': 20364, 'total_interest': 1820}Passed
control 2{'balloon_excess': 2, 'final_payment': 11280, 'payment': 11278, 'total_interest': 837}{'balloon_excess': 2, 'final_payment': 11280, 'payment': 11278, 'total_interest': 837}Passed
control 3{'balloon_excess': -20, 'final_payment': 16647, 'payment': 16667, 'total_interest': 0}{'balloon_excess': -20, 'final_payment': 16647, 'payment': 16667, 'total_interest': 0}Passed
control 4{'balloon_excess': -5, 'final_payment': 1987, 'payment': 1992, 'total_interest': 19515}{'balloon_excess': -5, 'final_payment': 1987, 'payment': 1992, 'total_interest': 19515}Passed
control 5{'balloon_excess': 4, 'final_payment': 83337, 'payment': 83333, 'total_interest': 0}{'balloon_excess': 4, 'final_payment': 83337, 'payment': 83333, 'total_interest': 0}Passed

SHA-256 / e6d08e8a9d1b31bdf766848e5b8bdf7786f2706bebf7c069b0cd91f8c1578b33

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    pay = level(P, x['amort_months']) if x['amort_months'] > x['term_months'] else level(P, x['term_months'])
    last_k = min(x['term_months'], x['amort_months'])
    bal = P
    total = 0
    final = 0
    for k in range(1, last_k + 1):
        interest = rnd(bal * bp, 120000)
        total += interest
        if k == last_k:
            final = bal + interest
        else:
            bal -= pay - interest
    return {'payment': pay, 'final_payment': final, 'balloon_excess': final - pay, 'total_interest': total}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: amortization basis', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 600, 'amort_months': 12, 'term_months': 84}, {'payment': 4781, 'final_payment': 4786, 'balloon_excess': 5, 'total_interest': 1822}], ['control 1', {'principal': 100000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 20364, 'final_payment': 20364, 'balloon_excess': 0, 'total_interest': 1820}], ['control 2', {'principal': 55555, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 11278, 'final_payment': 11280, 'balloon_excess': 2, 'total_interest': 837}], ['control 3', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 16667, 'final_payment': 16647, 'balloon_excess': -20, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 725, 'amort_months': 60, 'term_months': 60}, {'payment': 1992, 'final_payment': 1987, 'balloon_excess': -5, 'total_interest': 19515}], ['control 5', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 83333, 'final_payment': 83337, 'balloon_excess': 4, 'total_interest': 0}]], [['regression: amortization basis', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 8333, 'final_payment': 66668, 'balloon_excess': 58335, 'total_interest': 0}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 600, 'amort_months': 12, 'term_months': 60}, {'payment': 4781, 'final_payment': 4786, 'balloon_excess': 5, 'total_interest': 1822}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 20301, 'final_payment': 20300, 'balloon_excess': -1, 'total_interest': 1504}], ['control 2', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 60}, {'payment': 2224, 'final_payment': 2262, 'balloon_excess': 38, 'total_interest': 33478}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 1667, 'final_payment': 1647, 'balloon_excess': -20, 'total_interest': 0}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 60}, {'payment': 19333, 'final_payment': 19321, 'balloon_excess': -12, 'total_interest': 159968}], ['control 5', {'principal': 55555, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 11313, 'final_payment': 11314, 'balloon_excess': 1, 'total_interest': 1011}]], [['regression: amortization basis', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['regression: amortization basis, partial-repair probe', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 60}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['control 1', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}], ['control 2', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 8333, 'final_payment': 8337, 'balloon_excess': 4, 'total_interest': 0}], ['control 3', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 5}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['control 4', {'principal': 55555, 'rate_bp': 725, 'amort_months': 12, 'term_months': 12}, {'payment': 4813, 'final_payment': 4819, 'balloon_excess': 6, 'total_interest': 2207}], ['control 5', {'principal': 55555, 'rate_bp': 0, 'amort_months': 5, 'term_months': 5}, {'payment': 11111, 'final_payment': 11111, 'balloon_excess': 0, 'total_interest': 0}]], [['regression: amortization basis', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 12}, {'payment': 6822, 'final_payment': 997140, 'balloon_excess': 990318, 'total_interest': 72182}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 11111, 'final_payment': 11111, 'balloon_excess': 0, 'total_interest': 0}], ['control 1', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 203010, 'final_payment': 203010, 'balloon_excess': 0, 'total_interest': 15050}], ['control 2', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 5}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 1667, 'final_payment': 1647, 'balloon_excess': -20, 'total_interest': 0}], ['control 4', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 83333, 'final_payment': 83337, 'balloon_excess': 4, 'total_interest': 0}], ['control 5', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 60}, {'payment': 22244, 'final_payment': 22281, 'balloon_excess': 37, 'total_interest': 334677}]], [['regression: amortization basis', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 12}, {'payment': 66733, 'final_payment': 2600867, 'balloon_excess': 2534134, 'total_interest': 334930}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 60}, {'payment': 4936, 'final_payment': 4938, 'balloon_excess': 2, 'total_interest': 3679}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 60}, {'payment': 1933, 'final_payment': 1952, 'balloon_excess': 19, 'total_interest': 15999}], ['control 2', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}], ['control 3', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}], ['control 4', {'principal': 3000000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 610919, 'final_payment': 610918, 'balloon_excess': -1, 'total_interest': 54594}], ['control 5', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 203640, 'final_payment': 203639, 'balloon_excess': -1, 'total_interest': 18199}]]]
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: amortization basis{'balloon_excess': 40000, 'final_payment': 60000, 'payment': 20000, 'total_interest': 0}{'balloon_excess': 40000, 'final_payment': 60000, 'payment': 20000, 'total_interest': 0}Passed
regression: amortization basis, partial-repair probe{'balloon_excess': 48965, 'final_payment': 49777, 'payment': 812, 'total_interest': 3154}{'balloon_excess': 5, 'final_payment': 4786, 'payment': 4781, 'total_interest': 1822}Failed
control 1{'balloon_excess': 0, 'final_payment': 20364, 'payment': 20364, 'total_interest': 1820}{'balloon_excess': 0, 'final_payment': 20364, 'payment': 20364, 'total_interest': 1820}Passed
control 2{'balloon_excess': 2, 'final_payment': 11280, 'payment': 11278, 'total_interest': 837}{'balloon_excess': 2, 'final_payment': 11280, 'payment': 11278, 'total_interest': 837}Passed
control 3{'balloon_excess': -20, 'final_payment': 16647, 'payment': 16667, 'total_interest': 0}{'balloon_excess': -20, 'final_payment': 16647, 'payment': 16667, 'total_interest': 0}Passed
control 4{'balloon_excess': -5, 'final_payment': 1987, 'payment': 1992, 'total_interest': 19515}{'balloon_excess': -5, 'final_payment': 1987, 'payment': 1992, 'total_interest': 19515}Passed
control 5{'balloon_excess': 4, 'final_payment': 83337, 'payment': 83333, 'total_interest': 0}{'balloon_excess': 4, 'final_payment': 83337, 'payment': 83333, 'total_interest': 0}Passed

SHA-256 / 32870e5ec7b8fd746480813dce3c4aa47b16f499ac4d5dcf7cc6305cb769a68e

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    pay = level(P, x['amort_months'])
    last_k = min(x['term_months'], x['amort_months'])
    bal = P
    total = 0
    final = 0
    for k in range(1, last_k + 1):
        interest = rnd(bal * bp, 120000)
        total += interest
        if k == last_k:
            final = bal + interest
        else:
            bal -= pay - interest
    return {'payment': pay, 'final_payment': final, 'balloon_excess': final - pay, 'total_interest': total}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: amortization basis', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 600, 'amort_months': 12, 'term_months': 84}, {'payment': 4781, 'final_payment': 4786, 'balloon_excess': 5, 'total_interest': 1822}], ['control 1', {'principal': 100000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 20364, 'final_payment': 20364, 'balloon_excess': 0, 'total_interest': 1820}], ['control 2', {'principal': 55555, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 11278, 'final_payment': 11280, 'balloon_excess': 2, 'total_interest': 837}], ['control 3', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 16667, 'final_payment': 16647, 'balloon_excess': -20, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 725, 'amort_months': 60, 'term_months': 60}, {'payment': 1992, 'final_payment': 1987, 'balloon_excess': -5, 'total_interest': 19515}], ['control 5', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 83333, 'final_payment': 83337, 'balloon_excess': 4, 'total_interest': 0}]], [['regression: amortization basis', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 8333, 'final_payment': 66668, 'balloon_excess': 58335, 'total_interest': 0}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 600, 'amort_months': 12, 'term_months': 60}, {'payment': 4781, 'final_payment': 4786, 'balloon_excess': 5, 'total_interest': 1822}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 20301, 'final_payment': 20300, 'balloon_excess': -1, 'total_interest': 1504}], ['control 2', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 60}, {'payment': 2224, 'final_payment': 2262, 'balloon_excess': 38, 'total_interest': 33478}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 1667, 'final_payment': 1647, 'balloon_excess': -20, 'total_interest': 0}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 60}, {'payment': 19333, 'final_payment': 19321, 'balloon_excess': -12, 'total_interest': 159968}], ['control 5', {'principal': 55555, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 11313, 'final_payment': 11314, 'balloon_excess': 1, 'total_interest': 1011}]], [['regression: amortization basis', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['regression: amortization basis, partial-repair probe', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 60}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['control 1', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}], ['control 2', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 8333, 'final_payment': 8337, 'balloon_excess': 4, 'total_interest': 0}], ['control 3', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 5}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['control 4', {'principal': 55555, 'rate_bp': 725, 'amort_months': 12, 'term_months': 12}, {'payment': 4813, 'final_payment': 4819, 'balloon_excess': 6, 'total_interest': 2207}], ['control 5', {'principal': 55555, 'rate_bp': 0, 'amort_months': 5, 'term_months': 5}, {'payment': 11111, 'final_payment': 11111, 'balloon_excess': 0, 'total_interest': 0}]], [['regression: amortization basis', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 12}, {'payment': 6822, 'final_payment': 997140, 'balloon_excess': 990318, 'total_interest': 72182}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 11111, 'final_payment': 11111, 'balloon_excess': 0, 'total_interest': 0}], ['control 1', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 203010, 'final_payment': 203010, 'balloon_excess': 0, 'total_interest': 15050}], ['control 2', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 5}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 1667, 'final_payment': 1647, 'balloon_excess': -20, 'total_interest': 0}], ['control 4', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 83333, 'final_payment': 83337, 'balloon_excess': 4, 'total_interest': 0}], ['control 5', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 60}, {'payment': 22244, 'final_payment': 22281, 'balloon_excess': 37, 'total_interest': 334677}]], [['regression: amortization basis', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 12}, {'payment': 66733, 'final_payment': 2600867, 'balloon_excess': 2534134, 'total_interest': 334930}], ['regression: amortization basis, partial-repair probe', {'principal': 55555, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 60}, {'payment': 4936, 'final_payment': 4938, 'balloon_excess': 2, 'total_interest': 3679}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 60}, {'payment': 1933, 'final_payment': 1952, 'balloon_excess': 19, 'total_interest': 15999}], ['control 2', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}], ['control 3', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}], ['control 4', {'principal': 3000000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 610919, 'final_payment': 610918, 'balloon_excess': -1, 'total_interest': 54594}], ['control 5', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 203640, 'final_payment': 203639, 'balloon_excess': -1, 'total_interest': 18199}]]]
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: amortization basis{'balloon_excess': 40000, 'final_payment': 60000, 'payment': 20000, 'total_interest': 0}{'balloon_excess': 40000, 'final_payment': 60000, 'payment': 20000, 'total_interest': 0}Passed
regression: amortization basis, partial-repair probe{'balloon_excess': 5, 'final_payment': 4786, 'payment': 4781, 'total_interest': 1822}{'balloon_excess': 5, 'final_payment': 4786, 'payment': 4781, 'total_interest': 1822}Passed
control 1{'balloon_excess': 0, 'final_payment': 20364, 'payment': 20364, 'total_interest': 1820}{'balloon_excess': 0, 'final_payment': 20364, 'payment': 20364, 'total_interest': 1820}Passed
control 2{'balloon_excess': 2, 'final_payment': 11280, 'payment': 11278, 'total_interest': 837}{'balloon_excess': 2, 'final_payment': 11280, 'payment': 11278, 'total_interest': 837}Passed
control 3{'balloon_excess': -20, 'final_payment': 16647, 'payment': 16667, 'total_interest': 0}{'balloon_excess': -20, 'final_payment': 16647, 'payment': 16667, 'total_interest': 0}Passed
control 4{'balloon_excess': -5, 'final_payment': 1987, 'payment': 1992, 'total_interest': 19515}{'balloon_excess': -5, 'final_payment': 1987, 'payment': 1992, 'total_interest': 19515}Passed
control 5{'balloon_excess': 4, 'final_payment': 83337, 'payment': 83333, 'total_interest': 0}{'balloon_excess': 4, 'final_payment': 83337, 'payment': 83333, 'total_interest': 0}Passed

SHA-256 / cbdc8777f22c81f8551bac24df97cd04a0193f08e2eb812b4396147fbc699d9e

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.050058+00:00.

Case digest / cafef744e488695c39d5e024d42f47a808c2bba7179af4a515538a0f41bcb9a7