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

N = 1
observations = []
def solve(x):
    usable = x['pts'] // x['block'] * x['block']
    if usable < x['min_pts']:
        return {'error': 'below minimum'}
    miles = usable * x['den'] // x['num']
    bonus = miles * x['bonus_bp'] // 10000 if usable >= x['threshold'] else 0
    return {'used': usable, 'miles': miles, 'bonus': bonus, 'leftover': x['pts'] - usable}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal', {'pts': 12751, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 751}], ['below minimum after flooring', {'pts': 5998, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5099, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 99}], ['bonus threshold edge', {'pts': 10010, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 10}], ['threshold by raw only', {'pts': 10401, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 401}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 8, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 4375, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3001, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60020, 'leftover': 321}]], [['normal', {'pts': 12752, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 752}], ['below minimum after flooring', {'pts': 5997, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5199, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 199}], ['bonus threshold edge', {'pts': 10020, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 20}], ['threshold by raw only', {'pts': 10402, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 402}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 9, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3888, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3002, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60040, 'leftover': 321}]], [['normal', {'pts': 12753, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 753}], ['below minimum after flooring', {'pts': 5996, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5299, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 299}], ['bonus threshold edge', {'pts': 10030, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 30}], ['threshold by raw only', {'pts': 10403, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 403}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 10, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3500, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3003, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60060, 'leftover': 321}]], [['normal', {'pts': 12754, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 754}], ['below minimum after flooring', {'pts': 5995, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5399, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 399}], ['bonus threshold edge', {'pts': 10040, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 40}], ['threshold by raw only', {'pts': 10404, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 404}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 11, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3181, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3004, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60080, 'leftover': 321}]], [['normal', {'pts': 12755, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 755}], ['below minimum after flooring', {'pts': 5994, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5499, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 499}], ['bonus threshold edge', {'pts': 10050, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 50}], ['threshold by raw only', {'pts': 10405, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 405}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 12, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 2916, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3005, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60100, 'leftover': 321}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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):
    usable = x['pts'] // x['block'] * x['block']
    if usable < x['min_pts']:
        return {'error': 'below minimum'}
    miles = usable // x['den'] * x['num']
    bonus = miles * x['bonus_bp'] // 10000 if usable >= x['threshold'] else 0
    return {'used': usable, 'miles': miles, 'bonus': bonus, 'leftover': x['pts'] - usable}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal', {'pts': 12751, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 751}], ['below minimum after flooring', {'pts': 5998, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5099, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 99}], ['bonus threshold edge', {'pts': 10010, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 10}], ['threshold by raw only', {'pts': 10401, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 401}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 8, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 4375, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3001, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60020, 'leftover': 321}]], [['normal', {'pts': 12752, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 752}], ['below minimum after flooring', {'pts': 5997, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5199, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 199}], ['bonus threshold edge', {'pts': 10020, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 20}], ['threshold by raw only', {'pts': 10402, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 402}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 9, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3888, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3002, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60040, 'leftover': 321}]], [['normal', {'pts': 12753, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 753}], ['below minimum after flooring', {'pts': 5996, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5299, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 299}], ['bonus threshold edge', {'pts': 10030, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 30}], ['threshold by raw only', {'pts': 10403, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 403}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 10, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3500, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3003, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60060, 'leftover': 321}]], [['normal', {'pts': 12754, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 754}], ['below minimum after flooring', {'pts': 5995, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5399, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 399}], ['bonus threshold edge', {'pts': 10040, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 40}], ['threshold by raw only', {'pts': 10404, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 404}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 11, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3181, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3004, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60080, 'leftover': 321}]], [['normal', {'pts': 12755, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 755}], ['below minimum after flooring', {'pts': 5994, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5499, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 499}], ['bonus threshold edge', {'pts': 10050, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 50}], ['threshold by raw only', {'pts': 10405, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 405}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 12, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 2916, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3005, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60100, 'leftover': 321}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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:
Members get 4 miles for every 3 points on a 3:4 program.

Input {pts, block, min_pts, num, den, bonus_bp, threshold}. Usable points = pts floored to a block; usable below min_pts returns {"error"}. Miles = floor(usable*num/den). If usable >= threshold, bonus miles = floor(miles*bonus_bp/10000). Leftover points stay in the account. Return {used, miles, bonus, leftover}.

split: open-access


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

N = 1
observations = []
def solve(x):
    usable = x['pts'] // x['block'] * x['block']
    if usable < x['min_pts']:
        return {'error': 'below minimum'}
    miles = usable * x['num'] // x['den']
    bonus = miles * x['bonus_bp'] // 10000 if x['pts'] >= x['threshold'] else 0
    return {'used': usable, 'miles': miles, 'bonus': bonus, 'leftover': x['pts'] - usable}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal', {'pts': 12751, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 751}], ['below minimum after flooring', {'pts': 5998, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5099, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 99}], ['bonus threshold edge', {'pts': 10010, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 10}], ['threshold by raw only', {'pts': 10401, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 401}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 8, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 4375, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3001, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60020, 'leftover': 321}]], [['normal', {'pts': 12752, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 752}], ['below minimum after flooring', {'pts': 5997, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5199, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 199}], ['bonus threshold edge', {'pts': 10020, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 20}], ['threshold by raw only', {'pts': 10402, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 402}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 9, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3888, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3002, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60040, 'leftover': 321}]], [['normal', {'pts': 12753, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 753}], ['below minimum after flooring', {'pts': 5996, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5299, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 299}], ['bonus threshold edge', {'pts': 10030, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 30}], ['threshold by raw only', {'pts': 10403, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 403}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 10, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3500, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3003, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60060, 'leftover': 321}]], [['normal', {'pts': 12754, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 754}], ['below minimum after flooring', {'pts': 5995, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5399, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 399}], ['bonus threshold edge', {'pts': 10040, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 40}], ['threshold by raw only', {'pts': 10404, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 404}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 11, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3181, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3004, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60080, 'leftover': 321}]], [['normal', {'pts': 12755, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 755}], ['below minimum after flooring', {'pts': 5994, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5499, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 499}], ['bonus threshold edge', {'pts': 10050, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 50}], ['threshold by raw only', {'pts': 10405, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 405}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 12, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 2916, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3005, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60100, 'leftover': 321}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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):
    usable = x['pts'] // x['block'] * x['block']
    if usable < x['min_pts']:
        return {'error': 'below minimum'}
    miles = usable * x['num'] // x['den']
    bonus = miles * x['bonus_bp'] // 10000 if usable > x['threshold'] else 0
    return {'used': usable, 'miles': miles, 'bonus': bonus, 'leftover': x['pts'] - usable}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal', {'pts': 12751, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 751}], ['below minimum after flooring', {'pts': 5998, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5099, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 99}], ['bonus threshold edge', {'pts': 10010, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 10}], ['threshold by raw only', {'pts': 10401, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 401}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 8, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 4375, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3001, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60020, 'leftover': 321}]], [['normal', {'pts': 12752, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 752}], ['below minimum after flooring', {'pts': 5997, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5199, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 199}], ['bonus threshold edge', {'pts': 10020, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 20}], ['threshold by raw only', {'pts': 10402, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 402}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 9, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3888, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3002, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60040, 'leftover': 321}]], [['normal', {'pts': 12753, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 753}], ['below minimum after flooring', {'pts': 5996, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5299, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 299}], ['bonus threshold edge', {'pts': 10030, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 30}], ['threshold by raw only', {'pts': 10403, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 403}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 10, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3500, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3003, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60060, 'leftover': 321}]], [['normal', {'pts': 12754, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 754}], ['below minimum after flooring', {'pts': 5995, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5399, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 399}], ['bonus threshold edge', {'pts': 10040, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 40}], ['threshold by raw only', {'pts': 10404, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 404}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 11, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3181, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3004, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60080, 'leftover': 321}]], [['normal', {'pts': 12755, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 755}], ['below minimum after flooring', {'pts': 5994, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5499, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 499}], ['bonus threshold edge', {'pts': 10050, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 50}], ['threshold by raw only', {'pts': 10405, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 405}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 12, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 2916, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3005, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60100, 'leftover': 321}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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:
Members whose raw balance crosses the bonus threshold get a bonus though fewer usable points were converted.

Input {pts, block, min_pts, num, den, bonus_bp, threshold}. Usable points = pts floored to a block; usable below min_pts returns {"error"}. Miles = floor(usable*num/den). If usable >= threshold, bonus miles = floor(miles*bonus_bp/10000). Leftover points stay in the account. Return {used, miles, bonus, leftover}.

split: open-access


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

N = 1
observations = []
def solve(x):
    usable = x['pts'] // x['block'] * x['block']
    if usable < x['min_pts']:
        return {'error': 'below minimum'}
    miles = usable * x['num'] // x['den']
    bonus = usable * x['bonus_bp'] // 10000 if usable >= x['threshold'] else 0
    return {'used': usable, 'miles': miles, 'bonus': bonus, 'leftover': x['pts'] - usable}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal', {'pts': 12751, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 751}], ['below minimum after flooring', {'pts': 5998, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5099, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 99}], ['bonus threshold edge', {'pts': 10010, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 10}], ['threshold by raw only', {'pts': 10401, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 401}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 8, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 4375, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3001, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60020, 'leftover': 321}]], [['normal', {'pts': 12752, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 752}], ['below minimum after flooring', {'pts': 5997, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5199, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 199}], ['bonus threshold edge', {'pts': 10020, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 20}], ['threshold by raw only', {'pts': 10402, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 402}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 9, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3888, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3002, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60040, 'leftover': 321}]], [['normal', {'pts': 12753, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 753}], ['below minimum after flooring', {'pts': 5996, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5299, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 299}], ['bonus threshold edge', {'pts': 10030, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 30}], ['threshold by raw only', {'pts': 10403, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 403}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 10, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3500, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3003, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60060, 'leftover': 321}]], [['normal', {'pts': 12754, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 754}], ['below minimum after flooring', {'pts': 5995, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5399, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 399}], ['bonus threshold edge', {'pts': 10040, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 40}], ['threshold by raw only', {'pts': 10404, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 404}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 11, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3181, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3004, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60080, 'leftover': 321}]], [['normal', {'pts': 12755, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 755}], ['below minimum after flooring', {'pts': 5994, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5499, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 499}], ['bonus threshold edge', {'pts': 10050, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 50}], ['threshold by raw only', {'pts': 10405, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 405}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 12, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 2916, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3005, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60100, 'leftover': 321}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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):
    usable = x['pts'] // x['block'] * x['block']
    if usable < x['min_pts']:
        return {'error': 'below minimum'}
    miles = usable * x['num'] // x['den']
    bonus = (usable + miles) * x['bonus_bp'] // 20000 if usable >= x['threshold'] else 0
    return {'used': usable, 'miles': miles, 'bonus': bonus, 'leftover': x['pts'] - usable}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['normal', {'pts': 12751, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 751}], ['below minimum after flooring', {'pts': 5998, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5099, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 99}], ['bonus threshold edge', {'pts': 10010, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 10}], ['threshold by raw only', {'pts': 10401, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 401}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 8, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 4375, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3001, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60020, 'leftover': 321}]], [['normal', {'pts': 12752, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 752}], ['below minimum after flooring', {'pts': 5997, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5199, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 199}], ['bonus threshold edge', {'pts': 10020, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 20}], ['threshold by raw only', {'pts': 10402, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 402}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 9, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3888, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3002, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60040, 'leftover': 321}]], [['normal', {'pts': 12753, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 753}], ['below minimum after flooring', {'pts': 5996, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5299, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 299}], ['bonus threshold edge', {'pts': 10030, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 30}], ['threshold by raw only', {'pts': 10403, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 403}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 10, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3500, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3003, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60060, 'leftover': 321}]], [['normal', {'pts': 12754, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 754}], ['below minimum after flooring', {'pts': 5995, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5399, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 399}], ['bonus threshold edge', {'pts': 10040, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 40}], ['threshold by raw only', {'pts': 10404, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 404}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 11, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 3181, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3004, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60080, 'leftover': 321}]], [['normal', {'pts': 12755, 'block': 1000, 'min_pts': 5000, 'num': 3, 'den': 4, 'bonus_bp': 2500, 'threshold': 10000}, {'used': 12000, 'miles': 9000, 'bonus': 2250, 'leftover': 755}], ['below minimum after flooring', {'pts': 5994, 'block': 1000, 'min_pts': 5500, 'num': 1, 'den': 1, 'bonus_bp': 0, 'threshold': 10000}, {'error': 'below minimum'}], ['raw above minimum', {'pts': 5499, 'block': 1000, 'min_pts': 5000, 'num': 2, 'den': 3, 'bonus_bp': 1000, 'threshold': 5000}, {'used': 5000, 'miles': 3333, 'bonus': 333, 'leftover': 499}], ['bonus threshold edge', {'pts': 10050, 'block': 500, 'min_pts': 1000, 'num': 1, 'den': 2, 'bonus_bp': 1500, 'threshold': 10000}, {'used': 10000, 'miles': 5000, 'bonus': 750, 'leftover': 50}], ['threshold by raw only', {'pts': 10405, 'block': 1000, 'min_pts': 1000, 'num': 1, 'den': 1, 'bonus_bp': 2000, 'threshold': 10200}, {'used': 10000, 'miles': 10000, 'bonus': 0, 'leftover': 405}], ['awkward ratio', {'pts': 7000, 'block': 250, 'min_pts': 2500, 'num': 5, 'den': 12, 'bonus_bp': 0, 'threshold': 99999}, {'used': 7000, 'miles': 2916, 'bonus': 0, 'leftover': 0}], ['large', {'pts': 250321, 'block': 1000, 'min_pts': 5000, 'num': 4, 'den': 5, 'bonus_bp': 3005, 'threshold': 50000}, {'used': 250000, 'miles': 200000, 'bonus': 60100, 'leftover': 321}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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:
Bonus miles are computed on points, over- or under-paying on non 1:1 ratios.

Input {pts, block, min_pts, num, den, bonus_bp, threshold}. Usable points = pts floored to a block; usable below min_pts returns {"error"}. Miles = floor(usable*num/den). If usable >= threshold, bonus miles = floor(miles*bonus_bp/10000). Leftover points stay in the account. Return {used, miles, bonus, leftover}.

split: open-access


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

N = 1
observations = []
def solve(x):
    r = x['redemption']
    if x['cancel_day'] - r['day'] > x['max_age']:
        return {'error': 'too late'}
    lots = {}
    for exp, p in r['sources']:
        e = exp if exp > x['cancel_day'] else x['cancel_day'] + x['grace']
        lots[e] = lots.get(e, 0) + p
    return {'lots': [[e, lots[e]] for e in sorted(lots)], 'total': sum(lots.values())}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['mixed sources', {'redemption': {'day': 100, 'sources': [[91, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 51]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 51]], 'total': 51}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 102, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 11, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 101, 'grace': 14, 'max_age': 90}, {'lots': [[115, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 21], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 31], [300, 7]], 'total': 38}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 6]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 6], [150, 10], [200, 40]], 'total': 56}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 81, 'grace': 10, 'max_age': 90}, {'lots': [[91, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[31, 9]]}, 'cancel_day': 32, 'grace': 7, 'max_age': 90}, {'lots': [[39, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[92, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 52]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 52]], 'total': 52}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 103, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 12, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 102, 'grace': 14, 'max_age': 90}, {'lots': [[116, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 22], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 32], [300, 7]], 'total': 39}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 7]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 7], [150, 10], [200, 40]], 'total': 57}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 82, 'grace': 10, 'max_age': 90}, {'lots': [[92, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[32, 9]]}, 'cancel_day': 33, 'grace': 7, 'max_age': 90}, {'lots': [[40, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[93, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 53]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 53]], 'total': 53}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 104, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 13, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 103, 'grace': 14, 'max_age': 90}, {'lots': [[117, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 23], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 33], [300, 7]], 'total': 40}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 8]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 8], [150, 10], [200, 40]], 'total': 58}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 83, 'grace': 10, 'max_age': 90}, {'lots': [[93, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[33, 9]]}, 'cancel_day': 34, 'grace': 7, 'max_age': 90}, {'lots': [[41, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[94, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 54]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 54]], 'total': 54}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 105, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 14, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 104, 'grace': 14, 'max_age': 90}, {'lots': [[118, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 24], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 34], [300, 7]], 'total': 41}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 9]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 9], [150, 10], [200, 40]], 'total': 59}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 84, 'grace': 10, 'max_age': 90}, {'lots': [[94, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[34, 9]]}, 'cancel_day': 35, 'grace': 7, 'max_age': 90}, {'lots': [[42, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[95, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 55]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 55]], 'total': 55}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 106, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 15, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 105, 'grace': 14, 'max_age': 90}, {'lots': [[119, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 25], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 35], [300, 7]], 'total': 42}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 10]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 10], [150, 10], [200, 40]], 'total': 60}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 85, 'grace': 10, 'max_age': 90}, {'lots': [[95, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[35, 9]]}, 'cancel_day': 36, 'grace': 7, 'max_age': 90}, {'lots': [[43, 9]], 'total': 9}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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):
    r = x['redemption']
    if x['cancel_day'] - r['day'] > x['max_age']:
        return {'error': 'too late'}
    lots = {}
    for exp, p in r['sources']:
        e = exp if exp >= x['cancel_day'] - 1 else x['cancel_day'] + x['grace']
        lots[e] = lots.get(e, 0) + p
    return {'lots': [[e, lots[e]] for e in sorted(lots)], 'total': sum(lots.values())}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['mixed sources', {'redemption': {'day': 100, 'sources': [[91, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 51]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 51]], 'total': 51}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 102, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 11, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 101, 'grace': 14, 'max_age': 90}, {'lots': [[115, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 21], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 31], [300, 7]], 'total': 38}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 6]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 6], [150, 10], [200, 40]], 'total': 56}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 81, 'grace': 10, 'max_age': 90}, {'lots': [[91, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[31, 9]]}, 'cancel_day': 32, 'grace': 7, 'max_age': 90}, {'lots': [[39, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[92, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 52]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 52]], 'total': 52}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 103, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 12, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 102, 'grace': 14, 'max_age': 90}, {'lots': [[116, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 22], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 32], [300, 7]], 'total': 39}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 7]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 7], [150, 10], [200, 40]], 'total': 57}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 82, 'grace': 10, 'max_age': 90}, {'lots': [[92, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[32, 9]]}, 'cancel_day': 33, 'grace': 7, 'max_age': 90}, {'lots': [[40, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[93, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 53]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 53]], 'total': 53}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 104, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 13, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 103, 'grace': 14, 'max_age': 90}, {'lots': [[117, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 23], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 33], [300, 7]], 'total': 40}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 8]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 8], [150, 10], [200, 40]], 'total': 58}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 83, 'grace': 10, 'max_age': 90}, {'lots': [[93, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[33, 9]]}, 'cancel_day': 34, 'grace': 7, 'max_age': 90}, {'lots': [[41, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[94, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 54]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 54]], 'total': 54}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 105, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 14, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 104, 'grace': 14, 'max_age': 90}, {'lots': [[118, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 24], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 34], [300, 7]], 'total': 41}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 9]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 9], [150, 10], [200, 40]], 'total': 59}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 84, 'grace': 10, 'max_age': 90}, {'lots': [[94, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[34, 9]]}, 'cancel_day': 35, 'grace': 7, 'max_age': 90}, {'lots': [[42, 9]], 'total': 9}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[95, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 55]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 55]], 'total': 55}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 106, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 15, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 105, 'grace': 14, 'max_age': 90}, {'lots': [[119, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 25], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 35], [300, 7]], 'total': 42}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 10]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 10], [150, 10], [200, 40]], 'total': 60}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 85, 'grace': 10, 'max_age': 90}, {'lots': [[95, 70]], 'total': 70}], ['expired day before', {'redemption': {'day': 5, 'sources': [[35, 9]]}, 'cancel_day': 36, 'grace': 7, 'max_age': 90}, {'lots': [[43, 9]], 'total': 9}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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:
Points that are still valid on the cancellation day receive a new grace expiry instead of their original one.

Input {redemption:{day, sources:[[lot_expiry, pts]]}, cancel_day, grace, max_age}. Cancelling more than max_age days after the redemption returns {"error":"too late"}. Each source lot is reinstated with its original expiry if that expiry is on or after the cancel day, otherwise with expiry cancel_day + grace. Lots with the same resulting expiry are merged. Return {lots:[[expiry,pts]] by expiry, total}.

split: open-access


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

N = 1
observations = []
def solve(x):
    r = x['redemption']
    if x['cancel_day'] - r['day'] > x['max_age']:
        return {'error': 'too late'}
    lots = {}
    for exp, p in r['sources']:
        e = exp if exp >= x['cancel_day'] else exp + x['grace']
        lots[e] = lots.get(e, 0) + p
    return {'lots': [[e, lots[e]] for e in sorted(lots)], 'total': sum(lots.values())}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['mixed sources', {'redemption': {'day': 100, 'sources': [[91, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 51]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 51]], 'total': 51}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 102, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 11, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 101, 'grace': 14, 'max_age': 90}, {'lots': [[115, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 21], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 31], [300, 7]], 'total': 38}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 6]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 6], [150, 10], [200, 40]], 'total': 56}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 81, 'grace': 10, 'max_age': 90}, {'lots': [[91, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[92, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 52]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 52]], 'total': 52}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 103, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 12, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 102, 'grace': 14, 'max_age': 90}, {'lots': [[116, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 22], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 32], [300, 7]], 'total': 39}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 7]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 7], [150, 10], [200, 40]], 'total': 57}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 82, 'grace': 10, 'max_age': 90}, {'lots': [[92, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[93, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 53]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 53]], 'total': 53}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 104, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 13, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 103, 'grace': 14, 'max_age': 90}, {'lots': [[117, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 23], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 33], [300, 7]], 'total': 40}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 8]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 8], [150, 10], [200, 40]], 'total': 58}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 83, 'grace': 10, 'max_age': 90}, {'lots': [[93, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[94, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 54]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 54]], 'total': 54}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 105, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 14, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 104, 'grace': 14, 'max_age': 90}, {'lots': [[118, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 24], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 34], [300, 7]], 'total': 41}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 9]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 9], [150, 10], [200, 40]], 'total': 59}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 84, 'grace': 10, 'max_age': 90}, {'lots': [[94, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[95, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 55]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 55]], 'total': 55}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 106, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 15, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 105, 'grace': 14, 'max_age': 90}, {'lots': [[119, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 25], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 35], [300, 7]], 'total': 42}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 10]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 10], [150, 10], [200, 40]], 'total': 60}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 85, 'grace': 10, 'max_age': 90}, {'lots': [[95, 70]], 'total': 70}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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):
    r = x['redemption']
    if x['cancel_day'] - r['day'] > x['max_age']:
        return {'error': 'too late'}
    lots = {}
    for exp, p in r['sources']:
        e = exp if exp >= x['cancel_day'] else r['day'] + x['grace']
        lots[e] = lots.get(e, 0) + p
    return {'lots': [[e, lots[e]] for e in sorted(lots)], 'total': sum(lots.values())}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['mixed sources', {'redemption': {'day': 100, 'sources': [[91, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 51]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 51]], 'total': 51}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 102, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 11, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 101, 'grace': 14, 'max_age': 90}, {'lots': [[115, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 21], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 31], [300, 7]], 'total': 38}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 6]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 6], [150, 10], [200, 40]], 'total': 56}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 81, 'grace': 10, 'max_age': 90}, {'lots': [[91, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[92, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 52]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 52]], 'total': 52}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 103, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 12, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 102, 'grace': 14, 'max_age': 90}, {'lots': [[116, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 22], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 32], [300, 7]], 'total': 39}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 7]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 7], [150, 10], [200, 40]], 'total': 57}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 82, 'grace': 10, 'max_age': 90}, {'lots': [[92, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[93, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 53]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 53]], 'total': 53}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 104, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 13, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 103, 'grace': 14, 'max_age': 90}, {'lots': [[117, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 23], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 33], [300, 7]], 'total': 40}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 8]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 8], [150, 10], [200, 40]], 'total': 58}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 83, 'grace': 10, 'max_age': 90}, {'lots': [[93, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[94, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 54]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 54]], 'total': 54}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 105, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 14, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 104, 'grace': 14, 'max_age': 90}, {'lots': [[118, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 24], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 34], [300, 7]], 'total': 41}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 9]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 9], [150, 10], [200, 40]], 'total': 59}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 84, 'grace': 10, 'max_age': 90}, {'lots': [[94, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[95, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 55]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 55]], 'total': 55}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 106, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 15, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 105, 'grace': 14, 'max_age': 90}, {'lots': [[119, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 25], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 35], [300, 7]], 'total': 42}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 10]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 10], [150, 10], [200, 40]], 'total': 60}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 85, 'grace': 10, 'max_age': 90}, {'lots': [[95, 70]], 'total': 70}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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:
Reinstated points that had lapsed expire immediately or within days of reinstatement.

Input {redemption:{day, sources:[[lot_expiry, pts]]}, cancel_day, grace, max_age}. Cancelling more than max_age days after the redemption returns {"error":"too late"}. Each source lot is reinstated with its original expiry if that expiry is on or after the cancel day, otherwise with expiry cancel_day + grace. Lots with the same resulting expiry are merged. Return {lots:[[expiry,pts]] by expiry, total}.

split: open-access


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

N = 1
observations = []
def solve(x):
    r = x['redemption']
    if x['cancel_day'] - r['day'] > x['max_age']:
        return {'error': 'too late'}
    lots = {}
    for exp, p in r['sources']:
        e = exp if exp >= x['cancel_day'] else x['cancel_day'] + x['grace']
        lots[e] = p
    return {'lots': [[e, lots[e]] for e in sorted(lots)], 'total': sum(lots.values())}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['mixed sources', {'redemption': {'day': 100, 'sources': [[91, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 51]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 51]], 'total': 51}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 102, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 11, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 101, 'grace': 14, 'max_age': 90}, {'lots': [[115, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 21], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 31], [300, 7]], 'total': 38}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 6]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 6], [150, 10], [200, 40]], 'total': 56}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 81, 'grace': 10, 'max_age': 90}, {'lots': [[91, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[92, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 52]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 52]], 'total': 52}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 103, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 12, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 102, 'grace': 14, 'max_age': 90}, {'lots': [[116, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 22], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 32], [300, 7]], 'total': 39}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 7]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 7], [150, 10], [200, 40]], 'total': 57}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 82, 'grace': 10, 'max_age': 90}, {'lots': [[92, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[93, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 53]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 53]], 'total': 53}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 104, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 13, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 103, 'grace': 14, 'max_age': 90}, {'lots': [[117, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 23], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 33], [300, 7]], 'total': 40}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 8]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 8], [150, 10], [200, 40]], 'total': 58}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 83, 'grace': 10, 'max_age': 90}, {'lots': [[93, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[94, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 54]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 54]], 'total': 54}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 105, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 14, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 104, 'grace': 14, 'max_age': 90}, {'lots': [[118, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 24], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 34], [300, 7]], 'total': 41}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 9]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 9], [150, 10], [200, 40]], 'total': 59}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 84, 'grace': 10, 'max_age': 90}, {'lots': [[94, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[95, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 55]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 55]], 'total': 55}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 106, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 15, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 105, 'grace': 14, 'max_age': 90}, {'lots': [[119, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 25], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 35], [300, 7]], 'total': 42}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 10]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 10], [150, 10], [200, 40]], 'total': 60}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 85, 'grace': 10, 'max_age': 90}, {'lots': [[95, 70]], 'total': 70}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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):
    r = x['redemption']
    if x['cancel_day'] - r['day'] > x['max_age']:
        return {'error': 'too late'}
    lots = {}
    for exp, p in r['sources']:
        e = exp if exp >= x['cancel_day'] else x['cancel_day'] + x['grace']
        lots[e] = max(lots.get(e, 0), p)
    return {'lots': [[e, lots[e]] for e in sorted(lots)], 'total': sum(lots.values())}
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['mixed sources', {'redemption': {'day': 100, 'sources': [[91, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 51]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 51]], 'total': 51}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 102, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 11, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 101, 'grace': 14, 'max_age': 90}, {'lots': [[115, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 21], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 31], [300, 7]], 'total': 38}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 6]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 6], [150, 10], [200, 40]], 'total': 56}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 81, 'grace': 10, 'max_age': 90}, {'lots': [[91, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[92, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 52]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 52]], 'total': 52}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 103, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 12, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 102, 'grace': 14, 'max_age': 90}, {'lots': [[116, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 22], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 32], [300, 7]], 'total': 39}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 7]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 7], [150, 10], [200, 40]], 'total': 57}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 82, 'grace': 10, 'max_age': 90}, {'lots': [[92, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[93, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 53]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 53]], 'total': 53}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 104, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 13, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 103, 'grace': 14, 'max_age': 90}, {'lots': [[117, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 23], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 33], [300, 7]], 'total': 40}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 8]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 8], [150, 10], [200, 40]], 'total': 58}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 83, 'grace': 10, 'max_age': 90}, {'lots': [[93, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[94, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 54]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 54]], 'total': 54}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 105, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 14, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 104, 'grace': 14, 'max_age': 90}, {'lots': [[118, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 24], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 34], [300, 7]], 'total': 41}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 9]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 9], [150, 10], [200, 40]], 'total': 59}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 84, 'grace': 10, 'max_age': 90}, {'lots': [[94, 70]], 'total': 70}]], [['mixed sources', {'redemption': {'day': 100, 'sources': [[95, 300], [150, 200], [400, 100]]}, 'cancel_day': 110, 'grace': 30, 'max_age': 90}, {'lots': [[140, 300], [150, 200], [400, 100]], 'total': 600}], ['expires on cancel day', {'redemption': {'day': 100, 'sources': [[120, 55]]}, 'cancel_day': 120, 'grace': 30, 'max_age': 90}, {'lots': [[120, 55]], 'total': 55}], ['too late', {'redemption': {'day': 10, 'sources': [[500, 100]]}, 'cancel_day': 106, 'grace': 30, 'max_age': 90}, {'error': 'too late'}], ['age edge', {'redemption': {'day': 15, 'sources': [[500, 100], [20, 5]]}, 'cancel_day': 105, 'grace': 14, 'max_age': 90}, {'lots': [[119, 5], [500, 100]], 'total': 105}], ['merge grace lots', {'redemption': {'day': 50, 'sources': [[55, 10], [58, 25], [300, 7]]}, 'cancel_day': 60, 'grace': 45, 'max_age': 90}, {'lots': [[105, 35], [300, 7]], 'total': 42}], ['merge with original', {'redemption': {'day': 50, 'sources': [[200, 40], [150, 10], [90, 10]]}, 'cancel_day': 60, 'grace': 140, 'max_age': 90}, {'lots': [[90, 10], [150, 10], [200, 40]], 'total': 60}], ['late cancel grace base', {'redemption': {'day': 20, 'sources': [[25, 70]]}, 'cancel_day': 85, 'grace': 10, 'max_age': 90}, {'lots': [[95, 70]], 'total': 70}]]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(args), 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:
Two lots that end up with the same grace expiry show only the last lot points.

Input {redemption:{day, sources:[[lot_expiry, pts]]}, cancel_day, grace, max_age}. Cancelling more than max_age days after the redemption returns {"error":"too late"}. Each source lot is reinstated with its original expiry if that expiry is on or after the cancel day, otherwise with expiry cancel_day + grace. Lots with the same resulting expiry are merged. Return {lots:[[expiry,pts]] by expiry, total}.

split: open-access