broken_source:
"""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 = x['term_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: maturity month', {'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': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['control 2', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 360, 'term_months': 60}, {'payment': 5996, 'final_payment': 936506, 'balloon_excess': 930510, 'total_interest': 290270}], ['control 3', {'principal': 55555, 'rate_bp': 0, 'amort_months': 360, 'term_months': 5}, {'payment': 154, 'final_payment': 54939, 'balloon_excess': 54785, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 20364, 'final_payment': 20364, 'balloon_excess': 0, 'total_interest': 1820}], ['control 5', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 60, 'term_months': 3}, {'payment': 19919, 'final_payment': 978035, 'balloon_excess': 958116, 'total_interest': 17873}], ['control 6', {'principal': 55555, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 5}, {'payment': 1236, 'final_payment': 53321, 'balloon_excess': 52085, 'total_interest': 2710}]], [['regression: maturity month', {'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': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 8333, 'final_payment': 66668, 'balloon_excess': 58335, 'total_interest': 0}], ['control 2', {'principal': 100000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 5}, {'payment': 682, 'final_payment': 100288, 'balloon_excess': 99606, 'total_interest': 3016}], ['control 3', {'principal': 100000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 20301, 'final_payment': 20300, 'balloon_excess': -1, 'total_interest': 1504}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 60}, {'payment': 2224, 'final_payment': 2262, 'balloon_excess': 38, 'total_interest': 33478}], ['control 5', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 360, 'term_months': 84}, {'payment': 30858, 'final_payment': 2918745, 'balloon_excess': 2887887, 'total_interest': 2479959}], ['control 6', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 12}, {'payment': 50000, 'final_payment': 2450000, 'balloon_excess': 2400000, 'total_interest': 0}]], [['regression: maturity month', {'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': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['control 2', {'principal': 55555, 'rate_bp': 600, 'amort_months': 5, 'term_months': 3}, {'payment': 11278, 'final_payment': 33668, 'balloon_excess': 22390, 'total_interest': 669}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 5}, {'payment': 1667, 'final_payment': 93332, 'balloon_excess': 91665, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 600, 'amort_months': 12, 'term_months': 3}, {'payment': 8607, 'final_payment': 84164, 'balloon_excess': 75557, 'total_interest': 1378}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}], ['control 6', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 5}, {'payment': 19333, 'final_payment': 946947, 'balloon_excess': 927614, 'total_interest': 24279}]], [['regression: maturity month', {'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': 725, 'amort_months': 360, 'term_months': 12}, {'payment': 6822, 'final_payment': 997140, 'balloon_excess': 990318, 'total_interest': 72182}], ['control 2', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 360, 'term_months': 12}, {'payment': 10286, 'final_payment': 1006656, 'balloon_excess': 996370, 'total_interest': 119802}], ['control 3', {'principal': 55555, 'rate_bp': 725, 'amort_months': 360, 'term_months': 5}, {'payment': 379, 'final_payment': 55715, 'balloon_excess': 55336, 'total_interest': 1676}], ['control 4', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 3}, {'payment': 16667, 'final_payment': 966666, 'balloon_excess': 949999, 'total_interest': 0}], ['control 5', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 203010, 'final_payment': 203010, 'balloon_excess': 0, 'total_interest': 15050}], ['control 6', {'principal': 55555, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 3}, {'payment': 11447, 'final_payment': 34001, 'balloon_excess': 22554, 'total_interest': 1340}]], [['regression: maturity month', {'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': 3000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 12}, {'payment': 66733, 'final_payment': 2600867, 'balloon_excess': 2534134, 'total_interest': 334930}], ['control 2', {'principal': 55555, 'rate_bp': 725, 'amort_months': 12, 'term_months': 3}, {'payment': 4813, 'final_payment': 46855, 'balloon_excess': 42042, 'total_interest': 926}], ['control 3', {'principal': 100000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 60}, {'payment': 1933, 'final_payment': 1952, 'balloon_excess': 19, 'total_interest': 15999}], ['control 4', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}], ['control 5', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 60}, {'payment': 6822, 'final_payment': 950593, 'balloon_excess': 943771, 'total_interest': 353091}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}]]]
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)

source:
"""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']) - 1
    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: maturity month', {'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': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['control 2', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 360, 'term_months': 60}, {'payment': 5996, 'final_payment': 936506, 'balloon_excess': 930510, 'total_interest': 290270}], ['control 3', {'principal': 55555, 'rate_bp': 0, 'amort_months': 360, 'term_months': 5}, {'payment': 154, 'final_payment': 54939, 'balloon_excess': 54785, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 725, 'amort_months': 5, 'term_months': 5}, {'payment': 20364, 'final_payment': 20364, 'balloon_excess': 0, 'total_interest': 1820}], ['control 5', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 60, 'term_months': 3}, {'payment': 19919, 'final_payment': 978035, 'balloon_excess': 958116, 'total_interest': 17873}], ['control 6', {'principal': 55555, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 5}, {'payment': 1236, 'final_payment': 53321, 'balloon_excess': 52085, 'total_interest': 2710}]], [['regression: maturity month', {'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': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 8333, 'final_payment': 66668, 'balloon_excess': 58335, 'total_interest': 0}], ['control 2', {'principal': 100000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 5}, {'payment': 682, 'final_payment': 100288, 'balloon_excess': 99606, 'total_interest': 3016}], ['control 3', {'principal': 100000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 20301, 'final_payment': 20300, 'balloon_excess': -1, 'total_interest': 1504}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 60}, {'payment': 2224, 'final_payment': 2262, 'balloon_excess': 38, 'total_interest': 33478}], ['control 5', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 360, 'term_months': 84}, {'payment': 30858, 'final_payment': 2918745, 'balloon_excess': 2887887, 'total_interest': 2479959}], ['control 6', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 12}, {'payment': 50000, 'final_payment': 2450000, 'balloon_excess': 2400000, 'total_interest': 0}]], [['regression: maturity month', {'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': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['control 2', {'principal': 55555, 'rate_bp': 600, 'amort_months': 5, 'term_months': 3}, {'payment': 11278, 'final_payment': 33668, 'balloon_excess': 22390, 'total_interest': 669}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 5}, {'payment': 1667, 'final_payment': 93332, 'balloon_excess': 91665, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 600, 'amort_months': 12, 'term_months': 3}, {'payment': 8607, 'final_payment': 84164, 'balloon_excess': 75557, 'total_interest': 1378}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}], ['control 6', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 5}, {'payment': 19333, 'final_payment': 946947, 'balloon_excess': 927614, 'total_interest': 24279}]], [['regression: maturity month', {'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': 725, 'amort_months': 360, 'term_months': 12}, {'payment': 6822, 'final_payment': 997140, 'balloon_excess': 990318, 'total_interest': 72182}], ['control 2', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 360, 'term_months': 12}, {'payment': 10286, 'final_payment': 1006656, 'balloon_excess': 996370, 'total_interest': 119802}], ['control 3', {'principal': 55555, 'rate_bp': 725, 'amort_months': 360, 'term_months': 5}, {'payment': 379, 'final_payment': 55715, 'balloon_excess': 55336, 'total_interest': 1676}], ['control 4', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 3}, {'payment': 16667, 'final_payment': 966666, 'balloon_excess': 949999, 'total_interest': 0}], ['control 5', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 203010, 'final_payment': 203010, 'balloon_excess': 0, 'total_interest': 15050}], ['control 6', {'principal': 55555, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 3}, {'payment': 11447, 'final_payment': 34001, 'balloon_excess': 22554, 'total_interest': 1340}]], [['regression: maturity month', {'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': 3000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 12}, {'payment': 66733, 'final_payment': 2600867, 'balloon_excess': 2534134, 'total_interest': 334930}], ['control 2', {'principal': 55555, 'rate_bp': 725, 'amort_months': 12, 'term_months': 3}, {'payment': 4813, 'final_payment': 46855, 'balloon_excess': 42042, 'total_interest': 926}], ['control 3', {'principal': 100000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 60}, {'payment': 1933, 'final_payment': 1952, 'balloon_excess': 19, 'total_interest': 15999}], ['control 4', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}], ['control 5', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 60}, {'payment': 6822, 'final_payment': 950593, 'balloon_excess': 943771, 'total_interest': 353091}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}]]]
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)

prompt:
Loans whose amortization ends before the term keep charging payments into negative balances.

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'}.

split: open-access


broken_source:
"""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 + pay
        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: balloon payment composition', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['regression: balloon payment composition, 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}], ['sample 1', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 360, 'term_months': 60}, {'payment': 5996, 'final_payment': 936506, 'balloon_excess': 930510, 'total_interest': 290270}], ['sample 2', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 12, 'term_months': 84}, {'payment': 86066, 'final_payment': 86070, 'balloon_excess': 4, 'total_interest': 32796}], ['sample 3', {'principal': 3000000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 84}, {'payment': 609030, 'final_payment': 609030, 'balloon_excess': 0, 'total_interest': 45150}], ['sample 4', {'principal': 55555, 'rate_bp': 725, 'amort_months': 60, 'term_months': 84}, {'payment': 1107, 'final_payment': 1081, 'balloon_excess': -26, 'total_interest': 10839}], ['sample 5', {'principal': 55555, 'rate_bp': 0, 'amort_months': 360, 'term_months': 5}, {'payment': 154, 'final_payment': 54939, 'balloon_excess': 54785, 'total_interest': 0}]], [['regression: balloon payment composition', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 8333, 'final_payment': 66668, 'balloon_excess': 58335, 'total_interest': 0}], ['regression: balloon payment composition, partial-repair probe', {'principal': 100000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 5}, {'payment': 682, 'final_payment': 100288, 'balloon_excess': 99606, 'total_interest': 3016}], ['sample 1', {'principal': 100000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 20301, 'final_payment': 20300, 'balloon_excess': -1, 'total_interest': 1504}], ['sample 2', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 60}, {'payment': 2224, 'final_payment': 2262, 'balloon_excess': 38, 'total_interest': 33478}], ['sample 3', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 360, 'term_months': 84}, {'payment': 30858, 'final_payment': 2918745, 'balloon_excess': 2887887, 'total_interest': 2479959}], ['sample 4', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 12}, {'payment': 50000, 'final_payment': 2450000, 'balloon_excess': 2400000, 'total_interest': 0}], ['sample 5', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 5}, {'payment': 2224, 'final_payment': 95980, 'balloon_excess': 93756, 'total_interest': 4876}]], [['regression: balloon payment composition', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['regression: balloon payment composition, partial-repair probe', {'principal': 55555, 'rate_bp': 600, 'amort_months': 5, 'term_months': 3}, {'payment': 11278, 'final_payment': 33668, 'balloon_excess': 22390, 'total_interest': 669}], ['sample 1', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 60}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['sample 2', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 5}, {'payment': 1667, 'final_payment': 93332, 'balloon_excess': 91665, 'total_interest': 0}], ['sample 3', {'principal': 55555, 'rate_bp': 600, 'amort_months': 60, 'term_months': 84}, {'payment': 1074, 'final_payment': 1074, 'balloon_excess': 0, 'total_interest': 8885}], ['sample 4', {'principal': 100000, 'rate_bp': 600, 'amort_months': 12, 'term_months': 3}, {'payment': 8607, 'final_payment': 84164, 'balloon_excess': 75557, 'total_interest': 1378}], ['sample 5', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}]], [['regression: balloon payment composition', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 12}, {'payment': 6822, 'final_payment': 997140, 'balloon_excess': 990318, 'total_interest': 72182}], ['sample 1', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 360, 'term_months': 12}, {'payment': 10286, 'final_payment': 1006656, 'balloon_excess': 996370, 'total_interest': 119802}], ['sample 2', {'principal': 55555, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 11111, 'final_payment': 11111, 'balloon_excess': 0, 'total_interest': 0}], ['sample 3', {'principal': 55555, 'rate_bp': 725, 'amort_months': 360, 'term_months': 5}, {'payment': 379, 'final_payment': 55715, 'balloon_excess': 55336, 'total_interest': 1676}], ['sample 4', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 3}, {'payment': 16667, 'final_payment': 966666, 'balloon_excess': 949999, 'total_interest': 0}], ['sample 5', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 84}, {'payment': 50000, 'final_payment': 50000, 'balloon_excess': 0, 'total_interest': 0}], ['sample 6', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 203010, 'final_payment': 203010, 'balloon_excess': 0, 'total_interest': 15050}]], [['regression: balloon payment composition', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 12}, {'payment': 66733, 'final_payment': 2600867, 'balloon_excess': 2534134, 'total_interest': 334930}], ['sample 1', {'principal': 55555, 'rate_bp': 725, 'amort_months': 12, 'term_months': 3}, {'payment': 4813, 'final_payment': 46855, 'balloon_excess': 42042, 'total_interest': 926}], ['sample 2', {'principal': 100000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 60}, {'payment': 1933, 'final_payment': 1952, 'balloon_excess': 19, 'total_interest': 15999}], ['sample 3', {'principal': 55555, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 60}, {'payment': 4936, 'final_payment': 4938, 'balloon_excess': 2, 'total_interest': 3679}], ['sample 4', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}], ['sample 5', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 60}, {'payment': 6822, 'final_payment': 950593, 'balloon_excess': 943771, 'total_interest': 353091}], ['sample 6', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}]]]
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)

meaning: Fixture-count band, not measured model difficulty

deps: stdlib

source:
"""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 = max(bal, pay)
        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: balloon payment composition', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['regression: balloon payment composition, 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}], ['sample 1', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 360, 'term_months': 60}, {'payment': 5996, 'final_payment': 936506, 'balloon_excess': 930510, 'total_interest': 290270}], ['sample 2', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 12, 'term_months': 84}, {'payment': 86066, 'final_payment': 86070, 'balloon_excess': 4, 'total_interest': 32796}], ['sample 3', {'principal': 3000000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 84}, {'payment': 609030, 'final_payment': 609030, 'balloon_excess': 0, 'total_interest': 45150}], ['sample 4', {'principal': 55555, 'rate_bp': 725, 'amort_months': 60, 'term_months': 84}, {'payment': 1107, 'final_payment': 1081, 'balloon_excess': -26, 'total_interest': 10839}], ['sample 5', {'principal': 55555, 'rate_bp': 0, 'amort_months': 360, 'term_months': 5}, {'payment': 154, 'final_payment': 54939, 'balloon_excess': 54785, 'total_interest': 0}]], [['regression: balloon payment composition', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 8333, 'final_payment': 66668, 'balloon_excess': 58335, 'total_interest': 0}], ['regression: balloon payment composition, partial-repair probe', {'principal': 100000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 5}, {'payment': 682, 'final_payment': 100288, 'balloon_excess': 99606, 'total_interest': 3016}], ['sample 1', {'principal': 100000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 20301, 'final_payment': 20300, 'balloon_excess': -1, 'total_interest': 1504}], ['sample 2', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 60}, {'payment': 2224, 'final_payment': 2262, 'balloon_excess': 38, 'total_interest': 33478}], ['sample 3', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 360, 'term_months': 84}, {'payment': 30858, 'final_payment': 2918745, 'balloon_excess': 2887887, 'total_interest': 2479959}], ['sample 4', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 12}, {'payment': 50000, 'final_payment': 2450000, 'balloon_excess': 2400000, 'total_interest': 0}], ['sample 5', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 5}, {'payment': 2224, 'final_payment': 95980, 'balloon_excess': 93756, 'total_interest': 4876}]], [['regression: balloon payment composition', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['regression: balloon payment composition, partial-repair probe', {'principal': 55555, 'rate_bp': 600, 'amort_months': 5, 'term_months': 3}, {'payment': 11278, 'final_payment': 33668, 'balloon_excess': 22390, 'total_interest': 669}], ['sample 1', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 5, 'term_months': 60}, {'payment': 206040, 'final_payment': 206040, 'balloon_excess': 0, 'total_interest': 30200}], ['sample 2', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 5}, {'payment': 1667, 'final_payment': 93332, 'balloon_excess': 91665, 'total_interest': 0}], ['sample 3', {'principal': 55555, 'rate_bp': 600, 'amort_months': 60, 'term_months': 84}, {'payment': 1074, 'final_payment': 1074, 'balloon_excess': 0, 'total_interest': 8885}], ['sample 4', {'principal': 100000, 'rate_bp': 600, 'amort_months': 12, 'term_months': 3}, {'payment': 8607, 'final_payment': 84164, 'balloon_excess': 75557, 'total_interest': 1378}], ['sample 5', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}]], [['regression: balloon payment composition', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 12}, {'payment': 6822, 'final_payment': 997140, 'balloon_excess': 990318, 'total_interest': 72182}], ['sample 1', {'principal': 1000000, 'rate_bp': 1200, 'amort_months': 360, 'term_months': 12}, {'payment': 10286, 'final_payment': 1006656, 'balloon_excess': 996370, 'total_interest': 119802}], ['sample 2', {'principal': 55555, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 11111, 'final_payment': 11111, 'balloon_excess': 0, 'total_interest': 0}], ['sample 3', {'principal': 55555, 'rate_bp': 725, 'amort_months': 360, 'term_months': 5}, {'payment': 379, 'final_payment': 55715, 'balloon_excess': 55336, 'total_interest': 1676}], ['sample 4', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 3}, {'payment': 16667, 'final_payment': 966666, 'balloon_excess': 949999, 'total_interest': 0}], ['sample 5', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 84}, {'payment': 50000, 'final_payment': 50000, 'balloon_excess': 0, 'total_interest': 0}], ['sample 6', {'principal': 1000000, 'rate_bp': 600, 'amort_months': 5, 'term_months': 5}, {'payment': 203010, 'final_payment': 203010, 'balloon_excess': 0, 'total_interest': 15050}]], [['regression: balloon payment composition', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 12}, {'payment': 66733, 'final_payment': 2600867, 'balloon_excess': 2534134, 'total_interest': 334930}], ['sample 1', {'principal': 55555, 'rate_bp': 725, 'amort_months': 12, 'term_months': 3}, {'payment': 4813, 'final_payment': 46855, 'balloon_excess': 42042, 'total_interest': 926}], ['sample 2', {'principal': 100000, 'rate_bp': 600, 'amort_months': 60, 'term_months': 60}, {'payment': 1933, 'final_payment': 1952, 'balloon_excess': 19, 'total_interest': 15999}], ['sample 3', {'principal': 55555, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 60}, {'payment': 4936, 'final_payment': 4938, 'balloon_excess': 2, 'total_interest': 3679}], ['sample 4', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}], ['sample 5', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 60}, {'payment': 6822, 'final_payment': 950593, 'balloon_excess': 943771, 'total_interest': 353091}], ['sample 6', {'principal': 100000, 'rate_bp': 1200, 'amort_months': 12, 'term_months': 12}, {'payment': 8885, 'final_payment': 8884, 'balloon_excess': -1, 'total_interest': 6619}]]]
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)

prompt:
The balloon payment is inflated by a full regular payment.

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'}.

split: open-access


broken_source:
"""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 else 0
        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: interest total scope', {'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': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['control 2', {'principal': 55555, 'rate_bp': 0, 'amort_months': 360, 'term_months': 5}, {'payment': 154, 'final_payment': 54939, 'balloon_excess': 54785, 'total_interest': 0}], ['control 3', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 600000, 'final_payment': 600000, 'balloon_excess': 0, 'total_interest': 0}], ['control 4', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 360, 'term_months': 5}, {'payment': 8333, 'final_payment': 2966668, 'balloon_excess': 2958335, 'total_interest': 0}], ['control 5', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 5}, {'payment': 50000, 'final_payment': 2800000, 'balloon_excess': 2750000, 'total_interest': 0}], ['control 6', {'principal': 55555, 'rate_bp': 0, 'amort_months': 12, 'term_months': 3}, {'payment': 4630, 'final_payment': 46295, 'balloon_excess': 41665, 'total_interest': 0}]], [['regression: interest total scope', {'principal': 100000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 5}, {'payment': 682, 'final_payment': 100288, 'balloon_excess': 99606, 'total_interest': 3016}], ['control 1', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 8333, 'final_payment': 66668, 'balloon_excess': 58335, 'total_interest': 0}], ['control 2', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 12}, {'payment': 50000, 'final_payment': 2450000, 'balloon_excess': 2400000, 'total_interest': 0}], ['control 3', {'principal': 55555, 'rate_bp': 0, 'amort_months': 360, 'term_months': 84}, {'payment': 154, 'final_payment': 42773, 'balloon_excess': 42619, 'total_interest': 0}], ['control 4', {'principal': 55555, 'rate_bp': 0, 'amort_months': 360, 'term_months': 3}, {'payment': 154, 'final_payment': 55247, 'balloon_excess': 55093, 'total_interest': 0}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 1667, 'final_payment': 1647, 'balloon_excess': -20, 'total_interest': 0}], ['control 6', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 360, 'term_months': 60}, {'payment': 8333, 'final_payment': 2508353, 'balloon_excess': 2500020, 'total_interest': 0}]], [['regression: interest total scope', {'principal': 55555, 'rate_bp': 600, 'amort_months': 5, 'term_months': 3}, {'payment': 11278, 'final_payment': 33668, 'balloon_excess': 22390, 'total_interest': 669}], ['control 1', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['control 2', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 5}, {'payment': 1667, 'final_payment': 93332, 'balloon_excess': 91665, 'total_interest': 0}], ['control 3', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 60}, {'payment': 600000, 'final_payment': 600000, 'balloon_excess': 0, 'total_interest': 0}], ['control 4', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 250000, 'final_payment': 2000000, 'balloon_excess': 1750000, 'total_interest': 0}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 8333, 'final_payment': 8337, 'balloon_excess': 4, 'total_interest': 0}], ['control 6', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 360, 'term_months': 12}, {'payment': 8333, 'final_payment': 2908337, 'balloon_excess': 2900004, 'total_interest': 0}]], [['regression: interest total scope', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 12}, {'payment': 6822, 'final_payment': 997140, 'balloon_excess': 990318, 'total_interest': 72182}], ['control 1', {'principal': 55555, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 11111, 'final_payment': 11111, 'balloon_excess': 0, 'total_interest': 0}], ['control 2', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 3}, {'payment': 16667, 'final_payment': 966666, 'balloon_excess': 949999, 'total_interest': 0}], ['control 3', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 84}, {'payment': 50000, 'final_payment': 50000, 'balloon_excess': 0, 'total_interest': 0}], ['control 4', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 600000, 'final_payment': 600000, 'balloon_excess': 0, 'total_interest': 0}], ['control 5', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 12}, {'payment': 600000, 'final_payment': 600000, 'balloon_excess': 0, 'total_interest': 0}], ['control 6', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 12}, {'payment': 1667, 'final_payment': 81663, 'balloon_excess': 79996, 'total_interest': 0}]], [['regression: interest total scope', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 12}, {'payment': 66733, 'final_payment': 2600867, 'balloon_excess': 2534134, 'total_interest': 334930}], ['control 1', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}], ['control 2', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 200000, 'final_payment': 600000, 'balloon_excess': 400000, 'total_interest': 0}], ['control 3', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 360, 'term_months': 60}, {'payment': 2778, 'final_payment': 836098, 'balloon_excess': 833320, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 0, 'amort_months': 360, 'term_months': 3}, {'payment': 278, 'final_payment': 99444, 'balloon_excess': 99166, 'total_interest': 0}], ['control 5', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 600000, 'final_payment': 1800000, 'balloon_excess': 1200000, 'total_interest': 0}], ['control 6', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 84}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)

source:
"""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 > 1 else 0
        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: interest total scope', {'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': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['control 2', {'principal': 55555, 'rate_bp': 0, 'amort_months': 360, 'term_months': 5}, {'payment': 154, 'final_payment': 54939, 'balloon_excess': 54785, 'total_interest': 0}], ['control 3', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 600000, 'final_payment': 600000, 'balloon_excess': 0, 'total_interest': 0}], ['control 4', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 360, 'term_months': 5}, {'payment': 8333, 'final_payment': 2966668, 'balloon_excess': 2958335, 'total_interest': 0}], ['control 5', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 5}, {'payment': 50000, 'final_payment': 2800000, 'balloon_excess': 2750000, 'total_interest': 0}], ['control 6', {'principal': 55555, 'rate_bp': 0, 'amort_months': 12, 'term_months': 3}, {'payment': 4630, 'final_payment': 46295, 'balloon_excess': 41665, 'total_interest': 0}]], [['regression: interest total scope', {'principal': 100000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 5}, {'payment': 682, 'final_payment': 100288, 'balloon_excess': 99606, 'total_interest': 3016}], ['control 1', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 8333, 'final_payment': 66668, 'balloon_excess': 58335, 'total_interest': 0}], ['control 2', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 12}, {'payment': 50000, 'final_payment': 2450000, 'balloon_excess': 2400000, 'total_interest': 0}], ['control 3', {'principal': 55555, 'rate_bp': 0, 'amort_months': 360, 'term_months': 84}, {'payment': 154, 'final_payment': 42773, 'balloon_excess': 42619, 'total_interest': 0}], ['control 4', {'principal': 55555, 'rate_bp': 0, 'amort_months': 360, 'term_months': 3}, {'payment': 154, 'final_payment': 55247, 'balloon_excess': 55093, 'total_interest': 0}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 1667, 'final_payment': 1647, 'balloon_excess': -20, 'total_interest': 0}], ['control 6', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 360, 'term_months': 60}, {'payment': 8333, 'final_payment': 2508353, 'balloon_excess': 2500020, 'total_interest': 0}]], [['regression: interest total scope', {'principal': 55555, 'rate_bp': 600, 'amort_months': 5, 'term_months': 3}, {'payment': 11278, 'final_payment': 33668, 'balloon_excess': 22390, 'total_interest': 669}], ['control 1', {'principal': 100000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 20000, 'final_payment': 60000, 'balloon_excess': 40000, 'total_interest': 0}], ['control 2', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 5}, {'payment': 1667, 'final_payment': 93332, 'balloon_excess': 91665, 'total_interest': 0}], ['control 3', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 60}, {'payment': 600000, 'final_payment': 600000, 'balloon_excess': 0, 'total_interest': 0}], ['control 4', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 5}, {'payment': 250000, 'final_payment': 2000000, 'balloon_excess': 1750000, 'total_interest': 0}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'amort_months': 12, 'term_months': 12}, {'payment': 8333, 'final_payment': 8337, 'balloon_excess': 4, 'total_interest': 0}], ['control 6', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 360, 'term_months': 12}, {'payment': 8333, 'final_payment': 2908337, 'balloon_excess': 2900004, 'total_interest': 0}]], [['regression: interest total scope', {'principal': 1000000, 'rate_bp': 725, 'amort_months': 360, 'term_months': 12}, {'payment': 6822, 'final_payment': 997140, 'balloon_excess': 990318, 'total_interest': 72182}], ['control 1', {'principal': 55555, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 11111, 'final_payment': 11111, 'balloon_excess': 0, 'total_interest': 0}], ['control 2', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 3}, {'payment': 16667, 'final_payment': 966666, 'balloon_excess': 949999, 'total_interest': 0}], ['control 3', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 84}, {'payment': 50000, 'final_payment': 50000, 'balloon_excess': 0, 'total_interest': 0}], ['control 4', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 84}, {'payment': 600000, 'final_payment': 600000, 'balloon_excess': 0, 'total_interest': 0}], ['control 5', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 12}, {'payment': 600000, 'final_payment': 600000, 'balloon_excess': 0, 'total_interest': 0}], ['control 6', {'principal': 100000, 'rate_bp': 0, 'amort_months': 60, 'term_months': 12}, {'payment': 1667, 'final_payment': 81663, 'balloon_excess': 79996, 'total_interest': 0}]], [['regression: interest total scope', {'principal': 3000000, 'rate_bp': 1200, 'amort_months': 60, 'term_months': 12}, {'payment': 66733, 'final_payment': 2600867, 'balloon_excess': 2534134, 'total_interest': 334930}], ['control 1', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 60}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}], ['control 2', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 200000, 'final_payment': 600000, 'balloon_excess': 400000, 'total_interest': 0}], ['control 3', {'principal': 1000000, 'rate_bp': 0, 'amort_months': 360, 'term_months': 60}, {'payment': 2778, 'final_payment': 836098, 'balloon_excess': 833320, 'total_interest': 0}], ['control 4', {'principal': 100000, 'rate_bp': 0, 'amort_months': 360, 'term_months': 3}, {'payment': 278, 'final_payment': 99444, 'balloon_excess': 99166, 'total_interest': 0}], ['control 5', {'principal': 3000000, 'rate_bp': 0, 'amort_months': 5, 'term_months': 3}, {'payment': 600000, 'final_payment': 1800000, 'balloon_excess': 1200000, 'total_interest': 0}], ['control 6', {'principal': 55555, 'rate_bp': 0, 'amort_months': 60, 'term_months': 84}, {'payment': 926, 'final_payment': 921, 'balloon_excess': -5, 'total_interest': 0}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)

prompt:
Total interest excludes the interest in the balloon payment.

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'}.

split: open-access


broken_source:
"""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']
    n = x['months']
    bal = P
    pay = x['min_payment']
    deferred = 0
    recast = None
    limit = P * x['cap_pct_bp']
    for k in range(1, n + 1):
        interest = rnd(bal * bp, 120000)
        if pay < interest:
            deferred += interest - pay
        bal += interest - pay
        if recast is None and bal > P:
            recast = k
            pay = level(bal, n - k)
    return {'balance': bal, 'recast_month': recast, 'deferred': deferred, 'payment': pay}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: cap threshold scale', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1063081, 'recast_month': None, 'deferred': 63081, 'payment': 10000}], ['regression: cap threshold scale, partial-repair probe', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 52312, 'payment': 534061}], ['control 1', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 2', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 3', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 5000}], ['control 4', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 5', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}]], [['regression: cap threshold scale', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 0, 'recast_month': 2, 'deferred': 4040, 'payment': 22715}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 500}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 3', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 10200, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 96962, 'recast_month': None, 'deferred': 0, 'payment': 1000}]], [['regression: cap threshold scale', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['regression: cap threshold scale, partial-repair probe', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 4, 'recast_month': 2, 'deferred': 20200, 'payment': 113575}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 20000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 618521, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 3', {'principal': 100000, 'rate_bp': 600, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 90887, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 174636, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 5', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}]], [['regression: cap threshold scale', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 222078, 'recast_month': 6, 'deferred': 22078, 'payment': 0}], ['control 1', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 187696, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 2', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 3', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 250, 'cap_pct_bp': 10100, 'months': 12}, {'balance': -3, 'recast_month': 1, 'deferred': 1750, 'payment': 10397}], ['control 5', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: cap threshold scale', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 109498, 'payment': 571446}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 24}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10500, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 2500, 'cap_pct_bp': 10100, 'months': 24}, {'balance': -3, 'recast_month': 1, 'deferred': 17500, 'payment': 55625}], ['control 6', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}]]]
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)

source:
"""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']
    n = x['months']
    bal = P
    pay = x['min_payment']
    deferred = 0
    recast = None
    limit = P * x['cap_pct_bp']
    for k in range(1, n + 1):
        interest = rnd(bal * bp, 120000)
        if pay < interest:
            deferred += interest - pay
        bal += interest - pay
        if recast is None and (bal - P) * 10000 > limit:
            recast = k
            pay = level(bal, n - k)
    return {'balance': bal, 'recast_month': recast, 'deferred': deferred, 'payment': pay}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: cap threshold scale', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1063081, 'recast_month': None, 'deferred': 63081, 'payment': 10000}], ['regression: cap threshold scale, partial-repair probe', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 52312, 'payment': 534061}], ['control 1', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 2', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 3', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 5000}], ['control 4', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 5', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}]], [['regression: cap threshold scale', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 0, 'recast_month': 2, 'deferred': 4040, 'payment': 22715}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 500}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 3', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 10200, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 96962, 'recast_month': None, 'deferred': 0, 'payment': 1000}]], [['regression: cap threshold scale', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['regression: cap threshold scale, partial-repair probe', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 4, 'recast_month': 2, 'deferred': 20200, 'payment': 113575}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 20000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 618521, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 3', {'principal': 100000, 'rate_bp': 600, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 90887, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 174636, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 5', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}]], [['regression: cap threshold scale', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 222078, 'recast_month': 6, 'deferred': 22078, 'payment': 0}], ['control 1', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 187696, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 2', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 3', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 250, 'cap_pct_bp': 10100, 'months': 12}, {'balance': -3, 'recast_month': 1, 'deferred': 1750, 'payment': 10397}], ['control 5', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: cap threshold scale', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 109498, 'payment': 571446}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 24}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10500, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 2500, 'cap_pct_bp': 10100, 'months': 24}, {'balance': -3, 'recast_month': 1, 'deferred': 17500, 'payment': 55625}], ['control 6', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}]]]
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)

prompt:
Loans recast at the first cent of negative amortization.

x = {'principal', 'rate_bp', 'min_payment', 'cap_pct_bp' (e.g. 11000 = 110% of principal), 'months'}. Each month interest = round_half_up(balance*bp/120000); the shortfall interest - payment (if positive) is deferred; balance += interest - payment. The first time balance*10000 exceeds principal*cap_pct_bp the loan recasts once: payment = level(balance, months - k) (exact annuity, half-up). Return {'balance', 'recast_month' or None, 'deferred', 'payment'}.

split: open-access


broken_source:
"""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']
    n = x['months']
    bal = P
    pay = x['min_payment']
    deferred = 0
    recast = None
    limit = P * x['cap_pct_bp']
    for k in range(1, n + 1):
        interest = rnd(bal * bp, 120000)
        if pay < interest:
            deferred += interest - pay
        bal += interest - pay
        if recast is None and bal * 10000 > limit:
            recast = k
            pay = level(P, n - k)
    return {'balance': bal, 'recast_month': recast, 'deferred': deferred, 'payment': pay}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: recast principal', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 52312, 'payment': 534061}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1063081, 'recast_month': None, 'deferred': 63081, 'payment': 10000}], ['control 2', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 5000}], ['control 5', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 6', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}]], [['regression: recast principal', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 0, 'recast_month': 2, 'deferred': 4040, 'payment': 22715}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 500}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 3', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 10200, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 96962, 'recast_month': None, 'deferred': 0, 'payment': 1000}]], [['regression: recast principal', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 4, 'recast_month': 2, 'deferred': 20200, 'payment': 113575}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['control 3', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 2500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1015189, 'recast_month': None, 'deferred': 15189, 'payment': 2500}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 20000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 618521, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 5', {'principal': 100000, 'rate_bp': 600, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 90887, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 174636, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: recast principal', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 250, 'cap_pct_bp': 10100, 'months': 12}, {'balance': -3, 'recast_month': 1, 'deferred': 1750, 'payment': 10397}], ['control 1', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 187696, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 2', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 222078, 'recast_month': 6, 'deferred': 22078, 'payment': 0}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 5', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: recast principal', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 109498, 'payment': 571446}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 24}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 3', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['control 4', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10500, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 6', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}]]]
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)

source:
"""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']
    n = x['months']
    bal = P
    pay = x['min_payment']
    deferred = 0
    recast = None
    limit = P * x['cap_pct_bp']
    for k in range(1, n + 1):
        interest = rnd(bal * bp, 120000)
        if pay < interest:
            deferred += interest - pay
        bal += interest - pay
        if recast is None and bal * 10000 > limit:
            recast = k
            pay = level(bal - deferred, n - k)
    return {'balance': bal, 'recast_month': recast, 'deferred': deferred, 'payment': pay}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: recast principal', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 52312, 'payment': 534061}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1063081, 'recast_month': None, 'deferred': 63081, 'payment': 10000}], ['control 2', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 5000, 'cap_pct_bp': 10500, 'months': 12}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 5000}], ['control 5', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 6', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}]], [['regression: recast principal', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 0, 'recast_month': 2, 'deferred': 4040, 'payment': 22715}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 500}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 3', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 10200, 'months': 36}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 5', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 96962, 'recast_month': None, 'deferred': 0, 'payment': 1000}]], [['regression: recast principal', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 4, 'recast_month': 2, 'deferred': 20200, 'payment': 113575}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['control 3', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 2500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 1015189, 'recast_month': None, 'deferred': 15189, 'payment': 2500}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'min_payment': 20000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 618521, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 5', {'principal': 100000, 'rate_bp': 600, 'min_payment': 2000, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 90887, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 174636, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: recast principal', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 250, 'cap_pct_bp': 10100, 'months': 12}, {'balance': -3, 'recast_month': 1, 'deferred': 1750, 'payment': 10397}], ['control 1', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 6}, {'balance': 187696, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 2', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 500, 'cap_pct_bp': 11000, 'months': 6}, {'balance': 222078, 'recast_month': 6, 'deferred': 22078, 'payment': 0}], ['control 3', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 12}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 4', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 12}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 5', {'principal': 1000000, 'rate_bp': 1200, 'min_payment': 10000, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 10000}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'min_payment': 4000, 'cap_pct_bp': 10100, 'months': 36}, {'balance': 113849, 'recast_month': None, 'deferred': 0, 'payment': 4000}]], [['regression: recast principal', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 10000, 'cap_pct_bp': 11000, 'months': 12}, {'balance': 1, 'recast_month': 10, 'deferred': 109498, 'payment': 571446}], ['control 1', {'principal': 1000000, 'rate_bp': 2400, 'min_payment': 20000, 'cap_pct_bp': 11000, 'months': 36}, {'balance': 1000000, 'recast_month': None, 'deferred': 0, 'payment': 20000}], ['control 2', {'principal': 100000, 'rate_bp': 2400, 'min_payment': 2000, 'cap_pct_bp': 10100, 'months': 24}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 2000}], ['control 3', {'principal': 200000, 'rate_bp': 600, 'min_payment': 500, 'cap_pct_bp': 10200, 'months': 6}, {'balance': 203039, 'recast_month': None, 'deferred': 3039, 'payment': 500}], ['control 4', {'principal': 200000, 'rate_bp': 2400, 'min_payment': 4000, 'cap_pct_bp': 10200, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 4000}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'min_payment': 1000, 'cap_pct_bp': 10500, 'months': 6}, {'balance': 100000, 'recast_month': None, 'deferred': 0, 'payment': 1000}], ['control 6', {'principal': 200000, 'rate_bp': 600, 'min_payment': 1000, 'cap_pct_bp': 11000, 'months': 24}, {'balance': 200000, 'recast_month': None, 'deferred': 0, 'payment': 1000}]]]
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)

prompt:
The recast payment leaves capitalized interest unamortized.

x = {'principal', 'rate_bp', 'min_payment', 'cap_pct_bp' (e.g. 11000 = 110% of principal), 'months'}. Each month interest = round_half_up(balance*bp/120000); the shortfall interest - payment (if positive) is deferred; balance += interest - payment. The first time balance*10000 exceeds principal*cap_pct_bp the loan recasts once: payment = level(balance, months - k) (exact annuity, half-up). Return {'balance', 'recast_month' or None, 'deferred', 'payment'}.

split: open-access