FAILURE MAP
← Case archive

FA-92351 / HVAC thermostat control / Open access

Heating call ends at the start threshold instead of at setpoint · case 01

Rooms never reach the heating setpoint and the call chatters at the bottom of the band.

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

ROOT CAUSE

The heat end test uses heat_sp - differential.

VERIFIED REPAIR

End the heating call when temperature reaches heat_sp.

Unsuccessful approach: Ending only above setpoint overshoots by one tenth on every cycle.

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 - diff:
            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: 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']],
  ['regression: exact thresholds',
   [680, 720, [670, 669, 680, 731, 730, 720, 721]],
   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
  ['control: 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: short heat cycle then hot',
   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
  ['control: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['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 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: scenario 6',
   [680,
    690,
    [694, 696, 693, 697, 678, 655, 668, 684, 659, 671, 693, 680, 655, 660, 640, 655, 657, 634, 622, 620]],
   ['idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'heat',
    'heat',
    'idle',
    'heat',
    'heat',
    'idle',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat']],
  ['regression: scenario 29',
   [680, 700, [694, 696, 676, 693, 679, 684, 666, 676, 671, 659, 680, 680, 658, 650, 662, 677]],
   ['idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat']],
  ['control: 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: 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: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['control: 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']],
  ['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']]],
 [['regression: scenario 10',
   [680, 700, [693, 681, 656, 676, 697, 710, 694, 681, 682, 707, 717, 711, 736, 731, 735]],
   ['idle',
    'idle',
    'heat',
    'heat',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'cool',
    'cool',
    'cool']],
  ['regression: scenario 52',
   [680, 700, [670, 669, 652, 663, 640, 620, 634, 649, 660, 680]],
   ['idle', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'idle']],
  ['control: 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: 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: 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']],
  ['regression: 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']]],
 [['regression: scenario 9',
   [680, 690, [659, 637, 655, 663, 655, 654, 639, 635, 631, 656, 667, 672, 683, 704, 683, 696, 718]],
   ['heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle']],
  ['regression: scenario 35',
   [680,
    690,
    [712,
     690,
     682,
     662,
     680,
     655,
     665,
     643,
     622,
     636,
     657,
     681,
     658,
     663,
     656,
     663,
     649,
     661,
     657,
     643,
     667]],
   ['idle',
    'idle',
    'idle',
    'heat',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat']],
  ['control: 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: 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: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['regression: 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']],
  ['regression: 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 19',
   [680,
    690,
    [644, 668, 674, 684, 659, 674, 661, 653, 633, 642, 656, 675, 656, 679, 675, 677, 700, 714, 720]],
   ['heat',
    'heat',
    'heat',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'idle',
    'idle']],
  ['regression: exact thresholds',
   [680, 720, [670, 669, 680, 731, 730, 720, 721]],
   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
  ['control: 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: short heat cycle then hot',
   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
  ['control: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['control: 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']],
  ['control: 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']],
  ['control: scenario 3',
   [680, 720, [645, 664, 650, 632, 620, 628, 652, 666, 661, 660, 644]],
   ['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat']]]]
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: scenario 3['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'heat', 'heat', 'idle', 'heat', 'heat', 'heat', 'heat', 'idle']['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'idle', 'idle', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat']Failed
regression: exact thresholds['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']Passed
control: heat then locked cool['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', '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: deadband squeeze['idle', 'cool', 'cool', 'idle', 'idle']['idle', 'cool', 'cool', 'idle', 'idle']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 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 / 05efc62baa5ff4c86924658e92c95eaa41df8ace2653b21233890fc9cb59d290

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:
            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: 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']],
  ['regression: exact thresholds',
   [680, 720, [670, 669, 680, 731, 730, 720, 721]],
   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
  ['control: 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: short heat cycle then hot',
   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
  ['control: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['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 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: scenario 6',
   [680,
    690,
    [694, 696, 693, 697, 678, 655, 668, 684, 659, 671, 693, 680, 655, 660, 640, 655, 657, 634, 622, 620]],
   ['idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'heat',
    'heat',
    'idle',
    'heat',
    'heat',
    'idle',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat']],
  ['regression: scenario 29',
   [680, 700, [694, 696, 676, 693, 679, 684, 666, 676, 671, 659, 680, 680, 658, 650, 662, 677]],
   ['idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat']],
  ['control: 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: 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: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['control: 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']],
  ['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']]],
 [['regression: scenario 10',
   [680, 700, [693, 681, 656, 676, 697, 710, 694, 681, 682, 707, 717, 711, 736, 731, 735]],
   ['idle',
    'idle',
    'heat',
    'heat',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'cool',
    'cool',
    'cool']],
  ['regression: scenario 52',
   [680, 700, [670, 669, 652, 663, 640, 620, 634, 649, 660, 680]],
   ['idle', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'idle']],
  ['control: 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: 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: 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']],
  ['regression: 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']]],
 [['regression: scenario 9',
   [680, 690, [659, 637, 655, 663, 655, 654, 639, 635, 631, 656, 667, 672, 683, 704, 683, 696, 718]],
   ['heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle']],
  ['regression: scenario 35',
   [680,
    690,
    [712,
     690,
     682,
     662,
     680,
     655,
     665,
     643,
     622,
     636,
     657,
     681,
     658,
     663,
     656,
     663,
     649,
     661,
     657,
     643,
     667]],
   ['idle',
    'idle',
    'idle',
    'heat',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat']],
  ['control: 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: 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: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['regression: 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']],
  ['regression: 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 19',
   [680,
    690,
    [644, 668, 674, 684, 659, 674, 661, 653, 633, 642, 656, 675, 656, 679, 675, 677, 700, 714, 720]],
   ['heat',
    'heat',
    'heat',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'idle',
    'idle']],
  ['regression: exact thresholds',
   [680, 720, [670, 669, 680, 731, 730, 720, 721]],
   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
  ['control: 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: short heat cycle then hot',
   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
  ['control: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['control: 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']],
  ['control: 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']],
  ['control: scenario 3',
   [680, 720, [645, 664, 650, 632, 620, 628, 652, 666, 661, 660, 644]],
   ['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat']]]]
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: 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
regression: exact thresholds['idle', 'heat', 'heat', 'idle', 'idle', 'idle', 'idle']['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']Failed
control: heat then locked cool['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', '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: deadband squeeze['idle', 'cool', 'cool', 'idle', 'idle']['idle', 'cool', 'cool', 'idle', 'idle']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 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 / 764dd9351ca2dab7fcfc59a8d06f3d0fa5a38e30c9812623f5a50dff1101b856

3 / The verified repair

Exit 0
"""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: 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']],
  ['regression: exact thresholds',
   [680, 720, [670, 669, 680, 731, 730, 720, 721]],
   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
  ['control: 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: short heat cycle then hot',
   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
  ['control: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['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 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: scenario 6',
   [680,
    690,
    [694, 696, 693, 697, 678, 655, 668, 684, 659, 671, 693, 680, 655, 660, 640, 655, 657, 634, 622, 620]],
   ['idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'heat',
    'heat',
    'idle',
    'heat',
    'heat',
    'idle',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat']],
  ['regression: scenario 29',
   [680, 700, [694, 696, 676, 693, 679, 684, 666, 676, 671, 659, 680, 680, 658, 650, 662, 677]],
   ['idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat']],
  ['control: 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: 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: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['control: 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']],
  ['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']]],
 [['regression: scenario 10',
   [680, 700, [693, 681, 656, 676, 697, 710, 694, 681, 682, 707, 717, 711, 736, 731, 735]],
   ['idle',
    'idle',
    'heat',
    'heat',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle',
    'cool',
    'cool',
    'cool']],
  ['regression: scenario 52',
   [680, 700, [670, 669, 652, 663, 640, 620, 634, 649, 660, 680]],
   ['idle', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'idle']],
  ['control: 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: 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: 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']],
  ['regression: 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']]],
 [['regression: scenario 9',
   [680, 690, [659, 637, 655, 663, 655, 654, 639, 635, 631, 656, 667, 672, 683, 704, 683, 696, 718]],
   ['heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'idle',
    'idle',
    'idle',
    'idle']],
  ['regression: scenario 35',
   [680,
    690,
    [712,
     690,
     682,
     662,
     680,
     655,
     665,
     643,
     622,
     636,
     657,
     681,
     658,
     663,
     656,
     663,
     649,
     661,
     657,
     643,
     667]],
   ['idle',
    'idle',
    'idle',
    'heat',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat']],
  ['control: 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: 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: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['regression: 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']],
  ['regression: 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 19',
   [680,
    690,
    [644, 668, 674, 684, 659, 674, 661, 653, 633, 642, 656, 675, 656, 679, 675, 677, 700, 714, 720]],
   ['heat',
    'heat',
    'heat',
    'idle',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'heat',
    'idle',
    'idle',
    'idle']],
  ['regression: exact thresholds',
   [680, 720, [670, 669, 680, 731, 730, 720, 721]],
   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],
  ['control: 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: short heat cycle then hot',
   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],
   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],
  ['control: deadband squeeze',
   [700, 705, [740, 741, 735, 730, 729]],
   ['idle', 'cool', 'cool', 'idle', 'idle']],
  ['control: 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']],
  ['control: 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']],
  ['control: scenario 3',
   [680, 720, [645, 664, 650, 632, 620, 628, 652, 666, 661, 660, 644]],
   ['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat']]]]
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: 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
regression: exact thresholds['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']Passed
control: heat then locked cool['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', '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: deadband squeeze['idle', 'cool', 'cool', 'idle', 'idle']['idle', 'cool', 'cool', 'idle', 'idle']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 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 / cd356e0c7a5db4aab8bd9ebf549e12c431be5f53be131e3ad936a90a0d1d1e2b

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

Case digest / cbc6a46fbee2fcde8348a55e8bffe114cfa2ee1a7a5a325801b46673839e98fe