FAILURE MAP
← Case archive

FA-93036 / EV charging session scheduling / Open access

Ready-by departure latest start: efficiency direction · case 01

Cars finish short of their target because charging starts too late.

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

ROOT CAUSE

Charger efficiency multiplies the energy instead of dividing it, underestimating duration.

VERIFIED REPAIR

Divide by efficiency: duration = ceil(need*6000/(rate*eff)).

Unsuccessful approach: Ignoring efficiency altogether still underestimates duration below 100 percent.

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 * 60 * eff_pct // (rate_w * 100))
    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: efficiency direction', [153, 434, 2000, 11000, 85, 30], [390, 403, 'ok']],
  ['regression: efficiency 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: efficiency direction', [351, 1109, 18000, 3700, 95, 15], [780, 1088, 'ok']],
  ['regression: efficiency direction (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: efficiency direction', [84, 334, 30000, 7400, 95, 30], [84, 341, 'late']],
  ['regression: efficiency 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: efficiency direction', [496, 868, 11000, 3700, 95, 30], [645, 833, 'ok']],
  ['regression: efficiency direction (partial repair)', [342, 397, 18000, 11000, 95, 10],
   [342, 446, '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: efficiency direction', [522, 1168, 30000, 3700, 85, 30], [555, 1128, 'ok']],
  ['regression: efficiency 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 fixtureActualExpectedOutcome
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: efficiency direction[390, 400, 'ok'][390, 403, 'ok']Failed
regression: efficiency direction (partial repair)[840, 996, 'ok'][810, 983, 'ok']Failed
control 1[570, 627, 'ok'][555, 619, 'ok']Failed
control 2[None, 448, 'done'][None, 448, 'done']Passed

SHA-256 / f97b8041385c614042761c0af114a9b1a0dd090f6fbe40fd21c36d856547f9d2

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 * 60 // rate_w)
    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: efficiency direction', [153, 434, 2000, 11000, 85, 30], [390, 403, 'ok']],
  ['regression: efficiency 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: efficiency direction', [351, 1109, 18000, 3700, 95, 15], [780, 1088, 'ok']],
  ['regression: efficiency direction (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: efficiency direction', [84, 334, 30000, 7400, 95, 30], [84, 341, 'late']],
  ['regression: efficiency 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: efficiency direction', [496, 868, 11000, 3700, 95, 30], [645, 833, 'ok']],
  ['regression: efficiency direction (partial repair)', [342, 397, 18000, 11000, 95, 10],
   [342, 446, '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: efficiency direction', [522, 1168, 30000, 3700, 85, 30], [555, 1128, 'ok']],
  ['regression: efficiency 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 fixtureActualExpectedOutcome
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: efficiency direction[390, 401, 'ok'][390, 403, 'ok']Failed
regression: efficiency direction (partial repair)[825, 989, 'ok'][810, 983, 'ok']Failed
control 1[555, 615, 'ok'][555, 619, 'ok']Failed
control 2[None, 448, 'done'][None, 448, 'done']Passed

SHA-256 / bc72f055c5bb2166d07c0fce586afee1c4c75dd93105db82bf630111ef671495

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: efficiency direction', [153, 434, 2000, 11000, 85, 30], [390, 403, 'ok']],
  ['regression: efficiency 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: efficiency direction', [351, 1109, 18000, 3700, 95, 15], [780, 1088, 'ok']],
  ['regression: efficiency direction (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: efficiency direction', [84, 334, 30000, 7400, 95, 30], [84, 341, 'late']],
  ['regression: efficiency 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: efficiency direction', [496, 868, 11000, 3700, 95, 30], [645, 833, 'ok']],
  ['regression: efficiency direction (partial repair)', [342, 397, 18000, 11000, 95, 10],
   [342, 446, '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: efficiency direction', [522, 1168, 30000, 3700, 85, 30], [555, 1128, 'ok']],
  ['regression: efficiency 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 fixtureActualExpectedOutcome
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: efficiency direction[390, 403, 'ok'][390, 403, 'ok']Passed
regression: efficiency 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 / 7355d8749cbb056c92fd491eccfe20c2d4f4c79f14c8cf04159651dbe4b70243

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

Case digest / ccdd0c4c608086f7013437c06c6871fc2a93eaec52a7052e14e08f7725f90e71