FA-93056 / EV charging session scheduling / Open access
Ready-by departure latest start: late detection · case 01
Plans report starts in the past with status ok.
ROOT CAUSE
Lateness is judged by the departure time instead of the computed latest start.
VERIFIED REPAIR
Mark the plan late when the latest start is before now.
Unsuccessful approach: Allowing a quarter hour of slack still returns past start times.
Case contract
Minutes. The battery must receive need_wh; the charger draws rate_w at eff_pct percent efficiency, so duration = ceil(need_wh*6000/(rate_w*eff_pct)) minutes. Latest start = depart - buffer_min - duration, rounded down to a 15-minute boundary. If that is before now, start now with status 'late'. need_wh <= 0 returns [None, now, 'done']; rate_w or eff_pct <= 0 returns [None, None, 'no-power']. Return [start, finish, status].
Why this case matters
Depot, workplace and public EV chargers schedule sessions against prices, circuit limits and departure deadlines; a wrong decision silently strands a driver or overloads a feeder.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(now, depart, need_wh, rate_w, eff_pct, buffer_min):
if need_wh <= 0:
return [None, now, 'done']
if rate_w <= 0 or eff_pct <= 0:
return [None, None, 'no-power']
dur = -(-need_wh * 6000 // (rate_w * eff_pct))
latest = depart - buffer_min - dur
latest -= latest % 15
if depart < now:
return [now, now + dur, 'late']
return [latest, latest + dur, 'ok']
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [452, 609, 18000, 7400, 90, 0], [452, 615, 'late']],
['regression: late detection (partial repair)', [153, 726, 30000, 3700, 85, 0],
[153, 726, 'late']],
['control 1', [117, 637, 11000, 11000, 95, 10], [555, 619, 'ok']],
['control 2', [448, 1142, 0, 3700, 85, 0], [None, 448, 'done']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [66, 284, 18000, 3700, 100, 10], [66, 358, 'late']],
['regression: late detection (partial repair)', [153, 726, 30000, 3700, 85, 0],
[153, 726, 'late']],
['control 1', [416, 1003, 5000, 0, 85, 15], [None, None, 'no-power']],
['control 2', [221, 503, 5000, 0, 85, 30], [None, None, 'no-power']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [81, 103, 5000, 3700, 100, 10], [81, 163, 'late']],
['regression: late detection (partial repair)', [343, 518, 11000, 3700, 95, 0],
[343, 531, 'late']],
['control 1', [381, 632, 18000, 3700, 95, 30], [381, 689, 'late']],
['control 2', [19, 584, 30000, 11000, 90, 15], [375, 557, 'ok']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [342, 397, 18000, 11000, 95, 10], [342, 446, 'late']],
['regression: late detection (partial repair)', [343, 518, 11000, 3700, 95, 0],
[343, 531, 'late']],
['control 1', [66, 229, 11000, 0, 90, 15], [None, None, 'no-power']],
['control 2', [44, 377, 2000, 0, 100, 0], [None, None, 'no-power']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [394, 458, 30000, 11000, 85, 30], [394, 587, 'late']],
['regression: late detection (partial repair)', [153, 726, 30000, 3700, 85, 0],
[153, 726, 'late']],
['control 1', [342, 397, 18000, 11000, 95, 10], [342, 446, 'late']],
['control 2', [577, 1400, 11000, 7400, 85, 15], [1275, 1380, 'ok']]]]
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)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| boundary: nothing to charge | [None, 100, 'done'] | [None, 100, 'done'] | Passed |
| boundary: latest start exactly now | [330, 390, 'ok'] | [330, 390, 'ok'] | Passed |
| boundary: no charger power | [None, None, 'no-power'] | [None, None, 'no-power'] | Passed |
| regression: late detection | [435, 598, 'ok'] | [452, 615, 'late'] | Failed |
| regression: late detection (partial repair) | [150, 723, 'ok'] | [153, 726, 'late'] | Failed |
| control 1 | [555, 619, 'ok'] | [555, 619, 'ok'] | Passed |
| control 2 | [None, 448, 'done'] | [None, 448, 'done'] | Passed |
SHA-256 / 95eb4d74fc5d368e8689acbac745f1e365660145c76bc8aae1172924ec2a1ef6
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(now, depart, need_wh, rate_w, eff_pct, buffer_min):
if need_wh <= 0:
return [None, now, 'done']
if rate_w <= 0 or eff_pct <= 0:
return [None, None, 'no-power']
dur = -(-need_wh * 6000 // (rate_w * eff_pct))
latest = depart - buffer_min - dur
latest -= latest % 15
if latest <= now - 15:
return [now, now + dur, 'late']
return [latest, latest + dur, 'ok']
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [452, 609, 18000, 7400, 90, 0], [452, 615, 'late']],
['regression: late detection (partial repair)', [153, 726, 30000, 3700, 85, 0],
[153, 726, 'late']],
['control 1', [117, 637, 11000, 11000, 95, 10], [555, 619, 'ok']],
['control 2', [448, 1142, 0, 3700, 85, 0], [None, 448, 'done']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [66, 284, 18000, 3700, 100, 10], [66, 358, 'late']],
['regression: late detection (partial repair)', [153, 726, 30000, 3700, 85, 0],
[153, 726, 'late']],
['control 1', [416, 1003, 5000, 0, 85, 15], [None, None, 'no-power']],
['control 2', [221, 503, 5000, 0, 85, 30], [None, None, 'no-power']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [81, 103, 5000, 3700, 100, 10], [81, 163, 'late']],
['regression: late detection (partial repair)', [343, 518, 11000, 3700, 95, 0],
[343, 531, 'late']],
['control 1', [381, 632, 18000, 3700, 95, 30], [381, 689, 'late']],
['control 2', [19, 584, 30000, 11000, 90, 15], [375, 557, 'ok']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [342, 397, 18000, 11000, 95, 10], [342, 446, 'late']],
['regression: late detection (partial repair)', [343, 518, 11000, 3700, 95, 0],
[343, 531, 'late']],
['control 1', [66, 229, 11000, 0, 90, 15], [None, None, 'no-power']],
['control 2', [44, 377, 2000, 0, 100, 0], [None, None, 'no-power']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [394, 458, 30000, 11000, 85, 30], [394, 587, 'late']],
['regression: late detection (partial repair)', [153, 726, 30000, 3700, 85, 0],
[153, 726, 'late']],
['control 1', [342, 397, 18000, 11000, 95, 10], [342, 446, 'late']],
['control 2', [577, 1400, 11000, 7400, 85, 15], [1275, 1380, 'ok']]]]
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)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| boundary: nothing to charge | [None, 100, 'done'] | [None, 100, 'done'] | Passed |
| boundary: latest start exactly now | [330, 390, 'ok'] | [330, 390, 'ok'] | Passed |
| boundary: no charger power | [None, None, 'no-power'] | [None, None, 'no-power'] | Passed |
| regression: late detection | [452, 615, 'late'] | [452, 615, 'late'] | Passed |
| regression: late detection (partial repair) | [150, 723, 'ok'] | [153, 726, 'late'] | Failed |
| control 1 | [555, 619, 'ok'] | [555, 619, 'ok'] | Passed |
| control 2 | [None, 448, 'done'] | [None, 448, 'done'] | Passed |
SHA-256 / 76f010787628440719f0b707819fcf82d258165e5d5dc8263ff27c4c58028e08
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(now, depart, need_wh, rate_w, eff_pct, buffer_min):
if need_wh <= 0:
return [None, now, 'done']
if rate_w <= 0 or eff_pct <= 0:
return [None, None, 'no-power']
dur = -(-need_wh * 6000 // (rate_w * eff_pct))
latest = depart - buffer_min - dur
latest -= latest % 15
if latest < now:
return [now, now + dur, 'late']
return [latest, latest + dur, 'ok']
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [452, 609, 18000, 7400, 90, 0], [452, 615, 'late']],
['regression: late detection (partial repair)', [153, 726, 30000, 3700, 85, 0],
[153, 726, 'late']],
['control 1', [117, 637, 11000, 11000, 95, 10], [555, 619, 'ok']],
['control 2', [448, 1142, 0, 3700, 85, 0], [None, 448, 'done']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [66, 284, 18000, 3700, 100, 10], [66, 358, 'late']],
['regression: late detection (partial repair)', [153, 726, 30000, 3700, 85, 0],
[153, 726, 'late']],
['control 1', [416, 1003, 5000, 0, 85, 15], [None, None, 'no-power']],
['control 2', [221, 503, 5000, 0, 85, 30], [None, None, 'no-power']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [81, 103, 5000, 3700, 100, 10], [81, 163, 'late']],
['regression: late detection (partial repair)', [343, 518, 11000, 3700, 95, 0],
[343, 531, 'late']],
['control 1', [381, 632, 18000, 3700, 95, 30], [381, 689, 'late']],
['control 2', [19, 584, 30000, 11000, 90, 15], [375, 557, 'ok']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [342, 397, 18000, 11000, 95, 10], [342, 446, 'late']],
['regression: late detection (partial repair)', [343, 518, 11000, 3700, 95, 0],
[343, 531, 'late']],
['control 1', [66, 229, 11000, 0, 90, 15], [None, None, 'no-power']],
['control 2', [44, 377, 2000, 0, 100, 0], [None, None, 'no-power']]],
[['boundary: nothing to charge', [100, 400, 0, 0, 90, 10], [None, 100, 'done']],
['boundary: latest start exactly now', [300, 390, 7400, 7400, 100, 0], [330, 390, 'ok']],
['boundary: no charger power', [100, 400, 5000, 0, 90, 10], [None, None, 'no-power']],
['regression: late detection', [394, 458, 30000, 11000, 85, 30], [394, 587, 'late']],
['regression: late detection (partial repair)', [153, 726, 30000, 3700, 85, 0],
[153, 726, 'late']],
['control 1', [342, 397, 18000, 11000, 95, 10], [342, 446, 'late']],
['control 2', [577, 1400, 11000, 7400, 85, 15], [1275, 1380, 'ok']]]]
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)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| boundary: nothing to charge | [None, 100, 'done'] | [None, 100, 'done'] | Passed |
| boundary: latest start exactly now | [330, 390, 'ok'] | [330, 390, 'ok'] | Passed |
| boundary: no charger power | [None, None, 'no-power'] | [None, None, 'no-power'] | Passed |
| regression: late detection | [452, 615, 'late'] | [452, 615, 'late'] | Passed |
| regression: late detection (partial repair) | [153, 726, 'late'] | [153, 726, 'late'] | Passed |
| control 1 | [555, 619, 'ok'] | [555, 619, 'ok'] | Passed |
| control 2 | [None, 448, 'done'] | [None, 448, 'done'] | Passed |
SHA-256 / a054ea890ab6da11f4c7cc338fba2643483ede08e46cd73d975baeb93811cfcd
Verification & scope
Deterministic stipulated toy contract for teaching; no claim of conformance with any standard, vendor protocol or production controller. 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:51:51.670744+00:00.
Case digest / 94da7c2b660a9d1aa7ca16a5dc42a56dadb1a4a31b400508817748d07488e904