FAILURE MAP
← Case archive

FA-92486 / HVAC thermostat control / Open access

Temporary hold remembers the period of the first hold ever set · case 01

A temporary hold set in a later period expires immediately.

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

ROOT CAUSE

The hold period is stamped only once.

VERIFIED REPAIR

Stamp the current period each time a hold is set.

Unsuccessful approach: Restamping only permanent holds leaves later temporary holds with a stale period.

Case contract

Thermostat hold logic; samples are [schedule_period_id, scheduled_setpoint, action] where action is None, "resume", ["temp", sp] or ["perm", sp]. A hold setpoint is clamped to 50.0-90.0 F (500-900 tenths). A temporary hold lasts until the schedule period id changes from the one in which the hold was set; a permanent hold lasts until resume. A new hold of either kind replaces the current hold. Return the effective setpoint per sample.

Why this case matters

Residential and light-commercial thermostats make these decisions sample by sample; each defect changes equipment calls in a way that shows up as short cycling, comfort complaints or equipment stress.

1 / The failure

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

N = 1
observations = []
def solve(samples):
    hold = None
    hold_period = None
    out = []
    for period, sched, action in samples:
        if action == 'resume':
            hold = None
        elif action is not None:
            kind, sp = action
            sp = max(500, min(900, sp))
            hold = [kind, sp]
            if hold_period is None:
                hold_period = period
        if hold is not None and hold[0] == 'temp' and period != hold_period:
            hold = None
        out.append(sched if hold is None else hold[1])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 1',
   [[[0, 700, ['perm', 690]],
     [0, 700, None],
     [0, 700, 'resume'],
     [1, 700, None],
     [1, 700, None],
     [2, 600, None],
     [2, 600, None],
     [3, 680, ['perm', 856]],
     [4, 620, ['perm', 777]],
     [4, 620, None],
     [4, 620, None],
     [5, 600, ['temp', 764]],
     [5, 600, None],
     [6, 600, None],
     [7, 600, ['temp', 726]],
     [7, 600, None]]],
   [690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 764, 764, 600, 726, 726]],
  ['control: scenario 2',
   [[[1, 620, None],
     [1, 620, ['perm', 540]],
     [1, 620, None],
     [1, 620, None],
     [1, 620, ['temp', 863]],
     [2, 600, None],
     [3, 700, ['perm', 476]],
     [3, 700, None],
     [3, 700, None]]],
   [620, 540, 540, 540, 863, 600, 500, 500, 500]],
  ['regression: scenario 3',
   [[[1, 620, ['temp', 657]],
     [1, 620, ['temp', 940]],
     [1, 620, None],
     [1, 620, None],
     [1, 620, None],
     [2, 700, 'resume'],
     [2, 700, ['temp', 641]],
     [3, 620, None]]],
   [657, 900, 900, 900, 900, 700, 641, 620]],
  ['control: scenario 4',
   [[[0, 700, ['temp', 891]],
     [0, 700, None],
     [0, 700, None],
     [0, 700, ['temp', 877]],
     [0, 700, None],
     [0, 700, None],
     [1, 600, None],
     [1, 600, 'resume'],
     [2, 620, ['perm', 514]]]],
   [891, 891, 891, 877, 877, 877, 600, 600, 514]]],
 [['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 1',
   [[[1, 700, 'resume'],
     [1, 700, ['perm', 548]],
     [1, 700, None],
     [1, 700, None],
     [2, 700, None],
     [3, 620, None],
     [4, 620, None],
     [5, 680, None],
     [5, 680, ['temp', 806]],
     [5, 680, None],
     [5, 680, None],
     [6, 700, None],
     [6, 700, None],
     [6, 700, None]]],
   [700, 548, 548, 548, 548, 548, 548, 548, 806, 806, 806, 700, 700, 700]],
  ['regression: scenario 2',
   [[[0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, ['temp', 520]],
     [0, 680, None],
     [1, 680, None],
     [2, 620, None],
     [3, 680, ['perm', 745]],
     [3, 680, None],
     [3, 680, None],
     [4, 680, None],
     [5, 700, ['temp', 765]],
     [5, 700, None]]],
   [680, 680, 680, 680, 680, 680, 520, 520, 680, 620, 745, 745, 745, 745, 765, 765]],
  ['control: scenario 3',
   [[[0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, ['perm', 700]],
     [0, 620, ['temp', 750]],
     [1, 620, None],
     [2, 620, None],
     [2, 620, None]]],
   [620, 620, 620, 620, 620, 700, 750, 620, 620, 620]],
  ['regression: scenario 4',
   [[[0, 680, None],
     [1, 600, ['temp', 779]],
     [1, 600, ['temp', 782]],
     [2, 680, None],
     [3, 700, ['perm', 454]],
     [3, 700, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, ['temp', 831]],
     [3, 700, None],
     [3, 700, None]]],
   [680, 779, 782, 680, 500, 500, 500, 500, 500, 831, 831, 831]]],
 [['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['regression: scenario 6',
   [[[1, 600, ['temp', 644]],
     [1, 600, None],
     [2, 700, 'resume'],
     [3, 600, None],
     [3, 600, None],
     [4, 620, ['temp', 652]],
     [5, 620, None],
     [5, 620, 'resume'],
     [5, 620, None],
     [5, 620, None],
     [6, 700, ['perm', 898]],
     [6, 700, None],
     [7, 680, None],
     [7, 680, ['temp', 919]]]],
   [644, 644, 700, 600, 600, 652, 620, 620, 620, 620, 898, 898, 898, 900]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['control: scenario 1',
   [[[0, 620, None],
     [1, 680, None],
     [1, 680, None],
     [1, 680, None],
     [2, 680, ['temp', 617]],
     [2, 680, None],
     [2, 680, ['perm', 816]],
     [3, 620, ['perm', 930]],
     [3, 620, None]]],
   [620, 680, 680, 680, 617, 617, 816, 900, 900]],
  ['control: scenario 2',
   [[[0, 620, 'resume'],
     [0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, 'resume'],
     [1, 620, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, None],
     [3, 620, None],
     [3, 620, ['perm', 870]],
     [3, 620, None]]],
   [620, 620, 620, 620, 620, 620, 700, 700, 700, 700, 620, 870, 870]],
  ['control: scenario 3',
   [[[0, 680, 'resume'],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [2, 620, None],
     [2, 620, 'resume'],
     [2, 620, None],
     [2, 620, ['temp', 685]],
     [2, 620, ['perm', 890]],
     [2, 620, ['perm', 678]]]],
   [680, 700, 700, 700, 620, 620, 620, 685, 890, 678]]],
 [['regression: scenario 1',
   [[[1, 700, ['temp', 847]],
     [1, 700, None],
     [1, 700, None],
     [1, 700, 'resume'],
     [1, 700, None],
     [1, 700, None],
     [1, 700, ['perm', 465]],
     [1, 700, None],
     [2, 600, ['temp', 552]],
     [2, 600, None],
     [2, 600, ['temp', 898]]]],
   [847, 847, 847, 700, 700, 700, 500, 500, 552, 552, 898]],
  ['regression: scenario 5',
   [[[1, 600, ['perm', 826]],
     [2, 700, None],
     [2, 700, None],
     [3, 620, ['temp', 796]],
     [3, 620, None],
     [3, 620, ['perm', 703]],
     [3, 620, None],
     [3, 620, 'resume'],
     [3, 620, None],
     [4, 680, ['perm', 694]]]],
   [826, 826, 826, 796, 796, 703, 703, 620, 620, 694]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 2',
   [[[0, 680, 'resume'],
     [0, 680, None],
     [0, 680, ['perm', 681]],
     [0, 680, None],
     [1, 680, None],
     [1, 680, None],
     [1, 680, None],
     [1, 680, ['temp', 832]],
     [1, 680, ['temp', 511]],
     [1, 680, ['perm', 779]],
     [1, 680, ['temp', 724]],
     [2, 700, 'resume'],
     [3, 680, None],
     [3, 680, 'resume']]],
   [680, 680, 681, 681, 681, 681, 681, 832, 511, 779, 724, 700, 680, 680]],
  ['control: scenario 3',
   [[[0, 680, None],
     [1, 680, None],
     [2, 600, 'resume'],
     [2, 600, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, 'resume'],
     [4, 600, None],
     [4, 600, None],
     [4, 600, None],
     [5, 620, ['temp', 645]],
     [6, 700, None]]],
   [680, 680, 600, 600, 700, 700, 700, 700, 600, 600, 600, 645, 700]]],
 [['regression: scenario 3',
   [[[0, 680, None],
     [0, 680, None],
     [1, 700, ['perm', 502]],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [1, 700, ['perm', 754]],
     [1, 700, None],
     [2, 700, ['temp', 591]],
     [2, 700, None],
     [2, 700, None],
     [2, 700, ['temp', 502]],
     [3, 680, 'resume'],
     [3, 680, None]]],
   [680, 680, 502, 502, 502, 502, 502, 502, 754, 754, 591, 591, 591, 502, 680, 680]],
  ['regression: scenario 11',
   [[[0, 620, 'resume'],
     [0, 620, 'resume'],
     [0, 620, None],
     [0, 620, 'resume'],
     [1, 680, None],
     [1, 680, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, ['perm', 673]],
     [2, 700, None],
     [3, 620, None],
     [3, 620, None],
     [3, 620, None],
     [3, 620, ['temp', 583]]]],
   [620, 620, 620, 620, 680, 680, 700, 700, 700, 673, 673, 673, 673, 673, 583]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 1',
   [[[0, 700, None],
     [0, 700, None],
     [0, 700, None],
     [0, 700, ['perm', 578]],
     [1, 600, 'resume'],
     [2, 600, ['temp', 629]],
     [3, 700, None],
     [3, 700, ['perm', 796]],
     [3, 700, ['temp', 558]],
     [3, 700, None],
     [3, 700, None],
     [3, 700, ['temp', 796]],
     [4, 700, None],
     [4, 700, None],
     [4, 700, None],
     [4, 700, None]]],
   [700, 700, 700, 578, 600, 629, 700, 796, 558, 558, 558, 796, 700, 700, 700, 700]],
  ['control: scenario 2',
   [[[0, 600, None],
     [0, 600, None],
     [0, 600, None],
     [0, 600, None],
     [0, 600, None],
     [1, 680, ['temp', 866]],
     [1, 680, None],
     [1, 680, ['temp', 591]],
     [1, 680, ['temp', 671]],
     [2, 700, None],
     [2, 700, None]]],
   [600, 600, 600, 600, 600, 866, 866, 591, 671, 700, 700]]]]
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
regression: hold lifecycle[680, 720, 720, 620, 900, 900, 700, 650, 650][680, 720, 720, 620, 900, 900, 500, 650, 650]Failed
regression: second temporary hold[700, 620, 650, 650, 600][700, 620, 710, 710, 600]Failed
regression: temporary over permanent[720, 620, 620, 640][720, 690, 690, 640]Failed
control: clamp low[500, 500][500, 500]Passed
regression: scenario 1[690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 600, 600, 600, 600, 600][690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 764, 764, 600, 726, 726]Failed
control: scenario 2[620, 540, 540, 540, 863, 600, 500, 500, 500][620, 540, 540, 540, 863, 600, 500, 500, 500]Passed
regression: scenario 3[657, 900, 900, 900, 900, 700, 700, 620][657, 900, 900, 900, 900, 700, 641, 620]Failed
control: scenario 4[891, 891, 891, 877, 877, 877, 600, 600, 514][891, 891, 891, 877, 877, 877, 600, 600, 514]Passed

SHA-256 / 6a8db209cc9105245c0311520255668933d170f4d48653de357681a94d21fea9

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(samples):
    hold = None
    hold_period = None
    out = []
    for period, sched, action in samples:
        if action == 'resume':
            hold = None
        elif action is not None:
            kind, sp = action
            sp = max(500, min(900, sp))
            hold = [kind, sp]
            hold_period = period if kind == 'perm' else (hold_period or period)
        if hold is not None and hold[0] == 'temp' and period != hold_period:
            hold = None
        out.append(sched if hold is None else hold[1])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 1',
   [[[0, 700, ['perm', 690]],
     [0, 700, None],
     [0, 700, 'resume'],
     [1, 700, None],
     [1, 700, None],
     [2, 600, None],
     [2, 600, None],
     [3, 680, ['perm', 856]],
     [4, 620, ['perm', 777]],
     [4, 620, None],
     [4, 620, None],
     [5, 600, ['temp', 764]],
     [5, 600, None],
     [6, 600, None],
     [7, 600, ['temp', 726]],
     [7, 600, None]]],
   [690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 764, 764, 600, 726, 726]],
  ['control: scenario 2',
   [[[1, 620, None],
     [1, 620, ['perm', 540]],
     [1, 620, None],
     [1, 620, None],
     [1, 620, ['temp', 863]],
     [2, 600, None],
     [3, 700, ['perm', 476]],
     [3, 700, None],
     [3, 700, None]]],
   [620, 540, 540, 540, 863, 600, 500, 500, 500]],
  ['regression: scenario 3',
   [[[1, 620, ['temp', 657]],
     [1, 620, ['temp', 940]],
     [1, 620, None],
     [1, 620, None],
     [1, 620, None],
     [2, 700, 'resume'],
     [2, 700, ['temp', 641]],
     [3, 620, None]]],
   [657, 900, 900, 900, 900, 700, 641, 620]],
  ['control: scenario 4',
   [[[0, 700, ['temp', 891]],
     [0, 700, None],
     [0, 700, None],
     [0, 700, ['temp', 877]],
     [0, 700, None],
     [0, 700, None],
     [1, 600, None],
     [1, 600, 'resume'],
     [2, 620, ['perm', 514]]]],
   [891, 891, 891, 877, 877, 877, 600, 600, 514]]],
 [['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 1',
   [[[1, 700, 'resume'],
     [1, 700, ['perm', 548]],
     [1, 700, None],
     [1, 700, None],
     [2, 700, None],
     [3, 620, None],
     [4, 620, None],
     [5, 680, None],
     [5, 680, ['temp', 806]],
     [5, 680, None],
     [5, 680, None],
     [6, 700, None],
     [6, 700, None],
     [6, 700, None]]],
   [700, 548, 548, 548, 548, 548, 548, 548, 806, 806, 806, 700, 700, 700]],
  ['regression: scenario 2',
   [[[0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, ['temp', 520]],
     [0, 680, None],
     [1, 680, None],
     [2, 620, None],
     [3, 680, ['perm', 745]],
     [3, 680, None],
     [3, 680, None],
     [4, 680, None],
     [5, 700, ['temp', 765]],
     [5, 700, None]]],
   [680, 680, 680, 680, 680, 680, 520, 520, 680, 620, 745, 745, 745, 745, 765, 765]],
  ['control: scenario 3',
   [[[0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, ['perm', 700]],
     [0, 620, ['temp', 750]],
     [1, 620, None],
     [2, 620, None],
     [2, 620, None]]],
   [620, 620, 620, 620, 620, 700, 750, 620, 620, 620]],
  ['regression: scenario 4',
   [[[0, 680, None],
     [1, 600, ['temp', 779]],
     [1, 600, ['temp', 782]],
     [2, 680, None],
     [3, 700, ['perm', 454]],
     [3, 700, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, ['temp', 831]],
     [3, 700, None],
     [3, 700, None]]],
   [680, 779, 782, 680, 500, 500, 500, 500, 500, 831, 831, 831]]],
 [['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['regression: scenario 6',
   [[[1, 600, ['temp', 644]],
     [1, 600, None],
     [2, 700, 'resume'],
     [3, 600, None],
     [3, 600, None],
     [4, 620, ['temp', 652]],
     [5, 620, None],
     [5, 620, 'resume'],
     [5, 620, None],
     [5, 620, None],
     [6, 700, ['perm', 898]],
     [6, 700, None],
     [7, 680, None],
     [7, 680, ['temp', 919]]]],
   [644, 644, 700, 600, 600, 652, 620, 620, 620, 620, 898, 898, 898, 900]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['control: scenario 1',
   [[[0, 620, None],
     [1, 680, None],
     [1, 680, None],
     [1, 680, None],
     [2, 680, ['temp', 617]],
     [2, 680, None],
     [2, 680, ['perm', 816]],
     [3, 620, ['perm', 930]],
     [3, 620, None]]],
   [620, 680, 680, 680, 617, 617, 816, 900, 900]],
  ['control: scenario 2',
   [[[0, 620, 'resume'],
     [0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, 'resume'],
     [1, 620, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, None],
     [3, 620, None],
     [3, 620, ['perm', 870]],
     [3, 620, None]]],
   [620, 620, 620, 620, 620, 620, 700, 700, 700, 700, 620, 870, 870]],
  ['control: scenario 3',
   [[[0, 680, 'resume'],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [2, 620, None],
     [2, 620, 'resume'],
     [2, 620, None],
     [2, 620, ['temp', 685]],
     [2, 620, ['perm', 890]],
     [2, 620, ['perm', 678]]]],
   [680, 700, 700, 700, 620, 620, 620, 685, 890, 678]]],
 [['regression: scenario 1',
   [[[1, 700, ['temp', 847]],
     [1, 700, None],
     [1, 700, None],
     [1, 700, 'resume'],
     [1, 700, None],
     [1, 700, None],
     [1, 700, ['perm', 465]],
     [1, 700, None],
     [2, 600, ['temp', 552]],
     [2, 600, None],
     [2, 600, ['temp', 898]]]],
   [847, 847, 847, 700, 700, 700, 500, 500, 552, 552, 898]],
  ['regression: scenario 5',
   [[[1, 600, ['perm', 826]],
     [2, 700, None],
     [2, 700, None],
     [3, 620, ['temp', 796]],
     [3, 620, None],
     [3, 620, ['perm', 703]],
     [3, 620, None],
     [3, 620, 'resume'],
     [3, 620, None],
     [4, 680, ['perm', 694]]]],
   [826, 826, 826, 796, 796, 703, 703, 620, 620, 694]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 2',
   [[[0, 680, 'resume'],
     [0, 680, None],
     [0, 680, ['perm', 681]],
     [0, 680, None],
     [1, 680, None],
     [1, 680, None],
     [1, 680, None],
     [1, 680, ['temp', 832]],
     [1, 680, ['temp', 511]],
     [1, 680, ['perm', 779]],
     [1, 680, ['temp', 724]],
     [2, 700, 'resume'],
     [3, 680, None],
     [3, 680, 'resume']]],
   [680, 680, 681, 681, 681, 681, 681, 832, 511, 779, 724, 700, 680, 680]],
  ['control: scenario 3',
   [[[0, 680, None],
     [1, 680, None],
     [2, 600, 'resume'],
     [2, 600, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, 'resume'],
     [4, 600, None],
     [4, 600, None],
     [4, 600, None],
     [5, 620, ['temp', 645]],
     [6, 700, None]]],
   [680, 680, 600, 600, 700, 700, 700, 700, 600, 600, 600, 645, 700]]],
 [['regression: scenario 3',
   [[[0, 680, None],
     [0, 680, None],
     [1, 700, ['perm', 502]],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [1, 700, ['perm', 754]],
     [1, 700, None],
     [2, 700, ['temp', 591]],
     [2, 700, None],
     [2, 700, None],
     [2, 700, ['temp', 502]],
     [3, 680, 'resume'],
     [3, 680, None]]],
   [680, 680, 502, 502, 502, 502, 502, 502, 754, 754, 591, 591, 591, 502, 680, 680]],
  ['regression: scenario 11',
   [[[0, 620, 'resume'],
     [0, 620, 'resume'],
     [0, 620, None],
     [0, 620, 'resume'],
     [1, 680, None],
     [1, 680, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, ['perm', 673]],
     [2, 700, None],
     [3, 620, None],
     [3, 620, None],
     [3, 620, None],
     [3, 620, ['temp', 583]]]],
   [620, 620, 620, 620, 680, 680, 700, 700, 700, 673, 673, 673, 673, 673, 583]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 1',
   [[[0, 700, None],
     [0, 700, None],
     [0, 700, None],
     [0, 700, ['perm', 578]],
     [1, 600, 'resume'],
     [2, 600, ['temp', 629]],
     [3, 700, None],
     [3, 700, ['perm', 796]],
     [3, 700, ['temp', 558]],
     [3, 700, None],
     [3, 700, None],
     [3, 700, ['temp', 796]],
     [4, 700, None],
     [4, 700, None],
     [4, 700, None],
     [4, 700, None]]],
   [700, 700, 700, 578, 600, 629, 700, 796, 558, 558, 558, 796, 700, 700, 700, 700]],
  ['control: scenario 2',
   [[[0, 600, None],
     [0, 600, None],
     [0, 600, None],
     [0, 600, None],
     [0, 600, None],
     [1, 680, ['temp', 866]],
     [1, 680, None],
     [1, 680, ['temp', 591]],
     [1, 680, ['temp', 671]],
     [2, 700, None],
     [2, 700, None]]],
   [600, 600, 600, 600, 600, 866, 866, 591, 671, 700, 700]]]]
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
regression: hold lifecycle[680, 720, 720, 620, 900, 900, 700, 650, 650][680, 720, 720, 620, 900, 900, 500, 650, 650]Failed
regression: second temporary hold[700, 620, 650, 650, 600][700, 620, 710, 710, 600]Failed
regression: temporary over permanent[720, 620, 620, 640][720, 690, 690, 640]Failed
control: clamp low[500, 500][500, 500]Passed
regression: scenario 1[690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 600, 600, 600, 600, 600][690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 764, 764, 600, 726, 726]Failed
control: scenario 2[620, 540, 540, 540, 863, 600, 500, 500, 500][620, 540, 540, 540, 863, 600, 500, 500, 500]Passed
regression: scenario 3[657, 900, 900, 900, 900, 700, 700, 620][657, 900, 900, 900, 900, 700, 641, 620]Failed
control: scenario 4[891, 891, 891, 877, 877, 877, 600, 600, 514][891, 891, 891, 877, 877, 877, 600, 600, 514]Passed

SHA-256 / 61a20ee24ef369b7e41d2b0dae2e3c5ed35cdf00dd3a11ded81844c3706c4502

3 / The verified repair

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

N = 1
observations = []
def solve(samples):
    hold = None
    hold_period = None
    out = []
    for period, sched, action in samples:
        if action == 'resume':
            hold = None
        elif action is not None:
            kind, sp = action
            sp = max(500, min(900, sp))
            hold = [kind, sp]
            hold_period = period
        if hold is not None and hold[0] == 'temp' and period != hold_period:
            hold = None
        out.append(sched if hold is None else hold[1])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 1',
   [[[0, 700, ['perm', 690]],
     [0, 700, None],
     [0, 700, 'resume'],
     [1, 700, None],
     [1, 700, None],
     [2, 600, None],
     [2, 600, None],
     [3, 680, ['perm', 856]],
     [4, 620, ['perm', 777]],
     [4, 620, None],
     [4, 620, None],
     [5, 600, ['temp', 764]],
     [5, 600, None],
     [6, 600, None],
     [7, 600, ['temp', 726]],
     [7, 600, None]]],
   [690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 764, 764, 600, 726, 726]],
  ['control: scenario 2',
   [[[1, 620, None],
     [1, 620, ['perm', 540]],
     [1, 620, None],
     [1, 620, None],
     [1, 620, ['temp', 863]],
     [2, 600, None],
     [3, 700, ['perm', 476]],
     [3, 700, None],
     [3, 700, None]]],
   [620, 540, 540, 540, 863, 600, 500, 500, 500]],
  ['regression: scenario 3',
   [[[1, 620, ['temp', 657]],
     [1, 620, ['temp', 940]],
     [1, 620, None],
     [1, 620, None],
     [1, 620, None],
     [2, 700, 'resume'],
     [2, 700, ['temp', 641]],
     [3, 620, None]]],
   [657, 900, 900, 900, 900, 700, 641, 620]],
  ['control: scenario 4',
   [[[0, 700, ['temp', 891]],
     [0, 700, None],
     [0, 700, None],
     [0, 700, ['temp', 877]],
     [0, 700, None],
     [0, 700, None],
     [1, 600, None],
     [1, 600, 'resume'],
     [2, 620, ['perm', 514]]]],
   [891, 891, 891, 877, 877, 877, 600, 600, 514]]],
 [['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 1',
   [[[1, 700, 'resume'],
     [1, 700, ['perm', 548]],
     [1, 700, None],
     [1, 700, None],
     [2, 700, None],
     [3, 620, None],
     [4, 620, None],
     [5, 680, None],
     [5, 680, ['temp', 806]],
     [5, 680, None],
     [5, 680, None],
     [6, 700, None],
     [6, 700, None],
     [6, 700, None]]],
   [700, 548, 548, 548, 548, 548, 548, 548, 806, 806, 806, 700, 700, 700]],
  ['regression: scenario 2',
   [[[0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, None],
     [0, 680, ['temp', 520]],
     [0, 680, None],
     [1, 680, None],
     [2, 620, None],
     [3, 680, ['perm', 745]],
     [3, 680, None],
     [3, 680, None],
     [4, 680, None],
     [5, 700, ['temp', 765]],
     [5, 700, None]]],
   [680, 680, 680, 680, 680, 680, 520, 520, 680, 620, 745, 745, 745, 745, 765, 765]],
  ['control: scenario 3',
   [[[0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, ['perm', 700]],
     [0, 620, ['temp', 750]],
     [1, 620, None],
     [2, 620, None],
     [2, 620, None]]],
   [620, 620, 620, 620, 620, 700, 750, 620, 620, 620]],
  ['regression: scenario 4',
   [[[0, 680, None],
     [1, 600, ['temp', 779]],
     [1, 600, ['temp', 782]],
     [2, 680, None],
     [3, 700, ['perm', 454]],
     [3, 700, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, ['temp', 831]],
     [3, 700, None],
     [3, 700, None]]],
   [680, 779, 782, 680, 500, 500, 500, 500, 500, 831, 831, 831]]],
 [['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['regression: scenario 6',
   [[[1, 600, ['temp', 644]],
     [1, 600, None],
     [2, 700, 'resume'],
     [3, 600, None],
     [3, 600, None],
     [4, 620, ['temp', 652]],
     [5, 620, None],
     [5, 620, 'resume'],
     [5, 620, None],
     [5, 620, None],
     [6, 700, ['perm', 898]],
     [6, 700, None],
     [7, 680, None],
     [7, 680, ['temp', 919]]]],
   [644, 644, 700, 600, 600, 652, 620, 620, 620, 620, 898, 898, 898, 900]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['control: scenario 1',
   [[[0, 620, None],
     [1, 680, None],
     [1, 680, None],
     [1, 680, None],
     [2, 680, ['temp', 617]],
     [2, 680, None],
     [2, 680, ['perm', 816]],
     [3, 620, ['perm', 930]],
     [3, 620, None]]],
   [620, 680, 680, 680, 617, 617, 816, 900, 900]],
  ['control: scenario 2',
   [[[0, 620, 'resume'],
     [0, 620, None],
     [0, 620, None],
     [0, 620, None],
     [0, 620, 'resume'],
     [1, 620, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, None],
     [3, 620, None],
     [3, 620, ['perm', 870]],
     [3, 620, None]]],
   [620, 620, 620, 620, 620, 620, 700, 700, 700, 700, 620, 870, 870]],
  ['control: scenario 3',
   [[[0, 680, 'resume'],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [2, 620, None],
     [2, 620, 'resume'],
     [2, 620, None],
     [2, 620, ['temp', 685]],
     [2, 620, ['perm', 890]],
     [2, 620, ['perm', 678]]]],
   [680, 700, 700, 700, 620, 620, 620, 685, 890, 678]]],
 [['regression: scenario 1',
   [[[1, 700, ['temp', 847]],
     [1, 700, None],
     [1, 700, None],
     [1, 700, 'resume'],
     [1, 700, None],
     [1, 700, None],
     [1, 700, ['perm', 465]],
     [1, 700, None],
     [2, 600, ['temp', 552]],
     [2, 600, None],
     [2, 600, ['temp', 898]]]],
   [847, 847, 847, 700, 700, 700, 500, 500, 552, 552, 898]],
  ['regression: scenario 5',
   [[[1, 600, ['perm', 826]],
     [2, 700, None],
     [2, 700, None],
     [3, 620, ['temp', 796]],
     [3, 620, None],
     [3, 620, ['perm', 703]],
     [3, 620, None],
     [3, 620, 'resume'],
     [3, 620, None],
     [4, 680, ['perm', 694]]]],
   [826, 826, 826, 796, 796, 703, 703, 620, 620, 694]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 2',
   [[[0, 680, 'resume'],
     [0, 680, None],
     [0, 680, ['perm', 681]],
     [0, 680, None],
     [1, 680, None],
     [1, 680, None],
     [1, 680, None],
     [1, 680, ['temp', 832]],
     [1, 680, ['temp', 511]],
     [1, 680, ['perm', 779]],
     [1, 680, ['temp', 724]],
     [2, 700, 'resume'],
     [3, 680, None],
     [3, 680, 'resume']]],
   [680, 680, 681, 681, 681, 681, 681, 832, 511, 779, 724, 700, 680, 680]],
  ['control: scenario 3',
   [[[0, 680, None],
     [1, 680, None],
     [2, 600, 'resume'],
     [2, 600, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, None],
     [3, 700, 'resume'],
     [4, 600, None],
     [4, 600, None],
     [4, 600, None],
     [5, 620, ['temp', 645]],
     [6, 700, None]]],
   [680, 680, 600, 600, 700, 700, 700, 700, 600, 600, 600, 645, 700]]],
 [['regression: scenario 3',
   [[[0, 680, None],
     [0, 680, None],
     [1, 700, ['perm', 502]],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [1, 700, None],
     [1, 700, ['perm', 754]],
     [1, 700, None],
     [2, 700, ['temp', 591]],
     [2, 700, None],
     [2, 700, None],
     [2, 700, ['temp', 502]],
     [3, 680, 'resume'],
     [3, 680, None]]],
   [680, 680, 502, 502, 502, 502, 502, 502, 754, 754, 591, 591, 591, 502, 680, 680]],
  ['regression: scenario 11',
   [[[0, 620, 'resume'],
     [0, 620, 'resume'],
     [0, 620, None],
     [0, 620, 'resume'],
     [1, 680, None],
     [1, 680, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, None],
     [2, 700, ['perm', 673]],
     [2, 700, None],
     [3, 620, None],
     [3, 620, None],
     [3, 620, None],
     [3, 620, ['temp', 583]]]],
   [620, 620, 620, 620, 680, 680, 700, 700, 700, 673, 673, 673, 673, 673, 583]],
  ['regression: hold lifecycle',
   [[[1, 680, None],
     [1, 680, ['temp', 720]],
     [1, 680, None],
     [2, 620, None],
     [2, 620, ['perm', 950]],
     [3, 700, None],
     [3, 700, ['temp', 400]],
     [4, 650, None],
     [4, 650, 'resume']]],
   [680, 720, 720, 620, 900, 900, 500, 650, 650]],
  ['regression: second temporary hold',
   [[[1, 680, ['temp', 700]], [2, 620, None], [3, 650, ['temp', 710]], [3, 650, None], [4, 600, None]]],
   [700, 620, 710, 710, 600]],
  ['regression: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['control: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 1',
   [[[0, 700, None],
     [0, 700, None],
     [0, 700, None],
     [0, 700, ['perm', 578]],
     [1, 600, 'resume'],
     [2, 600, ['temp', 629]],
     [3, 700, None],
     [3, 700, ['perm', 796]],
     [3, 700, ['temp', 558]],
     [3, 700, None],
     [3, 700, None],
     [3, 700, ['temp', 796]],
     [4, 700, None],
     [4, 700, None],
     [4, 700, None],
     [4, 700, None]]],
   [700, 700, 700, 578, 600, 629, 700, 796, 558, 558, 558, 796, 700, 700, 700, 700]],
  ['control: scenario 2',
   [[[0, 600, None],
     [0, 600, None],
     [0, 600, None],
     [0, 600, None],
     [0, 600, None],
     [1, 680, ['temp', 866]],
     [1, 680, None],
     [1, 680, ['temp', 591]],
     [1, 680, ['temp', 671]],
     [2, 700, None],
     [2, 700, None]]],
   [600, 600, 600, 600, 600, 866, 866, 591, 671, 700, 700]]]]
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
regression: hold lifecycle[680, 720, 720, 620, 900, 900, 500, 650, 650][680, 720, 720, 620, 900, 900, 500, 650, 650]Passed
regression: second temporary hold[700, 620, 710, 710, 600][700, 620, 710, 710, 600]Passed
regression: temporary over permanent[720, 690, 690, 640][720, 690, 690, 640]Passed
control: clamp low[500, 500][500, 500]Passed
regression: scenario 1[690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 764, 764, 600, 726, 726][690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 764, 764, 600, 726, 726]Passed
control: scenario 2[620, 540, 540, 540, 863, 600, 500, 500, 500][620, 540, 540, 540, 863, 600, 500, 500, 500]Passed
regression: scenario 3[657, 900, 900, 900, 900, 700, 641, 620][657, 900, 900, 900, 900, 700, 641, 620]Passed
control: scenario 4[891, 891, 891, 877, 877, 877, 600, 600, 514][891, 891, 891, 877, 877, 877, 600, 600, 514]Passed

SHA-256 / 1cf4fa4723c7d2eeba367cb226928de0c6d271cd5b7b4d9a2b05e247380c82f6

Verification & scope

A deterministic bounded teaching model of one thermostat or HVAC controller decision evaluated per sample. Temperatures are integer tenths of a degree Fahrenheit unless stated otherwise. The contract is a stipulated toy convention, not a claim of conformance to any manufacturer, ASHRAE guideline or code. 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:46.266999+00:00.

Case digest / b405c9f56731427a1d70eb14e41e2300ac93cdd637dd7414bd02c4038c124958