FAILURE MAP
← Case archive

FA-92526 / HVAC thermostat control / Open access

Economizer enables right at the high limit with no differential · case 01

The economizer toggles on and off every sample when outdoor temperature hovers at the high limit.

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

ROOT CAUSE

The enable test uses outdoor <= high_limit instead of high_limit - 2.0 F.

VERIFIED REPAIR

Enable only at or below high_limit - 2.0 F; disable above high_limit.

Unsuccessful approach: Halving the differential still leaves too narrow a band.

Case contract

Rooftop economizer per sample; samples are [outdoor, return_air, cooling_call, mixed_air, occupied] in tenths F. Economizing is enabled when outdoor <= high_limit - 2.0 F and outdoor < return - diff, and disabled when outdoor > high_limit or outdoor >= return. Unoccupied: damper 0%. Occupied, enabled and cooling: if mixed air < mat_low the damper steps down 10% but not below min_pos, otherwise it steps up 20% from at least min_pos, capped at 100%. Otherwise the damper sits at min_pos. Return [enabled, damper%] per sample.

Why this case matters

Residential and light-commercial thermostats make these decisions sample by sample; each defect changes equipment calls in a way that shows up as short cycling, comfort complaints or equipment stress.

1 / The failure

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

N = 1
observations = []
def solve(settings, samples):
    enabled = False
    pos = 0
    out = []
    for outdoor, ret, cooling, mixed, occupied in samples:
        if enabled and (outdoor > settings['high_limit'] or outdoor >= ret):
            enabled = False
        elif not enabled and outdoor <= settings['high_limit'] and outdoor < ret - settings['diff']:
            enabled = True
        if not occupied:
            pos = 0
        elif enabled and cooling:
            if mixed < settings['mat_low']:
                pos = max(settings['min_pos'], pos - 10)
            else:
                pos = min(100, max(settings['min_pos'], pos) + 20)
        else:
            pos = settings['min_pos']
        out.append([enabled, pos])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['regression: scenario 4',
   [{'high_limit': 650, 'diff': 40, 'min_pos': 15, 'mat_low': 530},
    [[739, 710, True, 528, True],
     [721, 715, True, 537, True],
     [696, 760, False, 541, True],
     [680, 735, True, 575, True],
     [646, 726, True, 550, True],
     [633, 731, False, 494, True],
     [598, 704, True, 565, True],
     [608, 716, False, 614, True]]],
   [[False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [True, 35], [True, 15]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 750, 'diff': 0, 'min_pos': 20, 'mat_low': 500},
    [[648, 701, True, 604, True],
     [640, 708, True, 484, True],
     [671, 716, True, 536, True],
     [649, 721, True, 616, False],
     [640, 749, True, 619, True],
     [651, 751, True, 599, True],
     [669, 718, False, 504, True],
     [638, 704, False, 542, True]]],
   [[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]]],
  ['control: scenario 2',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[460, 757, True, 619, True],
     [462, 720, False, 618, True],
     [502, 734, True, 534, True],
     [488, 707, False, 516, True],
     [450, 735, False, 496, True],
     [472, 754, True, 606, True],
     [477, 741, False, 575, True],
     [452, 710, True, 531, True],
     [431, 759, False, 576, True]]],
   [[True, 40],
    [True, 20],
    [True, 40],
    [True, 20],
    [True, 20],
    [True, 40],
    [True, 20],
    [True, 40],
    [True, 20]]]],
 [['regression: scenario 4',
   [{'high_limit': 650, 'diff': 40, 'min_pos': 30, 'mat_low': 500},
    [[637, 708, True, 552, True],
     [645, 735, True, 591, True],
     [634, 703, True, 564, True],
     [622, 710, True, 543, True],
     [634, 716, True, 534, False],
     [647, 753, True, 529, True],
     [619, 706, True, 541, True],
     [596, 740, True, 617, True],
     [590, 749, True, 524, True],
     [592, 739, True, 566, False],
     [581, 721, True, 554, True],
     [593, 737, False, 599, True],
     [572, 760, True, 524, True],
     [560, 714, True, 512, True]]],
   [[False, 30],
    [False, 30],
    [False, 30],
    [True, 50],
    [True, 0],
    [True, 50],
    [True, 70],
    [True, 90],
    [True, 100],
    [True, 0],
    [True, 50],
    [True, 30],
    [True, 50],
    [True, 70]]],
  ['regression: scenario 10',
   [{'high_limit': 650, 'diff': 40, 'min_pos': 30, 'mat_low': 530},
    [[641, 705, True, 484, True],
     [633, 750, True, 549, True],
     [611, 743, False, 559, True],
     [596, 715, True, 522, True],
     [625, 713, True, 564, True],
     [641, 703, True, 560, True],
     [618, 717, True, 585, True],
     [632, 711, False, 552, True],
     [608, 758, True, 602, True]]],
   [[False, 30],
    [False, 30],
    [True, 30],
    [True, 30],
    [True, 50],
    [True, 70],
    [True, 90],
    [True, 30],
    [True, 50]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 650, 'diff': 0, 'min_pos': 30, 'mat_low': 500},
    [[754, 754, False, 483, False],
     [716, 749, True, 499, True],
     [699, 701, False, 573, True],
     [694, 750, True, 534, False],
     [704, 716, False, 517, True],
     [708, 701, False, 567, True],
     [743, 735, True, 609, True],
     [775, 747, False, 554, True],
     [743, 731, False, 583, False],
     [764, 708, False, 507, True],
     [795, 743, False, 528, True],
     [770, 706, True, 501, True],
     [764, 752, False, 578, True]]],
   [[False, 0],
    [False, 30],
    [False, 30],
    [False, 0],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 0],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30]]]],
 [['regression: scenario 6',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 20, 'mat_low': 530},
    [[763, 718, True, 585, True],
     [759, 718, True, 579, True],
     [756, 741, True, 590, True],
     [742, 724, True, 569, True],
     [765, 717, True, 612, True],
     [763, 727, False, 522, True],
     [735, 744, True, 480, True],
     [715, 733, True, 519, True],
     [685, 712, True, 608, True],
     [656, 730, True, 484, True],
     [659, 758, False, 561, True],
     [625, 726, True, 500, True],
     [640, 750, True, 506, True],
     [640, 711, False, 549, True]]],
   [[False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [True, 20],
    [True, 20],
    [True, 20],
    [True, 20],
    [True, 20]]],
  ['regression: scenario 16',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 30, 'mat_low': 500},
    [[672, 743, True, 486, True],
     [641, 721, True, 613, True],
     [636, 726, True, 567, True],
     [600, 723, False, 611, True],
     [561, 754, True, 587, True],
     [567, 709, True, 591, True],
     [546, 754, False, 499, False],
     [561, 729, False, 540, True]]],
   [[False, 30], [False, 30], [False, 30], [True, 30], [True, 50], [True, 70], [True, 0], [True, 30]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['regression: scenario 1',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[678, 721, True, 495, True],
     [646, 726, True, 616, True],
     [669, 714, True, 600, True],
     [630, 728, True, 542, True],
     [626, 751, True, 530, False],
     [638, 723, False, 607, True],
     [654, 706, True, 557, True],
     [625, 704, False, 618, True]]],
   [[False, 20], [False, 20], [False, 20], [True, 40], [True, 0], [True, 20], [False, 20], [True, 20]]]],
 [['regression: scenario 17',
   [{'high_limit': 700, 'diff': 40, 'min_pos': 15, 'mat_low': 530},
    [[669, 750, False, 557, False],
     [688, 701, True, 577, True],
     [671, 744, False, 534, True],
     [651, 716, False, 567, True],
     [680, 702, False, 569, True],
     [640, 740, True, 559, True],
     [657, 721, True, 556, True],
     [650, 721, True, 516, True],
     [682, 741, True, 511, True],
     [700, 718, True, 582, True],
     [730, 724, True, 482, True],
     [724, 715, False, 517, False],
     [685, 716, False, 568, True],
     [683, 729, True, 588, True]]],
   [[True, 0],
    [True, 35],
    [True, 15],
    [True, 15],
    [True, 15],
    [True, 35],
    [True, 55],
    [True, 45],
    [True, 35],
    [True, 55],
    [False, 15],
    [False, 0],
    [False, 15],
    [False, 15]]],
  ['regression: scenario 48',
   [{'high_limit': 750, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[746, 701, True, 531, True],
     [717, 709, True, 611, True],
     [737, 722, True, 489, True],
     [744, 712, True, 498, True],
     [731, 701, True, 552, True],
     [701, 730, True, 531, True],
     [738, 741, True, 619, True],
     [742, 738, True, 561, True],
     [771, 757, True, 582, True],
     [785, 738, False, 566, True],
     [760, 739, True, 489, True],
     [748, 757, True, 534, True],
     [734, 758, True, 568, True],
     [750, 722, False, 549, True]]],
   [[False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [True, 50],
    [True, 70],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 30, 'mat_low': 500},
    [[505, 738, False, 612, True],
     [535, 706, True, 498, True],
     [549, 734, False, 604, True],
     [558, 729, True, 521, True],
     [540, 724, True, 571, True],
     [530, 749, True, 592, True],
     [507, 715, True, 533, False],
     [468, 703, False, 516, True],
     [457, 715, False, 563, True],
     [444, 708, False, 513, True]]],
   [[True, 30],
    [True, 30],
    [True, 30],
    [True, 50],
    [True, 70],
    [True, 90],
    [True, 0],
    [True, 30],
    [True, 30],
    [True, 30]]]],
 [['regression: scenario 26',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[601, 732, False, 620, True],
     [635, 736, True, 571, True],
     [666, 716, False, 556, True],
     [646, 714, False, 491, True],
     [625, 747, True, 525, True],
     [631, 760, True, 584, True],
     [640, 723, True, 496, True],
     [600, 744, False, 537, True],
     [579, 717, True, 514, True],
     [611, 736, False, 614, True],
     [639, 745, False, 496, True],
     [602, 732, False, 490, False]]],
   [[True, 15],
    [True, 35],
    [False, 15],
    [False, 15],
    [True, 35],
    [True, 55],
    [True, 45],
    [True, 15],
    [True, 35],
    [True, 15],
    [True, 15],
    [True, 0]]],
  ['regression: scenario 36',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[679, 739, True, 601, True],
     [689, 742, True, 510, True],
     [652, 741, True, 598, True],
     [631, 734, True, 539, True],
     [671, 731, False, 552, False],
     [637, 737, False, 532, True],
     [675, 744, False, 599, True],
     [666, 748, True, 508, True],
     [661, 721, True, 577, True]]],
   [[False, 15],
    [False, 15],
    [False, 15],
    [False, 15],
    [False, 0],
    [False, 15],
    [False, 15],
    [False, 15],
    [False, 15]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 750, 'diff': 0, 'min_pos': 20, 'mat_low': 500},
    [[684, 714, False, 579, True],
     [715, 719, False, 481, True],
     [737, 759, True, 615, False],
     [715, 756, True, 609, False],
     [743, 742, True, 491, True],
     [776, 713, False, 491, True],
     [798, 748, False, 566, True],
     [800, 751, True, 547, True],
     [800, 736, True, 486, True],
     [760, 727, False, 570, True]]],
   [[True, 20],
    [True, 20],
    [True, 0],
    [True, 0],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20]]]]]
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: enable band edges[[True, 40], [True, 60], [True, 80], [True, 100], [False, 20]][[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]Failed
regression: scenario 4[[False, 15], [False, 15], [False, 15], [False, 15], [True, 35], [True, 15], [True, 35], [True, 15]][[False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [True, 35], [True, 15]]Failed
control: economizer cycle[[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]][[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]Passed
control: cold mixed air[[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]][[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]Passed
control: night cooling call[[True, 0], [True, 40]][[True, 0], [True, 40]]Passed
control: near return disable[[True, 35], [True, 55], [True, 75], [False, 15]][[True, 35], [True, 55], [True, 75], [False, 15]]Passed
control: scenario 1[[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]][[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]]Passed
control: scenario 2[[True, 40], [True, 20], [True, 40], [True, 20], [True, 20], [True, 40], [True, 20], [True, 40], [True, 20]][[True, 40], [True, 20], [True, 40], [True, 20], [True, 20], [True, 40], [True, 20], [True, 40], [True, 20]]Passed

SHA-256 / 2f08e64f5b28ab6ebf727cdffc650ab83ad79b6f21ef639ca81da0da19e94bf6

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(settings, samples):
    enabled = False
    pos = 0
    out = []
    for outdoor, ret, cooling, mixed, occupied in samples:
        if enabled and (outdoor > settings['high_limit'] or outdoor >= ret):
            enabled = False
        elif not enabled and outdoor <= settings['high_limit'] - 10 and outdoor < ret - settings['diff']:
            enabled = True
        if not occupied:
            pos = 0
        elif enabled and cooling:
            if mixed < settings['mat_low']:
                pos = max(settings['min_pos'], pos - 10)
            else:
                pos = min(100, max(settings['min_pos'], pos) + 20)
        else:
            pos = settings['min_pos']
        out.append([enabled, pos])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['regression: scenario 4',
   [{'high_limit': 650, 'diff': 40, 'min_pos': 15, 'mat_low': 530},
    [[739, 710, True, 528, True],
     [721, 715, True, 537, True],
     [696, 760, False, 541, True],
     [680, 735, True, 575, True],
     [646, 726, True, 550, True],
     [633, 731, False, 494, True],
     [598, 704, True, 565, True],
     [608, 716, False, 614, True]]],
   [[False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [True, 35], [True, 15]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 750, 'diff': 0, 'min_pos': 20, 'mat_low': 500},
    [[648, 701, True, 604, True],
     [640, 708, True, 484, True],
     [671, 716, True, 536, True],
     [649, 721, True, 616, False],
     [640, 749, True, 619, True],
     [651, 751, True, 599, True],
     [669, 718, False, 504, True],
     [638, 704, False, 542, True]]],
   [[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]]],
  ['control: scenario 2',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[460, 757, True, 619, True],
     [462, 720, False, 618, True],
     [502, 734, True, 534, True],
     [488, 707, False, 516, True],
     [450, 735, False, 496, True],
     [472, 754, True, 606, True],
     [477, 741, False, 575, True],
     [452, 710, True, 531, True],
     [431, 759, False, 576, True]]],
   [[True, 40],
    [True, 20],
    [True, 40],
    [True, 20],
    [True, 20],
    [True, 40],
    [True, 20],
    [True, 40],
    [True, 20]]]],
 [['regression: scenario 4',
   [{'high_limit': 650, 'diff': 40, 'min_pos': 30, 'mat_low': 500},
    [[637, 708, True, 552, True],
     [645, 735, True, 591, True],
     [634, 703, True, 564, True],
     [622, 710, True, 543, True],
     [634, 716, True, 534, False],
     [647, 753, True, 529, True],
     [619, 706, True, 541, True],
     [596, 740, True, 617, True],
     [590, 749, True, 524, True],
     [592, 739, True, 566, False],
     [581, 721, True, 554, True],
     [593, 737, False, 599, True],
     [572, 760, True, 524, True],
     [560, 714, True, 512, True]]],
   [[False, 30],
    [False, 30],
    [False, 30],
    [True, 50],
    [True, 0],
    [True, 50],
    [True, 70],
    [True, 90],
    [True, 100],
    [True, 0],
    [True, 50],
    [True, 30],
    [True, 50],
    [True, 70]]],
  ['regression: scenario 10',
   [{'high_limit': 650, 'diff': 40, 'min_pos': 30, 'mat_low': 530},
    [[641, 705, True, 484, True],
     [633, 750, True, 549, True],
     [611, 743, False, 559, True],
     [596, 715, True, 522, True],
     [625, 713, True, 564, True],
     [641, 703, True, 560, True],
     [618, 717, True, 585, True],
     [632, 711, False, 552, True],
     [608, 758, True, 602, True]]],
   [[False, 30],
    [False, 30],
    [True, 30],
    [True, 30],
    [True, 50],
    [True, 70],
    [True, 90],
    [True, 30],
    [True, 50]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 650, 'diff': 0, 'min_pos': 30, 'mat_low': 500},
    [[754, 754, False, 483, False],
     [716, 749, True, 499, True],
     [699, 701, False, 573, True],
     [694, 750, True, 534, False],
     [704, 716, False, 517, True],
     [708, 701, False, 567, True],
     [743, 735, True, 609, True],
     [775, 747, False, 554, True],
     [743, 731, False, 583, False],
     [764, 708, False, 507, True],
     [795, 743, False, 528, True],
     [770, 706, True, 501, True],
     [764, 752, False, 578, True]]],
   [[False, 0],
    [False, 30],
    [False, 30],
    [False, 0],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 0],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30]]]],
 [['regression: scenario 6',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 20, 'mat_low': 530},
    [[763, 718, True, 585, True],
     [759, 718, True, 579, True],
     [756, 741, True, 590, True],
     [742, 724, True, 569, True],
     [765, 717, True, 612, True],
     [763, 727, False, 522, True],
     [735, 744, True, 480, True],
     [715, 733, True, 519, True],
     [685, 712, True, 608, True],
     [656, 730, True, 484, True],
     [659, 758, False, 561, True],
     [625, 726, True, 500, True],
     [640, 750, True, 506, True],
     [640, 711, False, 549, True]]],
   [[False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [True, 20],
    [True, 20],
    [True, 20],
    [True, 20],
    [True, 20]]],
  ['regression: scenario 16',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 30, 'mat_low': 500},
    [[672, 743, True, 486, True],
     [641, 721, True, 613, True],
     [636, 726, True, 567, True],
     [600, 723, False, 611, True],
     [561, 754, True, 587, True],
     [567, 709, True, 591, True],
     [546, 754, False, 499, False],
     [561, 729, False, 540, True]]],
   [[False, 30], [False, 30], [False, 30], [True, 30], [True, 50], [True, 70], [True, 0], [True, 30]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['regression: scenario 1',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[678, 721, True, 495, True],
     [646, 726, True, 616, True],
     [669, 714, True, 600, True],
     [630, 728, True, 542, True],
     [626, 751, True, 530, False],
     [638, 723, False, 607, True],
     [654, 706, True, 557, True],
     [625, 704, False, 618, True]]],
   [[False, 20], [False, 20], [False, 20], [True, 40], [True, 0], [True, 20], [False, 20], [True, 20]]]],
 [['regression: scenario 17',
   [{'high_limit': 700, 'diff': 40, 'min_pos': 15, 'mat_low': 530},
    [[669, 750, False, 557, False],
     [688, 701, True, 577, True],
     [671, 744, False, 534, True],
     [651, 716, False, 567, True],
     [680, 702, False, 569, True],
     [640, 740, True, 559, True],
     [657, 721, True, 556, True],
     [650, 721, True, 516, True],
     [682, 741, True, 511, True],
     [700, 718, True, 582, True],
     [730, 724, True, 482, True],
     [724, 715, False, 517, False],
     [685, 716, False, 568, True],
     [683, 729, True, 588, True]]],
   [[True, 0],
    [True, 35],
    [True, 15],
    [True, 15],
    [True, 15],
    [True, 35],
    [True, 55],
    [True, 45],
    [True, 35],
    [True, 55],
    [False, 15],
    [False, 0],
    [False, 15],
    [False, 15]]],
  ['regression: scenario 48',
   [{'high_limit': 750, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[746, 701, True, 531, True],
     [717, 709, True, 611, True],
     [737, 722, True, 489, True],
     [744, 712, True, 498, True],
     [731, 701, True, 552, True],
     [701, 730, True, 531, True],
     [738, 741, True, 619, True],
     [742, 738, True, 561, True],
     [771, 757, True, 582, True],
     [785, 738, False, 566, True],
     [760, 739, True, 489, True],
     [748, 757, True, 534, True],
     [734, 758, True, 568, True],
     [750, 722, False, 549, True]]],
   [[False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [True, 50],
    [True, 70],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 30, 'mat_low': 500},
    [[505, 738, False, 612, True],
     [535, 706, True, 498, True],
     [549, 734, False, 604, True],
     [558, 729, True, 521, True],
     [540, 724, True, 571, True],
     [530, 749, True, 592, True],
     [507, 715, True, 533, False],
     [468, 703, False, 516, True],
     [457, 715, False, 563, True],
     [444, 708, False, 513, True]]],
   [[True, 30],
    [True, 30],
    [True, 30],
    [True, 50],
    [True, 70],
    [True, 90],
    [True, 0],
    [True, 30],
    [True, 30],
    [True, 30]]]],
 [['regression: scenario 26',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[601, 732, False, 620, True],
     [635, 736, True, 571, True],
     [666, 716, False, 556, True],
     [646, 714, False, 491, True],
     [625, 747, True, 525, True],
     [631, 760, True, 584, True],
     [640, 723, True, 496, True],
     [600, 744, False, 537, True],
     [579, 717, True, 514, True],
     [611, 736, False, 614, True],
     [639, 745, False, 496, True],
     [602, 732, False, 490, False]]],
   [[True, 15],
    [True, 35],
    [False, 15],
    [False, 15],
    [True, 35],
    [True, 55],
    [True, 45],
    [True, 15],
    [True, 35],
    [True, 15],
    [True, 15],
    [True, 0]]],
  ['regression: scenario 36',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[679, 739, True, 601, True],
     [689, 742, True, 510, True],
     [652, 741, True, 598, True],
     [631, 734, True, 539, True],
     [671, 731, False, 552, False],
     [637, 737, False, 532, True],
     [675, 744, False, 599, True],
     [666, 748, True, 508, True],
     [661, 721, True, 577, True]]],
   [[False, 15],
    [False, 15],
    [False, 15],
    [False, 15],
    [False, 0],
    [False, 15],
    [False, 15],
    [False, 15],
    [False, 15]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 750, 'diff': 0, 'min_pos': 20, 'mat_low': 500},
    [[684, 714, False, 579, True],
     [715, 719, False, 481, True],
     [737, 759, True, 615, False],
     [715, 756, True, 609, False],
     [743, 742, True, 491, True],
     [776, 713, False, 491, True],
     [798, 748, False, 566, True],
     [800, 751, True, 547, True],
     [800, 736, True, 486, True],
     [760, 727, False, 570, True]]],
   [[True, 20],
    [True, 20],
    [True, 0],
    [True, 0],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20]]]]]
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: enable band edges[[True, 40], [True, 60], [True, 80], [True, 100], [False, 20]][[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]Failed
regression: scenario 4[[False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [True, 15], [True, 35], [True, 15]][[False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [True, 35], [True, 15]]Failed
control: economizer cycle[[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]][[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]Passed
control: cold mixed air[[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]][[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]Passed
control: night cooling call[[True, 0], [True, 40]][[True, 0], [True, 40]]Passed
control: near return disable[[True, 35], [True, 55], [True, 75], [False, 15]][[True, 35], [True, 55], [True, 75], [False, 15]]Passed
control: scenario 1[[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]][[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]]Passed
control: scenario 2[[True, 40], [True, 20], [True, 40], [True, 20], [True, 20], [True, 40], [True, 20], [True, 40], [True, 20]][[True, 40], [True, 20], [True, 40], [True, 20], [True, 20], [True, 40], [True, 20], [True, 40], [True, 20]]Passed

SHA-256 / db9305bf3a3b4556f0a81fdbb214cec3c5cbb46d10705845b849da4fb04119a3

3 / The verified repair

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

N = 1
observations = []
def solve(settings, samples):
    enabled = False
    pos = 0
    out = []
    for outdoor, ret, cooling, mixed, occupied in samples:
        if enabled and (outdoor > settings['high_limit'] or outdoor >= ret):
            enabled = False
        elif not enabled and outdoor <= settings['high_limit'] - 20 and outdoor < ret - settings['diff']:
            enabled = True
        if not occupied:
            pos = 0
        elif enabled and cooling:
            if mixed < settings['mat_low']:
                pos = max(settings['min_pos'], pos - 10)
            else:
                pos = min(100, max(settings['min_pos'], pos) + 20)
        else:
            pos = settings['min_pos']
        out.append([enabled, pos])
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['regression: scenario 4',
   [{'high_limit': 650, 'diff': 40, 'min_pos': 15, 'mat_low': 530},
    [[739, 710, True, 528, True],
     [721, 715, True, 537, True],
     [696, 760, False, 541, True],
     [680, 735, True, 575, True],
     [646, 726, True, 550, True],
     [633, 731, False, 494, True],
     [598, 704, True, 565, True],
     [608, 716, False, 614, True]]],
   [[False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [True, 35], [True, 15]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 750, 'diff': 0, 'min_pos': 20, 'mat_low': 500},
    [[648, 701, True, 604, True],
     [640, 708, True, 484, True],
     [671, 716, True, 536, True],
     [649, 721, True, 616, False],
     [640, 749, True, 619, True],
     [651, 751, True, 599, True],
     [669, 718, False, 504, True],
     [638, 704, False, 542, True]]],
   [[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]]],
  ['control: scenario 2',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[460, 757, True, 619, True],
     [462, 720, False, 618, True],
     [502, 734, True, 534, True],
     [488, 707, False, 516, True],
     [450, 735, False, 496, True],
     [472, 754, True, 606, True],
     [477, 741, False, 575, True],
     [452, 710, True, 531, True],
     [431, 759, False, 576, True]]],
   [[True, 40],
    [True, 20],
    [True, 40],
    [True, 20],
    [True, 20],
    [True, 40],
    [True, 20],
    [True, 40],
    [True, 20]]]],
 [['regression: scenario 4',
   [{'high_limit': 650, 'diff': 40, 'min_pos': 30, 'mat_low': 500},
    [[637, 708, True, 552, True],
     [645, 735, True, 591, True],
     [634, 703, True, 564, True],
     [622, 710, True, 543, True],
     [634, 716, True, 534, False],
     [647, 753, True, 529, True],
     [619, 706, True, 541, True],
     [596, 740, True, 617, True],
     [590, 749, True, 524, True],
     [592, 739, True, 566, False],
     [581, 721, True, 554, True],
     [593, 737, False, 599, True],
     [572, 760, True, 524, True],
     [560, 714, True, 512, True]]],
   [[False, 30],
    [False, 30],
    [False, 30],
    [True, 50],
    [True, 0],
    [True, 50],
    [True, 70],
    [True, 90],
    [True, 100],
    [True, 0],
    [True, 50],
    [True, 30],
    [True, 50],
    [True, 70]]],
  ['regression: scenario 10',
   [{'high_limit': 650, 'diff': 40, 'min_pos': 30, 'mat_low': 530},
    [[641, 705, True, 484, True],
     [633, 750, True, 549, True],
     [611, 743, False, 559, True],
     [596, 715, True, 522, True],
     [625, 713, True, 564, True],
     [641, 703, True, 560, True],
     [618, 717, True, 585, True],
     [632, 711, False, 552, True],
     [608, 758, True, 602, True]]],
   [[False, 30],
    [False, 30],
    [True, 30],
    [True, 30],
    [True, 50],
    [True, 70],
    [True, 90],
    [True, 30],
    [True, 50]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 650, 'diff': 0, 'min_pos': 30, 'mat_low': 500},
    [[754, 754, False, 483, False],
     [716, 749, True, 499, True],
     [699, 701, False, 573, True],
     [694, 750, True, 534, False],
     [704, 716, False, 517, True],
     [708, 701, False, 567, True],
     [743, 735, True, 609, True],
     [775, 747, False, 554, True],
     [743, 731, False, 583, False],
     [764, 708, False, 507, True],
     [795, 743, False, 528, True],
     [770, 706, True, 501, True],
     [764, 752, False, 578, True]]],
   [[False, 0],
    [False, 30],
    [False, 30],
    [False, 0],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 0],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30]]]],
 [['regression: scenario 6',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 20, 'mat_low': 530},
    [[763, 718, True, 585, True],
     [759, 718, True, 579, True],
     [756, 741, True, 590, True],
     [742, 724, True, 569, True],
     [765, 717, True, 612, True],
     [763, 727, False, 522, True],
     [735, 744, True, 480, True],
     [715, 733, True, 519, True],
     [685, 712, True, 608, True],
     [656, 730, True, 484, True],
     [659, 758, False, 561, True],
     [625, 726, True, 500, True],
     [640, 750, True, 506, True],
     [640, 711, False, 549, True]]],
   [[False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [True, 20],
    [True, 20],
    [True, 20],
    [True, 20],
    [True, 20]]],
  ['regression: scenario 16',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 30, 'mat_low': 500},
    [[672, 743, True, 486, True],
     [641, 721, True, 613, True],
     [636, 726, True, 567, True],
     [600, 723, False, 611, True],
     [561, 754, True, 587, True],
     [567, 709, True, 591, True],
     [546, 754, False, 499, False],
     [561, 729, False, 540, True]]],
   [[False, 30], [False, 30], [False, 30], [True, 30], [True, 50], [True, 70], [True, 0], [True, 30]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['regression: scenario 1',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[678, 721, True, 495, True],
     [646, 726, True, 616, True],
     [669, 714, True, 600, True],
     [630, 728, True, 542, True],
     [626, 751, True, 530, False],
     [638, 723, False, 607, True],
     [654, 706, True, 557, True],
     [625, 704, False, 618, True]]],
   [[False, 20], [False, 20], [False, 20], [True, 40], [True, 0], [True, 20], [False, 20], [True, 20]]]],
 [['regression: scenario 17',
   [{'high_limit': 700, 'diff': 40, 'min_pos': 15, 'mat_low': 530},
    [[669, 750, False, 557, False],
     [688, 701, True, 577, True],
     [671, 744, False, 534, True],
     [651, 716, False, 567, True],
     [680, 702, False, 569, True],
     [640, 740, True, 559, True],
     [657, 721, True, 556, True],
     [650, 721, True, 516, True],
     [682, 741, True, 511, True],
     [700, 718, True, 582, True],
     [730, 724, True, 482, True],
     [724, 715, False, 517, False],
     [685, 716, False, 568, True],
     [683, 729, True, 588, True]]],
   [[True, 0],
    [True, 35],
    [True, 15],
    [True, 15],
    [True, 15],
    [True, 35],
    [True, 55],
    [True, 45],
    [True, 35],
    [True, 55],
    [False, 15],
    [False, 0],
    [False, 15],
    [False, 15]]],
  ['regression: scenario 48',
   [{'high_limit': 750, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[746, 701, True, 531, True],
     [717, 709, True, 611, True],
     [737, 722, True, 489, True],
     [744, 712, True, 498, True],
     [731, 701, True, 552, True],
     [701, 730, True, 531, True],
     [738, 741, True, 619, True],
     [742, 738, True, 561, True],
     [771, 757, True, 582, True],
     [785, 738, False, 566, True],
     [760, 739, True, 489, True],
     [748, 757, True, 534, True],
     [734, 758, True, 568, True],
     [750, 722, False, 549, True]]],
   [[False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [True, 50],
    [True, 70],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30],
    [False, 30]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 30, 'mat_low': 500},
    [[505, 738, False, 612, True],
     [535, 706, True, 498, True],
     [549, 734, False, 604, True],
     [558, 729, True, 521, True],
     [540, 724, True, 571, True],
     [530, 749, True, 592, True],
     [507, 715, True, 533, False],
     [468, 703, False, 516, True],
     [457, 715, False, 563, True],
     [444, 708, False, 513, True]]],
   [[True, 30],
    [True, 30],
    [True, 30],
    [True, 50],
    [True, 70],
    [True, 90],
    [True, 0],
    [True, 30],
    [True, 30],
    [True, 30]]]],
 [['regression: scenario 26',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[601, 732, False, 620, True],
     [635, 736, True, 571, True],
     [666, 716, False, 556, True],
     [646, 714, False, 491, True],
     [625, 747, True, 525, True],
     [631, 760, True, 584, True],
     [640, 723, True, 496, True],
     [600, 744, False, 537, True],
     [579, 717, True, 514, True],
     [611, 736, False, 614, True],
     [639, 745, False, 496, True],
     [602, 732, False, 490, False]]],
   [[True, 15],
    [True, 35],
    [False, 15],
    [False, 15],
    [True, 35],
    [True, 55],
    [True, 45],
    [True, 15],
    [True, 35],
    [True, 15],
    [True, 15],
    [True, 0]]],
  ['regression: scenario 36',
   [{'high_limit': 650, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[679, 739, True, 601, True],
     [689, 742, True, 510, True],
     [652, 741, True, 598, True],
     [631, 734, True, 539, True],
     [671, 731, False, 552, False],
     [637, 737, False, 532, True],
     [675, 744, False, 599, True],
     [666, 748, True, 508, True],
     [661, 721, True, 577, True]]],
   [[False, 15],
    [False, 15],
    [False, 15],
    [False, 15],
    [False, 0],
    [False, 15],
    [False, 15],
    [False, 15],
    [False, 15]]],
  ['control: economizer cycle',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[600, 740, True, 560, True],
     [600, 740, True, 560, True],
     [600, 740, True, 520, True],
     [690, 740, True, 560, True],
     [705, 740, True, 560, True],
     [600, 740, False, 560, True],
     [600, 740, True, 560, False]]],
   [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]],
  ['regression: enable band edges',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[681, 740, True, 560, True],
     [680, 700, True, 560, True],
     [680, 701, True, 560, True],
     [700, 750, True, 560, True],
     [710, 750, True, 560, True]]],
   [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]],
  ['control: cold mixed air',
   [{'high_limit': 700, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
    [[450, 720, True, 600, True],
     [450, 720, True, 600, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True],
     [450, 720, True, 500, True]]],
   [[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]],
  ['control: night cooling call',
   [{'high_limit': 700, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
    [[550, 740, True, 560, False], [550, 740, True, 560, True]]],
   [[True, 0], [True, 40]]],
  ['control: near return disable',
   [{'high_limit': 750, 'diff': 20, 'min_pos': 15, 'mat_low': 500},
    [[700, 730, True, 560, True],
     [715, 730, True, 560, True],
     [725, 730, True, 560, True],
     [730, 730, True, 560, True]]],
   [[True, 35], [True, 55], [True, 75], [False, 15]]],
  ['control: scenario 1',
   [{'high_limit': 750, 'diff': 0, 'min_pos': 20, 'mat_low': 500},
    [[684, 714, False, 579, True],
     [715, 719, False, 481, True],
     [737, 759, True, 615, False],
     [715, 756, True, 609, False],
     [743, 742, True, 491, True],
     [776, 713, False, 491, True],
     [798, 748, False, 566, True],
     [800, 751, True, 547, True],
     [800, 736, True, 486, True],
     [760, 727, False, 570, True]]],
   [[True, 20],
    [True, 20],
    [True, 0],
    [True, 0],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20],
    [False, 20]]]]]
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: enable band edges[[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]][[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]]Passed
regression: scenario 4[[False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [True, 35], [True, 15]][[False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [False, 15], [True, 35], [True, 15]]Passed
control: economizer cycle[[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]][[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]]Passed
control: cold mixed air[[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]][[True, 50], [True, 70], [True, 60], [True, 50], [True, 40], [True, 30]]Passed
control: night cooling call[[True, 0], [True, 40]][[True, 0], [True, 40]]Passed
control: near return disable[[True, 35], [True, 55], [True, 75], [False, 15]][[True, 35], [True, 55], [True, 75], [False, 15]]Passed
control: scenario 1[[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]][[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]]Passed
control: scenario 2[[True, 40], [True, 20], [True, 40], [True, 20], [True, 20], [True, 40], [True, 20], [True, 40], [True, 20]][[True, 40], [True, 20], [True, 40], [True, 20], [True, 20], [True, 40], [True, 20], [True, 40], [True, 20]]Passed

SHA-256 / 0e158c65b9a2769e35e65948eccdf6424faa60b54b3d02bca8f76eb2381bf91b

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

Case digest / 8ac148e55bfc57a9584860d9c238cb3bb56bc8e2ad1e9ebe2c90ebdb77bf3d67