FAILURE MAP
← Case archive

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

Garage hourly fee with grace and daily maximum: zero-length stays are rejected as invalid · case 01

Garage hourly fee with grace and daily maximum returns a wrong result when zero-length stays are rejected as invalid.

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

ROOT CAUSE

An exit in the same minute as entry is reported invalid instead of free.

VERIFIED REPAIR

Restore the invalid timestamp check rule so that the step reads `if x['exit'] < x['entry']:`.

Unsuccessful approach: Allowing exits up to grace minutes before entry bills clock-skewed tickets as free stays.

Case contract

Input {entry, exit (absolute minutes), grace, hourly, daily_max, lost_ticket, lost_fee}; cents. Exit before entry is 'invalid'. A stay no longer than grace is free (grace is not deducted from longer stays). Otherwise each full 1440-minute block costs daily_max and the remainder costs hourly per started hour, capped at daily_max. A lost ticket pays the greater of the computed fee and lost_fee.

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):
    if x['exit'] <= x['entry']:
        return 'invalid'
    dur = x['exit'] - x['entry']
    if dur <= x['grace']:
        fee = 0
    else:
        days, rem = divmod(dur, 1440)
        fee = days * x['daily_max'] + min(x['daily_max'], -(-rem // 60) * x['hourly'])
    if x['lost_ticket']:
        fee = max(fee, x['lost_fee'])
    return fee
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'entry': 631, 'exit': 638, 'grace': 20, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 3000}, 3000), ({'entry': 2581, 'exit': 2580, 'grace': 10, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600), ({'entry': 1676, 'exit': 1676, 'grace': 15, 'hourly': 450, 'daily_max': 1500, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 2473, 'exit': 2473, 'grace': 15, 'hourly': 200, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 360, 'exit': 346, 'grace': 15, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid')], [({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600), ({'entry': 1465, 'exit': 1462, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid'), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 406, 'exit': 561, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 900), ({'entry': 87, 'exit': 85, 'grace': 20, 'hourly': 200, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 1952, 'exit': 1952, 'grace': 0, 'hourly': 300, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 89, 'exit': 365, 'grace': 10, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 1500)], [({'entry': 717, 'exit': 699, 'grace': 20, 'hourly': 200, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 2276, 'exit': 2276, 'grace': 0, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 0), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 60, 'exit': 240, 'grace': 15, 'hourly': 450, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 1350), ({'entry': 5, 'exit': 5, 'grace': 0, 'hourly': 450, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 0), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 1064, 'exit': 1058, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid')], [({'entry': 60, 'exit': 240, 'grace': 15, 'hourly': 450, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 1350), ({'entry': 2683, 'exit': 5878, 'grace': 15, 'hourly': 450, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 6000), ({'entry': 169, 'exit': 161, 'grace': 15, 'hourly': 200, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid'), ({'entry': 2896, 'exit': 7216, 'grace': 10, 'hourly': 200, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 4500), ({'entry': 2250, 'exit': 2230, 'grace': 20, 'hourly': 200, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 1281, 'exit': 1281, 'grace': 20, 'hourly': 450, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 3000}, 3000), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0)], [({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 1799, 'exit': 1789, 'grace': 20, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 1549, 'exit': 4829, 'grace': 10, 'hourly': 450, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 4500), ({'entry': 1393, 'exit': 1446, 'grace': 0, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 300), ({'entry': 426, 'exit': 438, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 2821, 'exit': 2816, 'grace': 10, 'hourly': 200, 'daily_max': 2400, 'lost_ticket': True, 'lost_fee': 2500}, 'invalid'), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600)]]
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 030003000Passed
fee oracle 1invalidinvalidPassed
fee oracle 200Passed
fee oracle 326002600Passed
fee oracle 4invalid2500Failed
fee oracle 5invalid2500Failed
fee oracle 6invalid0Failed
fee oracle 7invalidinvalidPassed

SHA-256 / 8efe271f74099aa2b757d46f8b2f332b16ac8dcff8ba290197d3db99a52ca066

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    if x['exit'] < x['entry'] - x['grace']:
        return 'invalid'
    dur = x['exit'] - x['entry']
    if dur <= x['grace']:
        fee = 0
    else:
        days, rem = divmod(dur, 1440)
        fee = days * x['daily_max'] + min(x['daily_max'], -(-rem // 60) * x['hourly'])
    if x['lost_ticket']:
        fee = max(fee, x['lost_fee'])
    return fee
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'entry': 631, 'exit': 638, 'grace': 20, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 3000}, 3000), ({'entry': 2581, 'exit': 2580, 'grace': 10, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600), ({'entry': 1676, 'exit': 1676, 'grace': 15, 'hourly': 450, 'daily_max': 1500, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 2473, 'exit': 2473, 'grace': 15, 'hourly': 200, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 360, 'exit': 346, 'grace': 15, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid')], [({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600), ({'entry': 1465, 'exit': 1462, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid'), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 406, 'exit': 561, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 900), ({'entry': 87, 'exit': 85, 'grace': 20, 'hourly': 200, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 1952, 'exit': 1952, 'grace': 0, 'hourly': 300, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 89, 'exit': 365, 'grace': 10, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 1500)], [({'entry': 717, 'exit': 699, 'grace': 20, 'hourly': 200, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 2276, 'exit': 2276, 'grace': 0, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 0), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 60, 'exit': 240, 'grace': 15, 'hourly': 450, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 1350), ({'entry': 5, 'exit': 5, 'grace': 0, 'hourly': 450, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 0), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 1064, 'exit': 1058, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid')], [({'entry': 60, 'exit': 240, 'grace': 15, 'hourly': 450, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 1350), ({'entry': 2683, 'exit': 5878, 'grace': 15, 'hourly': 450, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 6000), ({'entry': 169, 'exit': 161, 'grace': 15, 'hourly': 200, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid'), ({'entry': 2896, 'exit': 7216, 'grace': 10, 'hourly': 200, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 4500), ({'entry': 2250, 'exit': 2230, 'grace': 20, 'hourly': 200, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 1281, 'exit': 1281, 'grace': 20, 'hourly': 450, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 3000}, 3000), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0)], [({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 1799, 'exit': 1789, 'grace': 20, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 1549, 'exit': 4829, 'grace': 10, 'hourly': 450, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 4500), ({'entry': 1393, 'exit': 1446, 'grace': 0, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 300), ({'entry': 426, 'exit': 438, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 2821, 'exit': 2816, 'grace': 10, 'hourly': 200, 'daily_max': 2400, 'lost_ticket': True, 'lost_fee': 2500}, 'invalid'), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600)]]
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 030003000Passed
fee oracle 10invalidFailed
fee oracle 200Passed
fee oracle 326002600Passed
fee oracle 425002500Passed
fee oracle 525002500Passed
fee oracle 600Passed
fee oracle 70invalidFailed

SHA-256 / 31b9840a782af8b0449921214273488f67bd18f8556d818ab6ff06829546f3c1

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    if x['exit'] < x['entry']:
        return 'invalid'
    dur = x['exit'] - x['entry']
    if dur <= x['grace']:
        fee = 0
    else:
        days, rem = divmod(dur, 1440)
        fee = days * x['daily_max'] + min(x['daily_max'], -(-rem // 60) * x['hourly'])
    if x['lost_ticket']:
        fee = max(fee, x['lost_fee'])
    return fee
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'entry': 631, 'exit': 638, 'grace': 20, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 3000}, 3000), ({'entry': 2581, 'exit': 2580, 'grace': 10, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600), ({'entry': 1676, 'exit': 1676, 'grace': 15, 'hourly': 450, 'daily_max': 1500, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 2473, 'exit': 2473, 'grace': 15, 'hourly': 200, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 360, 'exit': 346, 'grace': 15, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid')], [({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600), ({'entry': 1465, 'exit': 1462, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid'), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 406, 'exit': 561, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 900), ({'entry': 87, 'exit': 85, 'grace': 20, 'hourly': 200, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 1952, 'exit': 1952, 'grace': 0, 'hourly': 300, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 89, 'exit': 365, 'grace': 10, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 1500)], [({'entry': 717, 'exit': 699, 'grace': 20, 'hourly': 200, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 2276, 'exit': 2276, 'grace': 0, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 0), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 60, 'exit': 240, 'grace': 15, 'hourly': 450, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 1350), ({'entry': 5, 'exit': 5, 'grace': 0, 'hourly': 450, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 0), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 1064, 'exit': 1058, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid')], [({'entry': 60, 'exit': 240, 'grace': 15, 'hourly': 450, 'daily_max': 2400, 'lost_ticket': False, 'lost_fee': 3000}, 1350), ({'entry': 2683, 'exit': 5878, 'grace': 15, 'hourly': 450, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 6000), ({'entry': 169, 'exit': 161, 'grace': 15, 'hourly': 200, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 'invalid'), ({'entry': 2896, 'exit': 7216, 'grace': 10, 'hourly': 200, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 4500), ({'entry': 2250, 'exit': 2230, 'grace': 20, 'hourly': 200, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 1281, 'exit': 1281, 'grace': 20, 'hourly': 450, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 3000}, 3000), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0)], [({'entry': 100, 'exit': 115, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 500, 'exit': 500, 'grace': 0, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': True, 'lost_fee': 2500}, 2500), ({'entry': 1799, 'exit': 1789, 'grace': 20, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 3000}, 'invalid'), ({'entry': 1549, 'exit': 4829, 'grace': 10, 'hourly': 450, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 3000}, 4500), ({'entry': 1393, 'exit': 1446, 'grace': 0, 'hourly': 300, 'daily_max': 1500, 'lost_ticket': False, 'lost_fee': 2500}, 300), ({'entry': 426, 'exit': 438, 'grace': 15, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 0), ({'entry': 2821, 'exit': 2816, 'grace': 10, 'hourly': 200, 'daily_max': 2400, 'lost_ticket': True, 'lost_fee': 2500}, 'invalid'), ({'entry': 0, 'exit': 1560, 'grace': 10, 'hourly': 300, 'daily_max': 2000, 'lost_ticket': False, 'lost_fee': 2500}, 2600)]]
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 030003000Passed
fee oracle 1invalidinvalidPassed
fee oracle 200Passed
fee oracle 326002600Passed
fee oracle 425002500Passed
fee oracle 525002500Passed
fee oracle 600Passed
fee oracle 7invalidinvalidPassed

SHA-256 / 876246072a8beda9f3db0655e20f50cae10efe2d40488947e4c2c572d554c294

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:01.553828+00:00.

Case digest / 2c512b78af6437eb4cec5e26dcf863d5894b023a987095c2dc6e2fba7aa1b23d