broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    bal = x['start_balance']
    ev = sorted(x['events'])
    i = 0
    acc = 0
    for day in range(1, x['cycle_days'] + 1):
        while i < len(ev) and ev[i][0] == day:
            bal += ev[i][1]
            i += 1
        acc += bal
    interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
    owed = bal + interest
    if owed <= 0:
        minimum = 0
    else:
        minimum = min(owed, max(x['min_floor'], interest + rnd(max(bal, 0) * x['min_bp'], 10000)))
    return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': acc // x['cycle_days']}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: credit balance accrual', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[27, 10000], [8, 50000], [10, -80000], [12, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 289, 'balance': 60289, 'minimum': 10000, 'adb': 44285}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 10192, 'balance': 1010192, 'minimum': 25192, 'adb': 1000000}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[14, 50000], [31, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 943, 'balance': 200943, 'minimum': 10000, 'adb': 130645}]], [['regression: credit balance accrual', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[6, 50000], [1, -80000], [24, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 540, 'balance': 120540, 'minimum': 10000, 'adb': 74838}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[24, 50000], [22, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 957, 'balance': 130957, 'minimum': 10000, 'adb': 103928}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[4, 50000], [27, 10000], [8, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 7578, 'balance': 1117578, 'minimum': 24228, 'adb': 1084666}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[20, 10000], [1, 5000], [2, 5000], [23, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 416, 'balance': 60416, 'minimum': 10000, 'adb': 45178}]], [['regression: credit balance accrual', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[3, 5000], [20, 5000], [25, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 44, 'balance': -9956, 'minimum': 0, 'adb': 4821}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[29, 5000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 9866, 'balance': 1014866, 'minimum': 24941, 'adb': 1000333}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[10, -20000], [3, 50000], [2, -80000], [29, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 275, 'balance': 55275, 'minimum': 2500, 'adb': 55666}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[19, 50000], [18, 50000], [12, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 373, 'balance': 105373, 'minimum': 2500, 'adb': 40535}]], [['regression: credit balance accrual', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[22, 5000], [2, 50000], [3, -80000], [3, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 431, 'balance': 85431, 'minimum': 2500, 'adb': 84516}], ['control 3', {'rate_bp': 600, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 138, 'balance': 30138, 'minimum': 2500, 'adb': 30000}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[6, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 4527, 'balance': 984527, 'minimum': 19227, 'adb': 983571}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 10192, 'balance': 1010192, 'minimum': 20192, 'adb': 1000000}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 276, 'balance': 30276, 'minimum': 10000, 'adb': 30000}]], [['regression: credit balance accrual', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[28, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9912, 'balance': 1059912, 'minimum': 25662, 'adb': 1005000}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[31, -20000], [27, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 980, 'balance': 60980, 'minimum': 2500, 'adb': 96129}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[15, 10000], [15, -80000], [4, 10000], [10, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 408, 'balance': 20408, 'minimum': 2500, 'adb': 56451}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[27, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9192, 'balance': 989192, 'minimum': 23892, 'adb': 998571}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 31, 'events': [[20, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 345, 'balance': 40345, 'minimum': 2500, 'adb': 33870}]]]
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

N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    bal = x['start_balance']
    ev = sorted(x['events'])
    i = 0
    acc = 0
    for day in range(1, x['cycle_days'] + 1):
        while i < len(ev) and ev[i][0] == day:
            bal += ev[i][1]
            i += 1
        acc += abs(bal)
    interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
    owed = bal + interest
    if owed <= 0:
        minimum = 0
    else:
        minimum = min(owed, max(x['min_floor'], interest + rnd(max(bal, 0) * x['min_bp'], 10000)))
    return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': acc // x['cycle_days']}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: credit balance accrual', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[27, 10000], [8, 50000], [10, -80000], [12, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 289, 'balance': 60289, 'minimum': 10000, 'adb': 44285}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 10192, 'balance': 1010192, 'minimum': 25192, 'adb': 1000000}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[14, 50000], [31, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 943, 'balance': 200943, 'minimum': 10000, 'adb': 130645}]], [['regression: credit balance accrual', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[6, 50000], [1, -80000], [24, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 540, 'balance': 120540, 'minimum': 10000, 'adb': 74838}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[24, 50000], [22, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 957, 'balance': 130957, 'minimum': 10000, 'adb': 103928}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[4, 50000], [27, 10000], [8, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 7578, 'balance': 1117578, 'minimum': 24228, 'adb': 1084666}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[20, 10000], [1, 5000], [2, 5000], [23, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 416, 'balance': 60416, 'minimum': 10000, 'adb': 45178}]], [['regression: credit balance accrual', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[3, 5000], [20, 5000], [25, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 44, 'balance': -9956, 'minimum': 0, 'adb': 4821}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[29, 5000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 9866, 'balance': 1014866, 'minimum': 24941, 'adb': 1000333}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[10, -20000], [3, 50000], [2, -80000], [29, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 275, 'balance': 55275, 'minimum': 2500, 'adb': 55666}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[19, 50000], [18, 50000], [12, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 373, 'balance': 105373, 'minimum': 2500, 'adb': 40535}]], [['regression: credit balance accrual', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[22, 5000], [2, 50000], [3, -80000], [3, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 431, 'balance': 85431, 'minimum': 2500, 'adb': 84516}], ['control 3', {'rate_bp': 600, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 138, 'balance': 30138, 'minimum': 2500, 'adb': 30000}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[6, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 4527, 'balance': 984527, 'minimum': 19227, 'adb': 983571}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 10192, 'balance': 1010192, 'minimum': 20192, 'adb': 1000000}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 276, 'balance': 30276, 'minimum': 10000, 'adb': 30000}]], [['regression: credit balance accrual', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[28, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9912, 'balance': 1059912, 'minimum': 25662, 'adb': 1005000}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[31, -20000], [27, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 980, 'balance': 60980, 'minimum': 2500, 'adb': 96129}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[15, 10000], [15, -80000], [4, 10000], [10, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 408, 'balance': 20408, 'minimum': 2500, 'adb': 56451}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[27, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9192, 'balance': 989192, 'minimum': 23892, 'adb': 998571}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 31, 'events': [[20, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 345, 'balance': 40345, 'minimum': 2500, 'adb': 33870}]]]
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:
Overpaid lines earn negative interest that offsets later draws.

x = {'rate_bp', 'basis', 'start_balance', 'cycle_days', 'events': [[day 1..cycle_days, signed amount]], 'min_bp', 'min_floor'}. Events take effect at the start of their day. Each day adds max(balance, 0) to the accumulator; interest = round_half_up(acc*bp/(10000*basis)) once per cycle. owed = end balance + interest. Minimum = 0 if owed <= 0 else min(owed, max(min_floor, interest + round_half_up(max(end balance, 0)*min_bp/10000))). Return {'interest', 'balance': owed, 'minimum', 'adb': acc // cycle_days}.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    bal = x['start_balance']
    ev = sorted(x['events'])
    i = 0
    acc = 0
    for day in range(1, x['cycle_days'] + 1):
        while i < len(ev) and ev[i][0] == day:
            bal += ev[i][1]
            i += 1
        acc += max(bal, 0)
    interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
    owed = bal + interest
    if owed <= 0:
        minimum = 0
    else:
        minimum = (max(x['min_floor'], interest + rnd(max(bal, 0) * x['min_bp'], 10000)))
    return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': acc // x['cycle_days']}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: minimum payment cap', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[12, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 22, 'balance': 5022, 'minimum': 5022, 'adb': 3166}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[27, 10000], [8, 50000], [10, -80000], [12, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 289, 'balance': 60289, 'minimum': 10000, 'adb': 44285}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[9, 10000], [16, 10000], [22, -80000], [9, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 105, 'balance': -49895, 'minimum': 0, 'adb': 11428}]], [['regression: minimum payment cap', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 31, 'events': [[30, -80000], [4, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 713, 'balance': 713, 'minimum': 713, 'adb': 70000}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[18, 10000], [3, -80000], [4, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 20, 'balance': -34980, 'minimum': 0, 'adb': 2142}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[6, 50000], [1, -80000], [24, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 540, 'balance': 120540, 'minimum': 10000, 'adb': 74838}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[24, 50000], [22, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 957, 'balance': 130957, 'minimum': 10000, 'adb': 103928}]], [['regression: minimum payment cap', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 10000], [9, -80000], [6, 50000], [1, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 61, 'balance': 61, 'minimum': 61, 'adb': 12333}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[3, 5000], [20, 5000], [25, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 44, 'balance': -9956, 'minimum': 0, 'adb': 4821}], ['control 3', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -80000], [1, -80000], [8, -20000], [6, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -230000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[29, 5000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 9866, 'balance': 1014866, 'minimum': 24941, 'adb': 1000333}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -80000, 'minimum': 0, 'adb': 0}]], [['regression: minimum payment cap', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[22, -80000], [2, -20000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 279, 'balance': 279, 'minimum': 279, 'adb': 56666}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['control 3', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[22, 5000], [2, 50000], [3, -80000], [3, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 431, 'balance': 85431, 'minimum': 2500, 'adb': 84516}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 138, 'balance': 30138, 'minimum': 2500, 'adb': 30000}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[6, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 4527, 'balance': 984527, 'minimum': 19227, 'adb': 983571}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 10192, 'balance': 1010192, 'minimum': 20192, 'adb': 1000000}]], [['regression: minimum payment cap', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 31, 'events': [[26, -20000], [20, -20000], [31, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 207, 'balance': 207, 'minimum': 207, 'adb': 20322}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[28, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9912, 'balance': 1059912, 'minimum': 25662, 'adb': 1005000}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000], [22, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -75000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[31, -20000], [27, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 980, 'balance': 60980, 'minimum': 2500, 'adb': 96129}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[15, 10000], [15, -80000], [4, 10000], [10, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 408, 'balance': 20408, 'minimum': 2500, 'adb': 56451}]]]
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

N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    bal = x['start_balance']
    ev = sorted(x['events'])
    i = 0
    acc = 0
    for day in range(1, x['cycle_days'] + 1):
        while i < len(ev) and ev[i][0] == day:
            bal += ev[i][1]
            i += 1
        acc += max(bal, 0)
    interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
    owed = bal + interest
    if owed <= 0:
        minimum = 0
    else:
        minimum = min(bal, max(x['min_floor'], interest + rnd(max(bal, 0) * x['min_bp'], 10000)))
    return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': acc // x['cycle_days']}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: minimum payment cap', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[12, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 22, 'balance': 5022, 'minimum': 5022, 'adb': 3166}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[27, 10000], [8, 50000], [10, -80000], [12, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 289, 'balance': 60289, 'minimum': 10000, 'adb': 44285}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[9, 10000], [16, 10000], [22, -80000], [9, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 105, 'balance': -49895, 'minimum': 0, 'adb': 11428}]], [['regression: minimum payment cap', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 31, 'events': [[30, -80000], [4, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 713, 'balance': 713, 'minimum': 713, 'adb': 70000}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[18, 10000], [3, -80000], [4, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 20, 'balance': -34980, 'minimum': 0, 'adb': 2142}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[6, 50000], [1, -80000], [24, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 540, 'balance': 120540, 'minimum': 10000, 'adb': 74838}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[24, 50000], [22, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 957, 'balance': 130957, 'minimum': 10000, 'adb': 103928}]], [['regression: minimum payment cap', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 10000], [9, -80000], [6, 50000], [1, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 61, 'balance': 61, 'minimum': 61, 'adb': 12333}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[3, 5000], [20, 5000], [25, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 44, 'balance': -9956, 'minimum': 0, 'adb': 4821}], ['control 3', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -80000], [1, -80000], [8, -20000], [6, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -230000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[29, 5000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 9866, 'balance': 1014866, 'minimum': 24941, 'adb': 1000333}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -80000, 'minimum': 0, 'adb': 0}]], [['regression: minimum payment cap', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[22, -80000], [2, -20000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 279, 'balance': 279, 'minimum': 279, 'adb': 56666}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['control 3', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[22, 5000], [2, 50000], [3, -80000], [3, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 431, 'balance': 85431, 'minimum': 2500, 'adb': 84516}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 138, 'balance': 30138, 'minimum': 2500, 'adb': 30000}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[6, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 4527, 'balance': 984527, 'minimum': 19227, 'adb': 983571}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 10192, 'balance': 1010192, 'minimum': 20192, 'adb': 1000000}]], [['regression: minimum payment cap', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 31, 'events': [[26, -20000], [20, -20000], [31, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 207, 'balance': 207, 'minimum': 207, 'adb': 20322}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[28, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9912, 'balance': 1059912, 'minimum': 25662, 'adb': 1005000}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000], [22, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -75000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[31, -20000], [27, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 980, 'balance': 60980, 'minimum': 2500, 'adb': 96129}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[15, 10000], [15, -80000], [4, 10000], [10, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 408, 'balance': 20408, 'minimum': 2500, 'adb': 56451}]]]
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 minimum payment due exceeds the total owed on small balances.

x = {'rate_bp', 'basis', 'start_balance', 'cycle_days', 'events': [[day 1..cycle_days, signed amount]], 'min_bp', 'min_floor'}. Events take effect at the start of their day. Each day adds max(balance, 0) to the accumulator; interest = round_half_up(acc*bp/(10000*basis)) once per cycle. owed = end balance + interest. Minimum = 0 if owed <= 0 else min(owed, max(min_floor, interest + round_half_up(max(end balance, 0)*min_bp/10000))). Return {'interest', 'balance': owed, 'minimum', 'adb': acc // cycle_days}.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    bal = x['start_balance']
    ev = sorted(x['events'])
    i = 0
    acc = 0
    for day in range(1, x['cycle_days'] + 1):
        while i < len(ev) and ev[i][0] == day:
            bal += ev[i][1]
            i += 1
        acc += max(bal, 0)
    interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
    owed = bal + interest
    if owed <= 0:
        minimum = 0
    else:
        minimum = min(owed, max(x['min_floor'], rnd(max(bal, 0) * x['min_bp'], 10000)))
    return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': acc // x['cycle_days']}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: minimum payment components', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[27, 10000], [8, 50000], [10, -80000], [12, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 289, 'balance': 60289, 'minimum': 10000, 'adb': 44285}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[9, 10000], [16, 10000], [22, -80000], [9, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 105, 'balance': -49895, 'minimum': 0, 'adb': 11428}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[14, 50000], [31, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 943, 'balance': 200943, 'minimum': 10000, 'adb': 130645}]], [['regression: minimum payment components', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['control 2', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[18, 10000], [3, -80000], [4, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 20, 'balance': -34980, 'minimum': 0, 'adb': 2142}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[6, 50000], [1, -80000], [24, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 540, 'balance': 120540, 'minimum': 10000, 'adb': 74838}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[24, 50000], [22, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 957, 'balance': 130957, 'minimum': 10000, 'adb': 103928}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[11, -80000], [21, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 99, 'balance': -44901, 'minimum': 0, 'adb': 10000}]], [['regression: minimum payment components', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[3, 5000], [20, 5000], [25, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 44, 'balance': -9956, 'minimum': 0, 'adb': 4821}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -80000], [1, -80000], [8, -20000], [6, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -230000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -80000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 6', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[10, -20000], [3, 50000], [2, -80000], [29, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 275, 'balance': 55275, 'minimum': 2500, 'adb': 55666}]], [['regression: minimum payment components', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[22, 5000], [2, 50000], [3, -80000], [3, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 431, 'balance': 85431, 'minimum': 2500, 'adb': 84516}], ['control 3', {'rate_bp': 600, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 138, 'balance': 30138, 'minimum': 2500, 'adb': 30000}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 276, 'balance': 30276, 'minimum': 10000, 'adb': 30000}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[12, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 349, 'balance': 20349, 'minimum': 2500, 'adb': 48387}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[30, 10000], [22, -20000], [23, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -5000, 'minimum': 0, 'adb': 0}]], [['regression: minimum payment components', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000], [22, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -75000, 'minimum': 0, 'adb': 0}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[31, -20000], [27, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 980, 'balance': 60980, 'minimum': 2500, 'adb': 96129}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[15, 10000], [15, -80000], [4, 10000], [10, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 408, 'balance': 20408, 'minimum': 2500, 'adb': 56451}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 31, 'events': [[20, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 345, 'balance': 40345, 'minimum': 2500, 'adb': 33870}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[4, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 276, 'balance': 20276, 'minimum': 2500, 'adb': 28000}]]]
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

N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    bal = x['start_balance']
    ev = sorted(x['events'])
    i = 0
    acc = 0
    for day in range(1, x['cycle_days'] + 1):
        while i < len(ev) and ev[i][0] == day:
            bal += ev[i][1]
            i += 1
        acc += max(bal, 0)
    interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
    owed = bal + interest
    if owed <= 0:
        minimum = 0
    else:
        minimum = min(owed, max(x['min_floor'], rnd(owed * x['min_bp'], 10000)))
    return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': acc // x['cycle_days']}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: minimum payment components', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[27, 10000], [8, 50000], [10, -80000], [12, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 289, 'balance': 60289, 'minimum': 10000, 'adb': 44285}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[9, 10000], [16, 10000], [22, -80000], [9, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 105, 'balance': -49895, 'minimum': 0, 'adb': 11428}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[14, 50000], [31, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 943, 'balance': 200943, 'minimum': 10000, 'adb': 130645}]], [['regression: minimum payment components', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['control 2', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [[18, 10000], [3, -80000], [4, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 20, 'balance': -34980, 'minimum': 0, 'adb': 2142}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[6, 50000], [1, -80000], [24, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 540, 'balance': 120540, 'minimum': 10000, 'adb': 74838}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[24, 50000], [22, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 957, 'balance': 130957, 'minimum': 10000, 'adb': 103928}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[11, -80000], [21, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 99, 'balance': -44901, 'minimum': 0, 'adb': 10000}]], [['regression: minimum payment components', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[3, 5000], [20, 5000], [25, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 44, 'balance': -9956, 'minimum': 0, 'adb': 4821}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -80000], [1, -80000], [8, -20000], [6, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -230000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -80000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 6', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[10, -20000], [3, 50000], [2, -80000], [29, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 275, 'balance': 55275, 'minimum': 2500, 'adb': 55666}]], [['regression: minimum payment components', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 2', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[22, 5000], [2, 50000], [3, -80000], [3, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 431, 'balance': 85431, 'minimum': 2500, 'adb': 84516}], ['control 3', {'rate_bp': 600, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 138, 'balance': 30138, 'minimum': 2500, 'adb': 30000}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 276, 'balance': 30276, 'minimum': 10000, 'adb': 30000}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[12, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 349, 'balance': 20349, 'minimum': 2500, 'adb': 48387}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[30, 10000], [22, -20000], [23, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -5000, 'minimum': 0, 'adb': 0}]], [['regression: minimum payment components', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000], [22, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -75000, 'minimum': 0, 'adb': 0}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[31, -20000], [27, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 980, 'balance': 60980, 'minimum': 2500, 'adb': 96129}], ['control 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [[15, 10000], [15, -80000], [4, 10000], [10, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 408, 'balance': 20408, 'minimum': 2500, 'adb': 56451}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 31, 'events': [[20, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 345, 'balance': 40345, 'minimum': 2500, 'adb': 33870}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[4, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 276, 'balance': 20276, 'minimum': 2500, 'adb': 28000}]]]
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:
Minimum payments do not cover the cycle interest.

x = {'rate_bp', 'basis', 'start_balance', 'cycle_days', 'events': [[day 1..cycle_days, signed amount]], 'min_bp', 'min_floor'}. Events take effect at the start of their day. Each day adds max(balance, 0) to the accumulator; interest = round_half_up(acc*bp/(10000*basis)) once per cycle. owed = end balance + interest. Minimum = 0 if owed <= 0 else min(owed, max(min_floor, interest + round_half_up(max(end balance, 0)*min_bp/10000))). Return {'interest', 'balance': owed, 'minimum', 'adb': acc // cycle_days}.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    bal = x['start_balance']
    ev = sorted(x['events'])
    i = 0
    acc = 0
    for day in range(1, x['cycle_days'] + 1):
        while i < len(ev) and ev[i][0] == day:
            bal += ev[i][1]
            i += 1
        acc += max(bal, 0)
    interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
    owed = bal + interest
    if owed <= 0:
        minimum = 0
    else:
        minimum = min(owed, max(x['min_floor'], interest + rnd(max(bal, 0) * x['min_bp'], 10000)))
    return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': acc // 30}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[23, -20000], [29, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': -10000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[7, 50000], [15, 50000], [24, -20000], [23, -80000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 1387, 'balance': 101387, 'minimum': 10000, 'adb': 140666}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[15, 5000], [18, -80000], [8, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 38, 'balance': -64962, 'minimum': 0, 'adb': 3833}]], [['regression: average daily balance divisor', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[11, -80000], [21, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 99, 'balance': -44901, 'minimum': 0, 'adb': 10000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[4, 50000], [27, 10000], [8, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 7578, 'balance': 1117578, 'minimum': 24228, 'adb': 1084666}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[23, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 1118, 'balance': 151118, 'minimum': 2618, 'adb': 113333}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[28, -80000], [7, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 1302, 'balance': 71302, 'minimum': 2500, 'adb': 132000}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -80000], [1, -80000], [8, -20000], [6, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -230000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[29, 5000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 9866, 'balance': 1014866, 'minimum': 24941, 'adb': 1000333}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -80000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[10, -20000], [3, 50000], [2, -80000], [29, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 275, 'balance': 55275, 'minimum': 2500, 'adb': 55666}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[30, 10000], [22, -20000], [23, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -5000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 296, 'balance': 30296, 'minimum': 10000, 'adb': 30000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[7, -80000], [3, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 6409, 'balance': 906409, 'minimum': 15409, 'adb': 917333}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[2, -20000], [14, 10000], [20, -20000], [27, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -20000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[5, -20000], [14, -20000], [1, 5000], [20, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 7, 'balance': -24993, 'minimum': 0, 'adb': 666}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[28, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9912, 'balance': 1059912, 'minimum': 25662, 'adb': 1005000}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000], [22, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -75000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[4, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 276, 'balance': 20276, 'minimum': 2500, 'adb': 28000}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -20000], [3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 494, 'balance': 60494, 'minimum': 10000, 'adb': 70666}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[30, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 2, 'balance': 10002, 'minimum': 10000, 'adb': 333}]]]
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

N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    bal = x['start_balance']
    ev = sorted(x['events'])
    i = 0
    acc = 0
    for day in range(1, x['cycle_days'] + 1):
        while i < len(ev) and ev[i][0] == day:
            bal += ev[i][1]
            i += 1
        acc += max(bal, 0)
    interest = rnd(acc * x['rate_bp'], 10000 * x['basis'])
    owed = bal + interest
    if owed <= 0:
        minimum = 0
    else:
        minimum = min(owed, max(x['min_floor'], interest + rnd(max(bal, 0) * x['min_bp'], 10000)))
    return {'interest': interest, 'balance': owed, 'minimum': minimum, 'adb': rnd(acc, x['cycle_days'])}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[4, 5000], [24, -20000], [10, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 5129, 'balance': 1000129, 'minimum': 15079, 'adb': 1006451}], ['control 1', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 1447, 'balance': 151447, 'minimum': 10000, 'adb': 146666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[6, -80000], [30, -80000], [3, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 10, 'balance': -149990, 'minimum': 0, 'adb': 1000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[23, -20000], [29, 10000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 0, 'balance': -10000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[7, 50000], [15, 50000], [24, -20000], [23, -80000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 1387, 'balance': 101387, 'minimum': 10000, 'adb': 140666}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[15, 5000], [18, -80000], [8, 10000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 38, 'balance': -64962, 'minimum': 0, 'adb': 3833}]], [['regression: average daily balance divisor', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [[1, 50000], [6, 5000], [24, 10000], [18, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 1146, 'balance': 86146, 'minimum': 2500, 'adb': 124464}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [[25, 10000], [7, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5313, 'balance': 1065313, 'minimum': 15913, 'adb': 1042580}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[25, 10000], [30, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 12, 'balance': -9988, 'minimum': 0, 'adb': 1666}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[11, -80000], [21, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 99, 'balance': -44901, 'minimum': 0, 'adb': 10000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[4, 50000], [27, 10000], [8, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 7578, 'balance': 1117578, 'minimum': 24228, 'adb': 1084666}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[23, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 1118, 'balance': 151118, 'minimum': 2618, 'adb': 113333}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[28, -80000], [7, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 1302, 'balance': 71302, 'minimum': 2500, 'adb': 132000}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[8, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4620, 'balance': 1009620, 'minimum': 14670, 'adb': 1003750}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 5000], [17, 10000], [26, -20000], [10, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 440, 'balance': 75440, 'minimum': 2500, 'adb': 89166}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -80000], [1, -80000], [8, -20000], [6, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -230000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[29, 5000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 9866, 'balance': 1014866, 'minimum': 24941, 'adb': 1000333}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -80000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[10, -20000], [3, 50000], [2, -80000], [29, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 275, 'balance': 55275, 'minimum': 2500, 'adb': 55666}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[23, 50000], [7, 10000], [8, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 4723, 'balance': 1074723, 'minimum': 20773, 'adb': 1026071}], ['regression: average daily balance divisor, partial-repair probe', {'rate_bp': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[20, 50000], [29, -80000], [30, -80000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 773, 'balance': -9227, 'minimum': 0, 'adb': 110666}], ['control 1', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[30, 10000], [22, -20000], [23, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -5000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 296, 'balance': 30296, 'minimum': 10000, 'adb': 30000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[7, -80000], [3, -20000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 6409, 'balance': 906409, 'minimum': 15409, 'adb': 917333}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[2, -20000], [14, 10000], [20, -20000], [27, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -20000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[5, -20000], [14, -20000], [1, 5000], [20, 10000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 7, 'balance': -24993, 'minimum': 0, 'adb': 666}]], [['regression: average daily balance divisor', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 28, 'events': [[21, 10000], [25, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 4563, 'balance': 934563, 'minimum': 13863, 'adb': 991428}], ['control 1', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[15, 50000], [8, -80000], [21, 5000], [31, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -45000, 'minimum': 0, 'adb': 0}], ['control 2', {'rate_bp': 1200, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 30, 'events': [[28, 50000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 9912, 'balance': 1059912, 'minimum': 25662, 'adb': 1005000}], ['control 3', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[5, -80000], [22, 5000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 0, 'balance': -75000, 'minimum': 0, 'adb': 0}], ['control 4', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 30, 'events': [[4, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 276, 'balance': 20276, 'minimum': 2500, 'adb': 28000}], ['control 5', {'rate_bp': 850, 'basis': 365, 'start_balance': 30000, 'cycle_days': 30, 'events': [[22, -20000], [3, 50000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 494, 'balance': 60494, 'minimum': 10000, 'adb': 70666}], ['control 6', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [[30, 10000]], 'min_bp': 100, 'min_floor': 10000}, {'interest': 2, 'balance': 10002, 'minimum': 10000, 'adb': 333}]]]
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:
Average daily balance is wrong in 28 and 31 day cycles.

x = {'rate_bp', 'basis', 'start_balance', 'cycle_days', 'events': [[day 1..cycle_days, signed amount]], 'min_bp', 'min_floor'}. Events take effect at the start of their day. Each day adds max(balance, 0) to the accumulator; interest = round_half_up(acc*bp/(10000*basis)) once per cycle. owed = end balance + interest. Minimum = 0 if owed <= 0 else min(owed, max(min_floor, interest + round_half_up(max(end balance, 0)*min_bp/10000))). Return {'interest', 'balance': owed, 'minimum', 'adb': acc // cycle_days}.

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 = x['initial_payment']
    years = [pay]
    peak = bal
    for m in range(1, n + 1):
        t = (m - 1) // 12
        if m > 1 and (m - 1) % 12 == 0:
            if t <= x['steps']:
                pay = rnd(x['initial_payment'] * (10000 + x['step_bp'] * t), 10000)
            elif t == x['steps'] + 1:
                pay = level(bal, n - m + 1)
            years.append(pay)
        interest = rnd(bal * bp, 120000)
        bal += interest - pay
        peak = max(peak, bal)
    return {'year_payments': years, 'peak_balance': peak, 'end_balance': bal}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: step compounding base', {'principal': 1000000, 'rate_bp': 600, 'months': 36, 'initial_payment': 20000, 'step_bp': 750, 'steps': 2}, {'year_payments': [20000, 21500, 23113], 'peak_balance': 1000000, 'end_balance': 351915}], ['regression: step compounding base, partial-repair probe', {'principal': 100000, 'rate_bp': 900, 'months': 36, 'initial_payment': 500, 'step_bp': 250, 'steps': 2}, {'year_payments': [500, 513, 526], 'peak_balance': 109781, 'end_balance': 109781}], ['control 1', {'principal': 250000, 'rate_bp': 600, 'months': 24, 'initial_payment': 3000, 'step_bp': 250, 'steps': 3}, {'year_payments': [3000, 3075], 'peak_balance': 250000, 'end_balance': 204568}], ['control 2', {'principal': 250000, 'rate_bp': 900, 'months': 36, 'initial_payment': 8000, 'step_bp': 250, 'steps': 0}, {'year_payments': [8000, 7921, 7921], 'peak_balance': 250000, 'end_balance': 9}], ['control 3', {'principal': 100000, 'rate_bp': 600, 'months': 24, 'initial_payment': 500, 'step_bp': 250, 'steps': 0}, {'year_payments': [500, 8607], 'peak_balance': 100000, 'end_balance': -4}], ['control 4', {'principal': 250000, 'rate_bp': 900, 'months': 60, 'initial_payment': 8000, 'step_bp': 250, 'steps': 1}, {'year_payments': [8000, 8200, 2770, 2770, 2770], 'peak_balance': 250000, 'end_balance': -23}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 60, 'initial_payment': 20000, 'step_bp': 250, 'steps': 1}, {'year_payments': [20000, 20500, 0, 0, 0], 'peak_balance': 250000, 'end_balance': -326758}]], [['regression: step compounding base', {'principal': 1000000, 'rate_bp': 600, 'months': 60, 'initial_payment': 500, 'step_bp': 250, 'steps': 2}, {'year_payments': [500, 513, 526, 52144, 52144], 'peak_balance': 1176523, 'end_balance': 4}], ['control 1', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'initial_payment': 3000, 'step_bp': 250, 'steps': 2}, {'year_payments': [3000, 3075], 'peak_balance': 250000, 'end_balance': 235565}], ['control 2', {'principal': 250000, 'rate_bp': 600, 'months': 24, 'initial_payment': 3000, 'step_bp': 1000, 'steps': 1}, {'year_payments': [3000, 3300], 'peak_balance': 250000, 'end_balance': 201795}], ['control 3', {'principal': 100000, 'rate_bp': 900, 'months': 24, 'initial_payment': 500, 'step_bp': 1000, 'steps': 3}, {'year_payments': [500, 550], 'peak_balance': 105921, 'end_balance': 105921}], ['control 4', {'principal': 250000, 'rate_bp': 900, 'months': 60, 'initial_payment': 500, 'step_bp': 250, 'steps': 1}, {'year_payments': [500, 513, 9090, 9090, 9090], 'peak_balance': 285848, 'end_balance': -7}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 36, 'initial_payment': 8000, 'step_bp': 250, 'steps': 0}, {'year_payments': [8000, 8485, 8485], 'peak_balance': 250000, 'end_balance': -6}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 36, 'initial_payment': 500, 'step_bp': 250, 'steps': 0}, {'year_payments': [500, 5006, 5006], 'peak_balance': 106341, 'end_balance': -5}]], [['regression: step compounding base', {'principal': 1000000, 'rate_bp': 1200, 'months': 60, 'initial_payment': 8000, 'step_bp': 250, 'steps': 2}, {'year_payments': [8000, 8200, 8405, 50753, 50753], 'peak_balance': 1078159, 'end_balance': -8}], ['regression: step compounding base, partial-repair probe', {'principal': 100000, 'rate_bp': 600, 'months': 60, 'initial_payment': 500, 'step_bp': 250, 'steps': 3}, {'year_payments': [500, 513, 526, 539, 8520], 'peak_balance': 100000, 'end_balance': 6}], ['control 1', {'principal': 1000000, 'rate_bp': 900, 'months': 36, 'initial_payment': 3000, 'step_bp': 750, 'steps': 1}, {'year_payments': [3000, 3225, 97511], 'peak_balance': 1115035, 'end_balance': 6}], ['control 2', {'principal': 250000, 'rate_bp': 900, 'months': 24, 'initial_payment': 3000, 'step_bp': 750, 'steps': 1}, {'year_payments': [3000, 3225], 'peak_balance': 250000, 'end_balance': 217726}], ['control 3', {'principal': 1000000, 'rate_bp': 900, 'months': 36, 'initial_payment': 20000, 'step_bp': 1000, 'steps': 1}, {'year_payments': [20000, 22000, 56636], 'peak_balance': 1000000, 'end_balance': 0}], ['control 4', {'principal': 100000, 'rate_bp': 900, 'months': 24, 'initial_payment': 3000, 'step_bp': 750, 'steps': 0}, {'year_payments': [3000, 6284], 'peak_balance': 100000, 'end_balance': -2}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 60, 'initial_payment': 20000, 'step_bp': 250, 'steps': 0}, {'year_payments': [20000, 739, 739, 739, 739], 'peak_balance': 250000, 'end_balance': -10}]], [['regression: step compounding base', {'principal': 100000, 'rate_bp': 1200, 'months': 36, 'initial_payment': 3000, 'step_bp': 1000, 'steps': 2}, {'year_payments': [3000, 3300, 3630], 'peak_balance': 100000, 'end_balance': 1568}], ['regression: step compounding base, partial-repair probe', {'principal': 250000, 'rate_bp': 600, 'months': 36, 'initial_payment': 500, 'step_bp': 250, 'steps': 3}, {'year_payments': [500, 513, 526], 'peak_balance': 279010, 'end_balance': 279010}], ['control 1', {'principal': 100000, 'rate_bp': 900, 'months': 24, 'initial_payment': 20000, 'step_bp': 750, 'steps': 3}, {'year_payments': [20000, 21500], 'peak_balance': 100000, 'end_balance': -422889}], ['control 2', {'principal': 1000000, 'rate_bp': 900, 'months': 36, 'initial_payment': 8000, 'step_bp': 250, 'steps': 1}, {'year_payments': [8000, 8200, 86087], 'peak_balance': 1000000, 'end_balance': 5}], ['control 3', {'principal': 1000000, 'rate_bp': 900, 'months': 60, 'initial_payment': 500, 'step_bp': 250, 'steps': 0}, {'year_payments': [500, 27064, 27064, 27064, 27064], 'peak_balance': 1087553, 'end_balance': -13}], ['control 4', {'principal': 250000, 'rate_bp': 900, 'months': 36, 'initial_payment': 20000, 'step_bp': 750, 'steps': 1}, {'year_payments': [20000, 21500, 0], 'peak_balance': 250000, 'end_balance': -266261}], ['control 5', {'principal': 1000000, 'rate_bp': 600, 'months': 60, 'initial_payment': 8000, 'step_bp': 1000, 'steps': 0}, {'year_payments': [8000, 22616, 22616, 22616, 22616], 'peak_balance': 1000000, 'end_balance': 0}]], [['regression: step compounding base', {'principal': 100000, 'rate_bp': 600, 'months': 60, 'initial_payment': 500, 'step_bp': 1000, 'steps': 3}, {'year_payments': [500, 550, 605, 666, 8252], 'peak_balance': 100000, 'end_balance': 1}], ['regression: step compounding base, partial-repair probe', {'principal': 100000, 'rate_bp': 1200, 'months': 36, 'initial_payment': 500, 'step_bp': 250, 'steps': 2}, {'year_payments': [500, 513, 526], 'peak_balance': 121021, 'end_balance': 121021}], ['control 1', {'principal': 1000000, 'rate_bp': 600, 'months': 60, 'initial_payment': 20000, 'step_bp': 750, 'steps': 1}, {'year_payments': [20000, 21500, 18254, 18254, 18254], 'peak_balance': 1000000, 'end_balance': -11}], ['control 2', {'principal': 1000000, 'rate_bp': 900, 'months': 60, 'initial_payment': 3000, 'step_bp': 1000, 'steps': 1}, {'year_payments': [3000, 3300, 35428, 35428, 35428], 'peak_balance': 1114096, 'end_balance': -5}], ['control 3', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'initial_payment': 3000, 'step_bp': 250, 'steps': 1}, {'year_payments': [3000, 3075], 'peak_balance': 250000, 'end_balance': 235565}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'months': 36, 'initial_payment': 3000, 'step_bp': 1000, 'steps': 1}, {'year_payments': [3000, 3300, 90126], 'peak_balance': 1047163, 'end_balance': -6}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'initial_payment': 500, 'step_bp': 250, 'steps': 0}, {'year_payments': [500, 9448], 'peak_balance': 106341, 'end_balance': 5}]]]
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 = x['initial_payment']
    years = [pay]
    peak = bal
    for m in range(1, n + 1):
        t = (m - 1) // 12
        if m > 1 and (m - 1) % 12 == 0:
            if t <= x['steps']:
                pay = rnd(x['initial_payment'] * (10000 + x['step_bp']) ** t, 10000 ** t)
            elif t == x['steps'] + 1:
                pay = level(bal, n - m + 1)
            years.append(pay)
        interest = rnd(bal * bp, 120000)
        bal += interest - pay
        peak = max(peak, bal)
    return {'year_payments': years, 'peak_balance': peak, 'end_balance': bal}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: step compounding base', {'principal': 1000000, 'rate_bp': 600, 'months': 36, 'initial_payment': 20000, 'step_bp': 750, 'steps': 2}, {'year_payments': [20000, 21500, 23113], 'peak_balance': 1000000, 'end_balance': 351915}], ['regression: step compounding base, partial-repair probe', {'principal': 100000, 'rate_bp': 900, 'months': 36, 'initial_payment': 500, 'step_bp': 250, 'steps': 2}, {'year_payments': [500, 513, 526], 'peak_balance': 109781, 'end_balance': 109781}], ['control 1', {'principal': 250000, 'rate_bp': 600, 'months': 24, 'initial_payment': 3000, 'step_bp': 250, 'steps': 3}, {'year_payments': [3000, 3075], 'peak_balance': 250000, 'end_balance': 204568}], ['control 2', {'principal': 250000, 'rate_bp': 900, 'months': 36, 'initial_payment': 8000, 'step_bp': 250, 'steps': 0}, {'year_payments': [8000, 7921, 7921], 'peak_balance': 250000, 'end_balance': 9}], ['control 3', {'principal': 100000, 'rate_bp': 600, 'months': 24, 'initial_payment': 500, 'step_bp': 250, 'steps': 0}, {'year_payments': [500, 8607], 'peak_balance': 100000, 'end_balance': -4}], ['control 4', {'principal': 250000, 'rate_bp': 900, 'months': 60, 'initial_payment': 8000, 'step_bp': 250, 'steps': 1}, {'year_payments': [8000, 8200, 2770, 2770, 2770], 'peak_balance': 250000, 'end_balance': -23}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 60, 'initial_payment': 20000, 'step_bp': 250, 'steps': 1}, {'year_payments': [20000, 20500, 0, 0, 0], 'peak_balance': 250000, 'end_balance': -326758}]], [['regression: step compounding base', {'principal': 1000000, 'rate_bp': 600, 'months': 60, 'initial_payment': 500, 'step_bp': 250, 'steps': 2}, {'year_payments': [500, 513, 526, 52144, 52144], 'peak_balance': 1176523, 'end_balance': 4}], ['control 1', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'initial_payment': 3000, 'step_bp': 250, 'steps': 2}, {'year_payments': [3000, 3075], 'peak_balance': 250000, 'end_balance': 235565}], ['control 2', {'principal': 250000, 'rate_bp': 600, 'months': 24, 'initial_payment': 3000, 'step_bp': 1000, 'steps': 1}, {'year_payments': [3000, 3300], 'peak_balance': 250000, 'end_balance': 201795}], ['control 3', {'principal': 100000, 'rate_bp': 900, 'months': 24, 'initial_payment': 500, 'step_bp': 1000, 'steps': 3}, {'year_payments': [500, 550], 'peak_balance': 105921, 'end_balance': 105921}], ['control 4', {'principal': 250000, 'rate_bp': 900, 'months': 60, 'initial_payment': 500, 'step_bp': 250, 'steps': 1}, {'year_payments': [500, 513, 9090, 9090, 9090], 'peak_balance': 285848, 'end_balance': -7}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 36, 'initial_payment': 8000, 'step_bp': 250, 'steps': 0}, {'year_payments': [8000, 8485, 8485], 'peak_balance': 250000, 'end_balance': -6}], ['control 6', {'principal': 100000, 'rate_bp': 1200, 'months': 36, 'initial_payment': 500, 'step_bp': 250, 'steps': 0}, {'year_payments': [500, 5006, 5006], 'peak_balance': 106341, 'end_balance': -5}]], [['regression: step compounding base', {'principal': 1000000, 'rate_bp': 1200, 'months': 60, 'initial_payment': 8000, 'step_bp': 250, 'steps': 2}, {'year_payments': [8000, 8200, 8405, 50753, 50753], 'peak_balance': 1078159, 'end_balance': -8}], ['regression: step compounding base, partial-repair probe', {'principal': 100000, 'rate_bp': 600, 'months': 60, 'initial_payment': 500, 'step_bp': 250, 'steps': 3}, {'year_payments': [500, 513, 526, 539, 8520], 'peak_balance': 100000, 'end_balance': 6}], ['control 1', {'principal': 1000000, 'rate_bp': 900, 'months': 36, 'initial_payment': 3000, 'step_bp': 750, 'steps': 1}, {'year_payments': [3000, 3225, 97511], 'peak_balance': 1115035, 'end_balance': 6}], ['control 2', {'principal': 250000, 'rate_bp': 900, 'months': 24, 'initial_payment': 3000, 'step_bp': 750, 'steps': 1}, {'year_payments': [3000, 3225], 'peak_balance': 250000, 'end_balance': 217726}], ['control 3', {'principal': 1000000, 'rate_bp': 900, 'months': 36, 'initial_payment': 20000, 'step_bp': 1000, 'steps': 1}, {'year_payments': [20000, 22000, 56636], 'peak_balance': 1000000, 'end_balance': 0}], ['control 4', {'principal': 100000, 'rate_bp': 900, 'months': 24, 'initial_payment': 3000, 'step_bp': 750, 'steps': 0}, {'year_payments': [3000, 6284], 'peak_balance': 100000, 'end_balance': -2}], ['control 5', {'principal': 250000, 'rate_bp': 1200, 'months': 60, 'initial_payment': 20000, 'step_bp': 250, 'steps': 0}, {'year_payments': [20000, 739, 739, 739, 739], 'peak_balance': 250000, 'end_balance': -10}]], [['regression: step compounding base', {'principal': 100000, 'rate_bp': 1200, 'months': 36, 'initial_payment': 3000, 'step_bp': 1000, 'steps': 2}, {'year_payments': [3000, 3300, 3630], 'peak_balance': 100000, 'end_balance': 1568}], ['regression: step compounding base, partial-repair probe', {'principal': 250000, 'rate_bp': 600, 'months': 36, 'initial_payment': 500, 'step_bp': 250, 'steps': 3}, {'year_payments': [500, 513, 526], 'peak_balance': 279010, 'end_balance': 279010}], ['control 1', {'principal': 100000, 'rate_bp': 900, 'months': 24, 'initial_payment': 20000, 'step_bp': 750, 'steps': 3}, {'year_payments': [20000, 21500], 'peak_balance': 100000, 'end_balance': -422889}], ['control 2', {'principal': 1000000, 'rate_bp': 900, 'months': 36, 'initial_payment': 8000, 'step_bp': 250, 'steps': 1}, {'year_payments': [8000, 8200, 86087], 'peak_balance': 1000000, 'end_balance': 5}], ['control 3', {'principal': 1000000, 'rate_bp': 900, 'months': 60, 'initial_payment': 500, 'step_bp': 250, 'steps': 0}, {'year_payments': [500, 27064, 27064, 27064, 27064], 'peak_balance': 1087553, 'end_balance': -13}], ['control 4', {'principal': 250000, 'rate_bp': 900, 'months': 36, 'initial_payment': 20000, 'step_bp': 750, 'steps': 1}, {'year_payments': [20000, 21500, 0], 'peak_balance': 250000, 'end_balance': -266261}], ['control 5', {'principal': 1000000, 'rate_bp': 600, 'months': 60, 'initial_payment': 8000, 'step_bp': 1000, 'steps': 0}, {'year_payments': [8000, 22616, 22616, 22616, 22616], 'peak_balance': 1000000, 'end_balance': 0}]], [['regression: step compounding base', {'principal': 100000, 'rate_bp': 600, 'months': 60, 'initial_payment': 500, 'step_bp': 1000, 'steps': 3}, {'year_payments': [500, 550, 605, 666, 8252], 'peak_balance': 100000, 'end_balance': 1}], ['regression: step compounding base, partial-repair probe', {'principal': 100000, 'rate_bp': 1200, 'months': 36, 'initial_payment': 500, 'step_bp': 250, 'steps': 2}, {'year_payments': [500, 513, 526], 'peak_balance': 121021, 'end_balance': 121021}], ['control 1', {'principal': 1000000, 'rate_bp': 600, 'months': 60, 'initial_payment': 20000, 'step_bp': 750, 'steps': 1}, {'year_payments': [20000, 21500, 18254, 18254, 18254], 'peak_balance': 1000000, 'end_balance': -11}], ['control 2', {'principal': 1000000, 'rate_bp': 900, 'months': 60, 'initial_payment': 3000, 'step_bp': 1000, 'steps': 1}, {'year_payments': [3000, 3300, 35428, 35428, 35428], 'peak_balance': 1114096, 'end_balance': -5}], ['control 3', {'principal': 250000, 'rate_bp': 1200, 'months': 24, 'initial_payment': 3000, 'step_bp': 250, 'steps': 1}, {'year_payments': [3000, 3075], 'peak_balance': 250000, 'end_balance': 235565}], ['control 4', {'principal': 1000000, 'rate_bp': 600, 'months': 36, 'initial_payment': 3000, 'step_bp': 1000, 'steps': 1}, {'year_payments': [3000, 3300, 90126], 'peak_balance': 1047163, 'end_balance': -6}], ['control 5', {'principal': 100000, 'rate_bp': 1200, 'months': 24, 'initial_payment': 500, 'step_bp': 250, 'steps': 0}, {'year_payments': [500, 9448], 'peak_balance': 106341, 'end_balance': 5}]]]
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:
Later-year payments grow linearly instead of compounding.

x = {'principal', 'rate_bp', 'months', 'initial_payment', 'step_bp', 'steps'}. Month m (1-based) is in year t = (m-1)//12. For t < steps+1 the year payment starts at initial_payment and each new year multiplies the previous year payment by (10000+step_bp)/10000, rounded half-up. At the start of year steps+1 (if the loan runs that long) the payment is re-leveled on the balance over the remaining months (exact annuity, half-up) and stays fixed. Interest round_half_up(balance*bp/120000); balance += interest - payment (it may grow). Return {'year_payments', 'peak_balance', 'end_balance'}.

split: open-access