{"abstract":"Cooling can start immediately after a long heating call because the lockout already expired.","category":"HVAC thermostat control","checks":8,"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.","contract_signature":"heat_sp, cool_sp, temps","evaluation_group":"w2-hvac-thermostat-control-auto-changeover-deadband","failed_approach":"Stamping the last heating minute instead of the end minute releases the lockout a minute early.","family":"w2-hvac-thermostat-control-auto-changeover-deadband-changeover-reference","id":"FA-92361","implementations":{"attempt":{"sha256":"c19c637afc17900e8c9be2a5fa67ca44d9f1185a86065ca88ff30c1a481a880b","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\n\nN = 1\nobservations = []\ndef solve(heat_sp, cool_sp, temps):\n    diff = 10\n    if cool_sp - heat_sp < 30:\n        cool_sp = heat_sp + 30\n    state = 'idle'\n    last_heat_end = None\n    last_cool_end = None\n    out = []\n    for minute, temp in enumerate(temps):\n        if state == 'heat' and temp >= heat_sp:\n            state = 'idle'\n            last_heat_end = minute - 1\n        elif state == 'cool' and temp <= cool_sp:\n            state = 'idle'\n            last_cool_end = minute\n        if state == 'idle':\n            if temp < heat_sp - diff and (last_cool_end is None or minute - last_cool_end >= 5):\n                state = 'heat'\n            elif temp > cool_sp + diff and (last_heat_end is None or minute - last_heat_end >= 5):\n                state = 'cool'\n        out.append(state)\n    return out\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['regression: heat then locked cool',\n   [680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],\n   ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],\n  ['regression: short heat cycle then hot',\n   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],\n   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['control: exact thresholds',\n   [680, 720, [670, 669, 680, 731, 730, 720, 721]],\n   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['control: deadband squeeze',\n   [700, 705, [740, 741, 735, 730, 729]],\n   ['idle', 'cool', 'cool', 'idle', 'idle']],\n  ['control: scenario 1',\n   [680, 690, [693, 687, 709, 709, 685, 704, 684, 680, 686, 687, 679]],\n   ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['control: scenario 2',\n   [680, 700, [681, 686, 698, 703, 724, 744, 752, 732, 755, 742, 762, 772]],\n   ['idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool']],\n  ['control: scenario 3',\n   [680,\n    740,\n    [649,\n     667,\n     643,\n     622,\n     637,\n     657,\n     634,\n     649,\n     674,\n     654,\n     672,\n     675,\n     698,\n     687,\n     666,\n     654,\n     670,\n     645,\n     654,\n     637,\n     651,\n     672]],\n   ['heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'idle',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat']],\n  ['control: scenario 4',\n   [680, 720, [678, 693, 708, 706, 691, 698, 702, 725, 720, 725, 717, 703, 717, 731, 714, 728, 740, 735]],\n   ['idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'idle',\n    'idle',\n    'cool',\n    'cool']]],\n [['regression: short heat cycle then hot',\n   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],\n   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['regression: scenario 25',\n   [680, 710, [674, 651, 640, 644, 663, 686, 668, 681, 706, 727, 723, 727, 746, 737, 716, 713, 725, 747]],\n   ['idle',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'heat',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool']],\n  ['regression: heat then locked cool',\n   [680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],\n   ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],\n  ['control: exact thresholds',\n   [680, 720, [670, 669, 680, 731, 730, 720, 721]],\n   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['control: deadband squeeze',\n   [700, 705, [740, 741, 735, 730, 729]],\n   ['idle', 'cool', 'cool', 'idle', 'idle']],\n  ['control: scenario 1',\n   [680, 700, [687, 695, 710, 721, 712, 724, 736, 738, 737, 740, 748]],\n   ['idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool']],\n  ['control: scenario 2',\n   [680, 710, [674, 681, 688, 699, 718, 698, 716, 709, 691, 672, 678, 701, 724, 699, 683, 698, 691, 701]],\n   ['idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle']],\n  ['control: scenario 3',\n   [680, 690, [686, 679, 704, 696, 678, 658, 635, 648, 643, 657, 650, 661, 672, 691, 687, 685, 708, 687]],\n   ['idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle']]],\n [['regression: scenario 2',\n   [680, 740, [646, 654, 672, 672, 692, 717, 736, 759, 769, 790, 790]],\n   ['heat', 'heat', 'heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['regression: scenario 14',\n   [680, 710, [678, 658, 672, 689, 668, 655, 667, 652, 671, 687, 710, 730, 739, 757, 736, 715]],\n   ['idle',\n    'heat',\n    'heat',\n    'idle',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool']],\n  ['regression: heat then locked cool',\n   [680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],\n   ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],\n  ['control: exact thresholds',\n   [680, 720, [670, 669, 680, 731, 730, 720, 721]],\n   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['regression: short heat cycle then hot',\n   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],\n   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['control: deadband squeeze',\n   [700, 705, [740, 741, 735, 730, 729]],\n   ['idle', 'cool', 'cool', 'idle', 'idle']],\n  ['control: scenario 1',\n   [680, 710, [758, 778, 769, 788, 780, 787, 790, 773, 789, 766, 785, 779, 790, 790, 790, 777, 790]],\n   ['cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool']],\n  ['control: scenario 3',\n   [680, 710, [676, 683, 703, 725, 749, 746, 749, 735, 740, 755, 765, 742, 727, 737, 745, 751, 762]],\n   ['idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool']]],\n [['regression: heat then locked cool',\n   [680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],\n   ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],\n  ['regression: scenario 4',\n   [680,\n    690,\n    [646, 652, 631, 639, 659, 675, 688, 663, 665, 676, 683, 703, 716, 725, 740, 750, 730, 725, 738]],\n   ['heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool',\n    'cool',\n    'cool']],\n  ['control: exact thresholds',\n   [680, 720, [670, 669, 680, 731, 730, 720, 721]],\n   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['regression: short heat cycle then hot',\n   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],\n   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['control: deadband squeeze',\n   [700, 705, [740, 741, 735, 730, 729]],\n   ['idle', 'cool', 'cool', 'idle', 'idle']],\n  ['control: scenario 1',\n   [680,\n    690,\n    [671, 646, 636, 635, 620, 625, 635, 653, 659, 636, 645, 633, 620, 628, 620, 638, 653, 655, 666, 678]],\n   ['idle',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat']],\n  ['control: scenario 2',\n   [680, 740, [655, 638, 663, 641, 622, 636, 628, 621, 621, 620, 635, 632, 644, 651, 656, 675, 687]],\n   ['heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle']],\n  ['control: scenario 3',\n   [680, 690, [720, 710, 717, 702, 708, 715, 732, 753, 772, 753, 757]],\n   ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool']]],\n [['regression: heat then locked cool',\n   [680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],\n   ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],\n  ['regression: scenario 28',\n   [680, 710, [657, 667, 661, 671, 681, 698, 707, 731, 740, 738, 726, 715, 719, 716, 694, 717]],\n   ['heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'idle',\n    'idle']],\n  ['control: exact thresholds',\n   [680, 720, [670, 669, 680, 731, 730, 720, 721]],\n   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['regression: short heat cycle then hot',\n   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],\n   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['control: deadband squeeze',\n   [700, 705, [740, 741, 735, 730, 729]],\n   ['idle', 'cool', 'cool', 'idle', 'idle']],\n  ['control: scenario 1',\n   [680, 720, [774, 779, 785, 790, 790, 781, 778, 777, 770, 767, 743, 730, 708, 690]],\n   ['cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'idle',\n    'idle']],\n  ['control: scenario 2',\n   [680,\n    710,\n    [701,\n     679,\n     702,\n     721,\n     727,\n     748,\n     743,\n     720,\n     741,\n     738,\n     762,\n     769,\n     758,\n     740,\n     744,\n     725,\n     748,\n     728,\n     742,\n     755,\n     769]],\n   ['idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool']],\n  ['control: scenario 3',\n   [680, 720, [645, 664, 650, 632, 620, 628, 652, 666, 661, 660, 644]],\n   ['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat']]]]\nfor label, args, expected in fixtures[N-1]:\n    check(label, solve(*args), expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"},"broken":{"sha256":"72f66789348c9b4c25a3ea6b433e06c3f20238ae5925b3cbd3e90c4eaa0b0770","source":"\"\"\"Failure Map reference implementation. Python standard library only.\"\"\"\nimport json\n\nN = 1\nobservations = []\ndef solve(heat_sp, cool_sp, temps):\n    diff = 10\n    if cool_sp - heat_sp < 30:\n        cool_sp = heat_sp + 30\n    state = 'idle'\n    last_heat_end = None\n    last_cool_end = None\n    out = []\n    for minute, temp in enumerate(temps):\n        if state == 'heat' and temp >= heat_sp:\n            state = 'idle'\n        elif state == 'cool' and temp <= cool_sp:\n            state = 'idle'\n            last_cool_end = minute\n        if state == 'idle':\n            if temp < heat_sp - diff and (last_cool_end is None or minute - last_cool_end >= 5):\n                state = 'heat'\n                last_heat_end = minute\n            elif temp > cool_sp + diff and (last_heat_end is None or minute - last_heat_end >= 5):\n                state = 'cool'\n        out.append(state)\n    return out\ndef check(label, actual, expected):\n    observations.append({\"check\": label, \"actual\": actual, \"expected\": expected, \"passed\": actual == expected})\nfixtures = [[['regression: heat then locked cool',\n   [680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],\n   ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],\n  ['regression: short heat cycle then hot',\n   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],\n   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['control: exact thresholds',\n   [680, 720, [670, 669, 680, 731, 730, 720, 721]],\n   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['control: deadband squeeze',\n   [700, 705, [740, 741, 735, 730, 729]],\n   ['idle', 'cool', 'cool', 'idle', 'idle']],\n  ['control: scenario 1',\n   [680, 690, [693, 687, 709, 709, 685, 704, 684, 680, 686, 687, 679]],\n   ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['control: scenario 2',\n   [680, 700, [681, 686, 698, 703, 724, 744, 752, 732, 755, 742, 762, 772]],\n   ['idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool']],\n  ['control: scenario 3',\n   [680,\n    740,\n    [649,\n     667,\n     643,\n     622,\n     637,\n     657,\n     634,\n     649,\n     674,\n     654,\n     672,\n     675,\n     698,\n     687,\n     666,\n     654,\n     670,\n     645,\n     654,\n     637,\n     651,\n     672]],\n   ['heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'idle',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat']],\n  ['control: scenario 4',\n   [680, 720, [678, 693, 708, 706, 691, 698, 702, 725, 720, 725, 717, 703, 717, 731, 714, 728, 740, 735]],\n   ['idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'idle',\n    'idle',\n    'cool',\n    'cool']]],\n [['regression: short heat cycle then hot',\n   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],\n   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['regression: scenario 25',\n   [680, 710, [674, 651, 640, 644, 663, 686, 668, 681, 706, 727, 723, 727, 746, 737, 716, 713, 725, 747]],\n   ['idle',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'heat',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool']],\n  ['regression: heat then locked cool',\n   [680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],\n   ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],\n  ['control: exact thresholds',\n   [680, 720, [670, 669, 680, 731, 730, 720, 721]],\n   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['control: deadband squeeze',\n   [700, 705, [740, 741, 735, 730, 729]],\n   ['idle', 'cool', 'cool', 'idle', 'idle']],\n  ['control: scenario 1',\n   [680, 700, [687, 695, 710, 721, 712, 724, 736, 738, 737, 740, 748]],\n   ['idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool', 'cool']],\n  ['control: scenario 2',\n   [680, 710, [674, 681, 688, 699, 718, 698, 716, 709, 691, 672, 678, 701, 724, 699, 683, 698, 691, 701]],\n   ['idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle']],\n  ['control: scenario 3',\n   [680, 690, [686, 679, 704, 696, 678, 658, 635, 648, 643, 657, 650, 661, 672, 691, 687, 685, 708, 687]],\n   ['idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle']]],\n [['regression: scenario 2',\n   [680, 740, [646, 654, 672, 672, 692, 717, 736, 759, 769, 790, 790]],\n   ['heat', 'heat', 'heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['regression: scenario 14',\n   [680, 710, [678, 658, 672, 689, 668, 655, 667, 652, 671, 687, 710, 730, 739, 757, 736, 715]],\n   ['idle',\n    'heat',\n    'heat',\n    'idle',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool']],\n  ['regression: heat then locked cool',\n   [680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],\n   ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],\n  ['control: exact thresholds',\n   [680, 720, [670, 669, 680, 731, 730, 720, 721]],\n   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['regression: short heat cycle then hot',\n   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],\n   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['control: deadband squeeze',\n   [700, 705, [740, 741, 735, 730, 729]],\n   ['idle', 'cool', 'cool', 'idle', 'idle']],\n  ['control: scenario 1',\n   [680, 710, [758, 778, 769, 788, 780, 787, 790, 773, 789, 766, 785, 779, 790, 790, 790, 777, 790]],\n   ['cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool']],\n  ['control: scenario 3',\n   [680, 710, [676, 683, 703, 725, 749, 746, 749, 735, 740, 755, 765, 742, 727, 737, 745, 751, 762]],\n   ['idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool']]],\n [['regression: heat then locked cool',\n   [680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],\n   ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],\n  ['regression: scenario 4',\n   [680,\n    690,\n    [646, 652, 631, 639, 659, 675, 688, 663, 665, 676, 683, 703, 716, 725, 740, 750, 730, 725, 738]],\n   ['heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool',\n    'cool',\n    'cool']],\n  ['control: exact thresholds',\n   [680, 720, [670, 669, 680, 731, 730, 720, 721]],\n   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['regression: short heat cycle then hot',\n   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],\n   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['control: deadband squeeze',\n   [700, 705, [740, 741, 735, 730, 729]],\n   ['idle', 'cool', 'cool', 'idle', 'idle']],\n  ['control: scenario 1',\n   [680,\n    690,\n    [671, 646, 636, 635, 620, 625, 635, 653, 659, 636, 645, 633, 620, 628, 620, 638, 653, 655, 666, 678]],\n   ['idle',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat']],\n  ['control: scenario 2',\n   [680, 740, [655, 638, 663, 641, 622, 636, 628, 621, 621, 620, 635, 632, 644, 651, 656, 675, 687]],\n   ['heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle']],\n  ['control: scenario 3',\n   [680, 690, [720, 710, 717, 702, 708, 715, 732, 753, 772, 753, 757]],\n   ['idle', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'cool', 'cool', 'cool']]],\n [['regression: heat then locked cool',\n   [680, 700, [650, 660, 690, 700, 712, 725, 725, 725, 715, 710]],\n   ['heat', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool', 'idle']],\n  ['regression: scenario 28',\n   [680, 710, [657, 667, 661, 671, 681, 698, 707, 731, 740, 738, 726, 715, 719, 716, 694, 717]],\n   ['heat',\n    'heat',\n    'heat',\n    'heat',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'idle',\n    'idle']],\n  ['control: exact thresholds',\n   [680, 720, [670, 669, 680, 731, 730, 720, 721]],\n   ['idle', 'heat', 'idle', 'idle', 'idle', 'idle', 'idle']],\n  ['regression: short heat cycle then hot',\n   [680, 760, [660, 685, 790, 790, 790, 790, 790, 790]],\n   ['heat', 'idle', 'idle', 'idle', 'idle', 'idle', 'cool', 'cool']],\n  ['control: deadband squeeze',\n   [700, 705, [740, 741, 735, 730, 729]],\n   ['idle', 'cool', 'cool', 'idle', 'idle']],\n  ['control: scenario 1',\n   [680, 720, [774, 779, 785, 790, 790, 781, 778, 777, 770, 767, 743, 730, 708, 690]],\n   ['cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'idle',\n    'idle']],\n  ['control: scenario 2',\n   [680,\n    710,\n    [701,\n     679,\n     702,\n     721,\n     727,\n     748,\n     743,\n     720,\n     741,\n     738,\n     762,\n     769,\n     758,\n     740,\n     744,\n     725,\n     748,\n     728,\n     742,\n     755,\n     769]],\n   ['idle',\n    'idle',\n    'idle',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool',\n    'cool']],\n  ['control: scenario 3',\n   [680, 720, [645, 664, 650, 632, 620, 628, 652, 666, 661, 660, 644]],\n   ['heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat', 'heat']]]]\nfor label, args, expected in fixtures[N-1]:\n    check(label, solve(*args), expected)\nprint(json.dumps({\"observations\": observations, \"passed\": all(x[\"passed\"] for x in observations)}, ensure_ascii=False))\nraise SystemExit(0 if all(x[\"passed\"] for x in observations) else 1)\n"}},"limitations":"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.","method":"Deterministic executable model with adversarial boundary fixtures.","provenance":{"created_by":"Failure Map","dependencies":"Python standard library","family":"w2-hvac-thermostat-control-auto-changeover-deadband-changeover-reference","generated_at":"2026-09-29T14:51:44.998670+00:00","license":"CC0-1.0","python":"3.12.14","seed":1,"split":"open-access"},"relevance":"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.","root_cause":"The heating timestamp used for the changeover lockout is recorded at heat start rather than heat end.","sha256":"6b1656fb9bc34ea04d2040fd71ff7c354984535437db8b360a65afa400bf93a8","title":"Changeover delay is measured from when heating started · case 01","variant":1,"variant_policy":"Five numbered records share a model and may reuse boundary fixtures.","verified":true,"visibility":"public","verification":{"attempt":{"elapsed_ms":43.465,"exit_code":1,"observations":[{"actual":["heat","heat","idle","idle","idle","idle","cool","cool","cool","idle"],"check":"regression: heat then locked cool","expected":["heat","heat","idle","idle","idle","idle","idle","cool","cool","idle"],"passed":false},{"actual":["heat","idle","idle","idle","idle","cool","cool","cool"],"check":"regression: short heat cycle then hot","expected":["heat","idle","idle","idle","idle","idle","cool","cool"],"passed":false},{"actual":["idle","heat","idle","idle","idle","idle","idle"],"check":"control: exact thresholds","expected":["idle","heat","idle","idle","idle","idle","idle"],"passed":true},{"actual":["idle","cool","cool","idle","idle"],"check":"control: deadband squeeze","expected":["idle","cool","cool","idle","idle"],"passed":true},{"actual":["idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle"],"check":"control: scenario 1","expected":["idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle"],"passed":true},{"actual":["idle","idle","idle","idle","cool","cool","cool","cool","cool","cool","cool","cool"],"check":"control: scenario 2","expected":["idle","idle","idle","idle","cool","cool","cool","cool","cool","cool","cool","cool"],"passed":true},{"actual":["heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","idle","idle","heat","heat","heat","heat","heat","heat","heat","heat"],"check":"control: scenario 3","expected":["heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","idle","idle","heat","heat","heat","heat","heat","heat","heat","heat"],"passed":true},{"actual":["idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","cool","idle","idle","cool","cool"],"check":"control: scenario 4","expected":["idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","cool","idle","idle","cool","cool"],"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"regression: heat then locked cool\", \"actual\": [\"heat\", \"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\", \"cool\", \"idle\"], \"expected\": [\"heat\", \"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\", \"idle\"], \"passed\": false}, {\"check\": \"regression: short heat cycle then hot\", \"actual\": [\"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\", \"cool\"], \"expected\": [\"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\"], \"passed\": false}, {\"check\": \"control: exact thresholds\", \"actual\": [\"idle\", \"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\"], \"expected\": [\"idle\", \"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\"], \"passed\": true}, {\"check\": \"control: deadband squeeze\", \"actual\": [\"idle\", \"cool\", \"cool\", \"idle\", \"idle\"], \"expected\": [\"idle\", \"cool\", \"cool\", \"idle\", \"idle\"], \"passed\": true}, {\"check\": \"control: scenario 1\", \"actual\": [\"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\"], \"expected\": [\"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\"], \"passed\": true}, {\"check\": \"control: scenario 2\", \"actual\": [\"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\"], \"expected\": [\"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\"], \"passed\": true}, {\"check\": \"control: scenario 3\", \"actual\": [\"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"idle\", \"idle\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\"], \"expected\": [\"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"idle\", \"idle\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\"], \"passed\": true}, {\"check\": \"control: scenario 4\", \"actual\": [\"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"idle\", \"idle\", \"cool\", \"cool\"], \"expected\": [\"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"idle\", \"idle\", \"cool\", \"cool\"], \"passed\": true}], \"passed\": false}\n"},"broken":{"elapsed_ms":39.782,"exit_code":1,"observations":[{"actual":["heat","heat","idle","idle","idle","cool","cool","cool","cool","idle"],"check":"regression: heat then locked cool","expected":["heat","heat","idle","idle","idle","idle","idle","cool","cool","idle"],"passed":false},{"actual":["heat","idle","idle","idle","idle","cool","cool","cool"],"check":"regression: short heat cycle then hot","expected":["heat","idle","idle","idle","idle","idle","cool","cool"],"passed":false},{"actual":["idle","heat","idle","idle","idle","idle","idle"],"check":"control: exact thresholds","expected":["idle","heat","idle","idle","idle","idle","idle"],"passed":true},{"actual":["idle","cool","cool","idle","idle"],"check":"control: deadband squeeze","expected":["idle","cool","cool","idle","idle"],"passed":true},{"actual":["idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle"],"check":"control: scenario 1","expected":["idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle"],"passed":true},{"actual":["idle","idle","idle","idle","cool","cool","cool","cool","cool","cool","cool","cool"],"check":"control: scenario 2","expected":["idle","idle","idle","idle","cool","cool","cool","cool","cool","cool","cool","cool"],"passed":true},{"actual":["heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","idle","idle","heat","heat","heat","heat","heat","heat","heat","heat"],"check":"control: scenario 3","expected":["heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","heat","idle","idle","heat","heat","heat","heat","heat","heat","heat","heat"],"passed":true},{"actual":["idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","cool","idle","idle","cool","cool"],"check":"control: scenario 4","expected":["idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","idle","cool","idle","idle","cool","cool"],"passed":true}],"passed":false,"stderr":"","stdout":"{\"observations\": [{\"check\": \"regression: heat then locked cool\", \"actual\": [\"heat\", \"heat\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\", \"cool\", \"cool\", \"idle\"], \"expected\": [\"heat\", \"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\", \"idle\"], \"passed\": false}, {\"check\": \"regression: short heat cycle then hot\", \"actual\": [\"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\", \"cool\"], \"expected\": [\"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\"], \"passed\": false}, {\"check\": \"control: exact thresholds\", \"actual\": [\"idle\", \"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\"], \"expected\": [\"idle\", \"heat\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\"], \"passed\": true}, {\"check\": \"control: deadband squeeze\", \"actual\": [\"idle\", \"cool\", \"cool\", \"idle\", \"idle\"], \"expected\": [\"idle\", \"cool\", \"cool\", \"idle\", \"idle\"], \"passed\": true}, {\"check\": \"control: scenario 1\", \"actual\": [\"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\"], \"expected\": [\"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\"], \"passed\": true}, {\"check\": \"control: scenario 2\", \"actual\": [\"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\"], \"expected\": [\"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\", \"cool\"], \"passed\": true}, {\"check\": \"control: scenario 3\", \"actual\": [\"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"idle\", \"idle\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\"], \"expected\": [\"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"idle\", \"idle\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\", \"heat\"], \"passed\": true}, {\"check\": \"control: scenario 4\", \"actual\": [\"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"idle\", \"idle\", \"cool\", \"cool\"], \"expected\": [\"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"idle\", \"cool\", \"idle\", \"idle\", \"cool\", \"cool\"], \"passed\": true}], \"passed\": false}\n"}},"member_only":{"stages":["fixed"],"fields":["implementations.fixed","verification.fixed","harness","repair"],"note":"The verified repair, its recorded checks, the repair description, and the scoring harness are available to members."}}