FA-93046 / EV charging session scheduling / Open access
Ready-by departure latest start: buffer direction · case 01
Charging is planned to finish after the driver leaves.
ROOT CAUSE
The pre-departure buffer is added to the departure time instead of subtracted.
VERIFIED REPAIR
Subtract the buffer from the departure time.
Unsuccessful approach: Dropping the buffer finishes exactly at departure with no margin.
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 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: buffer direction', [153, 434, 2000, 11000, 85, 30], [390, 403, 'ok']],
['regression: buffer direction (partial repair)', [387, 1026, 30000, 11000, 95, 30],
[810, 983, 'ok']],
['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: buffer direction', [351, 1109, 18000, 3700, 95, 15], [780, 1088, 'ok']],
['regression: buffer direction (partial repair)', [20, 911, 30000, 7400, 100, 15],
[645, 889, 'ok']],
['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: buffer direction', [159, 777, 11000, 3700, 100, 30], [555, 734, 'ok']],
['regression: buffer direction (partial repair)', [12, 862, 30000, 11000, 95, 15],
[660, 833, 'ok']],
['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: buffer direction', [496, 868, 11000, 3700, 95, 30], [645, 833, 'ok']],
['regression: buffer direction (partial repair)', [577, 1400, 11000, 7400, 85, 15],
[1275, 1380, 'ok']],
['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: buffer direction', [522, 1168, 30000, 3700, 85, 30], [555, 1128, 'ok']],
['regression: buffer direction (partial repair)', [546, 1243, 11000, 7400, 85, 30],
[1095, 1200, 'ok']],
['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: buffer direction | [450, 463, 'ok'] | [390, 403, 'ok'] | Failed |
| regression: buffer direction (partial repair) | [870, 1043, 'ok'] | [810, 983, 'ok'] | Failed |
| control 1 | [570, 634, 'ok'] | [555, 619, 'ok'] | Failed |
| control 2 | [None, 448, 'done'] | [None, 448, 'done'] | Passed |
SHA-256 / d0c189d21951202be1a79c6a1c5b8beac7aba1f5073e58a7b49ca050079c0bd1
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 - 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: buffer direction', [153, 434, 2000, 11000, 85, 30], [390, 403, 'ok']],
['regression: buffer direction (partial repair)', [387, 1026, 30000, 11000, 95, 30],
[810, 983, 'ok']],
['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: buffer direction', [351, 1109, 18000, 3700, 95, 15], [780, 1088, 'ok']],
['regression: buffer direction (partial repair)', [20, 911, 30000, 7400, 100, 15],
[645, 889, 'ok']],
['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: buffer direction', [159, 777, 11000, 3700, 100, 30], [555, 734, 'ok']],
['regression: buffer direction (partial repair)', [12, 862, 30000, 11000, 95, 15],
[660, 833, 'ok']],
['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: buffer direction', [496, 868, 11000, 3700, 95, 30], [645, 833, 'ok']],
['regression: buffer direction (partial repair)', [577, 1400, 11000, 7400, 85, 15],
[1275, 1380, 'ok']],
['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: buffer direction', [522, 1168, 30000, 3700, 85, 30], [555, 1128, 'ok']],
['regression: buffer direction (partial repair)', [546, 1243, 11000, 7400, 85, 30],
[1095, 1200, 'ok']],
['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: buffer direction | [420, 433, 'ok'] | [390, 403, 'ok'] | Failed |
| regression: buffer direction (partial repair) | [840, 1013, 'ok'] | [810, 983, 'ok'] | Failed |
| control 1 | [570, 634, 'ok'] | [555, 619, 'ok'] | Failed |
| control 2 | [None, 448, 'done'] | [None, 448, 'done'] | Passed |
SHA-256 / 9a6e7fe6b3e2f03fb0826b33525d7703f5c9e3c70c92bdac493f80220388d649
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: buffer direction', [153, 434, 2000, 11000, 85, 30], [390, 403, 'ok']],
['regression: buffer direction (partial repair)', [387, 1026, 30000, 11000, 95, 30],
[810, 983, 'ok']],
['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: buffer direction', [351, 1109, 18000, 3700, 95, 15], [780, 1088, 'ok']],
['regression: buffer direction (partial repair)', [20, 911, 30000, 7400, 100, 15],
[645, 889, 'ok']],
['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: buffer direction', [159, 777, 11000, 3700, 100, 30], [555, 734, 'ok']],
['regression: buffer direction (partial repair)', [12, 862, 30000, 11000, 95, 15],
[660, 833, 'ok']],
['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: buffer direction', [496, 868, 11000, 3700, 95, 30], [645, 833, 'ok']],
['regression: buffer direction (partial repair)', [577, 1400, 11000, 7400, 85, 15],
[1275, 1380, 'ok']],
['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: buffer direction', [522, 1168, 30000, 3700, 85, 30], [555, 1128, 'ok']],
['regression: buffer direction (partial repair)', [546, 1243, 11000, 7400, 85, 30],
[1095, 1200, 'ok']],
['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: buffer direction | [390, 403, 'ok'] | [390, 403, 'ok'] | Passed |
| regression: buffer direction (partial repair) | [810, 983, 'ok'] | [810, 983, 'ok'] | Passed |
| control 1 | [555, 619, 'ok'] | [555, 619, 'ok'] | Passed |
| control 2 | [None, 448, 'done'] | [None, 448, 'done'] | Passed |
SHA-256 / 6f802d5b0650a7ae40045848e9e4b2b67c62a279ef0fbd26469352de0108c9a5
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.664953+00:00.
Case digest / ceff30c581aa487824c0d2d457201dcc44347cb7a0267c0fb33703e540dd9663