FA-68371 / Toll and parking fee computation / Open access
Tiered parking rate table: tiers are matched at hour granularity · case 01
Tiered parking rate table returns a wrong result when tiers are matched at hour granularity.
ROOT CAUSE
A stay of exactly the tier limit is billed in the next tier.
VERIFIED REPAIR
Restore the tier boundary rule so that the step reads `if m <= upto:`.
Unsuccessful approach: Comparing whole hours puts a 61 minute stay into a 60 minute tier.
Case contract
Input {minutes, tiers: [[upto minutes, cumulative price]] in any order, extra_per: [block minutes, cents], max}. A stay of 0 minutes is free. Tiers are applied in ascending upto order and a stay of exactly upto minutes belongs to that tier (price capped at max). Beyond the last tier each started block after its upto adds the block price; the whole fee is capped at 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):
m = x['minutes']
tiers = sorted(x['tiers'])
if m <= 0:
return 0
for upto, price in tiers:
if m < upto:
return min(price, x['max'])
last_upto, last_price = tiers[-1]
blk, add = x['extra_per']
extra = -(-(m - last_upto) // blk)
return min(last_price + extra * add, x['max'])
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'minutes': 90, 'tiers': [[90, 1200], [210, 2000]], 'extra_per': [30, 50], 'max': 4000}, 1200), ({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000), ({'minutes': 150, 'tiers': [[270, 1800], [240, 1050], [210, 450], [150, 200]], 'extra_per': [60, 100], 'max': 1500}, 200), ({'minutes': 30, 'tiers': [[30, 400], [270, 1550]], 'extra_per': [15, 150], 'max': 4000}, 400), ({'minutes': 1, 'tiers': [[30, 150], [60, 300]], 'extra_per': [30, 100], 'max': 1500}, 150), ({'minutes': 301, 'tiers': [[300, 100], [330, 1100]], 'extra_per': [30, 100], 'max': 4000}, 1100), ({'minutes': 267, 'tiers': [[180, 250]], 'extra_per': [30, 150], 'max': 1500}, 700)], [({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 240, 'tiers': [[150, 950], [270, 1850], [240, 1500], [210, 1050]], 'extra_per': [60, 100], 'max': 4000}, 1500), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000), ({'minutes': 361, 'tiers': [[360, 1850]], 'extra_per': [15, 50], 'max': 1500}, 1500), ({'minutes': 90, 'tiers': [[90, 400], [240, 500], [360, 1700]], 'extra_per': [60, 150], 'max': 2500}, 400), ({'minutes': 0, 'tiers': [[60, 300]], 'extra_per': [30, 100], 'max': 1500}, 0), ({'minutes': 91, 'tiers': [[60, 100], [90, 1000], [120, 1300], [300, 1500]], 'extra_per': [30, 50], 'max': 4000}, 1300), ({'minutes': 210, 'tiers': [[30, 500], [210, 800], [150, 750], [270, 1800]], 'extra_per': [15, 150], 'max': 1500}, 800)], [({'minutes': 741, 'tiers': [[60, 450], [150, 750], [270, 1450]], 'extra_per': [30, 100], 'max': 2500}, 2500), ({'minutes': 301, 'tiers': [[120, 300], [300, 850], [330, 950]], 'extra_per': [60, 150], 'max': 2500}, 950), ({'minutes': 16, 'tiers': [[90, 1900]], 'extra_per': [30, 150], 'max': 4000}, 1900), ({'minutes': 150, 'tiers': [[30, 200], [60, 650], [150, 1350], [270, 1500]], 'extra_per': [30, 50], 'max': 2500}, 1350), ({'minutes': 180, 'tiers': [[180, 600], [330, 1000]], 'extra_per': [20, 50], 'max': 1500}, 600), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000), ({'minutes': 0, 'tiers': [[60, 300]], 'extra_per': [30, 100], 'max': 1500}, 0), ({'minutes': 60, 'tiers': [[60, 300], [120, 500], [240, 800]], 'extra_per': [60, 200], 'max': 2000}, 300)], [({'minutes': 361, 'tiers': [[240, 600], [360, 1750]], 'extra_per': [20, 150], 'max': 1500}, 1500), ({'minutes': 150, 'tiers': [[150, 1650], [360, 1800]], 'extra_per': [15, 50], 'max': 4000}, 1650), ({'minutes': 61, 'tiers': [[30, 400], [60, 500], [300, 900]], 'extra_per': [15, 50], 'max': 2500}, 900), ({'minutes': 60, 'tiers': [[60, 300], [120, 500], [240, 800]], 'extra_per': [60, 200], 'max': 2000}, 300), ({'minutes': 180, 'tiers': [[30, 100], [330, 1850], [60, 700], [180, 1300]], 'extra_per': [60, 150], 'max': 2500}, 1300), ({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 74, 'tiers': [[330, 1100]], 'extra_per': [15, 100], 'max': 4000}, 1100), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000)], [({'minutes': 1, 'tiers': [[30, 150], [60, 300]], 'extra_per': [30, 100], 'max': 1500}, 150), ({'minutes': 300, 'tiers': [[300, 150], [330, 1500]], 'extra_per': [30, 150], 'max': 4000}, 150), ({'minutes': 120, 'tiers': [[120, 250], [180, 700], [300, 850], [360, 950]], 'extra_per': [60, 150], 'max': 4000}, 250), ({'minutes': 61, 'tiers': [[60, 1900]], 'extra_per': [60, 100], 'max': 2500}, 2000), ({'minutes': 0, 'tiers': [[60, 300]], 'extra_per': [30, 100], 'max': 1500}, 0), ({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 270, 'tiers': [[270, 250], [300, 400]], 'extra_per': [20, 50], 'max': 1500}, 250), ({'minutes': 271, 'tiers': [[60, 150], [270, 200]], 'extra_per': [30, 150], 'max': 1500}, 350)]]
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 | 2000 | 1200 | Failed |
| fee oracle 1 | 2500 | 2500 | Passed |
| fee oracle 2 | 1000 | 1000 | Passed |
| fee oracle 3 | 450 | 200 | Failed |
| fee oracle 4 | 1550 | 400 | Failed |
| fee oracle 5 | 150 | 150 | Passed |
| fee oracle 6 | 1100 | 1100 | Passed |
| fee oracle 7 | 700 | 700 | Passed |
SHA-256 / 28889f1b6c41afeebee197f9fb30ef4c1c7012e5c84b502b1a59daf785d1d5ba
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
m = x['minutes']
tiers = sorted(x['tiers'])
if m <= 0:
return 0
for upto, price in tiers:
if m // 60 <= upto // 60:
return min(price, x['max'])
last_upto, last_price = tiers[-1]
blk, add = x['extra_per']
extra = -(-(m - last_upto) // blk)
return min(last_price + extra * add, x['max'])
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'minutes': 90, 'tiers': [[90, 1200], [210, 2000]], 'extra_per': [30, 50], 'max': 4000}, 1200), ({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000), ({'minutes': 150, 'tiers': [[270, 1800], [240, 1050], [210, 450], [150, 200]], 'extra_per': [60, 100], 'max': 1500}, 200), ({'minutes': 30, 'tiers': [[30, 400], [270, 1550]], 'extra_per': [15, 150], 'max': 4000}, 400), ({'minutes': 1, 'tiers': [[30, 150], [60, 300]], 'extra_per': [30, 100], 'max': 1500}, 150), ({'minutes': 301, 'tiers': [[300, 100], [330, 1100]], 'extra_per': [30, 100], 'max': 4000}, 1100), ({'minutes': 267, 'tiers': [[180, 250]], 'extra_per': [30, 150], 'max': 1500}, 700)], [({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 240, 'tiers': [[150, 950], [270, 1850], [240, 1500], [210, 1050]], 'extra_per': [60, 100], 'max': 4000}, 1500), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000), ({'minutes': 361, 'tiers': [[360, 1850]], 'extra_per': [15, 50], 'max': 1500}, 1500), ({'minutes': 90, 'tiers': [[90, 400], [240, 500], [360, 1700]], 'extra_per': [60, 150], 'max': 2500}, 400), ({'minutes': 0, 'tiers': [[60, 300]], 'extra_per': [30, 100], 'max': 1500}, 0), ({'minutes': 91, 'tiers': [[60, 100], [90, 1000], [120, 1300], [300, 1500]], 'extra_per': [30, 50], 'max': 4000}, 1300), ({'minutes': 210, 'tiers': [[30, 500], [210, 800], [150, 750], [270, 1800]], 'extra_per': [15, 150], 'max': 1500}, 800)], [({'minutes': 741, 'tiers': [[60, 450], [150, 750], [270, 1450]], 'extra_per': [30, 100], 'max': 2500}, 2500), ({'minutes': 301, 'tiers': [[120, 300], [300, 850], [330, 950]], 'extra_per': [60, 150], 'max': 2500}, 950), ({'minutes': 16, 'tiers': [[90, 1900]], 'extra_per': [30, 150], 'max': 4000}, 1900), ({'minutes': 150, 'tiers': [[30, 200], [60, 650], [150, 1350], [270, 1500]], 'extra_per': [30, 50], 'max': 2500}, 1350), ({'minutes': 180, 'tiers': [[180, 600], [330, 1000]], 'extra_per': [20, 50], 'max': 1500}, 600), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000), ({'minutes': 0, 'tiers': [[60, 300]], 'extra_per': [30, 100], 'max': 1500}, 0), ({'minutes': 60, 'tiers': [[60, 300], [120, 500], [240, 800]], 'extra_per': [60, 200], 'max': 2000}, 300)], [({'minutes': 361, 'tiers': [[240, 600], [360, 1750]], 'extra_per': [20, 150], 'max': 1500}, 1500), ({'minutes': 150, 'tiers': [[150, 1650], [360, 1800]], 'extra_per': [15, 50], 'max': 4000}, 1650), ({'minutes': 61, 'tiers': [[30, 400], [60, 500], [300, 900]], 'extra_per': [15, 50], 'max': 2500}, 900), ({'minutes': 60, 'tiers': [[60, 300], [120, 500], [240, 800]], 'extra_per': [60, 200], 'max': 2000}, 300), ({'minutes': 180, 'tiers': [[30, 100], [330, 1850], [60, 700], [180, 1300]], 'extra_per': [60, 150], 'max': 2500}, 1300), ({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 74, 'tiers': [[330, 1100]], 'extra_per': [15, 100], 'max': 4000}, 1100), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000)], [({'minutes': 1, 'tiers': [[30, 150], [60, 300]], 'extra_per': [30, 100], 'max': 1500}, 150), ({'minutes': 300, 'tiers': [[300, 150], [330, 1500]], 'extra_per': [30, 150], 'max': 4000}, 150), ({'minutes': 120, 'tiers': [[120, 250], [180, 700], [300, 850], [360, 950]], 'extra_per': [60, 150], 'max': 4000}, 250), ({'minutes': 61, 'tiers': [[60, 1900]], 'extra_per': [60, 100], 'max': 2500}, 2000), ({'minutes': 0, 'tiers': [[60, 300]], 'extra_per': [30, 100], 'max': 1500}, 0), ({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 270, 'tiers': [[270, 250], [300, 400]], 'extra_per': [20, 50], 'max': 1500}, 250), ({'minutes': 271, 'tiers': [[60, 150], [270, 200]], 'extra_per': [30, 150], 'max': 1500}, 350)]]
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 | 1200 | 1200 | Passed |
| fee oracle 1 | 2500 | 2500 | Passed |
| fee oracle 2 | 800 | 1000 | Failed |
| fee oracle 3 | 200 | 200 | Passed |
| fee oracle 4 | 400 | 400 | Passed |
| fee oracle 5 | 150 | 150 | Passed |
| fee oracle 6 | 100 | 1100 | Failed |
| fee oracle 7 | 700 | 700 | Passed |
SHA-256 / 765e7b6d639902f52e3e8cdcf9abdfd83fcea899f78fe9b7c90f822e8085ac47
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
m = x['minutes']
tiers = sorted(x['tiers'])
if m <= 0:
return 0
for upto, price in tiers:
if m <= upto:
return min(price, x['max'])
last_upto, last_price = tiers[-1]
blk, add = x['extra_per']
extra = -(-(m - last_upto) // blk)
return min(last_price + extra * add, x['max'])
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[({'minutes': 90, 'tiers': [[90, 1200], [210, 2000]], 'extra_per': [30, 50], 'max': 4000}, 1200), ({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000), ({'minutes': 150, 'tiers': [[270, 1800], [240, 1050], [210, 450], [150, 200]], 'extra_per': [60, 100], 'max': 1500}, 200), ({'minutes': 30, 'tiers': [[30, 400], [270, 1550]], 'extra_per': [15, 150], 'max': 4000}, 400), ({'minutes': 1, 'tiers': [[30, 150], [60, 300]], 'extra_per': [30, 100], 'max': 1500}, 150), ({'minutes': 301, 'tiers': [[300, 100], [330, 1100]], 'extra_per': [30, 100], 'max': 4000}, 1100), ({'minutes': 267, 'tiers': [[180, 250]], 'extra_per': [30, 150], 'max': 1500}, 700)], [({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 240, 'tiers': [[150, 950], [270, 1850], [240, 1500], [210, 1050]], 'extra_per': [60, 100], 'max': 4000}, 1500), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000), ({'minutes': 361, 'tiers': [[360, 1850]], 'extra_per': [15, 50], 'max': 1500}, 1500), ({'minutes': 90, 'tiers': [[90, 400], [240, 500], [360, 1700]], 'extra_per': [60, 150], 'max': 2500}, 400), ({'minutes': 0, 'tiers': [[60, 300]], 'extra_per': [30, 100], 'max': 1500}, 0), ({'minutes': 91, 'tiers': [[60, 100], [90, 1000], [120, 1300], [300, 1500]], 'extra_per': [30, 50], 'max': 4000}, 1300), ({'minutes': 210, 'tiers': [[30, 500], [210, 800], [150, 750], [270, 1800]], 'extra_per': [15, 150], 'max': 1500}, 800)], [({'minutes': 741, 'tiers': [[60, 450], [150, 750], [270, 1450]], 'extra_per': [30, 100], 'max': 2500}, 2500), ({'minutes': 301, 'tiers': [[120, 300], [300, 850], [330, 950]], 'extra_per': [60, 150], 'max': 2500}, 950), ({'minutes': 16, 'tiers': [[90, 1900]], 'extra_per': [30, 150], 'max': 4000}, 1900), ({'minutes': 150, 'tiers': [[30, 200], [60, 650], [150, 1350], [270, 1500]], 'extra_per': [30, 50], 'max': 2500}, 1350), ({'minutes': 180, 'tiers': [[180, 600], [330, 1000]], 'extra_per': [20, 50], 'max': 1500}, 600), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000), ({'minutes': 0, 'tiers': [[60, 300]], 'extra_per': [30, 100], 'max': 1500}, 0), ({'minutes': 60, 'tiers': [[60, 300], [120, 500], [240, 800]], 'extra_per': [60, 200], 'max': 2000}, 300)], [({'minutes': 361, 'tiers': [[240, 600], [360, 1750]], 'extra_per': [20, 150], 'max': 1500}, 1500), ({'minutes': 150, 'tiers': [[150, 1650], [360, 1800]], 'extra_per': [15, 50], 'max': 4000}, 1650), ({'minutes': 61, 'tiers': [[30, 400], [60, 500], [300, 900]], 'extra_per': [15, 50], 'max': 2500}, 900), ({'minutes': 60, 'tiers': [[60, 300], [120, 500], [240, 800]], 'extra_per': [60, 200], 'max': 2000}, 300), ({'minutes': 180, 'tiers': [[30, 100], [330, 1850], [60, 700], [180, 1300]], 'extra_per': [60, 150], 'max': 2500}, 1300), ({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 74, 'tiers': [[330, 1100]], 'extra_per': [15, 100], 'max': 4000}, 1100), ({'minutes': 250, 'tiers': [[240, 800], [60, 300], [120, 500]], 'extra_per': [60, 200], 'max': 2000}, 1000)], [({'minutes': 1, 'tiers': [[30, 150], [60, 300]], 'extra_per': [30, 100], 'max': 1500}, 150), ({'minutes': 300, 'tiers': [[300, 150], [330, 1500]], 'extra_per': [30, 150], 'max': 4000}, 150), ({'minutes': 120, 'tiers': [[120, 250], [180, 700], [300, 850], [360, 950]], 'extra_per': [60, 150], 'max': 4000}, 250), ({'minutes': 61, 'tiers': [[60, 1900]], 'extra_per': [60, 100], 'max': 2500}, 2000), ({'minutes': 0, 'tiers': [[60, 300]], 'extra_per': [30, 100], 'max': 1500}, 0), ({'minutes': 1000, 'tiers': [[90, 400], [180, 700]], 'extra_per': [45, 150], 'max': 2500}, 2500), ({'minutes': 270, 'tiers': [[270, 250], [300, 400]], 'extra_per': [20, 50], 'max': 1500}, 250), ({'minutes': 271, 'tiers': [[60, 150], [270, 200]], 'extra_per': [30, 150], 'max': 1500}, 350)]]
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 | 1200 | 1200 | Passed |
| fee oracle 1 | 2500 | 2500 | Passed |
| fee oracle 2 | 1000 | 1000 | Passed |
| fee oracle 3 | 200 | 200 | Passed |
| fee oracle 4 | 400 | 400 | Passed |
| fee oracle 5 | 150 | 150 | Passed |
| fee oracle 6 | 1100 | 1100 | Passed |
| fee oracle 7 | 700 | 700 | Passed |
SHA-256 / 8ac9bfc84c69fa1cae0d1faca219f105fc040653d38fec889eca6b9c1f94e410
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.568238+00:00.
Case digest / e885f6e098e0adb3df21daa715335795ed7e4654e446682f90cbd18bfab6e74e