FA-93301 / EV charging session scheduling / Open access
Staggered start after tariff switch: deadline pull-back · case 01
A staggered start pushes a short-dwell session past its departure.
ROOT CAUSE
The start is never pulled back toward the departure deadline.
VERIFIED REPAIR
Pull the start back to max(depart - dur, now) when needed.
Unsuccessful approach: Pulling back to depart - dur without the now bound schedules starts in the past.
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 = max(tariff_start, now) + delay
latest = depart - dur
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: deadline pull-back', ['CP495', 117, 47, 359, 266, 0], [93, 0, 'ok']],
['regression: deadline pull-back (partial repair)', ['CP294', 180, 7, 58, 277, 5],
[7, 1, 'late']],
['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: deadline pull-back', ['CP272', 217, 184, 482, 298, 15], [184, 15, 'ok']],
['regression: deadline pull-back (partial repair)', ['CP811', 93, 183, 218, 113, 30],
[183, 30, 'late']],
['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: deadline pull-back', ['CP803', 164, 112, 215, 145, 5], [112, 2, 'late']],
['regression: deadline pull-back (partial repair)', ['CP542', 144, 130, 131, 254, 30],
[130, 7, 'late']],
['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: deadline pull-back', ['CP174', 226, 53, 139, 171, 5], [53, 2, 'late']],
['regression: deadline pull-back (partial repair)', ['CP172', 184, 1, 37, 143, 30],
[1, 7, 'late']],
['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: deadline pull-back', ['CP353', 241, 118, 320, 150, 5], [170, 1, 'ok']],
['regression: deadline pull-back (partial repair)', ['CP776', 104, 179, 210, 259, 30],
[179, 14, 'late']],
['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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| boundary: no stagger configured | [100, 0, 'ok'] | [100, 0, 'ok'] | Passed |
| boundary: deadline forces immediate start | [116, 16, 'late'] | [50, 16, 'ok'] | Failed |
| boundary: tariff already active | [56, 6, 'ok'] | [56, 6, 'ok'] | Passed |
| regression: deadline pull-back | [117, 0, 'late'] | [93, 0, 'ok'] | Failed |
| regression: deadline pull-back (partial repair) | [181, 1, 'late'] | [7, 1, 'late'] | Failed |
| control 1 | [208, 0, 'late'] | [40, 0, 'ok'] | Failed |
| control 2 | [304, 10, 'late'] | [150, 10, 'late'] | Failed |
SHA-256 / bfc1200081d5ec664e98bc4b44761a35f9ccaf7a9e2b359bcd11c331b01010ad
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 = max(tariff_start, now) + delay
latest = depart - dur
if start > latest:
start = latest
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: deadline pull-back', ['CP495', 117, 47, 359, 266, 0], [93, 0, 'ok']],
['regression: deadline pull-back (partial repair)', ['CP294', 180, 7, 58, 277, 5],
[7, 1, 'late']],
['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: deadline pull-back', ['CP272', 217, 184, 482, 298, 15], [184, 15, 'ok']],
['regression: deadline pull-back (partial repair)', ['CP811', 93, 183, 218, 113, 30],
[183, 30, 'late']],
['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: deadline pull-back', ['CP803', 164, 112, 215, 145, 5], [112, 2, 'late']],
['regression: deadline pull-back (partial repair)', ['CP542', 144, 130, 131, 254, 30],
[130, 7, 'late']],
['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: deadline pull-back', ['CP174', 226, 53, 139, 171, 5], [53, 2, 'late']],
['regression: deadline pull-back (partial repair)', ['CP172', 184, 1, 37, 143, 30],
[1, 7, 'late']],
['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: deadline pull-back', ['CP353', 241, 118, 320, 150, 5], [170, 1, 'ok']],
['regression: deadline pull-back (partial repair)', ['CP776', 104, 179, 210, 259, 30],
[179, 14, 'late']],
['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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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: deadline pull-back | [93, 0, 'ok'] | [93, 0, 'ok'] | Passed |
| regression: deadline pull-back (partial repair) | [-219, 1, 'ok'] | [7, 1, 'late'] | Failed |
| control 1 | [40, 0, 'ok'] | [40, 0, 'ok'] | Passed |
| control 2 | [7, 10, 'ok'] | [150, 10, 'late'] | Failed |
SHA-256 / f4a048b5cd5c574b5b09e53d353e33e670278341c3d58d10575eb1d54de2450b
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: deadline pull-back', ['CP495', 117, 47, 359, 266, 0], [93, 0, 'ok']],
['regression: deadline pull-back (partial repair)', ['CP294', 180, 7, 58, 277, 5],
[7, 1, 'late']],
['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: deadline pull-back', ['CP272', 217, 184, 482, 298, 15], [184, 15, 'ok']],
['regression: deadline pull-back (partial repair)', ['CP811', 93, 183, 218, 113, 30],
[183, 30, 'late']],
['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: deadline pull-back', ['CP803', 164, 112, 215, 145, 5], [112, 2, 'late']],
['regression: deadline pull-back (partial repair)', ['CP542', 144, 130, 131, 254, 30],
[130, 7, 'late']],
['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: deadline pull-back', ['CP174', 226, 53, 139, 171, 5], [53, 2, 'late']],
['regression: deadline pull-back (partial repair)', ['CP172', 184, 1, 37, 143, 30],
[1, 7, 'late']],
['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: deadline pull-back', ['CP353', 241, 118, 320, 150, 5], [170, 1, 'ok']],
['regression: deadline pull-back (partial repair)', ['CP776', 104, 179, 210, 259, 30],
[179, 14, 'late']],
['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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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: deadline pull-back | [93, 0, 'ok'] | [93, 0, 'ok'] | Passed |
| regression: deadline pull-back (partial repair) | [7, 1, 'late'] | [7, 1, 'late'] | Passed |
| control 1 | [40, 0, 'ok'] | [40, 0, 'ok'] | Passed |
| control 2 | [150, 10, 'late'] | [150, 10, 'late'] | Passed |
SHA-256 / fd120e693ed2a8ab8f0caf02a9c5ce1760e6bb1084fe2a676294e660e405d05c
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.844792+00:00.
Case digest / 9acb66bc22ecb44999bcf1db6ca6867fc2a36e76f123a700991bc9b315196a3f