FAILURE MAP
← Case archive

FA-93231 / EV charging session scheduling / Open access

Contiguous cheap charging blocks: final run flush · case 01

A cheap run that lasts until the end of the horizon is never used.

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

ROOT CAUSE

The run being collected when the loop ends is not appended.

VERIFIED REPAIR

Flush the trailing run after the loop.

Unsuccessful approach: Flushing it only when longer than min_len still drops trailing runs of exactly min_len.

Case contract

Slots with price <= threshold form runs of consecutive slots; runs shorter than min_len are ignored to avoid contactor cycling. Slots of qualifying runs are taken chronologically until need_slots are chosen. If still short, the remaining cheapest unchosen slots (ties: earlier) are added regardless of runs and fallback is True. Return [sorted slots, fallback].

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(prices, threshold, min_len, need_slots):
    runs = []
    cur = []
    for i, p in enumerate(prices):
        if p <= threshold:
            cur.append(i)
        else:
            if cur:
                runs.append(cur)
            cur = []
    chosen = []
    for run in runs:
        if len(run) >= min_len:
            for i in run:
                if len(chosen) < need_slots:
                    chosen.append(i)
    fallback = False
    if len(chosen) < need_slots:
        fallback = True
        rest = sorted((p, i) for i, p in enumerate(prices) if i not in chosen)
        for p, i in rest[:need_slots - len(chosen)]:
            chosen.append(i)
    return [sorted(chosen), fallback]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[8, 0, 12, 15, 10, 12, 12, 20, 10, 10, 8, 10, 8, 0], 10, 2, 3],
   [[0, 1, 8], False]],
  ['regression: final run flush (partial repair)', [[12, 15, 30, 5, 5, 5, 15, 10, 5, -2], 12, 3, 5],
   [[3, 4, 5, 7, 8], False]],
  ['control 1', [[5, 20, 15, 10, -2], 10, 3, 5], [[0, 1, 2, 3, 4], True]],
  ['control 2', [[30, 30, 0, -2, 20, 8, 5, 20, 0, 20], 0, 4, 1], [[3], True]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush',
   [[20, 5, 15, 12, 15, 30, 5, 20, 0, 20, 10, 0, 5, -2, 10], 12, 4, 6],
   [[8, 10, 11, 12, 13, 14], True]],
  ['regression: final run flush (partial repair)',
   [[-2, 30, 15, -2, 30, 30, 8, 5, 5, 8, 10, 0, -2], 0, 2, 2], [[11, 12], False]],
  ['control 1', [[30, -2, 5, 12, 8, 20, 20, 20], 12, 1, 7], [[1, 2, 3, 4, 5, 6, 7], True]],
  ['control 2', [[30, 8, 15, 0, 15], 8, 2, 2], [[1, 3], True]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[20, 30, 5, 5, 5], 10, 2, 3], [[2, 3, 4], False]],
  ['regression: final run flush (partial repair)',
   [[-2, 30, 15, -2, 30, 30, 8, 5, 5, 8, 10, 0, -2], 0, 2, 2], [[11, 12], False]],
  ['control 1', [[20, -2, 10, 8, 15, 10, 0, 8, 12, 5, 8, 0, 8], 0, 1, 1], [[1], False]],
  ['control 2', [[-2, 8, 8, 12, -2, -2, -2, 10, 12, 15], 0, 2, 7], [[0, 1, 2, 4, 5, 6, 7], True]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[10, 20, -2, 10, 10, 30, 12, 0], 12, 1, 5],
   [[0, 2, 3, 4, 6], False]],
  ['regression: final run flush (partial repair)',
   [[0, 15, 15, 0, 8, 30, 20, 10, 8, 5, 10, 15, 30, 20, 0, 0], 10, 2, 7],
   [[3, 4, 7, 8, 9, 10, 14], False]],
  ['control 1', [[-2, 12, 30, 10, 10, 10, 15, 0, 15, 10, -2, 20, 0], 12, 4, 5],
   [[0, 3, 7, 10, 12], True]],
  ['control 2', [[10, 30, 15, 20, -2, 20, 10, 20, -2, 8, 5, 0, 20], 8, 3, 1], [[8], False]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[8, 5, 20, 0, 0, 0, -2, -2, -2, -2, -2, 0, -2], 10, 3, 8],
   [[3, 4, 5, 6, 7, 8, 9, 10], False]],
  ['regression: final run flush (partial repair)', [[12, 15, 30, 5, 5, 5, 15, 10, 5, -2], 12, 3, 5],
   [[3, 4, 5, 7, 8], False]],
  ['control 1', [[20, 15, 8, 8, -2, 8, -2, 30, 12, 30, 10], 10, 2, 1], [[2], False]],
  ['control 2', [[5, 12, 20, 0, 15, -2], 12, 4, 6], [[0, 1, 2, 3, 4, 5], True]]]]
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: run ending at horizon[[2, 3], True][[2, 3], False]Failed
boundary: run exactly min_len[[0, 1], False][[0, 1], False]Passed
boundary: nothing needed[[], False][[], False]Passed
regression: final run flush[[0, 1, 13], True][[0, 1, 8], False]Failed
regression: final run flush (partial repair)[[3, 4, 5, 8, 9], True][[3, 4, 5, 7, 8], False]Failed
control 1[[0, 1, 2, 3, 4], True][[0, 1, 2, 3, 4], True]Passed
control 2[[3], True][[3], True]Passed

SHA-256 / a1878fa2f3ab1367f266e2efcf30554fbf85d70f6063cf244aec65ba51ee763a

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(prices, threshold, min_len, need_slots):
    runs = []
    cur = []
    for i, p in enumerate(prices):
        if p <= threshold:
            cur.append(i)
        else:
            if cur:
                runs.append(cur)
            cur = []
    if len(cur) > min_len:
        runs.append(cur)
    chosen = []
    for run in runs:
        if len(run) >= min_len:
            for i in run:
                if len(chosen) < need_slots:
                    chosen.append(i)
    fallback = False
    if len(chosen) < need_slots:
        fallback = True
        rest = sorted((p, i) for i, p in enumerate(prices) if i not in chosen)
        for p, i in rest[:need_slots - len(chosen)]:
            chosen.append(i)
    return [sorted(chosen), fallback]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[8, 0, 12, 15, 10, 12, 12, 20, 10, 10, 8, 10, 8, 0], 10, 2, 3],
   [[0, 1, 8], False]],
  ['regression: final run flush (partial repair)', [[12, 15, 30, 5, 5, 5, 15, 10, 5, -2], 12, 3, 5],
   [[3, 4, 5, 7, 8], False]],
  ['control 1', [[5, 20, 15, 10, -2], 10, 3, 5], [[0, 1, 2, 3, 4], True]],
  ['control 2', [[30, 30, 0, -2, 20, 8, 5, 20, 0, 20], 0, 4, 1], [[3], True]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush',
   [[20, 5, 15, 12, 15, 30, 5, 20, 0, 20, 10, 0, 5, -2, 10], 12, 4, 6],
   [[8, 10, 11, 12, 13, 14], True]],
  ['regression: final run flush (partial repair)',
   [[-2, 30, 15, -2, 30, 30, 8, 5, 5, 8, 10, 0, -2], 0, 2, 2], [[11, 12], False]],
  ['control 1', [[30, -2, 5, 12, 8, 20, 20, 20], 12, 1, 7], [[1, 2, 3, 4, 5, 6, 7], True]],
  ['control 2', [[30, 8, 15, 0, 15], 8, 2, 2], [[1, 3], True]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[20, 30, 5, 5, 5], 10, 2, 3], [[2, 3, 4], False]],
  ['regression: final run flush (partial repair)',
   [[-2, 30, 15, -2, 30, 30, 8, 5, 5, 8, 10, 0, -2], 0, 2, 2], [[11, 12], False]],
  ['control 1', [[20, -2, 10, 8, 15, 10, 0, 8, 12, 5, 8, 0, 8], 0, 1, 1], [[1], False]],
  ['control 2', [[-2, 8, 8, 12, -2, -2, -2, 10, 12, 15], 0, 2, 7], [[0, 1, 2, 4, 5, 6, 7], True]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[10, 20, -2, 10, 10, 30, 12, 0], 12, 1, 5],
   [[0, 2, 3, 4, 6], False]],
  ['regression: final run flush (partial repair)',
   [[0, 15, 15, 0, 8, 30, 20, 10, 8, 5, 10, 15, 30, 20, 0, 0], 10, 2, 7],
   [[3, 4, 7, 8, 9, 10, 14], False]],
  ['control 1', [[-2, 12, 30, 10, 10, 10, 15, 0, 15, 10, -2, 20, 0], 12, 4, 5],
   [[0, 3, 7, 10, 12], True]],
  ['control 2', [[10, 30, 15, 20, -2, 20, 10, 20, -2, 8, 5, 0, 20], 8, 3, 1], [[8], False]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[8, 5, 20, 0, 0, 0, -2, -2, -2, -2, -2, 0, -2], 10, 3, 8],
   [[3, 4, 5, 6, 7, 8, 9, 10], False]],
  ['regression: final run flush (partial repair)', [[12, 15, 30, 5, 5, 5, 15, 10, 5, -2], 12, 3, 5],
   [[3, 4, 5, 7, 8], False]],
  ['control 1', [[20, 15, 8, 8, -2, 8, -2, 30, 12, 30, 10], 10, 2, 1], [[2], False]],
  ['control 2', [[5, 12, 20, 0, 15, -2], 12, 4, 6], [[0, 1, 2, 3, 4, 5], True]]]]
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: run ending at horizon[[2, 3], True][[2, 3], False]Failed
boundary: run exactly min_len[[0, 1], False][[0, 1], False]Passed
boundary: nothing needed[[], False][[], False]Passed
regression: final run flush[[0, 1, 8], False][[0, 1, 8], False]Passed
regression: final run flush (partial repair)[[3, 4, 5, 8, 9], True][[3, 4, 5, 7, 8], False]Failed
control 1[[0, 1, 2, 3, 4], True][[0, 1, 2, 3, 4], True]Passed
control 2[[3], True][[3], True]Passed

SHA-256 / 2674372f6b574a1edbf59c0b78679a6c3a91c7c0facd9fcb7c667c46883854d2

3 / The verified repair

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

N = 1
observations = []
def solve(prices, threshold, min_len, need_slots):
    runs = []
    cur = []
    for i, p in enumerate(prices):
        if p <= threshold:
            cur.append(i)
        else:
            if cur:
                runs.append(cur)
            cur = []
    if cur:
        runs.append(cur)
    chosen = []
    for run in runs:
        if len(run) >= min_len:
            for i in run:
                if len(chosen) < need_slots:
                    chosen.append(i)
    fallback = False
    if len(chosen) < need_slots:
        fallback = True
        rest = sorted((p, i) for i, p in enumerate(prices) if i not in chosen)
        for p, i in rest[:need_slots - len(chosen)]:
            chosen.append(i)
    return [sorted(chosen), fallback]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[8, 0, 12, 15, 10, 12, 12, 20, 10, 10, 8, 10, 8, 0], 10, 2, 3],
   [[0, 1, 8], False]],
  ['regression: final run flush (partial repair)', [[12, 15, 30, 5, 5, 5, 15, 10, 5, -2], 12, 3, 5],
   [[3, 4, 5, 7, 8], False]],
  ['control 1', [[5, 20, 15, 10, -2], 10, 3, 5], [[0, 1, 2, 3, 4], True]],
  ['control 2', [[30, 30, 0, -2, 20, 8, 5, 20, 0, 20], 0, 4, 1], [[3], True]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush',
   [[20, 5, 15, 12, 15, 30, 5, 20, 0, 20, 10, 0, 5, -2, 10], 12, 4, 6],
   [[8, 10, 11, 12, 13, 14], True]],
  ['regression: final run flush (partial repair)',
   [[-2, 30, 15, -2, 30, 30, 8, 5, 5, 8, 10, 0, -2], 0, 2, 2], [[11, 12], False]],
  ['control 1', [[30, -2, 5, 12, 8, 20, 20, 20], 12, 1, 7], [[1, 2, 3, 4, 5, 6, 7], True]],
  ['control 2', [[30, 8, 15, 0, 15], 8, 2, 2], [[1, 3], True]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[20, 30, 5, 5, 5], 10, 2, 3], [[2, 3, 4], False]],
  ['regression: final run flush (partial repair)',
   [[-2, 30, 15, -2, 30, 30, 8, 5, 5, 8, 10, 0, -2], 0, 2, 2], [[11, 12], False]],
  ['control 1', [[20, -2, 10, 8, 15, 10, 0, 8, 12, 5, 8, 0, 8], 0, 1, 1], [[1], False]],
  ['control 2', [[-2, 8, 8, 12, -2, -2, -2, 10, 12, 15], 0, 2, 7], [[0, 1, 2, 4, 5, 6, 7], True]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[10, 20, -2, 10, 10, 30, 12, 0], 12, 1, 5],
   [[0, 2, 3, 4, 6], False]],
  ['regression: final run flush (partial repair)',
   [[0, 15, 15, 0, 8, 30, 20, 10, 8, 5, 10, 15, 30, 20, 0, 0], 10, 2, 7],
   [[3, 4, 7, 8, 9, 10, 14], False]],
  ['control 1', [[-2, 12, 30, 10, 10, 10, 15, 0, 15, 10, -2, 20, 0], 12, 4, 5],
   [[0, 3, 7, 10, 12], True]],
  ['control 2', [[10, 30, 15, 20, -2, 20, 10, 20, -2, 8, 5, 0, 20], 8, 3, 1], [[8], False]]],
 [['boundary: run ending at horizon', [[20, 20, 5, 5], 8, 2, 2], [[2, 3], False]],
  ['boundary: run exactly min_len', [[5, 5, 20, 5], 8, 2, 2], [[0, 1], False]],
  ['boundary: nothing needed', [[5, 5, 5], 8, 1, 0], [[], False]],
  ['regression: final run flush', [[8, 5, 20, 0, 0, 0, -2, -2, -2, -2, -2, 0, -2], 10, 3, 8],
   [[3, 4, 5, 6, 7, 8, 9, 10], False]],
  ['regression: final run flush (partial repair)', [[12, 15, 30, 5, 5, 5, 15, 10, 5, -2], 12, 3, 5],
   [[3, 4, 5, 7, 8], False]],
  ['control 1', [[20, 15, 8, 8, -2, 8, -2, 30, 12, 30, 10], 10, 2, 1], [[2], False]],
  ['control 2', [[5, 12, 20, 0, 15, -2], 12, 4, 6], [[0, 1, 2, 3, 4, 5], True]]]]
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: run ending at horizon[[2, 3], False][[2, 3], False]Passed
boundary: run exactly min_len[[0, 1], False][[0, 1], False]Passed
boundary: nothing needed[[], False][[], False]Passed
regression: final run flush[[0, 1, 8], False][[0, 1, 8], False]Passed
regression: final run flush (partial repair)[[3, 4, 5, 7, 8], False][[3, 4, 5, 7, 8], False]Passed
control 1[[0, 1, 2, 3, 4], True][[0, 1, 2, 3, 4], True]Passed
control 2[[3], True][[3], True]Passed

SHA-256 / 010ccc68e34da0e8197e8f01581cf397f6106aa8e7ceee592aaaee59db6ce86f

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

Case digest / 7418e72a229498d14c28d9409dcc777795a2641d539455cece65db978b5fbd0d