FA-92356 / HVAC thermostat control / Open access
Cooling call ends only once below the cooling setpoint · case 01
Air conditioner runs one extra sample past setpoint on every cycle.
ROOT CAUSE
The cool end test uses temp < cool_sp.
THE FAILURE
The cool end test uses temp < cool_sp.
Unsuccessful approach: Ending at the cooling start threshold stops the air conditioner before it reaches setpoint.
Case contract
Auto heat/cool thermostat sampled once per minute (temps in tenths F). If the cooling setpoint is less than 3.0 F above the heating setpoint it is pushed up to heat_sp + 30 (the heating setpoint wins). Heating starts below heat_sp - 1.0 F and ends at temp >= heat_sp; cooling starts above cool_sp + 1.0 F and ends at temp <= cool_sp. After a heating call ends, cooling may not start until 5 minutes later, and vice versa. Return heat, cool or idle per minute.
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(heat_sp, cool_sp, temps):
diff = 10
if cool_sp - heat_sp < 30:
cool_sp = heat_sp + 30
state = 'idle'
last_heat_end = None
last_cool_end = None
out = []
for minute, temp in enumerate(temps):
if state == 'heat' and temp >= heat_sp:
state = 'idle'
last_heat_end = minute
elif state == 'cool' and temp < cool_sp:
state = 'idle'
last_cool_end = minute
if state == 'idle':
if temp < heat_sp - diff and (last_cool_end is None or minute - last_cool_end >= 5):
state = 'heat'
elif temp > cool_sp + diff and (last_heat_end is None or minute - last_heat_end >= 5):
state = 'cool'
out.append(state)
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: heat then locked cool',
[680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],
['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],
['regression: deadband squeeze',
[700, 705, [740, 741, 735, 730, 729]],
['idle', 'cool', 'cool', 'idle', 'idle']],
['control: exact thresholds',
[680, 720, [670, 669, 680, 731, 730, 720, 721]],
['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: short heat cycle then hot',
[680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['control: scenario 1',
[680, 690, [693, 687, 709, 709, 685, 704, 684, 680, 686, 687, 679]],
['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: scenario 2',
[680, 700, [681, 686, 698, 703, 724, 744, 752, 732, 755, 742, 762, 772]],
['idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool']],
['control: scenario 3',
[680,
740,
[649,
667,
643,
622,
637,
657,
634,
649,
674,
654,
672,
675,
698,
687,
666,
654,
670,
645,
654,
637,
651,
672]],
['heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'idle',
'idle',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat']],
['control: scenario 4',
[680, 720, [678, 693, 708, 706, 691, 698, 702, 725, 720, 725, 717, 703, 717, 731, 714, 728, 740, 735]],
['idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'cool',
'idle',
'idle',
'cool',
'cool']]],
[['regression: deadband squeeze',
[700, 705, [740, 741, 735, 730, 729]],
['idle', 'cool', 'cool', 'idle', 'idle']],
['regression: scenario 1',
[680, 700, [687, 695, 710, 721, 712, 724, 736, 738, 737, 740, 748]],
['idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool']],
['regression: heat then locked cool',
[680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],
['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],
['control: exact thresholds',
[680, 720, [670, 669, 680, 731, 730, 720, 721]],
['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: short heat cycle then hot',
[680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['control: scenario 2',
[680, 710, [674, 681, 688, 699, 718, 698, 716, 709, 691, 672, 678, 701, 724, 699, 683, 698, 691, 701]],
['idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'cool',
'idle',
'idle',
'idle',
'idle',
'idle']],
['control: scenario 3',
[680, 690, [686, 679, 704, 696, 678, 658, 635, 648, 643, 657, 650, 661, 672, 691, 687, 685, 708, 687]],
['idle',
'idle',
'idle',
'idle',
'idle',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'idle',
'idle',
'idle',
'idle',
'idle']],
['control: scenario 4',
[680, 710, [747, 771, 775, 769, 770, 766, 790, 790, 790, 789, 781]],
['cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool']]],
[['regression: heat then locked cool',
[680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],
['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],
['regression: scenario 6',
[680, 700, [757, 740, 724, 721, 702, 699, 724, 717, 742, 721, 723, 700]],
['cool', 'cool', 'cool', 'cool', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'idle']],
['control: exact thresholds',
[680, 720, [670, 669, 680, 731, 730, 720, 721]],
['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: short heat cycle then hot',
[680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['regression: deadband squeeze',
[700, 705, [740, 741, 735, 730, 729]],
['idle', 'cool', 'cool', 'idle', 'idle']],
['control: scenario 1',
[680, 710, [758, 778, 769, 788, 780, 787, 790, 773, 789, 766, 785, 779, 790, 790, 790, 777, 790]],
['cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool']],
['control: scenario 2',
[680, 740, [646, 654, 672, 672, 692, 717, 736, 759, 769, 790, 790]],
['heat', 'heat', 'heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['control: scenario 3',
[680, 710, [676, 683, 703, 725, 749, 746, 749, 735, 740, 755, 765, 742, 727, 737, 745, 751, 762]],
['idle',
'idle',
'idle',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool']]],
[['regression: scenario 34',
[680, 700, [682, 703, 712, 736, 754, 777, 775, 764, 775, 787, 763, 766, 750, 731, 729, 719, 710]],
['idle',
'idle',
'idle',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'idle']],
['regression: scenario 13',
[680, 700, [732, 729, 742, 749, 733, 737, 714, 714, 716, 713, 725, 733, 737, 726, 729, 723, 745]],
['cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool']],
['regression: heat then locked cool',
[680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],
['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],
['control: exact thresholds',
[680, 720, [670, 669, 680, 731, 730, 720, 721]],
['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: short heat cycle then hot',
[680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['regression: deadband squeeze',
[700, 705, [740, 741, 735, 730, 729]],
['idle', 'cool', 'cool', 'idle', 'idle']],
['control: scenario 1',
[680,
690,
[671, 646, 636, 635, 620, 625, 635, 653, 659, 636, 645, 633, 620, 628, 620, 638, 653, 655, 666, 678]],
['idle',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat']],
['control: scenario 2',
[680, 740, [655, 638, 663, 641, 622, 636, 628, 621, 621, 620, 635, 632, 644, 651, 656, 675, 687]],
['heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'idle']]],
[['regression: scenario 33',
[680, 720, [682, 688, 681, 690, 702, 715, 721, 732, 753, 738, 741, 733, 720, 700, 712, 689, 710, 729]],
['idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'cool',
'cool',
'cool',
'cool',
'cool',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle']],
['regression: scenario 9',
[680, 690, [673, 694, 686, 686, 699, 720, 727, 736, 726, 719]],
['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool']],
['regression: heat then locked cool',
[680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],
['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],
['control: exact thresholds',
[680, 720, [670, 669, 680, 731, 730, 720, 721]],
['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: short heat cycle then hot',
[680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['regression: deadband squeeze',
[700, 705, [740, 741, 735, 730, 729]],
['idle', 'cool', 'cool', 'idle', 'idle']],
['regression: scenario 1',
[680, 720, [774, 779, 785, 790, 790, 781, 778, 777, 770, 767, 743, 730, 708, 690]],
['cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'idle',
'idle']],
['regression: scenario 2',
[680,
710,
[701,
679,
702,
721,
727,
748,
743,
720,
741,
738,
762,
769,
758,
740,
744,
725,
748,
728,
742,
755,
769]],
['idle',
'idle',
'idle',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool']]]]
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: heat then locked cool | ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool'] | ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle'] | Failed |
| regression: deadband squeeze | ['idle', 'cool', 'cool', 'cool', 'idle'] | ['idle', 'cool', 'cool', 'idle', 'idle'] | Failed |
| control: exact thresholds | ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle'] | ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle'] | Passed |
| control: short heat cycle then hot | ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool'] | ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool'] | Passed |
| control: scenario 1 | ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle'] | ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle'] | Passed |
| control: scenario 2 | ['idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool'] | ['idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool'] | Passed |
| control: scenario 3 | ['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'idle', 'idle', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat'] | ['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'idle', 'idle', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat'] | Passed |
| control: scenario 4 | ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'idle', 'idle', 'cool', 'cool'] | ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'idle', 'idle', 'cool', 'cool'] | Passed |
SHA-256 / 313bbeadd9b7c341852f633d3bfba0b362e98c7fe25cd1f7ea49236718634254
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(heat_sp, cool_sp, temps):
diff = 10
if cool_sp - heat_sp < 30:
cool_sp = heat_sp + 30
state = 'idle'
last_heat_end = None
last_cool_end = None
out = []
for minute, temp in enumerate(temps):
if state == 'heat' and temp >= heat_sp:
state = 'idle'
last_heat_end = minute
elif state == 'cool' and temp <= cool_sp + diff:
state = 'idle'
last_cool_end = minute
if state == 'idle':
if temp < heat_sp - diff and (last_cool_end is None or minute - last_cool_end >= 5):
state = 'heat'
elif temp > cool_sp + diff and (last_heat_end is None or minute - last_heat_end >= 5):
state = 'cool'
out.append(state)
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: heat then locked cool',
[680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],
['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],
['regression: deadband squeeze',
[700, 705, [740, 741, 735, 730, 729]],
['idle', 'cool', 'cool', 'idle', 'idle']],
['control: exact thresholds',
[680, 720, [670, 669, 680, 731, 730, 720, 721]],
['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: short heat cycle then hot',
[680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['control: scenario 1',
[680, 690, [693, 687, 709, 709, 685, 704, 684, 680, 686, 687, 679]],
['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: scenario 2',
[680, 700, [681, 686, 698, 703, 724, 744, 752, 732, 755, 742, 762, 772]],
['idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool']],
['control: scenario 3',
[680,
740,
[649,
667,
643,
622,
637,
657,
634,
649,
674,
654,
672,
675,
698,
687,
666,
654,
670,
645,
654,
637,
651,
672]],
['heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'idle',
'idle',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat']],
['control: scenario 4',
[680, 720, [678, 693, 708, 706, 691, 698, 702, 725, 720, 725, 717, 703, 717, 731, 714, 728, 740, 735]],
['idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'cool',
'idle',
'idle',
'cool',
'cool']]],
[['regression: deadband squeeze',
[700, 705, [740, 741, 735, 730, 729]],
['idle', 'cool', 'cool', 'idle', 'idle']],
['regression: scenario 1',
[680, 700, [687, 695, 710, 721, 712, 724, 736, 738, 737, 740, 748]],
['idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool']],
['regression: heat then locked cool',
[680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],
['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],
['control: exact thresholds',
[680, 720, [670, 669, 680, 731, 730, 720, 721]],
['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: short heat cycle then hot',
[680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['control: scenario 2',
[680, 710, [674, 681, 688, 699, 718, 698, 716, 709, 691, 672, 678, 701, 724, 699, 683, 698, 691, 701]],
['idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'cool',
'idle',
'idle',
'idle',
'idle',
'idle']],
['control: scenario 3',
[680, 690, [686, 679, 704, 696, 678, 658, 635, 648, 643, 657, 650, 661, 672, 691, 687, 685, 708, 687]],
['idle',
'idle',
'idle',
'idle',
'idle',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'idle',
'idle',
'idle',
'idle',
'idle']],
['control: scenario 4',
[680, 710, [747, 771, 775, 769, 770, 766, 790, 790, 790, 789, 781]],
['cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool']]],
[['regression: heat then locked cool',
[680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],
['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],
['regression: scenario 6',
[680, 700, [757, 740, 724, 721, 702, 699, 724, 717, 742, 721, 723, 700]],
['cool', 'cool', 'cool', 'cool', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'idle']],
['control: exact thresholds',
[680, 720, [670, 669, 680, 731, 730, 720, 721]],
['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: short heat cycle then hot',
[680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['regression: deadband squeeze',
[700, 705, [740, 741, 735, 730, 729]],
['idle', 'cool', 'cool', 'idle', 'idle']],
['control: scenario 1',
[680, 710, [758, 778, 769, 788, 780, 787, 790, 773, 789, 766, 785, 779, 790, 790, 790, 777, 790]],
['cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool']],
['control: scenario 2',
[680, 740, [646, 654, 672, 672, 692, 717, 736, 759, 769, 790, 790]],
['heat', 'heat', 'heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['control: scenario 3',
[680, 710, [676, 683, 703, 725, 749, 746, 749, 735, 740, 755, 765, 742, 727, 737, 745, 751, 762]],
['idle',
'idle',
'idle',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool']]],
[['regression: scenario 34',
[680, 700, [682, 703, 712, 736, 754, 777, 775, 764, 775, 787, 763, 766, 750, 731, 729, 719, 710]],
['idle',
'idle',
'idle',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'idle']],
['regression: scenario 13',
[680, 700, [732, 729, 742, 749, 733, 737, 714, 714, 716, 713, 725, 733, 737, 726, 729, 723, 745]],
['cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool']],
['regression: heat then locked cool',
[680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],
['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],
['control: exact thresholds',
[680, 720, [670, 669, 680, 731, 730, 720, 721]],
['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: short heat cycle then hot',
[680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['regression: deadband squeeze',
[700, 705, [740, 741, 735, 730, 729]],
['idle', 'cool', 'cool', 'idle', 'idle']],
['control: scenario 1',
[680,
690,
[671, 646, 636, 635, 620, 625, 635, 653, 659, 636, 645, 633, 620, 628, 620, 638, 653, 655, 666, 678]],
['idle',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat']],
['control: scenario 2',
[680, 740, [655, 638, 663, 641, 622, 636, 628, 621, 621, 620, 635, 632, 644, 651, 656, 675, 687]],
['heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'heat',
'idle']]],
[['regression: scenario 33',
[680, 720, [682, 688, 681, 690, 702, 715, 721, 732, 753, 738, 741, 733, 720, 700, 712, 689, 710, 729]],
['idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle',
'cool',
'cool',
'cool',
'cool',
'cool',
'idle',
'idle',
'idle',
'idle',
'idle',
'idle']],
['regression: scenario 9',
[680, 690, [673, 694, 686, 686, 699, 720, 727, 736, 726, 719]],
['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool']],
['regression: heat then locked cool',
[680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],
['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],
['control: exact thresholds',
[680, 720, [670, 669, 680, 731, 730, 720, 721]],
['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
['control: short heat cycle then hot',
[680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
['regression: deadband squeeze',
[700, 705, [740, 741, 735, 730, 729]],
['idle', 'cool', 'cool', 'idle', 'idle']],
['regression: scenario 1',
[680, 720, [774, 779, 785, 790, 790, 781, 778, 777, 770, 767, 743, 730, 708, 690]],
['cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'idle',
'idle']],
['regression: scenario 2',
[680,
710,
[701,
679,
702,
721,
727,
748,
743,
720,
741,
738,
762,
769,
758,
740,
744,
725,
748,
728,
742,
755,
769]],
['idle',
'idle',
'idle',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool',
'cool']]]]
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: heat then locked cool | ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'idle', 'idle'] | ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle'] | Failed |
| regression: deadband squeeze | ['idle', 'cool', 'idle', 'idle', 'idle'] | ['idle', 'cool', 'cool', 'idle', 'idle'] | Failed |
| control: exact thresholds | ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle'] | ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle'] | Passed |
| control: short heat cycle then hot | ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool'] | ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool'] | Passed |
| control: scenario 1 | ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle'] | ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle'] | Passed |
| control: scenario 2 | ['idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool'] | ['idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool'] | Passed |
| control: scenario 3 | ['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'idle', 'idle', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat'] | ['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'idle', 'idle', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat'] | Passed |
| control: scenario 4 | ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'idle', 'idle', 'cool', 'cool'] | ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'idle', 'idle', 'cool', 'cool'] | Passed |
SHA-256 / 01a04b53eb0270f0ea15ac363f3445cbdd6f39fec6afc200b6ce4690db82d64c
HELD IN THE MEMBER ARCHIVE
The verified repair and its recorded checks are member-only.
This mechanism has 8 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.
Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.
Member access is invitation-based. Sign in with your invited account to inspect the repair.
Sign in to the archive ↗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:44.989472+00:00.
Case digest / 4f19ac4c333a0ec20b815af9ea0b7afdd624438d9c761e768442b4e33792db1d