FA-92556 / HVAC thermostat control / Open access
Outdoor damper stays open while the building is unoccupied · case 01
The economizer brings in outdoor air overnight whenever a cooling call appears.
ROOT CAUSE
The unoccupied branch is missing.
VERIFIED REPAIR
Close the outdoor damper whenever unoccupied.
Unsuccessful approach: Closing only when there is no cooling call still ventilates an empty building.
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'] - 20 and outdoor < ret - settings['diff']:
enabled = True
if 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: 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: 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: 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: 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': 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 3',
[{'high_limit': 700, 'diff': 40, 'min_pos': 30, 'mat_low': 530},
[[608, 734, True, 493, True],
[609, 741, True, 531, True],
[587, 758, True, 553, True],
[561, 759, False, 498, False],
[530, 741, False, 509, True],
[544, 760, True, 493, True],
[548, 751, True, 583, True],
[512, 705, False, 540, False],
[490, 711, True, 562, True]]],
[[True, 30],
[True, 50],
[True, 70],
[True, 0],
[True, 30],
[True, 30],
[True, 50],
[True, 0],
[True, 50]]]],
[['regression: 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]]],
['regression: 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: 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: 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: 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 2',
[{'high_limit': 700, 'diff': 20, 'min_pos': 30, 'mat_low': 500},
[[492, 750, False, 518, True],
[495, 710, True, 564, True],
[507, 701, True, 603, True],
[545, 705, False, 490, True],
[529, 724, True, 489, False],
[508, 721, True, 529, False],
[482, 717, True, 619, True],
[464, 753, False, 608, True],
[478, 719, False, 559, False],
[481, 730, False, 494, True],
[511, 719, False, 613, True],
[479, 743, False, 542, True],
[477, 728, True, 597, True],
[493, 756, False, 531, True]]],
[[True, 30],
[True, 50],
[True, 70],
[True, 30],
[True, 0],
[True, 0],
[True, 50],
[True, 30],
[True, 0],
[True, 30],
[True, 30],
[True, 30],
[True, 50],
[True, 30]]],
['regression: scenario 3',
[{'high_limit': 750, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
[[715, 711, True, 506, True],
[744, 713, True, 551, True],
[732, 724, True, 568, True],
[758, 725, False, 532, True],
[795, 734, True, 552, True],
[788, 742, True, 497, True],
[760, 709, True, 515, False],
[769, 759, False, 564, True],
[795, 721, True, 546, True]]],
[[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 0],
[False, 20],
[False, 20]]]],
[['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 2',
[{'high_limit': 650, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
[[599, 747, False, 570, True],
[625, 701, False, 603, True],
[623, 732, False, 565, False],
[624, 737, True, 482, True],
[637, 740, False, 485, True],
[676, 702, True, 590, True],
[678, 755, True, 522, True],
[670, 733, True, 571, True],
[684, 712, True, 616, False],
[687, 736, True, 607, False],
[712, 739, False, 524, True]]],
[[True, 30],
[True, 30],
[True, 0],
[True, 30],
[True, 30],
[False, 30],
[False, 30],
[False, 30],
[False, 0],
[False, 0],
[False, 30]]],
['regression: 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: 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]]],
['regression: 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 3',
[{'high_limit': 650, 'diff': 0, 'min_pos': 20, 'mat_low': 500},
[[761, 714, True, 573, False],
[780, 758, False, 588, True],
[761, 703, True, 548, True],
[746, 756, True, 495, True],
[714, 752, True, 488, True],
[754, 748, True, 509, True],
[719, 713, True, 561, True],
[725, 724, False, 616, True],
[720, 740, False, 513, False],
[719, 725, True, 487, True],
[756, 716, False, 534, True],
[795, 715, True, 615, False]]],
[[False, 0],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 0],
[False, 20],
[False, 20],
[False, 0]]]],
[['regression: scenario 2',
[{'high_limit': 650, 'diff': 20, 'min_pos': 15, 'mat_low': 530},
[[741, 731, False, 559, True],
[755, 719, True, 563, False],
[721, 727, False, 535, True],
[760, 746, True, 518, False],
[752, 759, True, 565, True],
[721, 749, True, 610, True],
[697, 734, True, 612, True],
[689, 709, True, 487, True],
[680, 737, True, 520, True],
[641, 709, True, 611, True],
[637, 721, True, 540, False],
[603, 735, False, 541, True],
[586, 719, True, 618, True],
[618, 703, True, 613, True]]],
[[False, 15],
[False, 0],
[False, 15],
[False, 0],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 0],
[True, 15],
[True, 35],
[True, 55]]],
['regression: scenario 3',
[{'high_limit': 700, 'diff': 40, 'min_pos': 15, 'mat_low': 500},
[[648, 702, True, 512, True],
[638, 725, True, 584, True],
[603, 726, True, 591, True],
[610, 705, True, 566, False],
[612, 706, False, 595, True],
[587, 717, True, 552, True],
[623, 754, True, 482, True],
[595, 710, True, 491, True],
[615, 710, False, 485, True]]],
[[True, 35],
[True, 55],
[True, 75],
[True, 0],
[True, 15],
[True, 35],
[True, 25],
[True, 15],
[True, 15]]],
['regression: 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: 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]]],
['regression: 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': 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 6',
[{'high_limit': 650, 'diff': 40, 'min_pos': 30, 'mat_low': 530},
[[450, 740, True, 605, True],
[424, 725, True, 523, False],
[400, 741, True, 543, True],
[409, 711, True, 502, True],
[439, 732, False, 568, True],
[438, 760, True, 580, False],
[467, 735, True, 574, False],
[492, 716, False, 501, False],
[529, 729, True, 518, True]]],
[[True, 50], [True, 0], [True, 50], [True, 40], [True, 30], [True, 0], [True, 0], [True, 0], [True, 30]]],
['regression: scenario 7',
[{'high_limit': 700, 'diff': 40, 'min_pos': 15, 'mat_low': 500},
[[765, 744, True, 555, True],
[757, 729, False, 542, True],
[769, 703, True, 545, False],
[785, 759, False, 578, False],
[778, 747, False, 494, True],
[777, 719, True, 542, True],
[785, 717, False, 595, True],
[787, 756, True, 558, True],
[747, 728, True, 565, True],
[711, 741, False, 600, True],
[696, 745, True, 620, True]]],
[[False, 15],
[False, 15],
[False, 0],
[False, 0],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15]]],
['regression: 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: 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]]],
['regression: 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': 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: economizer cycle | [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 40]] | [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]] | Failed |
| regression: night cooling call | [[True, 40], [True, 60]] | [[True, 0], [True, 40]] | Failed |
| control: enable band edges | [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]] | [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]] | 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: near return disable | [[True, 35], [True, 55], [True, 75], [False, 15]] | [[True, 35], [True, 55], [True, 75], [False, 15]] | Passed |
| regression: scenario 1 | [[True, 40], [True, 30], [True, 50], [True, 70], [True, 90], [True, 100], [True, 20], [True, 20]] | [[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]] | Failed |
| 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 |
| regression: scenario 3 | [[True, 30], [True, 50], [True, 70], [True, 30], [True, 30], [True, 30], [True, 50], [True, 30], [True, 50]] | [[True, 30], [True, 50], [True, 70], [True, 0], [True, 30], [True, 30], [True, 50], [True, 0], [True, 50]] | Failed |
SHA-256 / 267f4b052334ba7b2b4f68bd3e1a506b65ad0d4e358b12a8f216f6cc72a3edcf
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'] - 20 and outdoor < ret - settings['diff']:
enabled = True
if not occupied and not cooling:
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: 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: 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: 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: 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': 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 3',
[{'high_limit': 700, 'diff': 40, 'min_pos': 30, 'mat_low': 530},
[[608, 734, True, 493, True],
[609, 741, True, 531, True],
[587, 758, True, 553, True],
[561, 759, False, 498, False],
[530, 741, False, 509, True],
[544, 760, True, 493, True],
[548, 751, True, 583, True],
[512, 705, False, 540, False],
[490, 711, True, 562, True]]],
[[True, 30],
[True, 50],
[True, 70],
[True, 0],
[True, 30],
[True, 30],
[True, 50],
[True, 0],
[True, 50]]]],
[['regression: 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]]],
['regression: 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: 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: 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: 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 2',
[{'high_limit': 700, 'diff': 20, 'min_pos': 30, 'mat_low': 500},
[[492, 750, False, 518, True],
[495, 710, True, 564, True],
[507, 701, True, 603, True],
[545, 705, False, 490, True],
[529, 724, True, 489, False],
[508, 721, True, 529, False],
[482, 717, True, 619, True],
[464, 753, False, 608, True],
[478, 719, False, 559, False],
[481, 730, False, 494, True],
[511, 719, False, 613, True],
[479, 743, False, 542, True],
[477, 728, True, 597, True],
[493, 756, False, 531, True]]],
[[True, 30],
[True, 50],
[True, 70],
[True, 30],
[True, 0],
[True, 0],
[True, 50],
[True, 30],
[True, 0],
[True, 30],
[True, 30],
[True, 30],
[True, 50],
[True, 30]]],
['regression: scenario 3',
[{'high_limit': 750, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
[[715, 711, True, 506, True],
[744, 713, True, 551, True],
[732, 724, True, 568, True],
[758, 725, False, 532, True],
[795, 734, True, 552, True],
[788, 742, True, 497, True],
[760, 709, True, 515, False],
[769, 759, False, 564, True],
[795, 721, True, 546, True]]],
[[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 0],
[False, 20],
[False, 20]]]],
[['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 2',
[{'high_limit': 650, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
[[599, 747, False, 570, True],
[625, 701, False, 603, True],
[623, 732, False, 565, False],
[624, 737, True, 482, True],
[637, 740, False, 485, True],
[676, 702, True, 590, True],
[678, 755, True, 522, True],
[670, 733, True, 571, True],
[684, 712, True, 616, False],
[687, 736, True, 607, False],
[712, 739, False, 524, True]]],
[[True, 30],
[True, 30],
[True, 0],
[True, 30],
[True, 30],
[False, 30],
[False, 30],
[False, 30],
[False, 0],
[False, 0],
[False, 30]]],
['regression: 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: 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]]],
['regression: 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 3',
[{'high_limit': 650, 'diff': 0, 'min_pos': 20, 'mat_low': 500},
[[761, 714, True, 573, False],
[780, 758, False, 588, True],
[761, 703, True, 548, True],
[746, 756, True, 495, True],
[714, 752, True, 488, True],
[754, 748, True, 509, True],
[719, 713, True, 561, True],
[725, 724, False, 616, True],
[720, 740, False, 513, False],
[719, 725, True, 487, True],
[756, 716, False, 534, True],
[795, 715, True, 615, False]]],
[[False, 0],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 0],
[False, 20],
[False, 20],
[False, 0]]]],
[['regression: scenario 2',
[{'high_limit': 650, 'diff': 20, 'min_pos': 15, 'mat_low': 530},
[[741, 731, False, 559, True],
[755, 719, True, 563, False],
[721, 727, False, 535, True],
[760, 746, True, 518, False],
[752, 759, True, 565, True],
[721, 749, True, 610, True],
[697, 734, True, 612, True],
[689, 709, True, 487, True],
[680, 737, True, 520, True],
[641, 709, True, 611, True],
[637, 721, True, 540, False],
[603, 735, False, 541, True],
[586, 719, True, 618, True],
[618, 703, True, 613, True]]],
[[False, 15],
[False, 0],
[False, 15],
[False, 0],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 0],
[True, 15],
[True, 35],
[True, 55]]],
['regression: scenario 3',
[{'high_limit': 700, 'diff': 40, 'min_pos': 15, 'mat_low': 500},
[[648, 702, True, 512, True],
[638, 725, True, 584, True],
[603, 726, True, 591, True],
[610, 705, True, 566, False],
[612, 706, False, 595, True],
[587, 717, True, 552, True],
[623, 754, True, 482, True],
[595, 710, True, 491, True],
[615, 710, False, 485, True]]],
[[True, 35],
[True, 55],
[True, 75],
[True, 0],
[True, 15],
[True, 35],
[True, 25],
[True, 15],
[True, 15]]],
['regression: 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: 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]]],
['regression: 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': 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 6',
[{'high_limit': 650, 'diff': 40, 'min_pos': 30, 'mat_low': 530},
[[450, 740, True, 605, True],
[424, 725, True, 523, False],
[400, 741, True, 543, True],
[409, 711, True, 502, True],
[439, 732, False, 568, True],
[438, 760, True, 580, False],
[467, 735, True, 574, False],
[492, 716, False, 501, False],
[529, 729, True, 518, True]]],
[[True, 50], [True, 0], [True, 50], [True, 40], [True, 30], [True, 0], [True, 0], [True, 0], [True, 30]]],
['regression: scenario 7',
[{'high_limit': 700, 'diff': 40, 'min_pos': 15, 'mat_low': 500},
[[765, 744, True, 555, True],
[757, 729, False, 542, True],
[769, 703, True, 545, False],
[785, 759, False, 578, False],
[778, 747, False, 494, True],
[777, 719, True, 542, True],
[785, 717, False, 595, True],
[787, 756, True, 558, True],
[747, 728, True, 565, True],
[711, 741, False, 600, True],
[696, 745, True, 620, True]]],
[[False, 15],
[False, 15],
[False, 0],
[False, 0],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15]]],
['regression: 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: 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]]],
['regression: 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': 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: economizer cycle | [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 40]] | [[True, 40], [True, 60], [True, 50], [True, 70], [False, 20], [True, 20], [True, 0]] | Failed |
| regression: night cooling call | [[True, 40], [True, 60]] | [[True, 0], [True, 40]] | Failed |
| control: enable band edges | [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]] | [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]] | 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: near return disable | [[True, 35], [True, 55], [True, 75], [False, 15]] | [[True, 35], [True, 55], [True, 75], [False, 15]] | Passed |
| regression: scenario 1 | [[True, 40], [True, 30], [True, 50], [True, 70], [True, 90], [True, 100], [True, 20], [True, 20]] | [[True, 40], [True, 30], [True, 50], [True, 0], [True, 40], [True, 60], [True, 20], [True, 20]] | Failed |
| 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 |
| regression: scenario 3 | [[True, 30], [True, 50], [True, 70], [True, 0], [True, 30], [True, 30], [True, 50], [True, 0], [True, 50]] | [[True, 30], [True, 50], [True, 70], [True, 0], [True, 30], [True, 30], [True, 50], [True, 0], [True, 50]] | Passed |
SHA-256 / 17b2ee4d88a6b4d90655ad7c15a01516359e58e491d2553c38fd8be10fd38f05
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: 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: 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: 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: 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': 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 3',
[{'high_limit': 700, 'diff': 40, 'min_pos': 30, 'mat_low': 530},
[[608, 734, True, 493, True],
[609, 741, True, 531, True],
[587, 758, True, 553, True],
[561, 759, False, 498, False],
[530, 741, False, 509, True],
[544, 760, True, 493, True],
[548, 751, True, 583, True],
[512, 705, False, 540, False],
[490, 711, True, 562, True]]],
[[True, 30],
[True, 50],
[True, 70],
[True, 0],
[True, 30],
[True, 30],
[True, 50],
[True, 0],
[True, 50]]]],
[['regression: 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]]],
['regression: 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: 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: 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: 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 2',
[{'high_limit': 700, 'diff': 20, 'min_pos': 30, 'mat_low': 500},
[[492, 750, False, 518, True],
[495, 710, True, 564, True],
[507, 701, True, 603, True],
[545, 705, False, 490, True],
[529, 724, True, 489, False],
[508, 721, True, 529, False],
[482, 717, True, 619, True],
[464, 753, False, 608, True],
[478, 719, False, 559, False],
[481, 730, False, 494, True],
[511, 719, False, 613, True],
[479, 743, False, 542, True],
[477, 728, True, 597, True],
[493, 756, False, 531, True]]],
[[True, 30],
[True, 50],
[True, 70],
[True, 30],
[True, 0],
[True, 0],
[True, 50],
[True, 30],
[True, 0],
[True, 30],
[True, 30],
[True, 30],
[True, 50],
[True, 30]]],
['regression: scenario 3',
[{'high_limit': 750, 'diff': 20, 'min_pos': 20, 'mat_low': 530},
[[715, 711, True, 506, True],
[744, 713, True, 551, True],
[732, 724, True, 568, True],
[758, 725, False, 532, True],
[795, 734, True, 552, True],
[788, 742, True, 497, True],
[760, 709, True, 515, False],
[769, 759, False, 564, True],
[795, 721, True, 546, True]]],
[[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 0],
[False, 20],
[False, 20]]]],
[['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 2',
[{'high_limit': 650, 'diff': 0, 'min_pos': 30, 'mat_low': 530},
[[599, 747, False, 570, True],
[625, 701, False, 603, True],
[623, 732, False, 565, False],
[624, 737, True, 482, True],
[637, 740, False, 485, True],
[676, 702, True, 590, True],
[678, 755, True, 522, True],
[670, 733, True, 571, True],
[684, 712, True, 616, False],
[687, 736, True, 607, False],
[712, 739, False, 524, True]]],
[[True, 30],
[True, 30],
[True, 0],
[True, 30],
[True, 30],
[False, 30],
[False, 30],
[False, 30],
[False, 0],
[False, 0],
[False, 30]]],
['regression: 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: 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]]],
['regression: 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 3',
[{'high_limit': 650, 'diff': 0, 'min_pos': 20, 'mat_low': 500},
[[761, 714, True, 573, False],
[780, 758, False, 588, True],
[761, 703, True, 548, True],
[746, 756, True, 495, True],
[714, 752, True, 488, True],
[754, 748, True, 509, True],
[719, 713, True, 561, True],
[725, 724, False, 616, True],
[720, 740, False, 513, False],
[719, 725, True, 487, True],
[756, 716, False, 534, True],
[795, 715, True, 615, False]]],
[[False, 0],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 20],
[False, 0],
[False, 20],
[False, 20],
[False, 0]]]],
[['regression: scenario 2',
[{'high_limit': 650, 'diff': 20, 'min_pos': 15, 'mat_low': 530},
[[741, 731, False, 559, True],
[755, 719, True, 563, False],
[721, 727, False, 535, True],
[760, 746, True, 518, False],
[752, 759, True, 565, True],
[721, 749, True, 610, True],
[697, 734, True, 612, True],
[689, 709, True, 487, True],
[680, 737, True, 520, True],
[641, 709, True, 611, True],
[637, 721, True, 540, False],
[603, 735, False, 541, True],
[586, 719, True, 618, True],
[618, 703, True, 613, True]]],
[[False, 15],
[False, 0],
[False, 15],
[False, 0],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 0],
[True, 15],
[True, 35],
[True, 55]]],
['regression: scenario 3',
[{'high_limit': 700, 'diff': 40, 'min_pos': 15, 'mat_low': 500},
[[648, 702, True, 512, True],
[638, 725, True, 584, True],
[603, 726, True, 591, True],
[610, 705, True, 566, False],
[612, 706, False, 595, True],
[587, 717, True, 552, True],
[623, 754, True, 482, True],
[595, 710, True, 491, True],
[615, 710, False, 485, True]]],
[[True, 35],
[True, 55],
[True, 75],
[True, 0],
[True, 15],
[True, 35],
[True, 25],
[True, 15],
[True, 15]]],
['regression: 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: 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]]],
['regression: 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': 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 6',
[{'high_limit': 650, 'diff': 40, 'min_pos': 30, 'mat_low': 530},
[[450, 740, True, 605, True],
[424, 725, True, 523, False],
[400, 741, True, 543, True],
[409, 711, True, 502, True],
[439, 732, False, 568, True],
[438, 760, True, 580, False],
[467, 735, True, 574, False],
[492, 716, False, 501, False],
[529, 729, True, 518, True]]],
[[True, 50], [True, 0], [True, 50], [True, 40], [True, 30], [True, 0], [True, 0], [True, 0], [True, 30]]],
['regression: scenario 7',
[{'high_limit': 700, 'diff': 40, 'min_pos': 15, 'mat_low': 500},
[[765, 744, True, 555, True],
[757, 729, False, 542, True],
[769, 703, True, 545, False],
[785, 759, False, 578, False],
[778, 747, False, 494, True],
[777, 719, True, 542, True],
[785, 717, False, 595, True],
[787, 756, True, 558, True],
[747, 728, True, 565, True],
[711, 741, False, 600, True],
[696, 745, True, 620, True]]],
[[False, 15],
[False, 15],
[False, 0],
[False, 0],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15],
[False, 15]]],
['regression: 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: 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]]],
['regression: 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': 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression: 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 |
| regression: night cooling call | [[True, 0], [True, 40]] | [[True, 0], [True, 40]] | Passed |
| control: enable band edges | [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]] | [[False, 20], [False, 20], [True, 40], [True, 60], [False, 20]] | 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: near return disable | [[True, 35], [True, 55], [True, 75], [False, 15]] | [[True, 35], [True, 55], [True, 75], [False, 15]] | Passed |
| regression: 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 |
| regression: scenario 3 | [[True, 30], [True, 50], [True, 70], [True, 0], [True, 30], [True, 30], [True, 50], [True, 0], [True, 50]] | [[True, 30], [True, 50], [True, 70], [True, 0], [True, 30], [True, 30], [True, 50], [True, 0], [True, 50]] | Passed |
SHA-256 / 9343bcf5e6a5f5c70fa20213503776c754b2ce1e06848d9ab021981ae2f9918f
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.937473+00:00.
Case digest / 1cbe46c6265af606c70471b3159d84bf8d8dabf59af028988bda3d89f6175255