FAILURE MAP
← Case archive

FA-92481 / HVAC thermostat control / Open access

Permanent hold expires at the next schedule period while temporary hold persists · case 01

A "hold until next period" never ends, while a permanent hold is dropped at the next program change.

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

ROOT CAUSE

The expiry check tests for the permanent hold kind instead of the temporary one.

VERIFIED REPAIR

Only temporary holds expire when the schedule period changes.

Unsuccessful approach: Expiring both kinds still drops permanent holds.

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]
            hold_period = period
        if hold is not None and hold[0] == 'perm' 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: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['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: 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]],
  ['regression: 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]],
  ['regression: 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: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['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: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['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]],
  ['regression: 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 4',
   [[[1, 700, ['perm', 595]],
     [2, 600, None],
     [2, 600, ['perm', 854]],
     [3, 600, 'resume'],
     [3, 600, ['perm', 923]],
     [3, 600, ['perm', 783]],
     [3, 600, None],
     [3, 600, ['temp', 512]]]],
   [595, 595, 854, 600, 900, 783, 783, 512]],
  ['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: 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: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 6',
   [[[0, 700, None],
     [0, 700, None],
     [0, 700, ['perm', 473]],
     [0, 700, None],
     [0, 700, None],
     [0, 700, None],
     [0, 700, None],
     [1, 600, None],
     [1, 600, None],
     [2, 600, None],
     [3, 680, None]]],
   [700, 700, 500, 500, 500, 500, 500, 500, 500, 500, 500]],
  ['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: 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 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]],
  ['regression: 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 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]],
  ['regression: scenario 7',
   [[[0, 680, None],
     [0, 680, None],
     [1, 680, ['perm', 831]],
     [1, 680, None],
     [1, 680, None],
     [2, 600, None],
     [2, 600, None],
     [2, 600, None],
     [2, 600, None],
     [2, 600, 'resume']]],
   [680, 680, 831, 831, 831, 831, 831, 831, 831, 600]],
  ['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]],
  ['regression: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: 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]],
  ['control: 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]]]]
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, 720, 900, 700, 500, 500, 650][680, 720, 720, 620, 900, 900, 500, 650, 650]Failed
regression: clamp low[500, 620][500, 500]Failed
regression: second temporary hold[700, 700, 710, 710, 710][700, 620, 710, 710, 600]Failed
regression: temporary over permanent[720, 690, 690, 690][720, 690, 690, 640]Failed
regression: scenario 1[690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 764, 764, 764, 726, 726][690, 690, 700, 700, 700, 600, 600, 856, 777, 777, 777, 764, 764, 600, 726, 726]Failed
regression: scenario 2[620, 540, 540, 540, 863, 863, 500, 500, 500][620, 540, 540, 540, 863, 600, 500, 500, 500]Failed
regression: scenario 3[657, 900, 900, 900, 900, 700, 641, 641][657, 900, 900, 900, 900, 700, 641, 620]Failed
regression: scenario 4[891, 891, 891, 877, 877, 877, 877, 600, 514][891, 891, 891, 877, 877, 877, 600, 600, 514]Failed

SHA-256 / f5537155dc5b6b812c637d7b8c87b2fb2623438edd0cffe293a1119e3badfa45

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 hold is not None and hold[0] in ('temp', 'perm') 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: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['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: 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]],
  ['regression: 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]],
  ['regression: 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: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['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: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['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]],
  ['regression: 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 4',
   [[[1, 700, ['perm', 595]],
     [2, 600, None],
     [2, 600, ['perm', 854]],
     [3, 600, 'resume'],
     [3, 600, ['perm', 923]],
     [3, 600, ['perm', 783]],
     [3, 600, None],
     [3, 600, ['temp', 512]]]],
   [595, 595, 854, 600, 900, 783, 783, 512]],
  ['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: 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: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 6',
   [[[0, 700, None],
     [0, 700, None],
     [0, 700, ['perm', 473]],
     [0, 700, None],
     [0, 700, None],
     [0, 700, None],
     [0, 700, None],
     [1, 600, None],
     [1, 600, None],
     [2, 600, None],
     [3, 680, None]]],
   [700, 700, 500, 500, 500, 500, 500, 500, 500, 500, 500]],
  ['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: 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 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]],
  ['regression: 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 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]],
  ['regression: scenario 7',
   [[[0, 680, None],
     [0, 680, None],
     [1, 680, ['perm', 831]],
     [1, 680, None],
     [1, 680, None],
     [2, 600, None],
     [2, 600, None],
     [2, 600, None],
     [2, 600, None],
     [2, 600, 'resume']]],
   [680, 680, 831, 831, 831, 831, 831, 831, 831, 600]],
  ['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]],
  ['regression: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: 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]],
  ['control: 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]]]]
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, 700, 500, 650, 650][680, 720, 720, 620, 900, 900, 500, 650, 650]Failed
regression: clamp low[500, 620][500, 500]Failed
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
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
regression: 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
regression: scenario 4[891, 891, 891, 877, 877, 877, 600, 600, 514][891, 891, 891, 877, 877, 877, 600, 600, 514]Passed

SHA-256 / 30f4dbfe29a0215e708f328ac72d1e98724d36e788efd2d20a2d874e6fc28b0d

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: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['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: 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]],
  ['regression: 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]],
  ['regression: 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: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['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: temporary over permanent',
   [[[1, 680, ['perm', 720]], [2, 620, ['temp', 690]], [2, 620, None], [3, 640, None]]],
   [720, 690, 690, 640]],
  ['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]],
  ['regression: 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 4',
   [[[1, 700, ['perm', 595]],
     [2, 600, None],
     [2, 600, ['perm', 854]],
     [3, 600, 'resume'],
     [3, 600, ['perm', 923]],
     [3, 600, ['perm', 783]],
     [3, 600, None],
     [3, 600, ['temp', 512]]]],
   [595, 595, 854, 600, 900, 783, 783, 512]],
  ['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: 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: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: scenario 6',
   [[[0, 700, None],
     [0, 700, None],
     [0, 700, ['perm', 473]],
     [0, 700, None],
     [0, 700, None],
     [0, 700, None],
     [0, 700, None],
     [1, 600, None],
     [1, 600, None],
     [2, 600, None],
     [3, 680, None]]],
   [700, 700, 500, 500, 500, 500, 500, 500, 500, 500, 500]],
  ['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: 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 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]],
  ['regression: 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 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]],
  ['regression: scenario 7',
   [[[0, 680, None],
     [0, 680, None],
     [1, 680, ['perm', 831]],
     [1, 680, None],
     [1, 680, None],
     [2, 600, None],
     [2, 600, None],
     [2, 600, None],
     [2, 600, None],
     [2, 600, 'resume']]],
   [680, 680, 831, 831, 831, 831, 831, 831, 831, 600]],
  ['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]],
  ['regression: clamp low', [[[1, 680, ['perm', 300]], [2, 620, None]]], [500, 500]],
  ['regression: 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]],
  ['control: 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]]]]
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: clamp low[500, 500][500, 500]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
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
regression: 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
regression: scenario 4[891, 891, 891, 877, 877, 877, 600, 600, 514][891, 891, 891, 877, 877, 877, 600, 600, 514]Passed

SHA-256 / e22ce9759b28206c4e63bf83972e9dcfce74a7bc00c0d500f4c5a986f8d2da25

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

Case digest / e8e7061bd9dbc1a87cc83b696a78b7a9cfe1f3624d81f4a96ac23e978838d371