FAILURE MAP
← Case archive

FA-93296 / EV charging session scheduling / Open access

Staggered start after tariff switch: stagger base time · case 01

Chargers plugged in after the tariff switch are scheduled to start in the past.

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

ROOT CAUSE

The stagger is added to the tariff start without considering the current time.

VERIFIED REPAIR

Stagger from max(tariff_start, now).

Unsuccessful approach: Staggering from now ignores the tariff switch and starts on the expensive tariff.

Case contract

To avoid a demand spike at a tariff switch each charger delays its start by delay = sum((i+1)*ord(c) for i, c in enumerate(cid)) % (max_delay+1) minutes after max(tariff_start, now). If that start is later than depart - dur it is pulled back to max(depart - dur, now). Status is 'ok' when start + dur <= depart else 'late'. Return [start, delay, 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(cid, tariff_start, now, depart, dur, max_delay):
    delay = sum((i + 1) * ord(c) for i, c in enumerate(cid)) % (max_delay + 1)
    start = tariff_start + delay
    latest = depart - dur
    if start > latest:
        start = max(latest, now)
    status = 'ok' if start + dur <= depart else 'late'
    return [start, delay, status]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP201', 49, 94, 552, 247, 5], [98, 4, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP778', 179, 65, 525, 61, 0],
   [179, 0, 'ok']],
  ['control 1', ['CP310', 208, 18, 199, 159, 0], [40, 0, 'ok']],
  ['control 2', ['CP766', 294, 150, 208, 201, 30], [150, 10, 'late']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP321', 7, 32, 498, 89, 15], [41, 9, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP363', 205, 166, 509, 88, 10],
   [209, 4, 'ok']],
  ['control 1', ['CP883', 235, 120, 211, 80, 5], [131, 4, 'ok']],
  ['control 2', ['CP742', 19, 177, 199, 247, 10], [177, 3, 'late']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP473', 52, 113, 448, 84, 0], [113, 0, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP201', 269, 83, 492, 35, 10],
   [269, 0, 'ok']],
  ['control 1', ['CP783', 204, 99, 341, 202, 5], [139, 1, 'ok']],
  ['control 2', ['CP490', 211, 107, 512, 167, 10], [215, 4, 'ok']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP951', 78, 134, 388, 193, 0], [134, 0, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP907', 199, 117, 410, 31, 0],
   [199, 0, 'ok']],
  ['control 1', ['CP137', 10, 19, 66, 6, 15], [24, 5, 'ok']],
  ['control 2', ['CP783', 210, 193, 350, 267, 30], [193, 3, 'late']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP544', 22, 72, 471, 102, 5], [74, 2, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP353', 241, 118, 320, 150, 5],
   [170, 1, 'ok']],
  ['control 1', ['CP873', 194, 7, 146, 117, 15], [29, 6, 'ok']],
  ['control 2', ['CP733', 56, 136, 418, 4, 0], [136, 0, '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: no stagger configured[100, 0, 'ok'][100, 0, 'ok']Passed
boundary: deadline forces immediate start[50, 16, 'ok'][50, 16, 'ok']Passed
boundary: tariff already active[16, 6, 'ok'][56, 6, 'ok']Failed
regression: stagger base time[53, 4, 'ok'][98, 4, 'ok']Failed
regression: stagger base time (partial repair)[179, 0, 'ok'][179, 0, 'ok']Passed
control 1[40, 0, 'ok'][40, 0, 'ok']Passed
control 2[150, 10, 'late'][150, 10, 'late']Passed

SHA-256 / 22ba04f6720bfb5ffa258ab6d687aa06a2c341941b98b076dbfa1fb1edadbd1f

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(cid, tariff_start, now, depart, dur, max_delay):
    delay = sum((i + 1) * ord(c) for i, c in enumerate(cid)) % (max_delay + 1)
    start = now + delay
    latest = depart - dur
    if start > latest:
        start = max(latest, now)
    status = 'ok' if start + dur <= depart else 'late'
    return [start, delay, status]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP201', 49, 94, 552, 247, 5], [98, 4, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP778', 179, 65, 525, 61, 0],
   [179, 0, 'ok']],
  ['control 1', ['CP310', 208, 18, 199, 159, 0], [40, 0, 'ok']],
  ['control 2', ['CP766', 294, 150, 208, 201, 30], [150, 10, 'late']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP321', 7, 32, 498, 89, 15], [41, 9, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP363', 205, 166, 509, 88, 10],
   [209, 4, 'ok']],
  ['control 1', ['CP883', 235, 120, 211, 80, 5], [131, 4, 'ok']],
  ['control 2', ['CP742', 19, 177, 199, 247, 10], [177, 3, 'late']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP473', 52, 113, 448, 84, 0], [113, 0, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP201', 269, 83, 492, 35, 10],
   [269, 0, 'ok']],
  ['control 1', ['CP783', 204, 99, 341, 202, 5], [139, 1, 'ok']],
  ['control 2', ['CP490', 211, 107, 512, 167, 10], [215, 4, 'ok']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP951', 78, 134, 388, 193, 0], [134, 0, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP907', 199, 117, 410, 31, 0],
   [199, 0, 'ok']],
  ['control 1', ['CP137', 10, 19, 66, 6, 15], [24, 5, 'ok']],
  ['control 2', ['CP783', 210, 193, 350, 267, 30], [193, 3, 'late']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP544', 22, 72, 471, 102, 5], [74, 2, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP353', 241, 118, 320, 150, 5],
   [170, 1, 'ok']],
  ['control 1', ['CP873', 194, 7, 146, 117, 15], [29, 6, 'ok']],
  ['control 2', ['CP733', 56, 136, 418, 4, 0], [136, 0, '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: no stagger configured[50, 0, 'ok'][100, 0, 'ok']Failed
boundary: deadline forces immediate start[50, 16, 'ok'][50, 16, 'ok']Passed
boundary: tariff already active[56, 6, 'ok'][56, 6, 'ok']Passed
regression: stagger base time[98, 4, 'ok'][98, 4, 'ok']Passed
regression: stagger base time (partial repair)[65, 0, 'ok'][179, 0, 'ok']Failed
control 1[18, 0, 'ok'][40, 0, 'ok']Failed
control 2[150, 10, 'late'][150, 10, 'late']Passed

SHA-256 / 4f82245a630d6f52f4ffd950c1afbd3cf9bf7c420bfe7fdf15e8d90cc60e565c

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(cid, tariff_start, now, depart, dur, max_delay):
    delay = sum((i + 1) * ord(c) for i, c in enumerate(cid)) % (max_delay + 1)
    start = max(tariff_start, now) + delay
    latest = depart - dur
    if start > latest:
        start = max(latest, now)
    status = 'ok' if start + dur <= depart else 'late'
    return [start, delay, status]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP201', 49, 94, 552, 247, 5], [98, 4, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP778', 179, 65, 525, 61, 0],
   [179, 0, 'ok']],
  ['control 1', ['CP310', 208, 18, 199, 159, 0], [40, 0, 'ok']],
  ['control 2', ['CP766', 294, 150, 208, 201, 30], [150, 10, 'late']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP321', 7, 32, 498, 89, 15], [41, 9, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP363', 205, 166, 509, 88, 10],
   [209, 4, 'ok']],
  ['control 1', ['CP883', 235, 120, 211, 80, 5], [131, 4, 'ok']],
  ['control 2', ['CP742', 19, 177, 199, 247, 10], [177, 3, 'late']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP473', 52, 113, 448, 84, 0], [113, 0, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP201', 269, 83, 492, 35, 10],
   [269, 0, 'ok']],
  ['control 1', ['CP783', 204, 99, 341, 202, 5], [139, 1, 'ok']],
  ['control 2', ['CP490', 211, 107, 512, 167, 10], [215, 4, 'ok']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP951', 78, 134, 388, 193, 0], [134, 0, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP907', 199, 117, 410, 31, 0],
   [199, 0, 'ok']],
  ['control 1', ['CP137', 10, 19, 66, 6, 15], [24, 5, 'ok']],
  ['control 2', ['CP783', 210, 193, 350, 267, 30], [193, 3, 'late']]],
 [['boundary: no stagger configured', ['CP7', 100, 50, 400, 60, 0], [100, 0, 'ok']],
  ['boundary: deadline forces immediate start', ['CP12', 100, 50, 120, 70, 30], [50, 16, 'ok']],
  ['boundary: tariff already active', ['CP3', 10, 50, 400, 60, 10], [56, 6, 'ok']],
  ['regression: stagger base time', ['CP544', 22, 72, 471, 102, 5], [74, 2, 'ok']],
  ['regression: stagger base time (partial repair)', ['CP353', 241, 118, 320, 150, 5],
   [170, 1, 'ok']],
  ['control 1', ['CP873', 194, 7, 146, 117, 15], [29, 6, 'ok']],
  ['control 2', ['CP733', 56, 136, 418, 4, 0], [136, 0, '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: no stagger configured[100, 0, 'ok'][100, 0, 'ok']Passed
boundary: deadline forces immediate start[50, 16, 'ok'][50, 16, 'ok']Passed
boundary: tariff already active[56, 6, 'ok'][56, 6, 'ok']Passed
regression: stagger base time[98, 4, 'ok'][98, 4, 'ok']Passed
regression: stagger base time (partial repair)[179, 0, 'ok'][179, 0, 'ok']Passed
control 1[40, 0, 'ok'][40, 0, 'ok']Passed
control 2[150, 10, 'late'][150, 10, 'late']Passed

SHA-256 / e8f8bf5a7742ac4ba8d45987de19ecd8c0d63986f78e0daf45de120fa245bf1d

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

Case digest / a68572439bfa463636c5afd82389eba0fcbeb82aa5585f0b97a0fa822b730b45