FAILURE MAP
← Case archive

FA-68451 / Toll and parking fee computation / Open access

Street meter time purchase: partial increments are credited · case 01

Street meter time purchase returns a wrong result when partial increments are credited.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Amounts that are not a whole increment are credited in full, buying time the tariff does not sell.

VERIFIED REPAIR

Restore the coin increment credit rule so that the step reads `x['paid_cents'] - x['paid_cents'] % inc`.

Unsuccessful approach: Rounding up to the next increment credits money that was never paid.

Case contract

Input {now, expiry (or null), paid_cents, rate_per_hour, max_minutes, enf_start, enf_end, increment}. Only whole increments are credited. At or after enforcement end nothing is sold: [null, 0]. Paid time starts at the later of now, enforcement start and any unexpired expiry; it ends at the earliest of start + purchased minutes (floor), max(now, enf_start) + max_minutes, and enf_end. Accepted cents = min(credited, ceil(used minutes * rate/60)). Return [new expiry, accepted cents].

Why this case matters

Fee engines bill customers in integer cents; a wrong boundary, rounding stage or cap scope silently over- or under-charges.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    inc = x['increment']
    c = x['paid_cents']
    now = x['now']
    if now >= x['enf_end']:
        return [None, 0]
    base = max(now, x['enf_start'])
    start = base if x['expiry'] is None else max(base, x['expiry'])
    mins = c * 60 // x['rate_per_hour']
    end = min(start + mins, base + x['max_minutes'], x['enf_end'])
    used = max(0, end - start)
    cost = min(c, -(-used * x['rate_per_hour'] // 60))
    return [max(end, start), cost]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'now': 1080, 'expiry': 1117, 'paid_cents': 337, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [None, 0]), ({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 662, 'expiry': 661, 'paid_cents': 587, 'rate_per_hour': 200, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [837, 584]), ({'now': 480, 'expiry': 471, 'paid_cents': 576, 'rate_per_hour': 200, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [540, 200]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 768, 'expiry': 755, 'paid_cents': 387, 'rate_per_hour': 150, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [918, 375]), ({'now': 480, 'expiry': 461, 'paid_cents': 174, 'rate_per_hour': 200, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [531, 170]), ({'now': 600, 'expiry': 650, 'paid_cents': 400, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [720, 234])], [({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 634, 'expiry': None, 'paid_cents': 224, 'rate_per_hour': 150, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [714, 200]), ({'now': 1103, 'expiry': 1153, 'paid_cents': 441, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 472, 'expiry': None, 'paid_cents': 54, 'rate_per_hour': 200, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [495, 50]), ({'now': 737, 'expiry': None, 'paid_cents': 305, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [917, 300]), ({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 1080, 'expiry': 1075, 'paid_cents': 372, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0])], [({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 480, 'expiry': 540, 'paid_cents': 241, 'rate_per_hour': 150, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 0]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 480, 'expiry': None, 'paid_cents': 195, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [585, 175]), ({'now': 1192, 'expiry': None, 'paid_cents': 253, 'rate_per_hour': 250, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 480, 'expiry': 518, 'paid_cents': 159, 'rate_per_hour': 250, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [554, 150]), ({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 430, 'expiry': None, 'paid_cents': 294, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [645, 275])], [({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 641, 'expiry': None, 'paid_cents': 104, 'rate_per_hour': 250, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [665, 100]), ({'now': 480, 'expiry': 462, 'paid_cents': 171, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 150]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 480, 'expiry': 540, 'paid_cents': 124, 'rate_per_hour': 150, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [588, 120]), ({'now': 1098, 'expiry': None, 'paid_cents': 140, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [None, 0]), ({'now': 480, 'expiry': None, 'paid_cents': 104, 'rate_per_hour': 150, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [520, 100]), ({'now': 600, 'expiry': 650, 'paid_cents': 400, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [720, 234])], [({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 913, 'expiry': 880, 'paid_cents': 89, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [958, 75]), ({'now': 850, 'expiry': 827, 'paid_cents': 491, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [970, 200]), ({'now': 318, 'expiry': 269, 'paid_cents': 362, 'rate_per_hour': 200, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [585, 350]), ({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 956, 'expiry': 942, 'paid_cents': 122, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [1028, 120]), ({'now': 40, 'expiry': 116, 'paid_cents': 331, 'rate_per_hour': 250, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 250]), ({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0])]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check('fee oracle' + ' %d' % i, 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)
Boundary fixtureActualExpectedOutcome
fee oracle 0[None, 0][None, 0]Passed
fee oracle 1[540, 200][540, 200]Passed
fee oracle 2[838, 587][837, 584]Failed
fee oracle 3[540, 200][540, 200]Passed
fee oracle 4[1080, 100][1080, 100]Passed
fee oracle 5[922, 385][918, 375]Failed
fee oracle 6[532, 174][531, 170]Failed
fee oracle 7[720, 234][720, 234]Passed

SHA-256 / b4aad171e8567837e41188d70d701dc9a598f95d5f7e3ecddc5f27080e35f132

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    inc = x['increment']
    c = -(-x['paid_cents'] // inc) * inc
    now = x['now']
    if now >= x['enf_end']:
        return [None, 0]
    base = max(now, x['enf_start'])
    start = base if x['expiry'] is None else max(base, x['expiry'])
    mins = c * 60 // x['rate_per_hour']
    end = min(start + mins, base + x['max_minutes'], x['enf_end'])
    used = max(0, end - start)
    cost = min(c, -(-used * x['rate_per_hour'] // 60))
    return [max(end, start), cost]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'now': 1080, 'expiry': 1117, 'paid_cents': 337, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [None, 0]), ({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 662, 'expiry': 661, 'paid_cents': 587, 'rate_per_hour': 200, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [837, 584]), ({'now': 480, 'expiry': 471, 'paid_cents': 576, 'rate_per_hour': 200, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [540, 200]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 768, 'expiry': 755, 'paid_cents': 387, 'rate_per_hour': 150, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [918, 375]), ({'now': 480, 'expiry': 461, 'paid_cents': 174, 'rate_per_hour': 200, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [531, 170]), ({'now': 600, 'expiry': 650, 'paid_cents': 400, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [720, 234])], [({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 634, 'expiry': None, 'paid_cents': 224, 'rate_per_hour': 150, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [714, 200]), ({'now': 1103, 'expiry': 1153, 'paid_cents': 441, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 472, 'expiry': None, 'paid_cents': 54, 'rate_per_hour': 200, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [495, 50]), ({'now': 737, 'expiry': None, 'paid_cents': 305, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [917, 300]), ({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 1080, 'expiry': 1075, 'paid_cents': 372, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0])], [({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 480, 'expiry': 540, 'paid_cents': 241, 'rate_per_hour': 150, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 0]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 480, 'expiry': None, 'paid_cents': 195, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [585, 175]), ({'now': 1192, 'expiry': None, 'paid_cents': 253, 'rate_per_hour': 250, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 480, 'expiry': 518, 'paid_cents': 159, 'rate_per_hour': 250, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [554, 150]), ({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 430, 'expiry': None, 'paid_cents': 294, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [645, 275])], [({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 641, 'expiry': None, 'paid_cents': 104, 'rate_per_hour': 250, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [665, 100]), ({'now': 480, 'expiry': 462, 'paid_cents': 171, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 150]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 480, 'expiry': 540, 'paid_cents': 124, 'rate_per_hour': 150, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [588, 120]), ({'now': 1098, 'expiry': None, 'paid_cents': 140, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [None, 0]), ({'now': 480, 'expiry': None, 'paid_cents': 104, 'rate_per_hour': 150, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [520, 100]), ({'now': 600, 'expiry': 650, 'paid_cents': 400, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [720, 234])], [({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 913, 'expiry': 880, 'paid_cents': 89, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [958, 75]), ({'now': 850, 'expiry': 827, 'paid_cents': 491, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [970, 200]), ({'now': 318, 'expiry': 269, 'paid_cents': 362, 'rate_per_hour': 200, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [585, 350]), ({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 956, 'expiry': 942, 'paid_cents': 122, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [1028, 120]), ({'now': 40, 'expiry': 116, 'paid_cents': 331, 'rate_per_hour': 250, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 250]), ({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0])]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check('fee oracle' + ' %d' % i, 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)
Boundary fixtureActualExpectedOutcome
fee oracle 0[None, 0][None, 0]Passed
fee oracle 1[540, 200][540, 200]Passed
fee oracle 2[839, 590][837, 584]Failed
fee oracle 3[540, 200][540, 200]Passed
fee oracle 4[1080, 100][1080, 100]Passed
fee oracle 5[928, 400][918, 375]Failed
fee oracle 6[532, 174][531, 170]Failed
fee oracle 7[720, 234][720, 234]Passed

SHA-256 / 67662e23b296beab4a6b6db7abd8a5fed2c9a133b7d87e4c51450dd01be36749

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    inc = x['increment']
    c = x['paid_cents'] - x['paid_cents'] % inc
    now = x['now']
    if now >= x['enf_end']:
        return [None, 0]
    base = max(now, x['enf_start'])
    start = base if x['expiry'] is None else max(base, x['expiry'])
    mins = c * 60 // x['rate_per_hour']
    end = min(start + mins, base + x['max_minutes'], x['enf_end'])
    used = max(0, end - start)
    cost = min(c, -(-used * x['rate_per_hour'] // 60))
    return [max(end, start), cost]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'now': 1080, 'expiry': 1117, 'paid_cents': 337, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [None, 0]), ({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 662, 'expiry': 661, 'paid_cents': 587, 'rate_per_hour': 200, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [837, 584]), ({'now': 480, 'expiry': 471, 'paid_cents': 576, 'rate_per_hour': 200, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [540, 200]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 768, 'expiry': 755, 'paid_cents': 387, 'rate_per_hour': 150, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [918, 375]), ({'now': 480, 'expiry': 461, 'paid_cents': 174, 'rate_per_hour': 200, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [531, 170]), ({'now': 600, 'expiry': 650, 'paid_cents': 400, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [720, 234])], [({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 634, 'expiry': None, 'paid_cents': 224, 'rate_per_hour': 150, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [714, 200]), ({'now': 1103, 'expiry': 1153, 'paid_cents': 441, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 472, 'expiry': None, 'paid_cents': 54, 'rate_per_hour': 200, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [495, 50]), ({'now': 737, 'expiry': None, 'paid_cents': 305, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [917, 300]), ({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 1080, 'expiry': 1075, 'paid_cents': 372, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0])], [({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 480, 'expiry': 540, 'paid_cents': 241, 'rate_per_hour': 150, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 0]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 480, 'expiry': None, 'paid_cents': 195, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [585, 175]), ({'now': 1192, 'expiry': None, 'paid_cents': 253, 'rate_per_hour': 250, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 480, 'expiry': 518, 'paid_cents': 159, 'rate_per_hour': 250, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [554, 150]), ({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 430, 'expiry': None, 'paid_cents': 294, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [645, 275])], [({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0]), ({'now': 641, 'expiry': None, 'paid_cents': 104, 'rate_per_hour': 250, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [665, 100]), ({'now': 480, 'expiry': 462, 'paid_cents': 171, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 150]), ({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 480, 'expiry': 540, 'paid_cents': 124, 'rate_per_hour': 150, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [588, 120]), ({'now': 1098, 'expiry': None, 'paid_cents': 140, 'rate_per_hour': 100, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [None, 0]), ({'now': 480, 'expiry': None, 'paid_cents': 104, 'rate_per_hour': 150, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [520, 100]), ({'now': 600, 'expiry': 650, 'paid_cents': 400, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [720, 234])], [({'now': 1040, 'expiry': None, 'paid_cents': 333, 'rate_per_hour': 150, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [1080, 100]), ({'now': 913, 'expiry': 880, 'paid_cents': 89, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [958, 75]), ({'now': 850, 'expiry': 827, 'paid_cents': 491, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [970, 200]), ({'now': 318, 'expiry': 269, 'paid_cents': 362, 'rate_per_hour': 200, 'max_minutes': 240, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [585, 350]), ({'now': 420, 'expiry': None, 'paid_cents': 200, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 200]), ({'now': 956, 'expiry': 942, 'paid_cents': 122, 'rate_per_hour': 100, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 5}, [1028, 120]), ({'now': 40, 'expiry': 116, 'paid_cents': 331, 'rate_per_hour': 250, 'max_minutes': 60, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [540, 250]), ({'now': 1080, 'expiry': 1100, 'paid_cents': 100, 'rate_per_hour': 200, 'max_minutes': 120, 'enf_start': 480, 'enf_end': 1080, 'increment': 25}, [None, 0])]]
for i, (args, expected) in enumerate(fixtures[N-1]):
    check('fee oracle' + ' %d' % i, 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)
Boundary fixtureActualExpectedOutcome
fee oracle 0[None, 0][None, 0]Passed
fee oracle 1[540, 200][540, 200]Passed
fee oracle 2[837, 584][837, 584]Passed
fee oracle 3[540, 200][540, 200]Passed
fee oracle 4[1080, 100][1080, 100]Passed
fee oracle 5[918, 375][918, 375]Passed
fee oracle 6[531, 170][531, 170]Passed
fee oracle 7[720, 234][720, 234]Passed

SHA-256 / 513dd9365ab93ab934548710fcc7995275aa1010230ef3d44d7cef3282cac9e2

Verification & scope

A deterministic, bounded toy model with a stipulated contract; it makes no claim of conformance to any agency manual or standard. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:48:02.316512+00:00.

Case digest / 3fd7ad9ac6687468a5b9df48e6a3403ce55c0642c195b07d82f6a81e489c46ec