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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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