broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    n = x['months']
    pay = level(P, n)
    sched = actual = P
    auto = request = None
    for k in range(1, n + 1):
        sched -= pay - rnd(sched * bp, 120000)
        if actual > 0:
            actual -= pay - rnd(actual * bp, 120000)
            actual -= min(x['prepay'].get(str(k), 0), max(actual, 0))
        if request is None and actual * 100 <= 80 * x['orig_value']:
            request = k
        if auto is None and sched * 100 <= 80 * x['orig_value']:
            auto = k
    mid = n // 2 + 1
    auto = mid if auto is None else min(auto, mid)
    return {'request': request, 'automatic': auto}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: automatic threshold', {'principal': 200000, 'rate_bp': 1200, 'months': 120, 'orig_value': 220000, 'prepay': {'11': 5000, '18': 40000}}, {'request': 18, 'automatic': 29}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'21': 5000}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 200000, 'prepay': {'6': 20000, '5': 5000}}, {'request': 5, 'automatic': 5}], ['control 3', {'principal': 200000, 'rate_bp': 1200, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 4, 'automatic': 4}], ['control 4', {'principal': 160000, 'rate_bp': 600, 'months': 36, 'orig_value': 205000, 'prepay': {'8': 40000}}, {'request': 1, 'automatic': 1}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 60, 'orig_value': 200000, 'prepay': {'6': 40000}}, {'request': 1, 'automatic': 1}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'months': 60, 'orig_value': 200000, 'prepay': {'24': 40000}}, {'request': 1, 'automatic': 1}]], [['regression: automatic threshold', {'principal': 190000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'12': 5000}}, {'request': 18, 'automatic': 35}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 24, 'orig_value': 205000, 'prepay': {'5': 20000}}, {'request': 5, 'automatic': 5}], ['control 2', {'principal': 160000, 'rate_bp': 1200, 'months': 48, 'orig_value': 205000, 'prepay': {'7': 40000}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 200000, 'rate_bp': 1500, 'months': 360, 'orig_value': 205000, 'prepay': {'24': 5000, '2': 20000}}, {'request': 33, 'automatic': 181}], ['control 4', {'principal': 215000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'21': 20000}}, {'request': 7, 'automatic': 7}], ['control 5', {'principal': 100000, 'rate_bp': 1500, 'months': 48, 'orig_value': 205000, 'prepay': {'3': 40000, '20': 5000}}, {'request': 1, 'automatic': 1}], ['control 6', {'principal': 180000, 'rate_bp': 1200, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}]], [['regression: automatic threshold', {'principal': 180000, 'rate_bp': 1200, 'months': 60, 'orig_value': 220000, 'prepay': {}}, {'request': 2, 'automatic': 4}], ['control 1', {'principal': 160000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 215000, 'rate_bp': 1200, 'months': 24, 'orig_value': 205000, 'prepay': {'2': 5000}}, {'request': 6, 'automatic': 7}], ['control 3', {'principal': 190000, 'rate_bp': 600, 'months': 24, 'orig_value': 205000, 'prepay': {}}, {'request': 4, 'automatic': 4}], ['control 4', {'principal': 100000, 'rate_bp': 1500, 'months': 360, 'orig_value': 250000, 'prepay': {'14': 5000, '22': 20000}}, {'request': 1, 'automatic': 1}], ['control 5', {'principal': 160000, 'rate_bp': 1200, 'months': 48, 'orig_value': 220000, 'prepay': {'3': 20000}}, {'request': 1, 'automatic': 1}], ['control 6', {'principal': 180000, 'rate_bp': 0, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}]], [['regression: automatic threshold', {'principal': 200000, 'rate_bp': 0, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 3}], ['control 1', {'principal': 190000, 'rate_bp': 1500, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 215000, 'rate_bp': 600, 'months': 36, 'orig_value': 220000, 'prepay': {}}, {'request': 8, 'automatic': 8}], ['control 3', {'principal': 160000, 'rate_bp': 0, 'months': 120, 'orig_value': 220000, 'prepay': {'4': 5000}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 160000, 'rate_bp': 0, 'months': 36, 'orig_value': 200000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 5', {'principal': 199000, 'rate_bp': 0, 'months': 36, 'orig_value': 250000, 'prepay': {'15': 40000}}, {'request': 1, 'automatic': 1}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'months': 24, 'orig_value': 200000, 'prepay': {'12': 40000}}, {'request': 6, 'automatic': 6}]], [['regression: automatic threshold', {'principal': 199000, 'rate_bp': 600, 'months': 360, 'orig_value': 200000, 'prepay': {'22': 5000}}, {'request': 116, 'automatic': 148}], ['control 1', {'principal': 199000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {}}, {'request': 194, 'automatic': 181}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {'6': 20000}}, {'request': 3, 'automatic': 3}], ['control 3', {'principal': 200000, 'rate_bp': 0, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 190000, 'rate_bp': 1500, 'months': 360, 'orig_value': 205000, 'prepay': {'18': 40000, '7': 40000}}, {'request': 7, 'automatic': 181}], ['control 5', {'principal': 180000, 'rate_bp': 1200, 'months': 36, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}], ['control 6', {'principal': 180000, 'rate_bp': 1500, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    n = x['months']
    pay = level(P, n)
    sched = actual = P
    auto = request = None
    for k in range(1, n + 1):
        sched -= pay - rnd(sched * bp, 120000)
        if actual > 0:
            actual -= pay - rnd(actual * bp, 120000)
            actual -= min(x['prepay'].get(str(k), 0), max(actual, 0))
        if request is None and actual * 100 <= 80 * x['orig_value']:
            request = k
        if auto is None and sched * 100 <= 78 * P:
            auto = k
    mid = n // 2 + 1
    auto = mid if auto is None else min(auto, mid)
    return {'request': request, 'automatic': auto}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: automatic threshold', {'principal': 200000, 'rate_bp': 1200, 'months': 120, 'orig_value': 220000, 'prepay': {'11': 5000, '18': 40000}}, {'request': 18, 'automatic': 29}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'21': 5000}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 200000, 'prepay': {'6': 20000, '5': 5000}}, {'request': 5, 'automatic': 5}], ['control 3', {'principal': 200000, 'rate_bp': 1200, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 4, 'automatic': 4}], ['control 4', {'principal': 160000, 'rate_bp': 600, 'months': 36, 'orig_value': 205000, 'prepay': {'8': 40000}}, {'request': 1, 'automatic': 1}], ['control 5', {'principal': 100000, 'rate_bp': 0, 'months': 60, 'orig_value': 200000, 'prepay': {'6': 40000}}, {'request': 1, 'automatic': 1}], ['control 6', {'principal': 100000, 'rate_bp': 600, 'months': 60, 'orig_value': 200000, 'prepay': {'24': 40000}}, {'request': 1, 'automatic': 1}]], [['regression: automatic threshold', {'principal': 190000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'12': 5000}}, {'request': 18, 'automatic': 35}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 24, 'orig_value': 205000, 'prepay': {'5': 20000}}, {'request': 5, 'automatic': 5}], ['control 2', {'principal': 160000, 'rate_bp': 1200, 'months': 48, 'orig_value': 205000, 'prepay': {'7': 40000}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 200000, 'rate_bp': 1500, 'months': 360, 'orig_value': 205000, 'prepay': {'24': 5000, '2': 20000}}, {'request': 33, 'automatic': 181}], ['control 4', {'principal': 215000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'21': 20000}}, {'request': 7, 'automatic': 7}], ['control 5', {'principal': 100000, 'rate_bp': 1500, 'months': 48, 'orig_value': 205000, 'prepay': {'3': 40000, '20': 5000}}, {'request': 1, 'automatic': 1}], ['control 6', {'principal': 180000, 'rate_bp': 1200, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}]], [['regression: automatic threshold', {'principal': 180000, 'rate_bp': 1200, 'months': 60, 'orig_value': 220000, 'prepay': {}}, {'request': 2, 'automatic': 4}], ['control 1', {'principal': 160000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 215000, 'rate_bp': 1200, 'months': 24, 'orig_value': 205000, 'prepay': {'2': 5000}}, {'request': 6, 'automatic': 7}], ['control 3', {'principal': 190000, 'rate_bp': 600, 'months': 24, 'orig_value': 205000, 'prepay': {}}, {'request': 4, 'automatic': 4}], ['control 4', {'principal': 100000, 'rate_bp': 1500, 'months': 360, 'orig_value': 250000, 'prepay': {'14': 5000, '22': 20000}}, {'request': 1, 'automatic': 1}], ['control 5', {'principal': 160000, 'rate_bp': 1200, 'months': 48, 'orig_value': 220000, 'prepay': {'3': 20000}}, {'request': 1, 'automatic': 1}], ['control 6', {'principal': 180000, 'rate_bp': 0, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}]], [['regression: automatic threshold', {'principal': 200000, 'rate_bp': 0, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 3}], ['control 1', {'principal': 190000, 'rate_bp': 1500, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 215000, 'rate_bp': 600, 'months': 36, 'orig_value': 220000, 'prepay': {}}, {'request': 8, 'automatic': 8}], ['control 3', {'principal': 160000, 'rate_bp': 0, 'months': 120, 'orig_value': 220000, 'prepay': {'4': 5000}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 160000, 'rate_bp': 0, 'months': 36, 'orig_value': 200000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 5', {'principal': 199000, 'rate_bp': 0, 'months': 36, 'orig_value': 250000, 'prepay': {'15': 40000}}, {'request': 1, 'automatic': 1}], ['control 6', {'principal': 200000, 'rate_bp': 1200, 'months': 24, 'orig_value': 200000, 'prepay': {'12': 40000}}, {'request': 6, 'automatic': 6}]], [['regression: automatic threshold', {'principal': 199000, 'rate_bp': 600, 'months': 360, 'orig_value': 200000, 'prepay': {'22': 5000}}, {'request': 116, 'automatic': 148}], ['control 1', {'principal': 199000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {}}, {'request': 194, 'automatic': 181}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {'6': 20000}}, {'request': 3, 'automatic': 3}], ['control 3', {'principal': 200000, 'rate_bp': 0, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 190000, 'rate_bp': 1500, 'months': 360, 'orig_value': 205000, 'prepay': {'18': 40000, '7': 40000}}, {'request': 7, 'automatic': 181}], ['control 5', {'principal': 180000, 'rate_bp': 1200, 'months': 36, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}], ['control 6', {'principal': 180000, 'rate_bp': 1500, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}]]]
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:
Insurance drops automatically at the borrower-request threshold.

x = {'principal', 'rate_bp', 'months' n, 'orig_value', 'prepay': {'k': extra}}. Level payment is the exact annuity half-up; interest round_half_up(balance*bp/120000). The scheduled balance ignores prepayments; the actual balance applies each extra after payment k (capped at the balance). The request month is the first k with actual*100 <= 80*orig_value; the automatic month is the first k with scheduled*100 <= 78*orig_value, but never later than the midpoint month n//2 + 1. Return {'request', 'automatic'}.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    n = x['months']
    pay = level(P, n)
    sched = actual = P
    auto = request = None
    for k in range(1, n + 1):
        sched -= pay - rnd(sched * bp, 120000)
        if actual > 0:
            actual -= pay - rnd(actual * bp, 120000)
            actual -= min(x['prepay'].get(str(k), 0), max(actual, 0))
        if request is None and actual * 100 <= 80 * x['orig_value']:
            request = k
        if auto is None and sched * 100 <= 78 * x['orig_value']:
            auto = k
    mid = n // 2
    auto = mid if auto is None else min(auto, mid)
    return {'request': request, 'automatic': auto}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: midpoint month', {'principal': 200000, 'rate_bp': 1200, 'months': 360, 'orig_value': 200000, 'prepay': {'23': 5000}}, {'request': 156, 'automatic': 181}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'21': 5000}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 200000, 'rate_bp': 1200, 'months': 120, 'orig_value': 220000, 'prepay': {'11': 5000, '18': 40000}}, {'request': 18, 'automatic': 29}], ['control 3', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 200000, 'prepay': {'6': 20000, '5': 5000}}, {'request': 5, 'automatic': 5}], ['control 4', {'principal': 200000, 'rate_bp': 1200, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 4, 'automatic': 4}], ['control 5', {'principal': 215000, 'rate_bp': 0, 'months': 120, 'orig_value': 220000, 'prepay': {}}, {'request': 22, 'automatic': 25}], ['control 6', {'principal': 160000, 'rate_bp': 600, 'months': 36, 'orig_value': 205000, 'prepay': {'8': 40000}}, {'request': 1, 'automatic': 1}]], [['regression: midpoint month', {'principal': 200000, 'rate_bp': 1500, 'months': 360, 'orig_value': 205000, 'prepay': {'24': 5000, '2': 20000}}, {'request': 33, 'automatic': 181}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 24, 'orig_value': 205000, 'prepay': {'5': 20000}}, {'request': 5, 'automatic': 5}], ['control 2', {'principal': 160000, 'rate_bp': 1200, 'months': 48, 'orig_value': 205000, 'prepay': {'7': 40000}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 215000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'21': 20000}}, {'request': 7, 'automatic': 7}], ['control 4', {'principal': 190000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'12': 5000}}, {'request': 18, 'automatic': 35}], ['control 5', {'principal': 199000, 'rate_bp': 600, 'months': 36, 'orig_value': 205000, 'prepay': {'8': 40000, '21': 5000}}, {'request': 7, 'automatic': 8}], ['control 6', {'principal': 180000, 'rate_bp': 600, 'months': 60, 'orig_value': 200000, 'prepay': {'17': 40000, '6': 5000}}, {'request': 6, 'automatic': 10}]], [['regression: midpoint month', {'principal': 190000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {'22': 20000}}, {'request': 22, 'automatic': 181}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 60, 'orig_value': 220000, 'prepay': {}}, {'request': 2, 'automatic': 4}], ['control 2', {'principal': 200000, 'rate_bp': 0, 'months': 48, 'orig_value': 220000, 'prepay': {}}, {'request': 6, 'automatic': 7}], ['control 3', {'principal': 160000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 215000, 'rate_bp': 1200, 'months': 24, 'orig_value': 205000, 'prepay': {'2': 5000}}, {'request': 6, 'automatic': 7}], ['control 5', {'principal': 215000, 'rate_bp': 600, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 3, 'automatic': 4}], ['control 6', {'principal': 190000, 'rate_bp': 600, 'months': 48, 'orig_value': 200000, 'prepay': {'6': 20000}}, {'request': 6, 'automatic': 10}]], [['regression: midpoint month', {'principal': 190000, 'rate_bp': 1200, 'months': 360, 'orig_value': 205000, 'prepay': {'17': 20000}}, {'request': 35, 'automatic': 181}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 3}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 199000, 'rate_bp': 1500, 'months': 120, 'orig_value': 200000, 'prepay': {'22': 20000}}, {'request': 23, 'automatic': 45}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'months': 360, 'orig_value': 205000, 'prepay': {'17': 20000}}, {'request': 54, 'automatic': 140}], ['control 5', {'principal': 199000, 'rate_bp': 1200, 'months': 48, 'orig_value': 200000, 'prepay': {'1': 5000}}, {'request': 10, 'automatic': 13}], ['control 6', {'principal': 180000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 1, 'automatic': 2}]], [['regression: midpoint month', {'principal': 199000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {}}, {'request': 194, 'automatic': 181}], ['control 1', {'principal': 199000, 'rate_bp': 600, 'months': 360, 'orig_value': 200000, 'prepay': {'22': 5000}}, {'request': 116, 'automatic': 148}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {'6': 20000}}, {'request': 3, 'automatic': 3}], ['control 3', {'principal': 200000, 'rate_bp': 0, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 190000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'9': 40000}}, {'request': 4, 'automatic': 5}], ['control 5', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'11': 20000}}, {'request': 8, 'automatic': 17}], ['control 6', {'principal': 180000, 'rate_bp': 1200, 'months': 36, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)

meaning: Fixture-count band, not measured model difficulty

deps: stdlib

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    n = x['months']
    pay = level(P, n)
    sched = actual = P
    auto = request = None
    for k in range(1, n + 1):
        sched -= pay - rnd(sched * bp, 120000)
        if actual > 0:
            actual -= pay - rnd(actual * bp, 120000)
            actual -= min(x['prepay'].get(str(k), 0), max(actual, 0))
        if request is None and actual * 100 <= 80 * x['orig_value']:
            request = k
        if auto is None and sched * 100 <= 78 * x['orig_value']:
            auto = k
    mid = (n + 1) // 2
    auto = mid if auto is None else min(auto, mid)
    return {'request': request, 'automatic': auto}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: midpoint month', {'principal': 200000, 'rate_bp': 1200, 'months': 360, 'orig_value': 200000, 'prepay': {'23': 5000}}, {'request': 156, 'automatic': 181}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'21': 5000}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 200000, 'rate_bp': 1200, 'months': 120, 'orig_value': 220000, 'prepay': {'11': 5000, '18': 40000}}, {'request': 18, 'automatic': 29}], ['control 3', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 200000, 'prepay': {'6': 20000, '5': 5000}}, {'request': 5, 'automatic': 5}], ['control 4', {'principal': 200000, 'rate_bp': 1200, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 4, 'automatic': 4}], ['control 5', {'principal': 215000, 'rate_bp': 0, 'months': 120, 'orig_value': 220000, 'prepay': {}}, {'request': 22, 'automatic': 25}], ['control 6', {'principal': 160000, 'rate_bp': 600, 'months': 36, 'orig_value': 205000, 'prepay': {'8': 40000}}, {'request': 1, 'automatic': 1}]], [['regression: midpoint month', {'principal': 200000, 'rate_bp': 1500, 'months': 360, 'orig_value': 205000, 'prepay': {'24': 5000, '2': 20000}}, {'request': 33, 'automatic': 181}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 24, 'orig_value': 205000, 'prepay': {'5': 20000}}, {'request': 5, 'automatic': 5}], ['control 2', {'principal': 160000, 'rate_bp': 1200, 'months': 48, 'orig_value': 205000, 'prepay': {'7': 40000}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 215000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'21': 20000}}, {'request': 7, 'automatic': 7}], ['control 4', {'principal': 190000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'12': 5000}}, {'request': 18, 'automatic': 35}], ['control 5', {'principal': 199000, 'rate_bp': 600, 'months': 36, 'orig_value': 205000, 'prepay': {'8': 40000, '21': 5000}}, {'request': 7, 'automatic': 8}], ['control 6', {'principal': 180000, 'rate_bp': 600, 'months': 60, 'orig_value': 200000, 'prepay': {'17': 40000, '6': 5000}}, {'request': 6, 'automatic': 10}]], [['regression: midpoint month', {'principal': 190000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {'22': 20000}}, {'request': 22, 'automatic': 181}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 60, 'orig_value': 220000, 'prepay': {}}, {'request': 2, 'automatic': 4}], ['control 2', {'principal': 200000, 'rate_bp': 0, 'months': 48, 'orig_value': 220000, 'prepay': {}}, {'request': 6, 'automatic': 7}], ['control 3', {'principal': 160000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 215000, 'rate_bp': 1200, 'months': 24, 'orig_value': 205000, 'prepay': {'2': 5000}}, {'request': 6, 'automatic': 7}], ['control 5', {'principal': 215000, 'rate_bp': 600, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 3, 'automatic': 4}], ['control 6', {'principal': 190000, 'rate_bp': 600, 'months': 48, 'orig_value': 200000, 'prepay': {'6': 20000}}, {'request': 6, 'automatic': 10}]], [['regression: midpoint month', {'principal': 190000, 'rate_bp': 1200, 'months': 360, 'orig_value': 205000, 'prepay': {'17': 20000}}, {'request': 35, 'automatic': 181}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 3}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 199000, 'rate_bp': 1500, 'months': 120, 'orig_value': 200000, 'prepay': {'22': 20000}}, {'request': 23, 'automatic': 45}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'months': 360, 'orig_value': 205000, 'prepay': {'17': 20000}}, {'request': 54, 'automatic': 140}], ['control 5', {'principal': 199000, 'rate_bp': 1200, 'months': 48, 'orig_value': 200000, 'prepay': {'1': 5000}}, {'request': 10, 'automatic': 13}], ['control 6', {'principal': 180000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 1, 'automatic': 2}]], [['regression: midpoint month', {'principal': 199000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {}}, {'request': 194, 'automatic': 181}], ['control 1', {'principal': 199000, 'rate_bp': 600, 'months': 360, 'orig_value': 200000, 'prepay': {'22': 5000}}, {'request': 116, 'automatic': 148}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {'6': 20000}}, {'request': 3, 'automatic': 3}], ['control 3', {'principal': 200000, 'rate_bp': 0, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 190000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'9': 40000}}, {'request': 4, 'automatic': 5}], ['control 5', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'11': 20000}}, {'request': 8, 'automatic': 17}], ['control 6', {'principal': 180000, 'rate_bp': 1200, 'months': 36, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}]]]
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:
Final termination at the midpoint happens a month early.

x = {'principal', 'rate_bp', 'months' n, 'orig_value', 'prepay': {'k': extra}}. Level payment is the exact annuity half-up; interest round_half_up(balance*bp/120000). The scheduled balance ignores prepayments; the actual balance applies each extra after payment k (capped at the balance). The request month is the first k with actual*100 <= 80*orig_value; the automatic month is the first k with scheduled*100 <= 78*orig_value, but never later than the midpoint month n//2 + 1. Return {'request', 'automatic'}.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    n = x['months']
    pay = level(P, n)
    sched = actual = P
    auto = request = None
    for k in range(1, n + 1):
        sched -= pay - rnd(sched * bp, 120000)
        if actual > 0:
            actual -= pay - rnd(actual * bp, 120000)
            actual -= min(x['prepay'].get(str(k), 0), max(actual, 0))
        if request is None and actual * 100 <= 80 * x['orig_value']:
            request = k
        if auto is None and sched * 100 <= 78 * x['orig_value']:
            auto = k
    mid = n // 2 + 1
    auto = mid if auto is None else auto
    return {'request': request, 'automatic': auto}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: midpoint precedence', {'principal': 200000, 'rate_bp': 1200, 'months': 360, 'orig_value': 200000, 'prepay': {'23': 5000}}, {'request': 156, 'automatic': 181}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'21': 5000}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 200000, 'rate_bp': 1200, 'months': 120, 'orig_value': 220000, 'prepay': {'11': 5000, '18': 40000}}, {'request': 18, 'automatic': 29}], ['control 3', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 200000, 'prepay': {'6': 20000, '5': 5000}}, {'request': 5, 'automatic': 5}], ['control 4', {'principal': 200000, 'rate_bp': 1200, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 4, 'automatic': 4}], ['control 5', {'principal': 215000, 'rate_bp': 0, 'months': 120, 'orig_value': 220000, 'prepay': {}}, {'request': 22, 'automatic': 25}], ['control 6', {'principal': 160000, 'rate_bp': 600, 'months': 36, 'orig_value': 205000, 'prepay': {'8': 40000}}, {'request': 1, 'automatic': 1}]], [['regression: midpoint precedence', {'principal': 200000, 'rate_bp': 1500, 'months': 360, 'orig_value': 205000, 'prepay': {'24': 5000, '2': 20000}}, {'request': 33, 'automatic': 181}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 24, 'orig_value': 205000, 'prepay': {'5': 20000}}, {'request': 5, 'automatic': 5}], ['control 2', {'principal': 160000, 'rate_bp': 1200, 'months': 48, 'orig_value': 205000, 'prepay': {'7': 40000}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 215000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'21': 20000}}, {'request': 7, 'automatic': 7}], ['control 4', {'principal': 190000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'12': 5000}}, {'request': 18, 'automatic': 35}], ['control 5', {'principal': 199000, 'rate_bp': 600, 'months': 36, 'orig_value': 205000, 'prepay': {'8': 40000, '21': 5000}}, {'request': 7, 'automatic': 8}], ['control 6', {'principal': 180000, 'rate_bp': 600, 'months': 60, 'orig_value': 200000, 'prepay': {'17': 40000, '6': 5000}}, {'request': 6, 'automatic': 10}]], [['regression: midpoint precedence', {'principal': 190000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {'22': 20000}}, {'request': 22, 'automatic': 181}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 60, 'orig_value': 220000, 'prepay': {}}, {'request': 2, 'automatic': 4}], ['control 2', {'principal': 200000, 'rate_bp': 0, 'months': 48, 'orig_value': 220000, 'prepay': {}}, {'request': 6, 'automatic': 7}], ['control 3', {'principal': 160000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 215000, 'rate_bp': 1200, 'months': 24, 'orig_value': 205000, 'prepay': {'2': 5000}}, {'request': 6, 'automatic': 7}], ['control 5', {'principal': 215000, 'rate_bp': 600, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 3, 'automatic': 4}], ['control 6', {'principal': 190000, 'rate_bp': 600, 'months': 48, 'orig_value': 200000, 'prepay': {'6': 20000}}, {'request': 6, 'automatic': 10}]], [['regression: midpoint precedence', {'principal': 190000, 'rate_bp': 1200, 'months': 360, 'orig_value': 205000, 'prepay': {'17': 20000}}, {'request': 35, 'automatic': 181}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 3}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 199000, 'rate_bp': 1500, 'months': 120, 'orig_value': 200000, 'prepay': {'22': 20000}}, {'request': 23, 'automatic': 45}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'months': 360, 'orig_value': 205000, 'prepay': {'17': 20000}}, {'request': 54, 'automatic': 140}], ['control 5', {'principal': 199000, 'rate_bp': 1200, 'months': 48, 'orig_value': 200000, 'prepay': {'1': 5000}}, {'request': 10, 'automatic': 13}], ['control 6', {'principal': 180000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 1, 'automatic': 2}]], [['regression: midpoint precedence', {'principal': 199000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {}}, {'request': 194, 'automatic': 181}], ['control 1', {'principal': 199000, 'rate_bp': 600, 'months': 360, 'orig_value': 200000, 'prepay': {'22': 5000}}, {'request': 116, 'automatic': 148}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {'6': 20000}}, {'request': 3, 'automatic': 3}], ['control 3', {'principal': 200000, 'rate_bp': 0, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 190000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'9': 40000}}, {'request': 4, 'automatic': 5}], ['control 5', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'11': 20000}}, {'request': 8, 'automatic': 17}], ['control 6', {'principal': 180000, 'rate_bp': 1200, 'months': 36, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)

source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    n = x['months']
    pay = level(P, n)
    sched = actual = P
    auto = request = None
    for k in range(1, n + 1):
        sched -= pay - rnd(sched * bp, 120000)
        if actual > 0:
            actual -= pay - rnd(actual * bp, 120000)
            actual -= min(x['prepay'].get(str(k), 0), max(actual, 0))
        if request is None and actual * 100 <= 80 * x['orig_value']:
            request = k
        if auto is None and sched * 100 <= 78 * x['orig_value']:
            auto = k
    mid = n // 2 + 1
    auto = mid if auto is None else max(auto, mid)
    return {'request': request, 'automatic': auto}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: midpoint precedence', {'principal': 200000, 'rate_bp': 1200, 'months': 360, 'orig_value': 200000, 'prepay': {'23': 5000}}, {'request': 156, 'automatic': 181}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'21': 5000}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 200000, 'rate_bp': 1200, 'months': 120, 'orig_value': 220000, 'prepay': {'11': 5000, '18': 40000}}, {'request': 18, 'automatic': 29}], ['control 3', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 200000, 'prepay': {'6': 20000, '5': 5000}}, {'request': 5, 'automatic': 5}], ['control 4', {'principal': 200000, 'rate_bp': 1200, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 4, 'automatic': 4}], ['control 5', {'principal': 215000, 'rate_bp': 0, 'months': 120, 'orig_value': 220000, 'prepay': {}}, {'request': 22, 'automatic': 25}], ['control 6', {'principal': 160000, 'rate_bp': 600, 'months': 36, 'orig_value': 205000, 'prepay': {'8': 40000}}, {'request': 1, 'automatic': 1}]], [['regression: midpoint precedence', {'principal': 200000, 'rate_bp': 1500, 'months': 360, 'orig_value': 205000, 'prepay': {'24': 5000, '2': 20000}}, {'request': 33, 'automatic': 181}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 24, 'orig_value': 205000, 'prepay': {'5': 20000}}, {'request': 5, 'automatic': 5}], ['control 2', {'principal': 160000, 'rate_bp': 1200, 'months': 48, 'orig_value': 205000, 'prepay': {'7': 40000}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 215000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'21': 20000}}, {'request': 7, 'automatic': 7}], ['control 4', {'principal': 190000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'12': 5000}}, {'request': 18, 'automatic': 35}], ['control 5', {'principal': 199000, 'rate_bp': 600, 'months': 36, 'orig_value': 205000, 'prepay': {'8': 40000, '21': 5000}}, {'request': 7, 'automatic': 8}], ['control 6', {'principal': 180000, 'rate_bp': 600, 'months': 60, 'orig_value': 200000, 'prepay': {'17': 40000, '6': 5000}}, {'request': 6, 'automatic': 10}]], [['regression: midpoint precedence', {'principal': 190000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {'22': 20000}}, {'request': 22, 'automatic': 181}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 60, 'orig_value': 220000, 'prepay': {}}, {'request': 2, 'automatic': 4}], ['control 2', {'principal': 200000, 'rate_bp': 0, 'months': 48, 'orig_value': 220000, 'prepay': {}}, {'request': 6, 'automatic': 7}], ['control 3', {'principal': 160000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 215000, 'rate_bp': 1200, 'months': 24, 'orig_value': 205000, 'prepay': {'2': 5000}}, {'request': 6, 'automatic': 7}], ['control 5', {'principal': 215000, 'rate_bp': 600, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 3, 'automatic': 4}], ['control 6', {'principal': 190000, 'rate_bp': 600, 'months': 48, 'orig_value': 200000, 'prepay': {'6': 20000}}, {'request': 6, 'automatic': 10}]], [['regression: midpoint precedence', {'principal': 190000, 'rate_bp': 1200, 'months': 360, 'orig_value': 205000, 'prepay': {'17': 20000}}, {'request': 35, 'automatic': 181}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 3}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 199000, 'rate_bp': 1500, 'months': 120, 'orig_value': 200000, 'prepay': {'22': 20000}}, {'request': 23, 'automatic': 45}], ['control 4', {'principal': 200000, 'rate_bp': 600, 'months': 360, 'orig_value': 205000, 'prepay': {'17': 20000}}, {'request': 54, 'automatic': 140}], ['control 5', {'principal': 199000, 'rate_bp': 1200, 'months': 48, 'orig_value': 200000, 'prepay': {'1': 5000}}, {'request': 10, 'automatic': 13}], ['control 6', {'principal': 180000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 1, 'automatic': 2}]], [['regression: midpoint precedence', {'principal': 199000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {}}, {'request': 194, 'automatic': 181}], ['control 1', {'principal': 199000, 'rate_bp': 600, 'months': 360, 'orig_value': 200000, 'prepay': {'22': 5000}}, {'request': 116, 'automatic': 148}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {'6': 20000}}, {'request': 3, 'automatic': 3}], ['control 3', {'principal': 200000, 'rate_bp': 0, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 190000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'9': 40000}}, {'request': 4, 'automatic': 5}], ['control 5', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'11': 20000}}, {'request': 8, 'automatic': 17}], ['control 6', {'principal': 180000, 'rate_bp': 1200, 'months': 36, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}]]]
for label, args, expected in fixtures[N-1]:
    try:
        actual = solve(args)
    except Exception as exc:
        actual = 'raised ' + type(exc).__name__
    check(label, actual, expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)

prompt:
Loans that reach 78 percent late keep insurance past the midpoint.

x = {'principal', 'rate_bp', 'months' n, 'orig_value', 'prepay': {'k': extra}}. Level payment is the exact annuity half-up; interest round_half_up(balance*bp/120000). The scheduled balance ignores prepayments; the actual balance applies each extra after payment k (capped at the balance). The request month is the first k with actual*100 <= 80*orig_value; the automatic month is the first k with scheduled*100 <= 78*orig_value, but never later than the midpoint month n//2 + 1. Return {'request', 'automatic'}.

split: open-access


broken_source:
"""Failure Map reference implementation. Python standard library only."""
import json
import math
from fractions import Fraction
N = 1
observations = []
def solve(x):
    def rnd(n, d):
        q, r = divmod(n, d)
        return q + (1 if 2 * r >= d else 0)
    def level(P, n):
        if n <= 0 or P <= 0:
            return 0
        if bp == 0:
            exact = Fraction(P, n)
        else:
            r = Fraction(bp, 120000)
            exact = P * r / (1 - (1 + r) ** -n)
        return math.floor(exact + Fraction(1, 2))
    bp = x['rate_bp']
    P = x['principal']
    n = x['months']
    pay = level(P, n)
    sched = actual = P
    auto = request = None
    for k in range(1, n + 1):
        sched -= pay - rnd(sched * bp, 120000)
        if actual > 0:
            actual -= pay - rnd(actual * bp, 120000)
            actual -= min(x['prepay'].get(str(k), 0), max(actual, 0))
        if request is None and actual * 100 < 80 * x['orig_value']:
            request = k
        if auto is None and sched * 100 <= 78 * x['orig_value']:
            auto = k
    mid = n // 2 + 1
    auto = mid if auto is None else min(auto, mid)
    return {'request': request, 'automatic': auto}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: actual balance exactly 80 percent', {'principal': 200000, 'rate_bp': 0, 'months': 20, 'orig_value': 225000, 'prepay': {}}, {'request': 2, 'automatic': 3}], ['regression: request threshold', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 205000, 'prepay': {'1': 5000, '4': 5000}}, {'request': 12, 'automatic': 41}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'21': 5000}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 200000, 'rate_bp': 1200, 'months': 120, 'orig_value': 220000, 'prepay': {'11': 5000, '18': 40000}}, {'request': 18, 'automatic': 29}], ['control 3', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 200000, 'prepay': {'6': 20000, '5': 5000}}, {'request': 5, 'automatic': 5}], ['control 4', {'principal': 200000, 'rate_bp': 1200, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 4, 'automatic': 4}], ['control 5', {'principal': 215000, 'rate_bp': 0, 'months': 120, 'orig_value': 220000, 'prepay': {}}, {'request': 22, 'automatic': 25}]], [['regression: actual balance exactly 80 percent', {'principal': 200000, 'rate_bp': 0, 'months': 20, 'orig_value': 225000, 'prepay': {}}, {'request': 2, 'automatic': 3}], ['regression: request threshold', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {}}, {'request': 8, 'automatic': 17}], ['regression: request threshold, partial-repair probe', {'principal': 200000, 'rate_bp': 1500, 'months': 360, 'orig_value': 205000, 'prepay': {'24': 5000, '2': 20000}}, {'request': 33, 'automatic': 181}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 24, 'orig_value': 205000, 'prepay': {'5': 20000}}, {'request': 5, 'automatic': 5}], ['control 2', {'principal': 160000, 'rate_bp': 1200, 'months': 48, 'orig_value': 205000, 'prepay': {'7': 40000}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 215000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'21': 20000}}, {'request': 7, 'automatic': 7}], ['control 4', {'principal': 190000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'12': 5000}}, {'request': 18, 'automatic': 35}]], [['regression: actual balance exactly 80 percent', {'principal': 200000, 'rate_bp': 0, 'months': 20, 'orig_value': 225000, 'prepay': {}}, {'request': 2, 'automatic': 3}], ['regression: request threshold', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 200000, 'prepay': {}}, {'request': 40, 'automatic': 48}], ['regression: request threshold, partial-repair probe', {'principal': 215000, 'rate_bp': 1200, 'months': 24, 'orig_value': 205000, 'prepay': {'2': 5000}}, {'request': 6, 'automatic': 7}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 60, 'orig_value': 220000, 'prepay': {}}, {'request': 2, 'automatic': 4}], ['control 2', {'principal': 200000, 'rate_bp': 0, 'months': 48, 'orig_value': 220000, 'prepay': {}}, {'request': 6, 'automatic': 7}], ['control 3', {'principal': 160000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 215000, 'rate_bp': 600, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 3, 'automatic': 4}]], [['regression: actual balance exactly 80 percent', {'principal': 200000, 'rate_bp': 0, 'months': 20, 'orig_value': 225000, 'prepay': {}}, {'request': 2, 'automatic': 3}], ['regression: request threshold, partial-repair probe', {'principal': 199000, 'rate_bp': 1500, 'months': 120, 'orig_value': 200000, 'prepay': {'22': 20000}}, {'request': 23, 'automatic': 45}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 3}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 200000, 'rate_bp': 600, 'months': 360, 'orig_value': 205000, 'prepay': {'17': 20000}}, {'request': 54, 'automatic': 140}], ['control 4', {'principal': 199000, 'rate_bp': 1200, 'months': 48, 'orig_value': 200000, 'prepay': {'1': 5000}}, {'request': 10, 'automatic': 13}], ['control 5', {'principal': 180000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 1, 'automatic': 2}]], [['regression: actual balance exactly 80 percent', {'principal': 200000, 'rate_bp': 0, 'months': 20, 'orig_value': 225000, 'prepay': {}}, {'request': 2, 'automatic': 3}], ['regression: request threshold', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'11': 20000}}, {'request': 8, 'automatic': 17}], ['regression: request threshold, partial-repair probe', {'principal': 199000, 'rate_bp': 600, 'months': 360, 'orig_value': 200000, 'prepay': {'22': 5000}}, {'request': 116, 'automatic': 148}], ['control 1', {'principal': 199000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {}}, {'request': 194, 'automatic': 181}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {'6': 20000}}, {'request': 3, 'automatic': 3}], ['control 3', {'principal': 200000, 'rate_bp': 0, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 190000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'9': 40000}}, {'request': 4, 'automatic': 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']
    P = x['principal']
    n = x['months']
    pay = level(P, n)
    sched = actual = P
    auto = request = None
    for k in range(1, n + 1):
        sched -= pay - rnd(sched * bp, 120000)
        if actual > 0:
            actual -= pay - rnd(actual * bp, 120000)
            actual -= min(x['prepay'].get(str(k), 0), max(actual, 0))
        if request is None and sched * 100 <= 80 * x['orig_value']:
            request = k
        if auto is None and sched * 100 <= 78 * x['orig_value']:
            auto = k
    mid = n // 2 + 1
    auto = mid if auto is None else min(auto, mid)
    return {'request': request, 'automatic': auto}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: actual balance exactly 80 percent', {'principal': 200000, 'rate_bp': 0, 'months': 20, 'orig_value': 225000, 'prepay': {}}, {'request': 2, 'automatic': 3}], ['regression: request threshold', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 205000, 'prepay': {'1': 5000, '4': 5000}}, {'request': 12, 'automatic': 41}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'21': 5000}}, {'request': 1, 'automatic': 1}], ['control 2', {'principal': 200000, 'rate_bp': 1200, 'months': 120, 'orig_value': 220000, 'prepay': {'11': 5000, '18': 40000}}, {'request': 18, 'automatic': 29}], ['control 3', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 200000, 'prepay': {'6': 20000, '5': 5000}}, {'request': 5, 'automatic': 5}], ['control 4', {'principal': 200000, 'rate_bp': 1200, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 4, 'automatic': 4}], ['control 5', {'principal': 215000, 'rate_bp': 0, 'months': 120, 'orig_value': 220000, 'prepay': {}}, {'request': 22, 'automatic': 25}]], [['regression: actual balance exactly 80 percent', {'principal': 200000, 'rate_bp': 0, 'months': 20, 'orig_value': 225000, 'prepay': {}}, {'request': 2, 'automatic': 3}], ['regression: request threshold', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {}}, {'request': 8, 'automatic': 17}], ['regression: request threshold, partial-repair probe', {'principal': 200000, 'rate_bp': 1500, 'months': 360, 'orig_value': 205000, 'prepay': {'24': 5000, '2': 20000}}, {'request': 33, 'automatic': 181}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 24, 'orig_value': 205000, 'prepay': {'5': 20000}}, {'request': 5, 'automatic': 5}], ['control 2', {'principal': 160000, 'rate_bp': 1200, 'months': 48, 'orig_value': 205000, 'prepay': {'7': 40000}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 215000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'21': 20000}}, {'request': 7, 'automatic': 7}], ['control 4', {'principal': 190000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'12': 5000}}, {'request': 18, 'automatic': 35}]], [['regression: actual balance exactly 80 percent', {'principal': 200000, 'rate_bp': 0, 'months': 20, 'orig_value': 225000, 'prepay': {}}, {'request': 2, 'automatic': 3}], ['regression: request threshold', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 200000, 'prepay': {}}, {'request': 40, 'automatic': 48}], ['regression: request threshold, partial-repair probe', {'principal': 215000, 'rate_bp': 1200, 'months': 24, 'orig_value': 205000, 'prepay': {'2': 5000}}, {'request': 6, 'automatic': 7}], ['control 1', {'principal': 180000, 'rate_bp': 1200, 'months': 60, 'orig_value': 220000, 'prepay': {}}, {'request': 2, 'automatic': 4}], ['control 2', {'principal': 200000, 'rate_bp': 0, 'months': 48, 'orig_value': 220000, 'prepay': {}}, {'request': 6, 'automatic': 7}], ['control 3', {'principal': 160000, 'rate_bp': 1200, 'months': 360, 'orig_value': 250000, 'prepay': {'5': 40000}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 215000, 'rate_bp': 600, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 3, 'automatic': 4}]], [['regression: actual balance exactly 80 percent', {'principal': 200000, 'rate_bp': 0, 'months': 20, 'orig_value': 225000, 'prepay': {}}, {'request': 2, 'automatic': 3}], ['regression: request threshold, partial-repair probe', {'principal': 199000, 'rate_bp': 1500, 'months': 120, 'orig_value': 200000, 'prepay': {'22': 20000}}, {'request': 23, 'automatic': 45}], ['control 1', {'principal': 200000, 'rate_bp': 0, 'months': 120, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 3}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 3', {'principal': 200000, 'rate_bp': 600, 'months': 360, 'orig_value': 205000, 'prepay': {'17': 20000}}, {'request': 54, 'automatic': 140}], ['control 4', {'principal': 199000, 'rate_bp': 1200, 'months': 48, 'orig_value': 200000, 'prepay': {'1': 5000}}, {'request': 10, 'automatic': 13}], ['control 5', {'principal': 180000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {}}, {'request': 1, 'automatic': 2}]], [['regression: actual balance exactly 80 percent', {'principal': 200000, 'rate_bp': 0, 'months': 20, 'orig_value': 225000, 'prepay': {}}, {'request': 2, 'automatic': 3}], ['regression: request threshold', {'principal': 180000, 'rate_bp': 0, 'months': 360, 'orig_value': 220000, 'prepay': {'11': 20000}}, {'request': 8, 'automatic': 17}], ['regression: request threshold, partial-repair probe', {'principal': 199000, 'rate_bp': 600, 'months': 360, 'orig_value': 200000, 'prepay': {'22': 5000}}, {'request': 116, 'automatic': 148}], ['control 1', {'principal': 199000, 'rate_bp': 1500, 'months': 360, 'orig_value': 220000, 'prepay': {}}, {'request': 194, 'automatic': 181}], ['control 2', {'principal': 190000, 'rate_bp': 1500, 'months': 24, 'orig_value': 220000, 'prepay': {'6': 20000}}, {'request': 3, 'automatic': 3}], ['control 3', {'principal': 200000, 'rate_bp': 0, 'months': 36, 'orig_value': 250000, 'prepay': {}}, {'request': 1, 'automatic': 1}], ['control 4', {'principal': 190000, 'rate_bp': 600, 'months': 24, 'orig_value': 200000, 'prepay': {'9': 40000}}, {'request': 4, 'automatic': 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:
Borrowers at exactly 80 percent cannot request cancellation.

x = {'principal', 'rate_bp', 'months' n, 'orig_value', 'prepay': {'k': extra}}. Level payment is the exact annuity half-up; interest round_half_up(balance*bp/120000). The scheduled balance ignores prepayments; the actual balance applies each extra after payment k (capped at the balance). The request month is the first k with actual*100 <= 80*orig_value; the automatic month is the first k with scheduled*100 <= 78*orig_value, but never later than the midpoint month n//2 + 1. Return {'request', 'automatic'}.

split: open-access


prompt:
After February every due date sticks to the 28th.

x = {'first_due': [y, m, d] (d may exceed the month length), 'count', 'roll': 'none'|'following'|'modified_following', 'holidays': ISO dates}. Installment i falls in month m+i on the anchor day d clamped to the month length (later months return to d). Unless roll is 'none', a Saturday, Sunday or holiday rolls forward to the next business day; under modified_following a roll that leaves the month instead rolls back to the previous business day. Return ISO date strings.

split: open-access


prompt:
Installments after December keep the starting year.

x = {'first_due': [y, m, d] (d may exceed the month length), 'count', 'roll': 'none'|'following'|'modified_following', 'holidays': ISO dates}. Installment i falls in month m+i on the anchor day d clamped to the month length (later months return to d). Unless roll is 'none', a Saturday, Sunday or holiday rolls forward to the next business day; under modified_following a roll that leaves the month instead rolls back to the previous business day. Return ISO date strings.

split: open-access


prompt:
Due dates on Saturdays are not rolled.

x = {'first_due': [y, m, d] (d may exceed the month length), 'count', 'roll': 'none'|'following'|'modified_following', 'holidays': ISO dates}. Installment i falls in month m+i on the anchor day d clamped to the month length (later months return to d). Unless roll is 'none', a Saturday, Sunday or holiday rolls forward to the next business day; under modified_following a roll that leaves the month instead rolls back to the previous business day. Return ISO date strings.

split: open-access


prompt:
Modified-following due dates roll into the next month.

x = {'first_due': [y, m, d] (d may exceed the month length), 'count', 'roll': 'none'|'following'|'modified_following', 'holidays': ISO dates}. Installment i falls in month m+i on the anchor day d clamped to the month length (later months return to d). Unless roll is 'none', a Saturday, Sunday or holiday rolls forward to the next business day; under modified_following a roll that leaves the month instead rolls back to the previous business day. Return ISO date strings.

split: open-access


prompt:
Loans configured without business-day rolling still move weekend due dates.

x = {'first_due': [y, m, d] (d may exceed the month length), 'count', 'roll': 'none'|'following'|'modified_following', 'holidays': ISO dates}. Installment i falls in month m+i on the anchor day d clamped to the month length (later months return to d). Unless roll is 'none', a Saturday, Sunday or holiday rolls forward to the next business day; under modified_following a roll that leaves the month instead rolls back to the previous business day. Return ISO date strings.

split: open-access


prompt:
Modified-following roll-backs land on a holiday.

x = {'first_due': [y, m, d] (d may exceed the month length), 'count', 'roll': 'none'|'following'|'modified_following', 'holidays': ISO dates}. Installment i falls in month m+i on the anchor day d clamped to the month length (later months return to d). Unless roll is 'none', a Saturday, Sunday or holiday rolls forward to the next business day; under modified_following a roll that leaves the month instead rolls back to the previous business day. Return ISO date strings.

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):
        acc += max(bal, 0)
        while i < len(ev) and ev[i][0] == day:
            bal += ev[i][1]
            i += 1
    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: event effective day', {'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': 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 2', {'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 3', {'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 4', {'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 5', {'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 6', {'rate_bp': 600, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 138, 'balance': 30138, 'minimum': 10000, 'adb': 30000}]], [['regression: event effective day', {'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 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': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 722, 'balance': 100722, 'minimum': 10000, 'adb': 100000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[9, -80000], [20, -20000], [4, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 6, 'balance': -94994, 'minimum': 0, 'adb': 806}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[23, 5000], [9, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -15000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[19, 5000], [4, 10000], [12, -80000], [5, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 2, 'balance': -84998, 'minimum': 0, 'adb': 357}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}]], [['regression: event effective day', {'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': 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 3', {'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 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 921, 'balance': 100921, 'minimum': 10000, 'adb': 100000}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[20, 5000], [23, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 5, 'balance': -74995, 'minimum': 0, 'adb': 483}]], [['regression: event effective day', {'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': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 138, 'balance': 30138, 'minimum': 2500, 'adb': 30000}], ['control 2', {'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 3', {'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 4', {'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 5', {'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 6', {'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}]], [['regression: event effective day', {'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': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[8, 5000], [3, 5000], [27, 10000], [28, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 39, 'balance': -59961, 'minimum': 0, 'adb': 7580}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 30000, 'cycle_days': 31, 'events': [[13, 50000], [16, -20000], [7, -80000], [18, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 99, 'balance': 30099, 'minimum': 2500, 'adb': 19354}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[4, -20000], [25, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -15000, 'minimum': 0, 'adb': 0}], ['control 6', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5096, 'balance': 1005096, 'minimum': 15096, 'adb': 1000000}]]]
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(0, x['cycle_days']):
        acc += max(bal, 0)
        while i < len(ev) and ev[i][0] == day:
            bal += ev[i][1]
            i += 1
    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: event effective day', {'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': 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 2', {'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 3', {'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 4', {'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 5', {'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 6', {'rate_bp': 600, 'basis': 365, 'start_balance': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 138, 'balance': 30138, 'minimum': 10000, 'adb': 30000}]], [['regression: event effective day', {'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 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': 850, 'basis': 365, 'start_balance': 100000, 'cycle_days': 31, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 722, 'balance': 100722, 'minimum': 10000, 'adb': 100000}], ['control 3', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[9, -80000], [20, -20000], [4, 5000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 6, 'balance': -94994, 'minimum': 0, 'adb': 806}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[23, 5000], [9, -20000]], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': -15000, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[19, 5000], [4, 10000], [12, -80000], [5, -20000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 2, 'balance': -84998, 'minimum': 0, 'adb': 357}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}]], [['regression: event effective day', {'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': 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 3', {'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 4', {'rate_bp': 850, 'basis': 365, 'start_balance': 0, 'cycle_days': 30, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 0, 'balance': 0, 'minimum': 0, 'adb': 0}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 100000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 10000}, {'interest': 921, 'balance': 100921, 'minimum': 10000, 'adb': 100000}], ['control 6', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[20, 5000], [23, -80000]], 'min_bp': 150, 'min_floor': 2500}, {'interest': 5, 'balance': -74995, 'minimum': 0, 'adb': 483}]], [['regression: event effective day', {'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': 30000, 'cycle_days': 28, 'events': [], 'min_bp': 150, 'min_floor': 2500}, {'interest': 138, 'balance': 30138, 'minimum': 2500, 'adb': 30000}], ['control 2', {'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 3', {'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 4', {'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 5', {'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 6', {'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}]], [['regression: event effective day', {'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': 600, 'basis': 365, 'start_balance': 0, 'cycle_days': 31, 'events': [[8, 5000], [3, 5000], [27, 10000], [28, -80000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 39, 'balance': -59961, 'minimum': 0, 'adb': 7580}], ['control 4', {'rate_bp': 600, 'basis': 365, 'start_balance': 30000, 'cycle_days': 31, 'events': [[13, 50000], [16, -20000], [7, -80000], [18, 50000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 99, 'balance': 30099, 'minimum': 2500, 'adb': 19354}], ['control 5', {'rate_bp': 1200, 'basis': 365, 'start_balance': 0, 'cycle_days': 28, 'events': [[4, -20000], [25, 5000]], 'min_bp': 100, 'min_floor': 2500}, {'interest': 0, 'balance': -15000, 'minimum': 0, 'adb': 0}], ['control 6', {'rate_bp': 600, 'basis': 365, 'start_balance': 1000000, 'cycle_days': 31, 'events': [], 'min_bp': 100, 'min_floor': 10000}, {'interest': 5096, 'balance': 1005096, 'minimum': 15096, 'adb': 1000000}]]]
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:
Draws accrue no interest on the day they are taken.

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