FAILURE MAP
← Case archive

FA-92511 / HVAC thermostat control / Open access

Celsius press moves a whole degree Celsius · case 01

Each arrow press in C mode jumps 1 C (1.8 F) instead of 0.5 C.

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

ROOT CAUSE

The stored value is rebuilt with 18 tenths F per half-degree step.

VERIFIED REPAIR

One half-degree C is exactly 9 tenths F.

Unsuccessful approach: Adding 9 tenths to the raw stored value keeps off-grid values off the 0.5 C grid.

Case contract

Thermostat setpoint keypad. The setpoint is stored in tenths F. In C mode the display shows the stored value converted to C and rounded half up to 0.5 C; a press moves the displayed value by 0.5 C and stores exactly 320 + half_degrees*9 tenths F. In F mode the display rounds half up to whole F and a press moves the whole-degree value by 1 F. After each press the stored value is clamped to 45.0-90.0 F. Return [display, stored_tenths_F] per press.

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(unit, presses, start_f10):
    f10 = start_f10
    out = []
    for p in presses:
        step = 1 if p == 'up' else -1
        if unit == 'C':
            c_half = (2 * (f10 - 320) + 9) // 18
            f10 = 320 + (c_half + step) * 18
        else:
            f_whole = (f10 + 5) // 10
            f10 = (f_whole + step) * 10
        f10 = max(450, min(900, f10))
        if unit == 'C':
            c_half = (2 * (f10 - 320) + 9) // 18
            out.append(['%d.%d' % (c_half // 2, 5 * (c_half % 2)), f10])
        else:
            out.append([str((f10 + 5) // 10), f10])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['control: scenario 1',
   ['F', ['down', 'down', 'up', 'up', 'up', 'down', 'down', 'up'], 716],
   [['71', 710], ['70', 700], ['71', 710], ['72', 720], ['73', 730], ['72', 720], ['71', 710], ['72', 720]]],
  ['regression: scenario 2',
   ['C', ['down', 'down', 'down', 'up', 'down', 'down', 'up'], 716],
   [['21.5', 707],
    ['21.0', 698],
    ['20.5', 689],
    ['21.0', 698],
    ['20.5', 689],
    ['20.0', 680],
    ['20.5', 689]]]],
 [['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['control: scenario 1',
   ['F', ['down', 'down', 'up', 'up', 'down', 'down', 'down', 'up'], 652],
   [['64', 640], ['63', 630], ['64', 640], ['65', 650], ['64', 640], ['63', 630], ['62', 620], ['63', 630]]],
  ['control: scenario 2',
   ['F', ['down', 'down', 'up', 'down', 'up', 'down', 'up', 'up'], 725],
   [['72', 720], ['71', 710], ['72', 720], ['71', 710], ['72', 720], ['71', 710], ['72', 720], ['73', 730]]]],
 [['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['regression: scenario 7',
   ['C', ['up', 'down', 'up', 'up'], 905],
   [['32.0', 900], ['31.5', 887], ['32.0', 896], ['32.0', 900]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['regression: scenario 1',
   ['C', ['up', 'up', 'down', 'up', 'down'], 707],
   [['22.0', 716], ['22.5', 725], ['22.0', 716], ['22.5', 725], ['22.0', 716]]]],
 [['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['regression: scenario 6',
   ['C', ['up', 'up', 'down', 'down', 'down', 'down', 'down', 'down'], 905],
   [['32.0', 900],
    ['32.0', 900],
    ['31.5', 887],
    ['31.0', 878],
    ['30.5', 869],
    ['30.0', 860],
    ['29.5', 851],
    ['29.0', 842]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['control: scenario 1',
   ['F', ['down', 'up', 'up', 'down', 'up', 'up', 'down', 'down'], 716],
   [['71', 710], ['72', 720], ['73', 730], ['72', 720], ['73', 730], ['74', 740], ['73', 730], ['72', 720]]]],
 [['regression: scenario 3',
   ['C', ['up', 'up', 'down', 'down', 'up', 'down', 'down'], 707],
   [['22.0', 716], ['22.5', 725], ['22.0', 716], ['21.5', 707], ['22.0', 716], ['21.5', 707], ['21.0', 698]]],
  ['regression: scenario 10',
   ['C', ['up', 'down', 'down', 'up', 'down', 'up', 'down', 'down', 'down'], 577],
   [['15.0', 590],
    ['14.5', 581],
    ['14.0', 572],
    ['14.5', 581],
    ['14.0', 572],
    ['14.5', 581],
    ['14.0', 572],
    ['13.5', 563],
    ['13.0', 554]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]]]]
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: celsius steps[['32.0', 900], ['32.0', 900], ['32.0', 900]][['22.5', 725], ['23.0', 734], ['22.5', 725]]Failed
regression: celsius lower clamp[['14.0', 572], ['27.0', 806], ['32.0', 900]][['7.0', 450], ['7.0', 450], ['7.5', 455]]Failed
control: fahrenheit half value[['74', 740], ['73', 730], ['72', 720]][['74', 740], ['73', 730], ['72', 720]]Passed
control: upper clamp[['90', 900]][['90', 900]]Passed
regression: celsius off-grid start[['32.0', 900], ['32.0', 900]][['21.5', 707], ['22.0', 716]]Failed
regression: celsius half-step display[['32.0', 900]][['22.0', 716]]Failed
control: scenario 1[['71', 710], ['70', 700], ['71', 710], ['72', 720], ['73', 730], ['72', 720], ['71', 710], ['72', 720]][['71', 710], ['70', 700], ['71', 710], ['72', 720], ['73', 730], ['72', 720], ['71', 710], ['72', 720]]Passed
regression: scenario 2[['32.0', 900], ['32.0', 900], ['32.0', 900], ['32.0', 900], ['32.0', 900], ['32.0', 900], ['32.0', 900]][['21.5', 707], ['21.0', 698], ['20.5', 689], ['21.0', 698], ['20.5', 689], ['20.0', 680], ['20.5', 689]]Failed

SHA-256 / 4ac10f6f38bdb3bf542b028e94081be48378900aaaaaa928f954ca1b399d9e2b

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(unit, presses, start_f10):
    f10 = start_f10
    out = []
    for p in presses:
        step = 1 if p == 'up' else -1
        if unit == 'C':
            c_half = (2 * (f10 - 320) + 9) // 18
            f10 = f10 + step * 9
        else:
            f_whole = (f10 + 5) // 10
            f10 = (f_whole + step) * 10
        f10 = max(450, min(900, f10))
        if unit == 'C':
            c_half = (2 * (f10 - 320) + 9) // 18
            out.append(['%d.%d' % (c_half // 2, 5 * (c_half % 2)), f10])
        else:
            out.append([str((f10 + 5) // 10), f10])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['control: scenario 1',
   ['F', ['down', 'down', 'up', 'up', 'up', 'down', 'down', 'up'], 716],
   [['71', 710], ['70', 700], ['71', 710], ['72', 720], ['73', 730], ['72', 720], ['71', 710], ['72', 720]]],
  ['regression: scenario 2',
   ['C', ['down', 'down', 'down', 'up', 'down', 'down', 'up'], 716],
   [['21.5', 707],
    ['21.0', 698],
    ['20.5', 689],
    ['21.0', 698],
    ['20.5', 689],
    ['20.0', 680],
    ['20.5', 689]]]],
 [['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['control: scenario 1',
   ['F', ['down', 'down', 'up', 'up', 'down', 'down', 'down', 'up'], 652],
   [['64', 640], ['63', 630], ['64', 640], ['65', 650], ['64', 640], ['63', 630], ['62', 620], ['63', 630]]],
  ['control: scenario 2',
   ['F', ['down', 'down', 'up', 'down', 'up', 'down', 'up', 'up'], 725],
   [['72', 720], ['71', 710], ['72', 720], ['71', 710], ['72', 720], ['71', 710], ['72', 720], ['73', 730]]]],
 [['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['regression: scenario 7',
   ['C', ['up', 'down', 'up', 'up'], 905],
   [['32.0', 900], ['31.5', 887], ['32.0', 896], ['32.0', 900]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['regression: scenario 1',
   ['C', ['up', 'up', 'down', 'up', 'down'], 707],
   [['22.0', 716], ['22.5', 725], ['22.0', 716], ['22.5', 725], ['22.0', 716]]]],
 [['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['regression: scenario 6',
   ['C', ['up', 'up', 'down', 'down', 'down', 'down', 'down', 'down'], 905],
   [['32.0', 900],
    ['32.0', 900],
    ['31.5', 887],
    ['31.0', 878],
    ['30.5', 869],
    ['30.0', 860],
    ['29.5', 851],
    ['29.0', 842]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['control: scenario 1',
   ['F', ['down', 'up', 'up', 'down', 'up', 'up', 'down', 'down'], 716],
   [['71', 710], ['72', 720], ['73', 730], ['72', 720], ['73', 730], ['74', 740], ['73', 730], ['72', 720]]]],
 [['regression: scenario 3',
   ['C', ['up', 'up', 'down', 'down', 'up', 'down', 'down'], 707],
   [['22.0', 716], ['22.5', 725], ['22.0', 716], ['21.5', 707], ['22.0', 716], ['21.5', 707], ['21.0', 698]]],
  ['regression: scenario 10',
   ['C', ['up', 'down', 'down', 'up', 'down', 'up', 'down', 'down', 'down'], 577],
   [['15.0', 590],
    ['14.5', 581],
    ['14.0', 572],
    ['14.5', 581],
    ['14.0', 572],
    ['14.5', 581],
    ['14.0', 572],
    ['13.5', 563],
    ['13.0', 554]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]]]]
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: celsius steps[['22.5', 729], ['23.0', 738], ['22.5', 729]][['22.5', 725], ['23.0', 734], ['22.5', 725]]Failed
regression: celsius lower clamp[['7.0', 450], ['7.0', 450], ['7.5', 459]][['7.0', 450], ['7.0', 450], ['7.5', 455]]Failed
control: fahrenheit half value[['74', 740], ['73', 730], ['72', 720]][['74', 740], ['73', 730], ['72', 720]]Passed
control: upper clamp[['90', 900]][['90', 900]]Passed
regression: celsius off-grid start[['21.5', 707], ['22.0', 716]][['21.5', 707], ['22.0', 716]]Passed
regression: celsius half-step display[['22.0', 716]][['22.0', 716]]Passed
control: scenario 1[['71', 710], ['70', 700], ['71', 710], ['72', 720], ['73', 730], ['72', 720], ['71', 710], ['72', 720]][['71', 710], ['70', 700], ['71', 710], ['72', 720], ['73', 730], ['72', 720], ['71', 710], ['72', 720]]Passed
regression: scenario 2[['21.5', 707], ['21.0', 698], ['20.5', 689], ['21.0', 698], ['20.5', 689], ['20.0', 680], ['20.5', 689]][['21.5', 707], ['21.0', 698], ['20.5', 689], ['21.0', 698], ['20.5', 689], ['20.0', 680], ['20.5', 689]]Passed

SHA-256 / a06f25df3b7162cb65189ce604a5d77b959821a2275b6e8ed511a0d0c15f92cf

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(unit, presses, start_f10):
    f10 = start_f10
    out = []
    for p in presses:
        step = 1 if p == 'up' else -1
        if unit == 'C':
            c_half = (2 * (f10 - 320) + 9) // 18
            f10 = 320 + (c_half + step) * 9
        else:
            f_whole = (f10 + 5) // 10
            f10 = (f_whole + step) * 10
        f10 = max(450, min(900, f10))
        if unit == 'C':
            c_half = (2 * (f10 - 320) + 9) // 18
            out.append(['%d.%d' % (c_half // 2, 5 * (c_half % 2)), f10])
        else:
            out.append([str((f10 + 5) // 10), f10])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['control: scenario 1',
   ['F', ['down', 'down', 'up', 'up', 'up', 'down', 'down', 'up'], 716],
   [['71', 710], ['70', 700], ['71', 710], ['72', 720], ['73', 730], ['72', 720], ['71', 710], ['72', 720]]],
  ['regression: scenario 2',
   ['C', ['down', 'down', 'down', 'up', 'down', 'down', 'up'], 716],
   [['21.5', 707],
    ['21.0', 698],
    ['20.5', 689],
    ['21.0', 698],
    ['20.5', 689],
    ['20.0', 680],
    ['20.5', 689]]]],
 [['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['control: scenario 1',
   ['F', ['down', 'down', 'up', 'up', 'down', 'down', 'down', 'up'], 652],
   [['64', 640], ['63', 630], ['64', 640], ['65', 650], ['64', 640], ['63', 630], ['62', 620], ['63', 630]]],
  ['control: scenario 2',
   ['F', ['down', 'down', 'up', 'down', 'up', 'down', 'up', 'up'], 725],
   [['72', 720], ['71', 710], ['72', 720], ['71', 710], ['72', 720], ['71', 710], ['72', 720], ['73', 730]]]],
 [['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['regression: scenario 7',
   ['C', ['up', 'down', 'up', 'up'], 905],
   [['32.0', 900], ['31.5', 887], ['32.0', 896], ['32.0', 900]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['regression: scenario 1',
   ['C', ['up', 'up', 'down', 'up', 'down'], 707],
   [['22.0', 716], ['22.5', 725], ['22.0', 716], ['22.5', 725], ['22.0', 716]]]],
 [['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]],
  ['regression: scenario 6',
   ['C', ['up', 'up', 'down', 'down', 'down', 'down', 'down', 'down'], 905],
   [['32.0', 900],
    ['32.0', 900],
    ['31.5', 887],
    ['31.0', 878],
    ['30.5', 869],
    ['30.0', 860],
    ['29.5', 851],
    ['29.0', 842]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['control: scenario 1',
   ['F', ['down', 'up', 'up', 'down', 'up', 'up', 'down', 'down'], 716],
   [['71', 710], ['72', 720], ['73', 730], ['72', 720], ['73', 730], ['74', 740], ['73', 730], ['72', 720]]]],
 [['regression: scenario 3',
   ['C', ['up', 'up', 'down', 'down', 'up', 'down', 'down'], 707],
   [['22.0', 716], ['22.5', 725], ['22.0', 716], ['21.5', 707], ['22.0', 716], ['21.5', 707], ['21.0', 698]]],
  ['regression: scenario 10',
   ['C', ['up', 'down', 'down', 'up', 'down', 'up', 'down', 'down', 'down'], 577],
   [['15.0', 590],
    ['14.5', 581],
    ['14.0', 572],
    ['14.5', 581],
    ['14.0', 572],
    ['14.5', 581],
    ['14.0', 572],
    ['13.5', 563],
    ['13.0', 554]]],
  ['regression: celsius steps',
   ['C', ['up', 'up', 'down'], 720],
   [['22.5', 725], ['23.0', 734], ['22.5', 725]]],
  ['control: fahrenheit half value',
   ['F', ['up', 'down', 'down'], 725],
   [['74', 740], ['73', 730], ['72', 720]]],
  ['control: upper clamp', ['F', ['up'], 895], [['90', 900]]],
  ['regression: celsius off-grid start', ['C', ['down', 'up'], 716], [['21.5', 707], ['22.0', 716]]],
  ['regression: celsius lower clamp',
   ['C', ['down', 'down', 'up'], 452],
   [['7.0', 450], ['7.0', 450], ['7.5', 455]]],
  ['regression: celsius half-step display', ['C', ['up'], 707], [['22.0', 716]]]]]
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: celsius steps[['22.5', 725], ['23.0', 734], ['22.5', 725]][['22.5', 725], ['23.0', 734], ['22.5', 725]]Passed
regression: celsius lower clamp[['7.0', 450], ['7.0', 450], ['7.5', 455]][['7.0', 450], ['7.0', 450], ['7.5', 455]]Passed
control: fahrenheit half value[['74', 740], ['73', 730], ['72', 720]][['74', 740], ['73', 730], ['72', 720]]Passed
control: upper clamp[['90', 900]][['90', 900]]Passed
regression: celsius off-grid start[['21.5', 707], ['22.0', 716]][['21.5', 707], ['22.0', 716]]Passed
regression: celsius half-step display[['22.0', 716]][['22.0', 716]]Passed
control: scenario 1[['71', 710], ['70', 700], ['71', 710], ['72', 720], ['73', 730], ['72', 720], ['71', 710], ['72', 720]][['71', 710], ['70', 700], ['71', 710], ['72', 720], ['73', 730], ['72', 720], ['71', 710], ['72', 720]]Passed
regression: scenario 2[['21.5', 707], ['21.0', 698], ['20.5', 689], ['21.0', 698], ['20.5', 689], ['20.0', 680], ['20.5', 689]][['21.5', 707], ['21.0', 698], ['20.5', 689], ['21.0', 698], ['20.5', 689], ['20.0', 680], ['20.5', 689]]Passed

SHA-256 / 502c9ca86a044c6e2f0611442f1fedcd24747d9864d7033126236ef4a585d679

Verification & scope

A deterministic bounded teaching model of one thermostat or HVAC controller decision evaluated per sample. Temperatures are integer tenths of a degree Fahrenheit unless stated otherwise. The contract is a stipulated toy convention, not a claim of conformance to any manufacturer, ASHRAE guideline or code. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:51:46.455042+00:00.

Case digest / 519a6169ee57ded5cb5c71c497363215d2cf18e8833c7b44e83d8abe880947ac