FA-68431 / Toll and parking fee computation / Open access
Time-band rate with calendar day cap: band minutes are rounded down to quarters · case 01
Time-band rate with calendar day cap returns a wrong result when band minutes are rounded down to quarters.
ROOT CAUSE
Partial quarter hours are free.
VERIFIED REPAIR
Restore the quarter-hour ceiling rule so that the step reads `-(-m // 15) * rate`.
Unsuccessful approach: Rounding up to whole hours overcharges short stays.
Case contract
Input {bands: [[start minute of day, cents per quarter hour]] sorted with first start 0, entry, exit (absolute minutes), daily_max}. Minutes of the stay are totalled per calendar day and band; each total is rounded up to whole quarter hours and priced at its band rate. Each calendar day's charge is capped at daily_max.
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):
bands = x['bands']
per = {}
t = x['entry']
while t < x['exit']:
day, tod = divmod(t, 1440)
i = max(k for k in range(len(bands)) if bands[k][0] <= tod)
nxt = bands[i + 1][0] if i + 1 < len(bands) else 1440
step = min(x['exit'] - t, nxt - tod)
key = (day, bands[i][1])
per[key] = per.get(key, 0) + step
t += step
days = {}
for (day, rate), m in per.items():
days[day] = days.get(day, 0) + m // 15 * rate
return sum(min(v, x['daily_max']) for v in days.values())
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'bands': [[0, 50], [630, 100], [1380, 100]], 'entry': 1427, 'exit': 1817, 'daily_max': 1500}, 1400), ({'bands': [[0, 50], [330, 100]], 'entry': 656, 'exit': 1034, 'daily_max': 2500}, 2500), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 25], [300, 50]], 'entry': 1203, 'exit': 1320, 'daily_max': 4000}, 400), ({'bands': [[0, 50], [600, 50], [900, 25]], 'entry': 1880, 'exit': 4249, 'daily_max': 1500}, 3000), ({'bands': [[0, 50], [1320, 100]], 'entry': 1793, 'exit': 2565, 'daily_max': 1500}, 1500), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550)], [({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 25], [270, 50]], 'entry': 1500, 'exit': 2160, 'daily_max': 1500}, 1500), ({'bands': [[0, 50], [390, 100]], 'entry': 1045, 'exit': 1320, 'daily_max': 4000}, 1900), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 25], [1290, 25]], 'entry': 715, 'exit': 1515, 'daily_max': 2500}, 1350), ({'bands': [[0, 25], [960, 100], [1200, 100]], 'entry': 748, 'exit': 2538, 'daily_max': 2500}, 5000), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 25], [1170, 100]], 'entry': 408, 'exit': 1419, 'daily_max': 2500}, 2500)], [({'bands': [[0, 25], [300, 100], [780, 50], [1380, 25]], 'entry': 410, 'exit': 992, 'daily_max': 2500}, 2500), ({'bands': [[0, 25], [90, 100]], 'entry': 15, 'exit': 2161, 'daily_max': 2500}, 5000), ({'bands': [[0, 50], [990, 50]], 'entry': 1775, 'exit': 1955, 'daily_max': 1500}, 600), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 50], [480, 50], [1080, 50]], 'entry': 361, 'exit': 2927, 'daily_max': 1500}, 3200), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 50], [780, 50], [870, 25], [900, 100]], 'entry': 540, 'exit': 797, 'daily_max': 1500}, 900)], [({'bands': [[0, 50], [150, 50], [570, 50], [1320, 50]], 'entry': 615, 'exit': 735, 'daily_max': 2500}, 400), ({'bands': [[0, 25], [420, 100]], 'entry': 364, 'exit': 610, 'daily_max': 2500}, 1400), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 25], [480, 100], [750, 50]], 'entry': 1968, 'exit': 2797, 'daily_max': 1500}, 1500), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [210, 25]], 'entry': 1069, 'exit': 3448, 'daily_max': 1500}, 3425), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 50], [360, 100], [570, 100]], 'entry': 629, 'exit': 2400, 'daily_max': 2500}, 5000)], [({'bands': [[0, 50], [480, 25], [1200, 50], [1230, 50]], 'entry': 1584, 'exit': 3685, 'daily_max': 1500}, 3000), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [900, 50]], 'entry': 347, 'exit': 388, 'daily_max': 4000}, 150), ({'bands': [[0, 50], [60, 100], [450, 100], [1260, 25]], 'entry': 1372, 'exit': 4316, 'daily_max': 2500}, 5125), ({'bands': [[0, 50], [1170, 25]], 'entry': 1899, 'exit': 2697, 'daily_max': 1500}, 1500), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 50], [330, 50]], 'entry': 901, 'exit': 3378, 'daily_max': 1500}, 4500)]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| fee oracle 0 | 1250 | 1400 | Failed |
| fee oracle 1 | 2500 | 2500 | Passed |
| fee oracle 2 | 4000 | 4000 | Passed |
| fee oracle 3 | 400 | 500 | Failed |
| fee oracle 4 | 350 | 400 | Failed |
| fee oracle 5 | 3000 | 3000 | Passed |
| fee oracle 6 | 1500 | 1500 | Passed |
| fee oracle 7 | 4550 | 4550 | Passed |
SHA-256 / c9a41aef628d3d9ec44baa7046862c1b1ecfeae7b76c045fae9619694727650a
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
bands = x['bands']
per = {}
t = x['entry']
while t < x['exit']:
day, tod = divmod(t, 1440)
i = max(k for k in range(len(bands)) if bands[k][0] <= tod)
nxt = bands[i + 1][0] if i + 1 < len(bands) else 1440
step = min(x['exit'] - t, nxt - tod)
key = (day, bands[i][1])
per[key] = per.get(key, 0) + step
t += step
days = {}
for (day, rate), m in per.items():
days[day] = days.get(day, 0) + -(-m // 60) * 4 * rate
return sum(min(v, x['daily_max']) for v in days.values())
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'bands': [[0, 50], [630, 100], [1380, 100]], 'entry': 1427, 'exit': 1817, 'daily_max': 1500}, 1400), ({'bands': [[0, 50], [330, 100]], 'entry': 656, 'exit': 1034, 'daily_max': 2500}, 2500), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 25], [300, 50]], 'entry': 1203, 'exit': 1320, 'daily_max': 4000}, 400), ({'bands': [[0, 50], [600, 50], [900, 25]], 'entry': 1880, 'exit': 4249, 'daily_max': 1500}, 3000), ({'bands': [[0, 50], [1320, 100]], 'entry': 1793, 'exit': 2565, 'daily_max': 1500}, 1500), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550)], [({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 25], [270, 50]], 'entry': 1500, 'exit': 2160, 'daily_max': 1500}, 1500), ({'bands': [[0, 50], [390, 100]], 'entry': 1045, 'exit': 1320, 'daily_max': 4000}, 1900), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 25], [1290, 25]], 'entry': 715, 'exit': 1515, 'daily_max': 2500}, 1350), ({'bands': [[0, 25], [960, 100], [1200, 100]], 'entry': 748, 'exit': 2538, 'daily_max': 2500}, 5000), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 25], [1170, 100]], 'entry': 408, 'exit': 1419, 'daily_max': 2500}, 2500)], [({'bands': [[0, 25], [300, 100], [780, 50], [1380, 25]], 'entry': 410, 'exit': 992, 'daily_max': 2500}, 2500), ({'bands': [[0, 25], [90, 100]], 'entry': 15, 'exit': 2161, 'daily_max': 2500}, 5000), ({'bands': [[0, 50], [990, 50]], 'entry': 1775, 'exit': 1955, 'daily_max': 1500}, 600), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 50], [480, 50], [1080, 50]], 'entry': 361, 'exit': 2927, 'daily_max': 1500}, 3200), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 50], [780, 50], [870, 25], [900, 100]], 'entry': 540, 'exit': 797, 'daily_max': 1500}, 900)], [({'bands': [[0, 50], [150, 50], [570, 50], [1320, 50]], 'entry': 615, 'exit': 735, 'daily_max': 2500}, 400), ({'bands': [[0, 25], [420, 100]], 'entry': 364, 'exit': 610, 'daily_max': 2500}, 1400), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 25], [480, 100], [750, 50]], 'entry': 1968, 'exit': 2797, 'daily_max': 1500}, 1500), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [210, 25]], 'entry': 1069, 'exit': 3448, 'daily_max': 1500}, 3425), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 50], [360, 100], [570, 100]], 'entry': 629, 'exit': 2400, 'daily_max': 2500}, 5000)], [({'bands': [[0, 50], [480, 25], [1200, 50], [1230, 50]], 'entry': 1584, 'exit': 3685, 'daily_max': 1500}, 3000), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [900, 50]], 'entry': 347, 'exit': 388, 'daily_max': 4000}, 150), ({'bands': [[0, 50], [60, 100], [450, 100], [1260, 25]], 'entry': 1372, 'exit': 4316, 'daily_max': 2500}, 5125), ({'bands': [[0, 50], [1170, 25]], 'entry': 1899, 'exit': 2697, 'daily_max': 1500}, 1500), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 50], [330, 50]], 'entry': 901, 'exit': 3378, 'daily_max': 1500}, 4500)]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| fee oracle 0 | 1800 | 1400 | Failed |
| fee oracle 1 | 2500 | 2500 | Passed |
| fee oracle 2 | 4000 | 4000 | Passed |
| fee oracle 3 | 600 | 500 | Failed |
| fee oracle 4 | 400 | 400 | Passed |
| fee oracle 5 | 3000 | 3000 | Passed |
| fee oracle 6 | 1500 | 1500 | Passed |
| fee oracle 7 | 4600 | 4550 | Failed |
SHA-256 / 74b976d76662df7562e96a87c1344db25d9bc9bc6b3d9db76c8cbac4986b659f
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
bands = x['bands']
per = {}
t = x['entry']
while t < x['exit']:
day, tod = divmod(t, 1440)
i = max(k for k in range(len(bands)) if bands[k][0] <= tod)
nxt = bands[i + 1][0] if i + 1 < len(bands) else 1440
step = min(x['exit'] - t, nxt - tod)
key = (day, bands[i][1])
per[key] = per.get(key, 0) + step
t += step
days = {}
for (day, rate), m in per.items():
days[day] = days.get(day, 0) + -(-m // 15) * rate
return sum(min(v, x['daily_max']) for v in days.values())
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'bands': [[0, 50], [630, 100], [1380, 100]], 'entry': 1427, 'exit': 1817, 'daily_max': 1500}, 1400), ({'bands': [[0, 50], [330, 100]], 'entry': 656, 'exit': 1034, 'daily_max': 2500}, 2500), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 25], [300, 50]], 'entry': 1203, 'exit': 1320, 'daily_max': 4000}, 400), ({'bands': [[0, 50], [600, 50], [900, 25]], 'entry': 1880, 'exit': 4249, 'daily_max': 1500}, 3000), ({'bands': [[0, 50], [1320, 100]], 'entry': 1793, 'exit': 2565, 'daily_max': 1500}, 1500), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550)], [({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 25], [270, 50]], 'entry': 1500, 'exit': 2160, 'daily_max': 1500}, 1500), ({'bands': [[0, 50], [390, 100]], 'entry': 1045, 'exit': 1320, 'daily_max': 4000}, 1900), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 25], [1290, 25]], 'entry': 715, 'exit': 1515, 'daily_max': 2500}, 1350), ({'bands': [[0, 25], [960, 100], [1200, 100]], 'entry': 748, 'exit': 2538, 'daily_max': 2500}, 5000), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 25], [1170, 100]], 'entry': 408, 'exit': 1419, 'daily_max': 2500}, 2500)], [({'bands': [[0, 25], [300, 100], [780, 50], [1380, 25]], 'entry': 410, 'exit': 992, 'daily_max': 2500}, 2500), ({'bands': [[0, 25], [90, 100]], 'entry': 15, 'exit': 2161, 'daily_max': 2500}, 5000), ({'bands': [[0, 50], [990, 50]], 'entry': 1775, 'exit': 1955, 'daily_max': 1500}, 600), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 50], [480, 50], [1080, 50]], 'entry': 361, 'exit': 2927, 'daily_max': 1500}, 3200), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 50], [780, 50], [870, 25], [900, 100]], 'entry': 540, 'exit': 797, 'daily_max': 1500}, 900)], [({'bands': [[0, 50], [150, 50], [570, 50], [1320, 50]], 'entry': 615, 'exit': 735, 'daily_max': 2500}, 400), ({'bands': [[0, 25], [420, 100]], 'entry': 364, 'exit': 610, 'daily_max': 2500}, 1400), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 25], [480, 100], [750, 50]], 'entry': 1968, 'exit': 2797, 'daily_max': 1500}, 1500), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [210, 25]], 'entry': 1069, 'exit': 3448, 'daily_max': 1500}, 3425), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 50], [360, 100], [570, 100]], 'entry': 629, 'exit': 2400, 'daily_max': 2500}, 5000)], [({'bands': [[0, 50], [480, 25], [1200, 50], [1230, 50]], 'entry': 1584, 'exit': 3685, 'daily_max': 1500}, 3000), ({'bands': [[0, 25], [420, 100], [1080, 25]], 'entry': 350, 'exit': 1100, 'daily_max': 9000}, 4550), ({'bands': [[0, 25], [420, 100], [1080, 50]], 'entry': 400, 'exit': 1100, 'daily_max': 4000}, 4000), ({'bands': [[0, 50], [900, 50]], 'entry': 347, 'exit': 388, 'daily_max': 4000}, 150), ({'bands': [[0, 50], [60, 100], [450, 100], [1260, 25]], 'entry': 1372, 'exit': 4316, 'daily_max': 2500}, 5125), ({'bands': [[0, 50], [1170, 25]], 'entry': 1899, 'exit': 2697, 'daily_max': 1500}, 1500), ({'bands': [[0, 50], [720, 100]], 'entry': 1400, 'exit': 1500, 'daily_max': 1500}, 500), ({'bands': [[0, 50], [330, 50]], 'entry': 901, 'exit': 3378, 'daily_max': 1500}, 4500)]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| fee oracle 0 | 1400 | 1400 | Passed |
| fee oracle 1 | 2500 | 2500 | Passed |
| fee oracle 2 | 4000 | 4000 | Passed |
| fee oracle 3 | 500 | 500 | Passed |
| fee oracle 4 | 400 | 400 | Passed |
| fee oracle 5 | 3000 | 3000 | Passed |
| fee oracle 6 | 1500 | 1500 | Passed |
| fee oracle 7 | 4550 | 4550 | Passed |
SHA-256 / a9fafe8adf5464d7bdb09d1be5b160cd31841550d3dba1aed682b5d192f41814
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.177188+00:00.
Case digest / 94eff589b7ac3cf534d8f0c6b186ea2652a24fd7f83d7e35cc32d361e03a7e01