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']
    n = x['months']
    bal = x['principal']
    pay = level(bal, n)
    history = [pay]
    total = 0
    k = 0
    final = 0
    while bal > 0 and k < n:
        k += 1
        interest = rnd(bal * bp, 120000)
        total += interest
        if bal + interest <= pay or k == n:
            final = bal + interest
            bal = 0
            break
        bal -= pay - interest
        extra = min(x['prepay'].get(str(k), 0), bal)
        if extra:
            bal -= extra
            if bal > 0:
                pay = level(bal, n - k)
                history.append(pay)
            if bal == 0:
                final = pay + extra
    return {'months_paid': k, 'total_interest': total, 'final_payment': final, 'payments': history}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: recast mode gate', {'principal': 60000, 'rate_bp': 1200, 'months': 12, 'prepay': {'5': 5000, '11': 1000, '6': 200000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 2832, 'final_payment': 31177, 'payments': [5331]}], ['regression: recast mode gate, partial-repair probe', {'principal': 100000, 'rate_bp': 600, 'months': 6, 'prepay': {'1': 20000, '6': 2000000}, 'mode': 'shorten'}, {'months_paid': 5, 'total_interest': 1270, 'final_payment': 13430, 'payments': [16960]}], ['control 1', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'21': 5000, '12': 200000, '35': 2000000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 41663, 'payments': [1667]}], ['control 2', {'principal': 60000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000, '7': 20000}, 'mode': 'recast'}, {'months_paid': 8, 'total_interest': 1612, 'final_payment': 5464, 'payments': [5164, 1104]}], ['control 3', {'principal': 60000, 'rate_bp': 1200, 'months': 36, 'prepay': {'31': 200000, '10': 20000, '20': 1000}, 'mode': 'recast'}, {'months_paid': 31, 'total_interest': 8690, 'final_payment': 6130, 'payments': [1993, 1115, 1048]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'26': 20000, '10': 5000, '3': 2000000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 56666, 'payments': [1667]}], ['control 5', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'10': 1000, '6': 5000, '7': 5000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 18501, 'payments': [20833, 20000, 19000, 18501]}]], [['regression: recast mode gate', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'11': 20000, '19': 20000, '2': 1000}, 'mode': 'shorten'}, {'months_paid': 19, 'total_interest': 0, 'final_payment': 8994, 'payments': [1667]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 24, 'prepay': {'24': 200000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 10409, 'payments': [10417]}], ['control 2', {'principal': 250000, 'rate_bp': 0, 'months': 6, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 41665, 'payments': [41667]}], ['control 3', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'10': 5000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 12595, 'final_payment': 4326, 'payments': [4707, 4323]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 4159, 'payments': [4167]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 12979, 'final_payment': 4718, 'payments': [4707]}]], [['regression: recast mode gate', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'12': 5000, '20': 20000, '4': 1000}, 'mode': 'shorten'}, {'months_paid': 20, 'total_interest': 11928, 'final_payment': 16495, 'payments': [4707]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 12, 'prepay': {'10': 1000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 3272, 'final_payment': 8101, 'payments': [8607, 8101]}], ['control 2', {'principal': 100000, 'rate_bp': 600, 'months': 6, 'prepay': {'5': 1000, '3': 20000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 1552, 'final_payment': 9220, 'payments': [16960, 10226, 9220]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'23': 1000, '10': 1000, '33': 200000}, 'mode': 'recast'}, {'months_paid': 33, 'total_interest': 0, 'final_payment': 6207, 'payments': [1667, 1628, 1551]}], ['control 4', {'principal': 60000, 'rate_bp': 600, 'months': 24, 'prepay': {'15': 20000, '5': 1000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 3267, 'final_payment': 324, 'payments': [2659, 2604, 326]}], ['control 5', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'5': 2000000, '1': 200000, '3': 20000}, 'mode': 'recast'}, {'months_paid': 1, 'total_interest': 0, 'final_payment': 60000, 'payments': [10000]}], ['control 6', {'principal': 60000, 'rate_bp': 1200, 'months': 12, 'prepay': {'4': 200000}, 'mode': 'recast'}, {'months_paid': 4, 'total_interest': 2115, 'final_payment': 46122, 'payments': [5331]}]], [['regression: recast mode gate', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'7': 200000, '3': 5000}, 'mode': 'shorten'}, {'months_paid': 7, 'total_interest': 0, 'final_payment': 120002, 'payments': [20833]}], ['regression: recast mode gate, partial-repair probe', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'9': 5000, '24': 5000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 9520, 'final_payment': 3050, 'payments': [3042]}], ['control 2', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 3', {'principal': 60000, 'rate_bp': 600, 'months': 36, 'prepay': {'24': 2000000, '33': 1000, '12': 2000000}, 'mode': 'shorten'}, {'months_paid': 12, 'total_interest': 3088, 'final_payment': 43013, 'payments': [1825]}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'20': 5000, '18': 2000000, '15': 1000}, 'mode': 'recast'}, {'months_paid': 18, 'total_interest': 11987, 'final_payment': 31200, 'payments': [4707, 4591]}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 12, 'prepay': {'1': 200000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 4349, 'final_payment': 2927, 'payments': [22212, 2921]}]], [['regression: recast mode gate', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'4': 1000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 9000, 'payments': [10000]}], ['regression: recast mode gate, partial-repair probe', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'10': 5000}, 'mode': 'shorten'}, {'months_paid': 11, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 1', {'principal': 250000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000}, 'mode': 'shorten'}, {'months_paid': 8, 'total_interest': 7134, 'final_payment': 106515, 'payments': [21517]}], ['control 2', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'months': 6, 'prepay': {'3': 1000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 16333, 'payments': [16667, 16333]}], ['control 4', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {'34': 2000000, '15': 200000}, 'mode': 'recast'}, {'months_paid': 15, 'total_interest': 6138, 'final_payment': 63550, 'payments': [3042]}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 12, 'prepay': {'6': 2000000, '10': 20000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 12005, 'final_payment': 150945, 'payments': [22212]}]]]
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']
    n = x['months']
    bal = x['principal']
    pay = level(bal, n)
    history = [pay]
    total = 0
    k = 0
    final = 0
    while bal > 0 and k < n:
        k += 1
        interest = rnd(bal * bp, 120000)
        total += interest
        if bal + interest <= pay or k == n:
            final = bal + interest
            bal = 0
            break
        bal -= pay - interest
        extra = min(x['prepay'].get(str(k), 0), bal)
        if extra:
            bal -= extra
            if (x['mode'] == 'recast' or extra >= pay) and bal > 0:
                pay = level(bal, n - k)
                history.append(pay)
            if bal == 0:
                final = pay + extra
    return {'months_paid': k, 'total_interest': total, 'final_payment': final, 'payments': history}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: recast mode gate', {'principal': 60000, 'rate_bp': 1200, 'months': 12, 'prepay': {'5': 5000, '11': 1000, '6': 200000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 2832, 'final_payment': 31177, 'payments': [5331]}], ['regression: recast mode gate, partial-repair probe', {'principal': 100000, 'rate_bp': 600, 'months': 6, 'prepay': {'1': 20000, '6': 2000000}, 'mode': 'shorten'}, {'months_paid': 5, 'total_interest': 1270, 'final_payment': 13430, 'payments': [16960]}], ['control 1', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'21': 5000, '12': 200000, '35': 2000000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 41663, 'payments': [1667]}], ['control 2', {'principal': 60000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000, '7': 20000}, 'mode': 'recast'}, {'months_paid': 8, 'total_interest': 1612, 'final_payment': 5464, 'payments': [5164, 1104]}], ['control 3', {'principal': 60000, 'rate_bp': 1200, 'months': 36, 'prepay': {'31': 200000, '10': 20000, '20': 1000}, 'mode': 'recast'}, {'months_paid': 31, 'total_interest': 8690, 'final_payment': 6130, 'payments': [1993, 1115, 1048]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'26': 20000, '10': 5000, '3': 2000000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 56666, 'payments': [1667]}], ['control 5', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'10': 1000, '6': 5000, '7': 5000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 18501, 'payments': [20833, 20000, 19000, 18501]}]], [['regression: recast mode gate', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'11': 20000, '19': 20000, '2': 1000}, 'mode': 'shorten'}, {'months_paid': 19, 'total_interest': 0, 'final_payment': 8994, 'payments': [1667]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 24, 'prepay': {'24': 200000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 10409, 'payments': [10417]}], ['control 2', {'principal': 250000, 'rate_bp': 0, 'months': 6, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 41665, 'payments': [41667]}], ['control 3', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'10': 5000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 12595, 'final_payment': 4326, 'payments': [4707, 4323]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 4159, 'payments': [4167]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 12979, 'final_payment': 4718, 'payments': [4707]}]], [['regression: recast mode gate', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'12': 5000, '20': 20000, '4': 1000}, 'mode': 'shorten'}, {'months_paid': 20, 'total_interest': 11928, 'final_payment': 16495, 'payments': [4707]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 12, 'prepay': {'10': 1000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 3272, 'final_payment': 8101, 'payments': [8607, 8101]}], ['control 2', {'principal': 100000, 'rate_bp': 600, 'months': 6, 'prepay': {'5': 1000, '3': 20000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 1552, 'final_payment': 9220, 'payments': [16960, 10226, 9220]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'23': 1000, '10': 1000, '33': 200000}, 'mode': 'recast'}, {'months_paid': 33, 'total_interest': 0, 'final_payment': 6207, 'payments': [1667, 1628, 1551]}], ['control 4', {'principal': 60000, 'rate_bp': 600, 'months': 24, 'prepay': {'15': 20000, '5': 1000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 3267, 'final_payment': 324, 'payments': [2659, 2604, 326]}], ['control 5', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'5': 2000000, '1': 200000, '3': 20000}, 'mode': 'recast'}, {'months_paid': 1, 'total_interest': 0, 'final_payment': 60000, 'payments': [10000]}], ['control 6', {'principal': 60000, 'rate_bp': 1200, 'months': 12, 'prepay': {'4': 200000}, 'mode': 'recast'}, {'months_paid': 4, 'total_interest': 2115, 'final_payment': 46122, 'payments': [5331]}]], [['regression: recast mode gate', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'7': 200000, '3': 5000}, 'mode': 'shorten'}, {'months_paid': 7, 'total_interest': 0, 'final_payment': 120002, 'payments': [20833]}], ['regression: recast mode gate, partial-repair probe', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'9': 5000, '24': 5000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 9520, 'final_payment': 3050, 'payments': [3042]}], ['control 2', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 3', {'principal': 60000, 'rate_bp': 600, 'months': 36, 'prepay': {'24': 2000000, '33': 1000, '12': 2000000}, 'mode': 'shorten'}, {'months_paid': 12, 'total_interest': 3088, 'final_payment': 43013, 'payments': [1825]}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'20': 5000, '18': 2000000, '15': 1000}, 'mode': 'recast'}, {'months_paid': 18, 'total_interest': 11987, 'final_payment': 31200, 'payments': [4707, 4591]}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 12, 'prepay': {'1': 200000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 4349, 'final_payment': 2927, 'payments': [22212, 2921]}]], [['regression: recast mode gate', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'4': 1000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 9000, 'payments': [10000]}], ['regression: recast mode gate, partial-repair probe', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'10': 5000}, 'mode': 'shorten'}, {'months_paid': 11, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 1', {'principal': 250000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000}, 'mode': 'shorten'}, {'months_paid': 8, 'total_interest': 7134, 'final_payment': 106515, 'payments': [21517]}], ['control 2', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'months': 6, 'prepay': {'3': 1000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 16333, 'payments': [16667, 16333]}], ['control 4', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {'34': 2000000, '15': 200000}, 'mode': 'recast'}, {'months_paid': 15, 'total_interest': 6138, 'final_payment': 63550, 'payments': [3042]}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 12, 'prepay': {'6': 2000000, '10': 20000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 12005, 'final_payment': 150945, 'payments': [22212]}]]]
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:
Borrowers who chose term shortening see their payment reduced.

x = {'principal', 'rate_bp', 'months', 'prepay': {'k': extra cents after payment k}, 'mode': 'shorten'|'recast'}. The level payment is the exact annuity rounded half-up. Each month: interest = round_half_up(balance*bp/120000); if balance + interest <= payment or it is month n, pay balance + interest and stop. Otherwise reduce the balance by payment - interest, then apply the extra (capped at the balance). In recast mode a nonzero extra that leaves a balance re-levels the payment over the n - k remaining months. An extra that clears the balance ends the loan with final payment = payment + extra. Return {'months_paid', 'total_interest', 'final_payment', 'payments': payment history}.

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']
    n = x['months']
    bal = x['principal']
    pay = level(bal, n)
    history = [pay]
    total = 0
    k = 0
    final = 0
    while bal > 0 and k < n:
        k += 1
        interest = rnd(bal * bp, 120000)
        total += interest
        if bal + interest < pay or k == n:
            final = bal + interest
            bal = 0
            break
        bal -= pay - interest
        extra = min(x['prepay'].get(str(k), 0), bal)
        if extra:
            bal -= extra
            if x['mode'] == 'recast' and bal > 0:
                pay = level(bal, n - k)
                history.append(pay)
            if bal == 0:
                final = pay + extra
    return {'months_paid': k, 'total_interest': total, 'final_payment': final, 'payments': history}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: balance plus interest exactly one payment', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 30000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 10000, 'payments': [10000]}], ['regression: payoff month test', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'18': 200000, '14': 20000}, 'mode': 'shorten'}, {'months_paid': 16, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['regression: payoff month test, partial-repair probe', {'principal': 60000, 'rate_bp': 1200, 'months': 24, 'prepay': {'16': 5000, '9': 1000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 7480, 'final_payment': 2100, 'payments': [2824, 2753, 2099]}], ['control 1', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'21': 5000, '12': 200000, '35': 2000000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 41663, 'payments': [1667]}], ['control 2', {'principal': 60000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000, '7': 20000}, 'mode': 'recast'}, {'months_paid': 8, 'total_interest': 1612, 'final_payment': 5464, 'payments': [5164, 1104]}], ['control 3', {'principal': 60000, 'rate_bp': 1200, 'months': 36, 'prepay': {'31': 200000, '10': 20000, '20': 1000}, 'mode': 'recast'}, {'months_paid': 31, 'total_interest': 8690, 'final_payment': 6130, 'payments': [1993, 1115, 1048]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'26': 20000, '10': 5000, '3': 2000000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 56666, 'payments': [1667]}]], [['regression: balance plus interest exactly one payment', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 30000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 10000, 'payments': [10000]}], ['regression: payoff month test, partial-repair probe', {'principal': 250000, 'rate_bp': 0, 'months': 24, 'prepay': {'24': 200000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 10409, 'payments': [10417]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 6, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 41665, 'payments': [41667]}], ['control 2', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'10': 5000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 12595, 'final_payment': 4326, 'payments': [4707, 4323]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 4', {'principal': 100000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 4159, 'payments': [4167]}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 12979, 'final_payment': 4718, 'payments': [4707]}]], [['regression: balance plus interest exactly one payment', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 30000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 10000, 'payments': [10000]}], ['regression: payoff month test', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'3': 5000, '11': 1000}, 'mode': 'shorten'}, {'months_paid': 11, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['regression: payoff month test, partial-repair probe', {'principal': 100000, 'rate_bp': 600, 'months': 12, 'prepay': {'10': 1000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 3272, 'final_payment': 8101, 'payments': [8607, 8101]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 6, 'prepay': {'5': 1000, '3': 20000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 1552, 'final_payment': 9220, 'payments': [16960, 10226, 9220]}], ['control 2', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'12': 5000, '20': 20000, '4': 1000}, 'mode': 'shorten'}, {'months_paid': 20, 'total_interest': 11928, 'final_payment': 16495, 'payments': [4707]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'23': 1000, '10': 1000, '33': 200000}, 'mode': 'recast'}, {'months_paid': 33, 'total_interest': 0, 'final_payment': 6207, 'payments': [1667, 1628, 1551]}], ['control 4', {'principal': 60000, 'rate_bp': 600, 'months': 24, 'prepay': {'15': 20000, '5': 1000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 3267, 'final_payment': 324, 'payments': [2659, 2604, 326]}]], [['regression: balance plus interest exactly one payment', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 30000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 10000, 'payments': [10000]}], ['regression: payoff month test', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'9': 5000, '24': 5000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['regression: payoff month test, partial-repair probe', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 9520, 'final_payment': 3050, 'payments': [3042]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'7': 200000, '3': 5000}, 'mode': 'shorten'}, {'months_paid': 7, 'total_interest': 0, 'final_payment': 120002, 'payments': [20833]}], ['control 2', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 3', {'principal': 60000, 'rate_bp': 600, 'months': 36, 'prepay': {'24': 2000000, '33': 1000, '12': 2000000}, 'mode': 'shorten'}, {'months_paid': 12, 'total_interest': 3088, 'final_payment': 43013, 'payments': [1825]}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'20': 5000, '18': 2000000, '15': 1000}, 'mode': 'recast'}, {'months_paid': 18, 'total_interest': 11987, 'final_payment': 31200, 'payments': [4707, 4591]}]], [['regression: balance plus interest exactly one payment', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 30000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 10000, 'payments': [10000]}], ['regression: payoff month test', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'10': 5000}, 'mode': 'shorten'}, {'months_paid': 11, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['regression: payoff month test, partial-repair probe', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'4': 1000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 9000, 'payments': [10000]}], ['control 1', {'principal': 250000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000}, 'mode': 'shorten'}, {'months_paid': 8, 'total_interest': 7134, 'final_payment': 106515, 'payments': [21517]}], ['control 2', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'4': 200000, '1': 1000, '6': 200000}, 'mode': 'shorten'}, {'months_paid': 4, 'total_interest': 0, 'final_payment': 29000, 'payments': [10000]}], ['control 4', {'principal': 100000, 'rate_bp': 0, 'months': 6, 'prepay': {'3': 1000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 16333, 'payments': [16667, 16333]}]]]
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']
    n = x['months']
    bal = x['principal']
    pay = level(bal, n)
    history = [pay]
    total = 0
    k = 0
    final = 0
    while bal > 0 and k < n:
        k += 1
        interest = rnd(bal * bp, 120000)
        total += interest
        if bal + interest - pay < pay or k == n:
            final = bal + interest
            bal = 0
            break
        bal -= pay - interest
        extra = min(x['prepay'].get(str(k), 0), bal)
        if extra:
            bal -= extra
            if x['mode'] == 'recast' and bal > 0:
                pay = level(bal, n - k)
                history.append(pay)
            if bal == 0:
                final = pay + extra
    return {'months_paid': k, 'total_interest': total, 'final_payment': final, 'payments': history}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: balance plus interest exactly one payment', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 30000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 10000, 'payments': [10000]}], ['regression: payoff month test', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'18': 200000, '14': 20000}, 'mode': 'shorten'}, {'months_paid': 16, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['regression: payoff month test, partial-repair probe', {'principal': 60000, 'rate_bp': 1200, 'months': 24, 'prepay': {'16': 5000, '9': 1000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 7480, 'final_payment': 2100, 'payments': [2824, 2753, 2099]}], ['control 1', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'21': 5000, '12': 200000, '35': 2000000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 41663, 'payments': [1667]}], ['control 2', {'principal': 60000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000, '7': 20000}, 'mode': 'recast'}, {'months_paid': 8, 'total_interest': 1612, 'final_payment': 5464, 'payments': [5164, 1104]}], ['control 3', {'principal': 60000, 'rate_bp': 1200, 'months': 36, 'prepay': {'31': 200000, '10': 20000, '20': 1000}, 'mode': 'recast'}, {'months_paid': 31, 'total_interest': 8690, 'final_payment': 6130, 'payments': [1993, 1115, 1048]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'26': 20000, '10': 5000, '3': 2000000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 56666, 'payments': [1667]}]], [['regression: balance plus interest exactly one payment', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 30000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 10000, 'payments': [10000]}], ['regression: payoff month test, partial-repair probe', {'principal': 250000, 'rate_bp': 0, 'months': 24, 'prepay': {'24': 200000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 10409, 'payments': [10417]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 6, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 41665, 'payments': [41667]}], ['control 2', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'10': 5000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 12595, 'final_payment': 4326, 'payments': [4707, 4323]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 4', {'principal': 100000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 4159, 'payments': [4167]}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 12979, 'final_payment': 4718, 'payments': [4707]}]], [['regression: balance plus interest exactly one payment', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 30000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 10000, 'payments': [10000]}], ['regression: payoff month test', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'3': 5000, '11': 1000}, 'mode': 'shorten'}, {'months_paid': 11, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['regression: payoff month test, partial-repair probe', {'principal': 100000, 'rate_bp': 600, 'months': 12, 'prepay': {'10': 1000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 3272, 'final_payment': 8101, 'payments': [8607, 8101]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 6, 'prepay': {'5': 1000, '3': 20000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 1552, 'final_payment': 9220, 'payments': [16960, 10226, 9220]}], ['control 2', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'12': 5000, '20': 20000, '4': 1000}, 'mode': 'shorten'}, {'months_paid': 20, 'total_interest': 11928, 'final_payment': 16495, 'payments': [4707]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'23': 1000, '10': 1000, '33': 200000}, 'mode': 'recast'}, {'months_paid': 33, 'total_interest': 0, 'final_payment': 6207, 'payments': [1667, 1628, 1551]}], ['control 4', {'principal': 60000, 'rate_bp': 600, 'months': 24, 'prepay': {'15': 20000, '5': 1000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 3267, 'final_payment': 324, 'payments': [2659, 2604, 326]}]], [['regression: balance plus interest exactly one payment', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 30000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 10000, 'payments': [10000]}], ['regression: payoff month test', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'9': 5000, '24': 5000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['regression: payoff month test, partial-repair probe', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 9520, 'final_payment': 3050, 'payments': [3042]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'7': 200000, '3': 5000}, 'mode': 'shorten'}, {'months_paid': 7, 'total_interest': 0, 'final_payment': 120002, 'payments': [20833]}], ['control 2', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 3', {'principal': 60000, 'rate_bp': 600, 'months': 36, 'prepay': {'24': 2000000, '33': 1000, '12': 2000000}, 'mode': 'shorten'}, {'months_paid': 12, 'total_interest': 3088, 'final_payment': 43013, 'payments': [1825]}], ['control 4', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'20': 5000, '18': 2000000, '15': 1000}, 'mode': 'recast'}, {'months_paid': 18, 'total_interest': 11987, 'final_payment': 31200, 'payments': [4707, 4591]}]], [['regression: balance plus interest exactly one payment', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 30000}, 'mode': 'shorten'}, {'months_paid': 3, 'total_interest': 0, 'final_payment': 10000, 'payments': [10000]}], ['regression: payoff month test', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'10': 5000}, 'mode': 'shorten'}, {'months_paid': 11, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['regression: payoff month test, partial-repair probe', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'4': 1000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 9000, 'payments': [10000]}], ['control 1', {'principal': 250000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000}, 'mode': 'shorten'}, {'months_paid': 8, 'total_interest': 7134, 'final_payment': 106515, 'payments': [21517]}], ['control 2', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'4': 200000, '1': 1000, '6': 200000}, 'mode': 'shorten'}, {'months_paid': 4, 'total_interest': 0, 'final_payment': 29000, 'payments': [10000]}], ['control 4', {'principal': 100000, 'rate_bp': 0, 'months': 6, 'prepay': {'3': 1000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 16333, 'payments': [16667, 16333]}]]]
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:
A loan whose balance plus interest equals the payment reports a zero final payment.

x = {'principal', 'rate_bp', 'months', 'prepay': {'k': extra cents after payment k}, 'mode': 'shorten'|'recast'}. The level payment is the exact annuity rounded half-up. Each month: interest = round_half_up(balance*bp/120000); if balance + interest <= payment or it is month n, pay balance + interest and stop. Otherwise reduce the balance by payment - interest, then apply the extra (capped at the balance). In recast mode a nonzero extra that leaves a balance re-levels the payment over the n - k remaining months. An extra that clears the balance ends the loan with final payment = payment + extra. Return {'months_paid', 'total_interest', 'final_payment', 'payments': payment history}.

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']
    n = x['months']
    bal = x['principal']
    pay = level(bal, n)
    history = [pay]
    total = 0
    k = 0
    final = 0
    while bal > 0 and k < n:
        k += 1
        interest = rnd(bal * bp, 120000)
        total += interest
        if bal + interest <= pay or k == n:
            final = bal + interest
            bal = 0
            break
        bal -= pay - interest
        extra = min(x['prepay'].get(str(k - 1), 0), bal)
        if extra:
            bal -= extra
            if x['mode'] == 'recast' and bal > 0:
                pay = level(bal, n - k)
                history.append(pay)
            if bal == 0:
                final = pay + extra
    return {'months_paid': k, 'total_interest': total, 'final_payment': final, 'payments': history}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: prepayment month key', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'21': 5000, '12': 200000, '35': 2000000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 41663, 'payments': [1667]}], ['control 1', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 2', {'principal': 100000, 'rate_bp': 0, 'months': 36, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 36, 'total_interest': 0, 'final_payment': 2770, 'payments': [2778]}], ['control 3', {'principal': 60000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 5712, 'final_payment': 1837, 'payments': [1825]}], ['control 4', {'principal': 100000, 'rate_bp': 0, 'months': 6, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 16665, 'payments': [16667]}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 8337, 'payments': [8333]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 6619, 'final_payment': 8884, 'payments': [8885]}]], [['regression: prepayment month key', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'10': 5000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 12595, 'final_payment': 4326, 'payments': [4707, 4323]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 24, 'prepay': {'24': 200000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 10409, 'payments': [10417]}], ['control 2', {'principal': 250000, 'rate_bp': 0, 'months': 6, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 41665, 'payments': [41667]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 4', {'principal': 100000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 4159, 'payments': [4167]}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 12979, 'final_payment': 4718, 'payments': [4707]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 6, 'prepay': {'6': 200000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 3528, 'final_payment': 17253, 'payments': [17255]}]], [['regression: prepayment month key', {'principal': 100000, 'rate_bp': 600, 'months': 12, 'prepay': {'10': 1000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 3272, 'final_payment': 8101, 'payments': [8607, 8101]}], ['control 1', {'principal': 100000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 8337, 'payments': [8333]}], ['control 2', {'principal': 60000, 'rate_bp': 600, 'months': 6, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 1055, 'final_payment': 10175, 'payments': [10176]}], ['control 3', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 5', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 6371, 'final_payment': 4435, 'payments': [4432]}]], [['regression: prepayment month key', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'7': 200000, '3': 5000}, 'mode': 'shorten'}, {'months_paid': 7, 'total_interest': 0, 'final_payment': 120002, 'payments': [20833]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 9520, 'final_payment': 3050, 'payments': [3042]}], ['control 2', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'9': 5000, '24': 5000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 4', {'principal': 60000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 7789, 'final_payment': 2837, 'payments': [2824]}], ['control 5', {'principal': 250000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 36, 'total_interest': 23797, 'final_payment': 7622, 'payments': [7605]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 6, 'prepay': {'6': 2000000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 3528, 'final_payment': 17253, 'payments': [17255]}]], [['regression: prepayment month key', {'principal': 250000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000}, 'mode': 'shorten'}, {'months_paid': 8, 'total_interest': 7134, 'final_payment': 106515, 'payments': [21517]}], ['control 1', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'4': 1000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 9000, 'payments': [10000]}], ['control 2', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 3', {'principal': 60000, 'rate_bp': 1200, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 11744, 'final_payment': 1989, 'payments': [1993]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'9': 5000, '22': 200000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 5', {'principal': 100000, 'rate_bp': 600, 'months': 6, 'prepay': {'6': 1000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 1757, 'final_payment': 16957, 'payments': [16960]}], ['control 6', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'9': 1000}, 'mode': 'shorten'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 4000, 'payments': [5000]}]]]
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']
    n = x['months']
    bal = x['principal']
    pay = level(bal, n)
    history = [pay]
    total = 0
    k = 0
    final = 0
    while bal > 0 and k < n:
        k += 1
        interest = rnd(bal * bp, 120000)
        total += interest
        if bal + interest <= pay or k == n:
            final = bal + interest
            bal = 0
            break
        bal -= pay - interest
        extra = min(x['prepay'].get(str(k + 1), 0), bal)
        if extra:
            bal -= extra
            if x['mode'] == 'recast' and bal > 0:
                pay = level(bal, n - k)
                history.append(pay)
            if bal == 0:
                final = pay + extra
    return {'months_paid': k, 'total_interest': total, 'final_payment': final, 'payments': history}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: prepayment month key', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'21': 5000, '12': 200000, '35': 2000000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 41663, 'payments': [1667]}], ['control 1', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 2', {'principal': 100000, 'rate_bp': 0, 'months': 36, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 36, 'total_interest': 0, 'final_payment': 2770, 'payments': [2778]}], ['control 3', {'principal': 60000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 5712, 'final_payment': 1837, 'payments': [1825]}], ['control 4', {'principal': 100000, 'rate_bp': 0, 'months': 6, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 16665, 'payments': [16667]}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 8337, 'payments': [8333]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 6619, 'final_payment': 8884, 'payments': [8885]}]], [['regression: prepayment month key', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'10': 5000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 12595, 'final_payment': 4326, 'payments': [4707, 4323]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 24, 'prepay': {'24': 200000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 10409, 'payments': [10417]}], ['control 2', {'principal': 250000, 'rate_bp': 0, 'months': 6, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 41665, 'payments': [41667]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 4', {'principal': 100000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 4159, 'payments': [4167]}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 12979, 'final_payment': 4718, 'payments': [4707]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 6, 'prepay': {'6': 200000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 3528, 'final_payment': 17253, 'payments': [17255]}]], [['regression: prepayment month key', {'principal': 100000, 'rate_bp': 600, 'months': 12, 'prepay': {'10': 1000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 3272, 'final_payment': 8101, 'payments': [8607, 8101]}], ['control 1', {'principal': 100000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 8337, 'payments': [8333]}], ['control 2', {'principal': 60000, 'rate_bp': 600, 'months': 6, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 1055, 'final_payment': 10175, 'payments': [10176]}], ['control 3', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 5', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 6371, 'final_payment': 4435, 'payments': [4432]}]], [['regression: prepayment month key', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'7': 200000, '3': 5000}, 'mode': 'shorten'}, {'months_paid': 7, 'total_interest': 0, 'final_payment': 120002, 'payments': [20833]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 9520, 'final_payment': 3050, 'payments': [3042]}], ['control 2', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 3', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'9': 5000, '24': 5000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 4', {'principal': 60000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 7789, 'final_payment': 2837, 'payments': [2824]}], ['control 5', {'principal': 250000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 36, 'total_interest': 23797, 'final_payment': 7622, 'payments': [7605]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 6, 'prepay': {'6': 2000000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 3528, 'final_payment': 17253, 'payments': [17255]}]], [['regression: prepayment month key', {'principal': 250000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000}, 'mode': 'shorten'}, {'months_paid': 8, 'total_interest': 7134, 'final_payment': 106515, 'payments': [21517]}], ['control 1', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'4': 1000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 9000, 'payments': [10000]}], ['control 2', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 3', {'principal': 60000, 'rate_bp': 1200, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 11744, 'final_payment': 1989, 'payments': [1993]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'9': 5000, '22': 200000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 5', {'principal': 100000, 'rate_bp': 600, 'months': 6, 'prepay': {'6': 1000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 1757, 'final_payment': 16957, 'payments': [16960]}], ['control 6', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'9': 1000}, 'mode': 'shorten'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 4000, 'payments': [5000]}]]]
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:
Extra principal is applied one month after the borrower sent it.

x = {'principal', 'rate_bp', 'months', 'prepay': {'k': extra cents after payment k}, 'mode': 'shorten'|'recast'}. The level payment is the exact annuity rounded half-up. Each month: interest = round_half_up(balance*bp/120000); if balance + interest <= payment or it is month n, pay balance + interest and stop. Otherwise reduce the balance by payment - interest, then apply the extra (capped at the balance). In recast mode a nonzero extra that leaves a balance re-levels the payment over the n - k remaining months. An extra that clears the balance ends the loan with final payment = payment + extra. Return {'months_paid', 'total_interest', 'final_payment', 'payments': payment history}.

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']
    n = x['months']
    bal = x['principal']
    pay = level(bal, n)
    history = [pay]
    total = 0
    k = 0
    final = 0
    while bal > 0 and k < n:
        k += 1
        interest = rnd(bal * bp, 120000)
        total += interest
        if bal + interest <= pay or k == n:
            final = bal + interest
            bal = 0
            break
        bal -= pay - interest
        extra = min(x['prepay'].get(str(k), 0), bal)
        if extra:
            bal -= extra
            if x['mode'] == 'recast' and bal > 0:
                pay = level(bal, n - k)
                history.append(pay)
            if bal == 0:
                final = extra
    return {'months_paid': k, 'total_interest': total, 'final_payment': final, 'payments': history}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: payoff by curtailment', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'21': 5000, '12': 200000, '35': 2000000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 41663, 'payments': [1667]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'10': 1000, '6': 5000, '7': 5000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 18501, 'payments': [20833, 20000, 19000, 18501]}], ['control 2', {'principal': 60000, 'rate_bp': 1200, 'months': 24, 'prepay': {'16': 5000, '9': 1000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 7480, 'final_payment': 2100, 'payments': [2824, 2753, 2099]}], ['control 3', {'principal': 100000, 'rate_bp': 1200, 'months': 6, 'prepay': {'5': 5000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 3478, 'final_payment': 12203, 'payments': [17255, 12203]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'9': 5000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 3334, 'payments': [5000, 3333]}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 12, 'prepay': {'8': 20000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 6117, 'final_payment': 3760, 'payments': [8885, 3759]}]], [['regression: payoff by curtailment', {'principal': 100000, 'rate_bp': 0, 'months': 24, 'prepay': {'19': 200000, '18': 20000, '24': 5000}, 'mode': 'recast'}, {'months_paid': 19, 'total_interest': 0, 'final_payment': 4994, 'payments': [4167, 832]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 24, 'prepay': {'24': 200000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 10409, 'payments': [10417]}], ['control 2', {'principal': 250000, 'rate_bp': 0, 'months': 6, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 41665, 'payments': [41667]}], ['control 3', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'10': 5000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 12595, 'final_payment': 4326, 'payments': [4707, 4323]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 4159, 'payments': [4167]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 12979, 'final_payment': 4718, 'payments': [4707]}]], [['regression: payoff by curtailment', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'12': 5000, '20': 20000, '4': 1000}, 'mode': 'shorten'}, {'months_paid': 20, 'total_interest': 11928, 'final_payment': 16495, 'payments': [4707]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 12, 'prepay': {'10': 1000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 3272, 'final_payment': 8101, 'payments': [8607, 8101]}], ['control 2', {'principal': 100000, 'rate_bp': 600, 'months': 6, 'prepay': {'5': 1000, '3': 20000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 1552, 'final_payment': 9220, 'payments': [16960, 10226, 9220]}], ['control 3', {'principal': 60000, 'rate_bp': 600, 'months': 24, 'prepay': {'15': 20000, '5': 1000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 3267, 'final_payment': 324, 'payments': [2659, 2604, 326]}], ['control 4', {'principal': 250000, 'rate_bp': 600, 'months': 36, 'prepay': {'10': 20000, '11': 1000}, 'mode': 'shorten'}, {'months_paid': 33, 'total_interest': 21026, 'final_payment': 6666, 'payments': [7605]}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 8337, 'payments': [8333]}], ['control 6', {'principal': 60000, 'rate_bp': 600, 'months': 6, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 1055, 'final_payment': 10175, 'payments': [10176]}]], [['regression: payoff by curtailment', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'7': 200000, '3': 5000}, 'mode': 'shorten'}, {'months_paid': 7, 'total_interest': 0, 'final_payment': 120002, 'payments': [20833]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 9520, 'final_payment': 3050, 'payments': [3042]}], ['control 2', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 3', {'principal': 250000, 'rate_bp': 1200, 'months': 12, 'prepay': {'1': 200000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 4349, 'final_payment': 2927, 'payments': [22212, 2921]}], ['control 4', {'principal': 60000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 5000}, 'mode': 'shorten'}, {'months_paid': 12, 'total_interest': 1867, 'final_payment': 63, 'payments': [5164]}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 5000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 15665, 'payments': [16667, 15667]}], ['control 6', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'9': 5000, '24': 5000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}]], [['regression: payoff by curtailment', {'principal': 250000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000}, 'mode': 'shorten'}, {'months_paid': 8, 'total_interest': 7134, 'final_payment': 106515, 'payments': [21517]}], ['control 1', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'4': 1000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 9000, 'payments': [10000]}], ['control 2', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'months': 6, 'prepay': {'3': 1000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 16333, 'payments': [16667, 16333]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'10': 5000}, 'mode': 'shorten'}, {'months_paid': 11, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {'4': 1000, '9': 20000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 29136, 'final_payment': 11008, 'payments': [11768]}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {'22': 5000}, 'mode': 'shorten'}, {'months_paid': 35, 'total_interest': 9173, 'final_payment': 745, 'payments': [3042]}]]]
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']
    n = x['months']
    bal = x['principal']
    pay = level(bal, n)
    history = [pay]
    total = 0
    k = 0
    final = 0
    while bal > 0 and k < n:
        k += 1
        interest = rnd(bal * bp, 120000)
        total += interest
        if bal + interest <= pay or k == n:
            final = bal + interest
            bal = 0
            break
        bal -= pay - interest
        extra = min(x['prepay'].get(str(k), 0), bal)
        if extra:
            bal -= extra
            if x['mode'] == 'recast' and bal > 0:
                pay = level(bal, n - k)
                history.append(pay)
            if bal == 0:
                final = pay
    return {'months_paid': k, 'total_interest': total, 'final_payment': final, 'payments': history}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: payoff by curtailment', {'principal': 60000, 'rate_bp': 0, 'months': 36, 'prepay': {'21': 5000, '12': 200000, '35': 2000000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 41663, 'payments': [1667]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'10': 1000, '6': 5000, '7': 5000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 18501, 'payments': [20833, 20000, 19000, 18501]}], ['control 2', {'principal': 60000, 'rate_bp': 1200, 'months': 24, 'prepay': {'16': 5000, '9': 1000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 7480, 'final_payment': 2100, 'payments': [2824, 2753, 2099]}], ['control 3', {'principal': 100000, 'rate_bp': 1200, 'months': 6, 'prepay': {'5': 5000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 3478, 'final_payment': 12203, 'payments': [17255, 12203]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'9': 5000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 3334, 'payments': [5000, 3333]}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 12, 'prepay': {'8': 20000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 6117, 'final_payment': 3760, 'payments': [8885, 3759]}]], [['regression: payoff by curtailment', {'principal': 100000, 'rate_bp': 0, 'months': 24, 'prepay': {'19': 200000, '18': 20000, '24': 5000}, 'mode': 'recast'}, {'months_paid': 19, 'total_interest': 0, 'final_payment': 4994, 'payments': [4167, 832]}], ['control 1', {'principal': 250000, 'rate_bp': 0, 'months': 24, 'prepay': {'24': 200000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 10409, 'payments': [10417]}], ['control 2', {'principal': 250000, 'rate_bp': 0, 'months': 6, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 41665, 'payments': [41667]}], ['control 3', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'10': 5000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 12595, 'final_payment': 4326, 'payments': [4707, 4323]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 4159, 'payments': [4167]}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 12979, 'final_payment': 4718, 'payments': [4707]}]], [['regression: payoff by curtailment', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'prepay': {'12': 5000, '20': 20000, '4': 1000}, 'mode': 'shorten'}, {'months_paid': 20, 'total_interest': 11928, 'final_payment': 16495, 'payments': [4707]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 12, 'prepay': {'10': 1000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 3272, 'final_payment': 8101, 'payments': [8607, 8101]}], ['control 2', {'principal': 100000, 'rate_bp': 600, 'months': 6, 'prepay': {'5': 1000, '3': 20000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 1552, 'final_payment': 9220, 'payments': [16960, 10226, 9220]}], ['control 3', {'principal': 60000, 'rate_bp': 600, 'months': 24, 'prepay': {'15': 20000, '5': 1000}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 3267, 'final_payment': 324, 'payments': [2659, 2604, 326]}], ['control 4', {'principal': 250000, 'rate_bp': 600, 'months': 36, 'prepay': {'10': 20000, '11': 1000}, 'mode': 'shorten'}, {'months_paid': 33, 'total_interest': 21026, 'final_payment': 6666, 'payments': [7605]}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 12, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 0, 'final_payment': 8337, 'payments': [8333]}], ['control 6', {'principal': 60000, 'rate_bp': 600, 'months': 6, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 1055, 'final_payment': 10175, 'payments': [10176]}]], [['regression: payoff by curtailment', {'principal': 250000, 'rate_bp': 0, 'months': 12, 'prepay': {'7': 200000, '3': 5000}, 'mode': 'shorten'}, {'months_paid': 7, 'total_interest': 0, 'final_payment': 120002, 'payments': [20833]}], ['control 1', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 36, 'total_interest': 9520, 'final_payment': 3050, 'payments': [3042]}], ['control 2', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {}, 'mode': 'recast'}, {'months_paid': 24, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}], ['control 3', {'principal': 250000, 'rate_bp': 1200, 'months': 12, 'prepay': {'1': 200000}, 'mode': 'recast'}, {'months_paid': 12, 'total_interest': 4349, 'final_payment': 2927, 'payments': [22212, 2921]}], ['control 4', {'principal': 60000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 5000}, 'mode': 'shorten'}, {'months_paid': 12, 'total_interest': 1867, 'final_payment': 63, 'payments': [5164]}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 6, 'prepay': {'1': 5000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 15665, 'payments': [16667, 15667]}], ['control 6', {'principal': 60000, 'rate_bp': 0, 'months': 24, 'prepay': {'9': 5000, '24': 5000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 0, 'final_payment': 2500, 'payments': [2500]}]], [['regression: payoff by curtailment', {'principal': 250000, 'rate_bp': 600, 'months': 12, 'prepay': {'8': 200000}, 'mode': 'shorten'}, {'months_paid': 8, 'total_interest': 7134, 'final_payment': 106515, 'payments': [21517]}], ['control 1', {'principal': 60000, 'rate_bp': 0, 'months': 6, 'prepay': {'4': 1000}, 'mode': 'shorten'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 9000, 'payments': [10000]}], ['control 2', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {}, 'mode': 'shorten'}, {'months_paid': 24, 'total_interest': 32442, 'final_payment': 11778, 'payments': [11768]}], ['control 3', {'principal': 100000, 'rate_bp': 0, 'months': 6, 'prepay': {'3': 1000}, 'mode': 'recast'}, {'months_paid': 6, 'total_interest': 0, 'final_payment': 16333, 'payments': [16667, 16333]}], ['control 4', {'principal': 60000, 'rate_bp': 0, 'months': 12, 'prepay': {'10': 5000}, 'mode': 'shorten'}, {'months_paid': 11, 'total_interest': 0, 'final_payment': 5000, 'payments': [5000]}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'prepay': {'4': 1000, '9': 20000}, 'mode': 'shorten'}, {'months_paid': 22, 'total_interest': 29136, 'final_payment': 11008, 'payments': [11768]}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'months': 36, 'prepay': {'22': 5000}, 'mode': 'shorten'}, {'months_paid': 35, 'total_interest': 9173, 'final_payment': 745, 'payments': [3042]}]]]
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:
A loan paid off by an extra payment reports only the extra as the final payment.

x = {'principal', 'rate_bp', 'months', 'prepay': {'k': extra cents after payment k}, 'mode': 'shorten'|'recast'}. The level payment is the exact annuity rounded half-up. Each month: interest = round_half_up(balance*bp/120000); if balance + interest <= payment or it is month n, pay balance + interest and stop. Otherwise reduce the balance by payment - interest, then apply the extra (capped at the balance). In recast mode a nonzero extra that leaves a balance re-levels the payment over the n - k remaining months. An extra that clears the balance ends the loan with final payment = payment + extra. Return {'months_paid', 'total_interest', 'final_payment', 'payments': payment history}.

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']
    io_bills = []
    total_int = 0
    for m in range(x['io_months']):
        days = 31 - x['fund_day'] if m == 0 else 30
        interest = rnd(P * bp * days, 120000 * 30)
        io_bills.append(interest)
        total_int += interest
    pay = level(P, x['io_months'] + x['amort_months'])
    bal = P
    final = 0
    for k in range(1, x['amort_months'] + 1):
        interest = rnd(bal * bp, 120000)
        total_int += interest
        if k == x['amort_months']:
            final = bal + interest
        else:
            bal -= pay - interest
    return {'io_bills': io_bills, 'payment': pay, 'final_payment': final, 'total_interest': total_int}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: amortization term', {'principal': 3000000, 'rate_bp': 500, 'io_months': 3, 'amort_months': 60, 'fund_day': 1}, {'io_bills': [12500, 12500, 12500], 'payment': 56614, 'final_payment': 56596, 'total_interest': 434322}], ['sample 1', {'principal': 12345, 'rate_bp': 725, 'io_months': 12, 'amort_months': 12, 'fund_day': 15}, {'io_bills': [40, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75], 'payment': 1070, 'final_payment': 1065, 'total_interest': 1355}], ['sample 2', {'principal': 250000, 'rate_bp': 725, 'io_months': 3, 'amort_months': 12, 'fund_day': 29}, {'io_bills': [101, 1510, 1510], 'payment': 21661, 'final_payment': 21655, 'total_interest': 13047}], ['sample 3', {'principal': 250000, 'rate_bp': 1200, 'io_months': 3, 'amort_months': 24, 'fund_day': 29}, {'io_bills': [167, 2500, 2500], 'payment': 11768, 'final_payment': 11778, 'total_interest': 37609}], ['sample 4', {'principal': 3000000, 'rate_bp': 1200, 'io_months': 3, 'amort_months': 1, 'fund_day': 2}, {'io_bills': [29000, 30000, 30000], 'payment': 3030000, 'final_payment': 3030000, 'total_interest': 119000}], ['sample 5', {'principal': 12345, 'rate_bp': 0, 'io_months': 1, 'amort_months': 24, 'fund_day': 2}, {'io_bills': [0], 'payment': 514, 'final_payment': 523, 'total_interest': 0}], ['sample 6', {'principal': 12345, 'rate_bp': 500, 'io_months': 3, 'amort_months': 12, 'fund_day': 15}, {'io_bills': [27, 51, 51], 'payment': 1057, 'final_payment': 1054, 'total_interest': 465}]], [['regression: amortization term', {'principal': 12345, 'rate_bp': 0, 'io_months': 3, 'amort_months': 12, 'fund_day': 1}, {'io_bills': [0, 0, 0], 'payment': 1029, 'final_payment': 1026, 'total_interest': 0}], ['regression: amortization term, partial-repair probe', {'principal': 100000, 'rate_bp': 725, 'io_months': 2, 'amort_months': 60, 'fund_day': 1}, {'io_bills': [604, 604], 'payment': 1992, 'final_payment': 1987, 'total_interest': 20723}], ['sample 1', {'principal': 250000, 'rate_bp': 500, 'io_months': 2, 'amort_months': 60, 'fund_day': 30}, {'io_bills': [35, 1042], 'payment': 4718, 'final_payment': 4703, 'total_interest': 34142}], ['sample 2', {'principal': 3000000, 'rate_bp': 0, 'io_months': 12, 'amort_months': 60, 'fund_day': 2}, {'io_bills': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'payment': 50000, 'final_payment': 50000, 'total_interest': 0}], ['sample 3', {'principal': 100000, 'rate_bp': 1200, 'io_months': 3, 'amort_months': 24, 'fund_day': 30}, {'io_bills': [33, 1000, 1000], 'payment': 4707, 'final_payment': 4718, 'total_interest': 15012}], ['sample 4', {'principal': 12345, 'rate_bp': 725, 'io_months': 3, 'amort_months': 60, 'fund_day': 15}, {'io_bills': [40, 75, 75], 'payment': 246, 'final_payment': 239, 'total_interest': 2598}], ['sample 5', {'principal': 3000000, 'rate_bp': 500, 'io_months': 3, 'amort_months': 12, 'fund_day': 1}, {'io_bills': [12500, 12500, 12500], 'payment': 256822, 'final_payment': 256827, 'total_interest': 119369}]], [['regression: amortization term', {'principal': 3000000, 'rate_bp': 500, 'io_months': 2, 'amort_months': 24, 'fund_day': 29}, {'io_bills': [833, 12500], 'payment': 131614, 'final_payment': 131618, 'total_interest': 172073}], ['sample 1', {'principal': 3000000, 'rate_bp': 500, 'io_months': 3, 'amort_months': 24, 'fund_day': 1}, {'io_bills': [12500, 12500, 12500], 'payment': 131614, 'final_payment': 131618, 'total_interest': 196240}], ['sample 2', {'principal': 12345, 'rate_bp': 500, 'io_months': 2, 'amort_months': 12, 'fund_day': 15}, {'io_bills': [27, 51], 'payment': 1057, 'final_payment': 1054, 'total_interest': 414}], ['sample 3', {'principal': 100000, 'rate_bp': 0, 'io_months': 12, 'amort_months': 24, 'fund_day': 29}, {'io_bills': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'payment': 4167, 'final_payment': 4159, 'total_interest': 0}], ['sample 4', {'principal': 100000, 'rate_bp': 500, 'io_months': 12, 'amort_months': 24, 'fund_day': 15}, {'io_bills': [222, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417], 'payment': 4387, 'final_payment': 4389, 'total_interest': 10099}], ['sample 5', {'principal': 100000, 'rate_bp': 500, 'io_months': 12, 'amort_months': 12, 'fund_day': 29}, {'io_bills': [28, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417], 'payment': 8561, 'final_payment': 8559, 'total_interest': 7345}], ['sample 6', {'principal': 12345, 'rate_bp': 0, 'io_months': 2, 'amort_months': 24, 'fund_day': 30}, {'io_bills': [0, 0], 'payment': 514, 'final_payment': 523, 'total_interest': 0}]], [['regression: amortization term', {'principal': 250000, 'rate_bp': 500, 'io_months': 12, 'amort_months': 60, 'fund_day': 15}, {'io_bills': [556, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042], 'payment': 4718, 'final_payment': 4703, 'total_interest': 45083}], ['sample 1', {'principal': 100000, 'rate_bp': 0, 'io_months': 12, 'amort_months': 60, 'fund_day': 29}, {'io_bills': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'payment': 1667, 'final_payment': 1647, 'total_interest': 0}], ['sample 2', {'principal': 250000, 'rate_bp': 0, 'io_months': 3, 'amort_months': 1, 'fund_day': 2}, {'io_bills': [0, 0, 0], 'payment': 250000, 'final_payment': 250000, 'total_interest': 0}], ['sample 3', {'principal': 250000, 'rate_bp': 1200, 'io_months': 12, 'amort_months': 24, 'fund_day': 15}, {'io_bills': [1333, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500], 'payment': 11768, 'final_payment': 11778, 'total_interest': 61275}], ['sample 4', {'principal': 100000, 'rate_bp': 725, 'io_months': 12, 'amort_months': 24, 'fund_day': 15}, {'io_bills': [322, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604], 'payment': 4489, 'final_payment': 4477, 'total_interest': 14690}], ['sample 5', {'principal': 12345, 'rate_bp': 1200, 'io_months': 2, 'amort_months': 24, 'fund_day': 30}, {'io_bills': [4, 123], 'payment': 581, 'final_payment': 584, 'total_interest': 1729}], ['sample 6', {'principal': 100000, 'rate_bp': 500, 'io_months': 1, 'amort_months': 12, 'fund_day': 30}, {'io_bills': [14], 'payment': 8561, 'final_payment': 8559, 'total_interest': 2744}]], [['regression: amortization term', {'principal': 12345, 'rate_bp': 725, 'io_months': 1, 'amort_months': 12, 'fund_day': 2}, {'io_bills': [72], 'payment': 1070, 'final_payment': 1065, 'total_interest': 562}], ['sample 1', {'principal': 3000000, 'rate_bp': 725, 'io_months': 1, 'amort_months': 60, 'fund_day': 29}, {'io_bills': [1208], 'payment': 59758, 'final_payment': 59764, 'total_interest': 586694}], ['sample 2', {'principal': 100000, 'rate_bp': 500, 'io_months': 3, 'amort_months': 60, 'fund_day': 2}, {'io_bills': [403, 417, 417], 'payment': 1887, 'final_payment': 1894, 'total_interest': 14464}], ['sample 3', {'principal': 250000, 'rate_bp': 725, 'io_months': 1, 'amort_months': 60, 'fund_day': 15}, {'io_bills': [806], 'payment': 4980, 'final_payment': 4967, 'total_interest': 49593}], ['sample 4', {'principal': 100000, 'rate_bp': 0, 'io_months': 12, 'amort_months': 24, 'fund_day': 30}, {'io_bills': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'payment': 4167, 'final_payment': 4159, 'total_interest': 0}], ['sample 5', {'principal': 12345, 'rate_bp': 725, 'io_months': 2, 'amort_months': 24, 'fund_day': 15}, {'io_bills': [40, 75], 'payment': 554, 'final_payment': 559, 'total_interest': 1071}], ['sample 6', {'principal': 3000000, 'rate_bp': 0, 'io_months': 12, 'amort_months': 24, 'fund_day': 15}, {'io_bills': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'payment': 125000, 'final_payment': 125000, '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']
    io_bills = []
    total_int = 0
    for m in range(x['io_months']):
        days = 31 - x['fund_day'] if m == 0 else 30
        interest = rnd(P * bp * days, 120000 * 30)
        io_bills.append(interest)
        total_int += interest
    pay = level(P - sum(io_bills), x['amort_months'])
    bal = P
    final = 0
    for k in range(1, x['amort_months'] + 1):
        interest = rnd(bal * bp, 120000)
        total_int += interest
        if k == x['amort_months']:
            final = bal + interest
        else:
            bal -= pay - interest
    return {'io_bills': io_bills, 'payment': pay, 'final_payment': final, 'total_interest': total_int}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: amortization term', {'principal': 3000000, 'rate_bp': 500, 'io_months': 3, 'amort_months': 60, 'fund_day': 1}, {'io_bills': [12500, 12500, 12500], 'payment': 56614, 'final_payment': 56596, 'total_interest': 434322}], ['sample 1', {'principal': 12345, 'rate_bp': 725, 'io_months': 12, 'amort_months': 12, 'fund_day': 15}, {'io_bills': [40, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75], 'payment': 1070, 'final_payment': 1065, 'total_interest': 1355}], ['sample 2', {'principal': 250000, 'rate_bp': 725, 'io_months': 3, 'amort_months': 12, 'fund_day': 29}, {'io_bills': [101, 1510, 1510], 'payment': 21661, 'final_payment': 21655, 'total_interest': 13047}], ['sample 3', {'principal': 250000, 'rate_bp': 1200, 'io_months': 3, 'amort_months': 24, 'fund_day': 29}, {'io_bills': [167, 2500, 2500], 'payment': 11768, 'final_payment': 11778, 'total_interest': 37609}], ['sample 4', {'principal': 3000000, 'rate_bp': 1200, 'io_months': 3, 'amort_months': 1, 'fund_day': 2}, {'io_bills': [29000, 30000, 30000], 'payment': 3030000, 'final_payment': 3030000, 'total_interest': 119000}], ['sample 5', {'principal': 12345, 'rate_bp': 0, 'io_months': 1, 'amort_months': 24, 'fund_day': 2}, {'io_bills': [0], 'payment': 514, 'final_payment': 523, 'total_interest': 0}], ['sample 6', {'principal': 12345, 'rate_bp': 500, 'io_months': 3, 'amort_months': 12, 'fund_day': 15}, {'io_bills': [27, 51, 51], 'payment': 1057, 'final_payment': 1054, 'total_interest': 465}]], [['regression: amortization term', {'principal': 12345, 'rate_bp': 0, 'io_months': 3, 'amort_months': 12, 'fund_day': 1}, {'io_bills': [0, 0, 0], 'payment': 1029, 'final_payment': 1026, 'total_interest': 0}], ['regression: amortization term, partial-repair probe', {'principal': 100000, 'rate_bp': 725, 'io_months': 2, 'amort_months': 60, 'fund_day': 1}, {'io_bills': [604, 604], 'payment': 1992, 'final_payment': 1987, 'total_interest': 20723}], ['sample 1', {'principal': 250000, 'rate_bp': 500, 'io_months': 2, 'amort_months': 60, 'fund_day': 30}, {'io_bills': [35, 1042], 'payment': 4718, 'final_payment': 4703, 'total_interest': 34142}], ['sample 2', {'principal': 3000000, 'rate_bp': 0, 'io_months': 12, 'amort_months': 60, 'fund_day': 2}, {'io_bills': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'payment': 50000, 'final_payment': 50000, 'total_interest': 0}], ['sample 3', {'principal': 100000, 'rate_bp': 1200, 'io_months': 3, 'amort_months': 24, 'fund_day': 30}, {'io_bills': [33, 1000, 1000], 'payment': 4707, 'final_payment': 4718, 'total_interest': 15012}], ['sample 4', {'principal': 12345, 'rate_bp': 725, 'io_months': 3, 'amort_months': 60, 'fund_day': 15}, {'io_bills': [40, 75, 75], 'payment': 246, 'final_payment': 239, 'total_interest': 2598}], ['sample 5', {'principal': 3000000, 'rate_bp': 500, 'io_months': 3, 'amort_months': 12, 'fund_day': 1}, {'io_bills': [12500, 12500, 12500], 'payment': 256822, 'final_payment': 256827, 'total_interest': 119369}]], [['regression: amortization term', {'principal': 3000000, 'rate_bp': 500, 'io_months': 2, 'amort_months': 24, 'fund_day': 29}, {'io_bills': [833, 12500], 'payment': 131614, 'final_payment': 131618, 'total_interest': 172073}], ['sample 1', {'principal': 3000000, 'rate_bp': 500, 'io_months': 3, 'amort_months': 24, 'fund_day': 1}, {'io_bills': [12500, 12500, 12500], 'payment': 131614, 'final_payment': 131618, 'total_interest': 196240}], ['sample 2', {'principal': 12345, 'rate_bp': 500, 'io_months': 2, 'amort_months': 12, 'fund_day': 15}, {'io_bills': [27, 51], 'payment': 1057, 'final_payment': 1054, 'total_interest': 414}], ['sample 3', {'principal': 100000, 'rate_bp': 0, 'io_months': 12, 'amort_months': 24, 'fund_day': 29}, {'io_bills': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'payment': 4167, 'final_payment': 4159, 'total_interest': 0}], ['sample 4', {'principal': 100000, 'rate_bp': 500, 'io_months': 12, 'amort_months': 24, 'fund_day': 15}, {'io_bills': [222, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417], 'payment': 4387, 'final_payment': 4389, 'total_interest': 10099}], ['sample 5', {'principal': 100000, 'rate_bp': 500, 'io_months': 12, 'amort_months': 12, 'fund_day': 29}, {'io_bills': [28, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417, 417], 'payment': 8561, 'final_payment': 8559, 'total_interest': 7345}], ['sample 6', {'principal': 12345, 'rate_bp': 0, 'io_months': 2, 'amort_months': 24, 'fund_day': 30}, {'io_bills': [0, 0], 'payment': 514, 'final_payment': 523, 'total_interest': 0}]], [['regression: amortization term', {'principal': 250000, 'rate_bp': 500, 'io_months': 12, 'amort_months': 60, 'fund_day': 15}, {'io_bills': [556, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042, 1042], 'payment': 4718, 'final_payment': 4703, 'total_interest': 45083}], ['sample 1', {'principal': 100000, 'rate_bp': 0, 'io_months': 12, 'amort_months': 60, 'fund_day': 29}, {'io_bills': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'payment': 1667, 'final_payment': 1647, 'total_interest': 0}], ['sample 2', {'principal': 250000, 'rate_bp': 0, 'io_months': 3, 'amort_months': 1, 'fund_day': 2}, {'io_bills': [0, 0, 0], 'payment': 250000, 'final_payment': 250000, 'total_interest': 0}], ['sample 3', {'principal': 250000, 'rate_bp': 1200, 'io_months': 12, 'amort_months': 24, 'fund_day': 15}, {'io_bills': [1333, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500], 'payment': 11768, 'final_payment': 11778, 'total_interest': 61275}], ['sample 4', {'principal': 100000, 'rate_bp': 725, 'io_months': 12, 'amort_months': 24, 'fund_day': 15}, {'io_bills': [322, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604, 604], 'payment': 4489, 'final_payment': 4477, 'total_interest': 14690}], ['sample 5', {'principal': 12345, 'rate_bp': 1200, 'io_months': 2, 'amort_months': 24, 'fund_day': 30}, {'io_bills': [4, 123], 'payment': 581, 'final_payment': 584, 'total_interest': 1729}], ['sample 6', {'principal': 100000, 'rate_bp': 500, 'io_months': 1, 'amort_months': 12, 'fund_day': 30}, {'io_bills': [14], 'payment': 8561, 'final_payment': 8559, 'total_interest': 2744}]], [['regression: amortization term', {'principal': 12345, 'rate_bp': 725, 'io_months': 1, 'amort_months': 12, 'fund_day': 2}, {'io_bills': [72], 'payment': 1070, 'final_payment': 1065, 'total_interest': 562}], ['sample 1', {'principal': 3000000, 'rate_bp': 725, 'io_months': 1, 'amort_months': 60, 'fund_day': 29}, {'io_bills': [1208], 'payment': 59758, 'final_payment': 59764, 'total_interest': 586694}], ['sample 2', {'principal': 100000, 'rate_bp': 500, 'io_months': 3, 'amort_months': 60, 'fund_day': 2}, {'io_bills': [403, 417, 417], 'payment': 1887, 'final_payment': 1894, 'total_interest': 14464}], ['sample 3', {'principal': 250000, 'rate_bp': 725, 'io_months': 1, 'amort_months': 60, 'fund_day': 15}, {'io_bills': [806], 'payment': 4980, 'final_payment': 4967, 'total_interest': 49593}], ['sample 4', {'principal': 100000, 'rate_bp': 0, 'io_months': 12, 'amort_months': 24, 'fund_day': 30}, {'io_bills': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'payment': 4167, 'final_payment': 4159, 'total_interest': 0}], ['sample 5', {'principal': 12345, 'rate_bp': 725, 'io_months': 2, 'amort_months': 24, 'fund_day': 15}, {'io_bills': [40, 75], 'payment': 554, 'final_payment': 559, 'total_interest': 1071}], ['sample 6', {'principal': 3000000, 'rate_bp': 0, 'io_months': 12, 'amort_months': 24, 'fund_day': 15}, {'io_bills': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'payment': 125000, 'final_payment': 125000, '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:
The amortizing payment is too small and a large final payment remains.

x = {'principal', 'rate_bp', 'io_months' >= 1, 'amort_months' >= 1, 'fund_day' 1..30}. IO month m pays interest round_half_up(P*bp*days/(120000*30)) with days = 31 - fund_day for the first IO month and 30 otherwise. Then the level payment on the full principal over amort_months (exact annuity, half-up) amortizes with monthly interest round_half_up(balance*bp/120000); the last month pays balance + interest. Return {'io_bills': list of IO payments, 'payment', 'final_payment', 'total_interest'}.

split: open-access