Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

HP-0007 — Heat-pump compressor proof-of-operation failure

Statusverified — engine e2ff2f8, cxf:fnv1a128:3666ba36035e5c81277de17f12f77ae3, 2026-08-20
Severity2
Methodrule
Phase2
CategoryPROTECTIVE
ConfidenceMEDIUM
EstimationPROXY_ESTIMATION
G36
Clusters
Suppresses
Suppressed by
RelatedHP-0001, HP-0002, HP-0003, HP-0004, HP-0005, HP-0006, RTU-0001, HP-0008
Playbooksproof-of-operation, heat-pump-faults
SourceLibrary proof-of-operation precedents AHU-0039, TOWER-0004, PMP-0003, and HW-0009 — final command versus independent status, separate direction timers, and initialization-safe persistence; ASHRAE Guideline 36-2021 section 5.1.6 — equipment is proven when its digital status matches the state set by its digital command; semantic grounding only, not a source for these shipped timer values; Library-authored heat-pump compressor timing adaptation; no cited source publishes 300 s start and 120 s stop proof limits as portable values
Operating statesall states in which a final per-compressor command and independent proof for that same compressor or circuit are authoritative

Preconditions (host-enforced): Bind comp_cmd downstream of anti-short-cycle delay, safety lockouts, staging, demand response, and OEM permissives; an upstream heating/cooling demand is not a final compressor command. Bind comp_status to actual compressor electrical, inverter, pressure, or auxiliary proof, not demand or command echo. Instantiate per compressor/circuit where possible. An OR command/status pair proves only that some compressor runs and cannot detect a failed lag compressor while the lead remains on. Exclude defrost transitions, pump-down, crankcase or oil-management sequences, emergency heat, service, and manufacturer restart delays not represented in the final command. Inputs must be fresh and aligned; unmet obligations are NO_EVAL, not healthy.

Points: comp_cmd, comp_status

Outputs:

  • yFault — True while either final command/status mismatch has matured through its own proof timer
  • yFailToStart — Diagnostic direction flag; true after a final start command remains without independent run proof for start_proof_time. False never means NO_EVAL
  • yUnexpectedRun — Diagnostic direction flag; true after independent operation continues without a final run command for stop_proof_time. False never means NO_EVAL

Parameters:

NameDefaultUnitCXF pathDescription
start_proof_time300.0sstartProof.delayTimeADOPTED_TUNABLE final-command-to-proof allowance. Commission above normal device response, acceleration, proof pickup, and telemetry latency.
stop_proof_time120.0sstopProof.delayTimeADOPTED_TUNABLE command-off-to-proof-dropout allowance. Commission above normal deceleration, coast-down, proof dropout, and telemetry latency; intentional sequence operation must remain represented in the final command.

Description

This rule checks whether the heat-pump compressor did what its final Boolean command requested. Commanded on without independent proof is a fail-to-start; proven on without command is unexpected operation. The direction identifies the mismatch, not its cause, and neither diagnostic output is an evaluability gate.

Detection Logic

fail_to_start  = comp_cmd AND NOT comp_status
unexpected_run = NOT comp_cmd AND comp_status

yFailToStart   = fail_to_start sustained for start_proof_time
yUnexpectedRun = unexpected_run sustained for stop_proof_time
yFault         = yFailToStart OR yUnexpectedRun

HP-0007 block graph

Each direction has its own TrueDelay(delayOnInit=true). Agreement clears both lanes immediately. A direct mismatch reversal clears the old diagnostic and starts the other timer from zero; elapsed time never transfers between lanes.

Possible Diagnoses

  1. Compressor, contactor, inverter, capacitor, disconnect, or power failure.
  2. High/low-pressure, temperature, current, oil, or OEM safety lockout.
  3. Final command incorrectly bound upstream of anti-cycle or permissive logic.
  4. Bad compressor proof, command/status wiring, or multi-compressor aggregation.
  5. OEM defrost, pump-down, protection sequence, or local service control.

Energy Impact

The effect is direction-dependent. Unexpected operation can waste measured electrical energy during the mismatch. Fail-to-start is primarily availability, comfort, and diagnostic-coverage loss; these two booleans cannot price it.

Emissions Impact

Scope 2 is proxy-only for unexpected operation: multiply independently measured device kW by mismatch hours and an appropriate operating emissions factor. Do not claim avoided energy or emissions for fail-to-start without another model.

Deviations

  • Both timers are adopted commissioning values. No cited source establishes universal heat-pump compressor proof windows. Configure them independently around the actual sequence, proof device, sampling, and network latency.
  • The command is final and device-scoped. An upstream enable, demand, or fleet request can disagree with status while downstream logic works correctly.
  • Status is independent proof. Command echo makes the graph tautological; proof type determines whether electrical operation, rotation, or delivery was actually demonstrated.
  • No whole-rule suppression is encoded. Fail-to-start can invalidate another rule’s running premise, but unexpected operation may leave that rule physically meaningful; current metadata cannot suppress by direction.
  • delayOnInit=true is explicit on both lanes. Evaluator restart into an existing mismatch must serve the full configured proof time.
  • No empirical FPR or TPR is claimed. Current simulation telemetry cannot provide both an independent final command and field-like proof for this device.
  • The 300 s start allowance is an adopted commissioning placeholder for final-command-to-proof latency. It is not permission to bind upstream demand; ordinary anti-short-cycle timing belongs before comp_cmd.
  • Fleet OR aggregation has a documented blind spot: a lag compressor can fail while the lead keeps both OR signals true. Per-compressor instances are required whenever telemetry permits.
  • A brief compressor-off interval during defrost is raw fail-to-start if the final command stays true. The host excludes the transition unless the OEM final command already represents it.

Notes

Use yFailToStart to question the running premise of HP-0001 through HP-0006 and the compressor-status premise of related RTU-0001. yUnexpectedRun may leave those measurements meaningful, so no whole-rule suppression is encoded.

Test Vectors

27 scenarios, clock step 10 s over 2400 s.

ScenarioDescription
agreement_stoppedCommand and independent proof agree off; every direction remains clear.
agreement_runningCommand and independent proof agree on; every direction remains clear.
fail_to_start_at_initializationA standing commanded-on/unproved mismatch is clear one tick before 300 s, asserts exactly at 300 s, and remains true after the initialization-safe start timer.
unexpected_run_at_initializationA standing command-off/proven-on mismatch is clear one tick before 120 s, asserts exactly at 120 s, and remains true after the initialization-safe stop timer.
normal_delayed_start_and_stopStatus proves one tick inside both configured allowances, so ordinary start and stop latency never alarms.
start_mismatch_ends_one_tick_before_proofA start mismatch beginning at 300 s clears at 590 s, one 10 s evaluator step before maturity, and never alarms.
start_mismatch_ends_at_proof_boundaryThe start mismatch clears exactly 300 s after it begins. Agreement on the maturity tick wins and no alarm is asserted.
stop_mismatch_ends_one_tick_before_proofA stop mismatch beginning at 300 s clears at 410 s, one 10 s evaluator step before maturity, and never alarms.
stop_mismatch_ends_at_proof_boundaryThe stop mismatch clears exactly 120 s after it begins. Agreement on the maturity tick wins and no alarm is asserted.
mature_start_alarm_recoversA fail-to-start persists past its proof time, then clears immediately when independent proof arrives.
mature_stop_alarm_recoversAn unexpected run persists past its proof time, then clears immediately when proof drops.
pending_direction_reversal_starts_fresh_laneCommand and status swap while fail-to-start is pending. The old lane resets and the unexpected-run lane must serve its complete independent timer.
mature_direction_reversal_hands_offA mature fail-to-start reverses directly into unexpected-run. The old flag clears on the reversal tick, yFault has a real gap, and the opposite lane serves a fresh complete stop timer.
two_short_start_mismatches_do_not_accumulateTwo commanded-on/unproved episodes are separated by agreement; elapsed time cannot transfer across the reset.
two_short_stop_mismatches_do_not_accumulateTwo proven-on/uncommanded episodes are separated by agreement; elapsed time cannot transfer across the reset.
command_chatter_never_maturesThe final command chatters faster than either proof window while status is off; no continuous mismatch matures.
status_chatter_never_maturesIndependent proof chatters against a held-on command; every unproved interval clears before the start timer.
unexpected_run_chatter_never_maturesIndependent proof chatters on against a held-off command; every unexpected-run interval clears before the stop timer.
upstream_enable_raw_false_positiveAn upstream enable held true while downstream logic correctly withholds operation produces a raw fail-to-start alarm; this pins why only the final command is a valid binding.
command_echo_raw_blind_spotA status echo follows the command while the device is mechanically stopped. The pair agrees and the graph stays silent, pinning why proof must be independent.
manual_local_run_is_host_excludedA locally commanded device is proven on while the BAS final command is off. The raw unexpected-run alarm is correct; approved service or hand operation is host NO_EVAL.
normal_start_proof_latency_just_inside_timerA legitimate final compressor command precedes independent proof by 290 s, just inside the 300 s timer, and does not alarm.
downstream_anti_cycle_behavior_stays_in_agreementUpstream demand is intentionally absent from the graph: after the OEM anti-cycle interval, the final command and independent proof rise together and remain in agreement.
start_delay_just_beyond_timerProof arrives 310 s after command, one tick beyond the 300 s timer. Fail-to-start asserts exactly at 600 s and clears at 610 s.
defrost_transition_final_pair_agreesWhen the OEM final command represents a defrost compressor-off interval, command and proof drop together for 180 s and the proof rule stays in agreement.
brief_defrost_off_with_upstream_command_is_host_excludedIf an upstream request remains true while OEM defrost logic stops the compressor for 180 s, the raw pair is mis-scoped. It stays below 300 s here, but the host must gate it or bind the downstream final command.
multi_compressor_or_hides_lag_failureAn OR command and OR status both remain true because the lead compressor runs, hiding a failed lag-compressor request. The graph stays silent and documents the aggregation blind spot.
vectors.json
{
  "schema": "cxf-library/vectors/v1",
  "clock": {
    "step_s": 10,
    "horizon_s": 2400
  },
  "scenarios": [
    {
      "name": "agreement_stopped",
      "description": "Command and independent proof agree off; every direction remains clear.",
      "inputs": {
        "comp_cmd": false,
        "comp_status": false
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "agreement_running",
      "description": "Command and independent proof agree on; every direction remains clear.",
      "inputs": {
        "comp_cmd": true,
        "comp_status": true
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "fail_to_start_at_initialization",
      "description": "A standing commanded-on/unproved mismatch is clear one tick before 300 s, asserts exactly at 300 s, and remains true after the initialization-safe start timer.",
      "inputs": {
        "comp_cmd": true,
        "comp_status": false
      },
      "expect": [
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 290,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 300,
          "to_s": 2400,
          "equals": true
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 290,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 300,
          "to_s": 2400,
          "equals": true
        }
      ]
    },
    {
      "name": "unexpected_run_at_initialization",
      "description": "A standing command-off/proven-on mismatch is clear one tick before 120 s, asserts exactly at 120 s, and remains true after the initialization-safe stop timer.",
      "inputs": {
        "comp_cmd": false,
        "comp_status": true
      },
      "expect": [
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 110,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 120,
          "to_s": 2400,
          "equals": true
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 110,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 120,
          "to_s": 2400,
          "equals": true
        }
      ]
    },
    {
      "name": "normal_delayed_start_and_stop",
      "description": "Status proves one tick inside both configured allowances, so ordinary start and stop latency never alarms.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          },
          {
            "t": 1200,
            "value": false
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 590,
            "value": true
          },
          {
            "t": 1310,
            "value": false
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "start_mismatch_ends_one_tick_before_proof",
      "description": "A start mismatch beginning at 300 s clears at 590 s, one 10 s evaluator step before maturity, and never alarms.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 590,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "start_mismatch_ends_at_proof_boundary",
      "description": "The start mismatch clears exactly 300 s after it begins. Agreement on the maturity tick wins and no alarm is asserted.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 600,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "stop_mismatch_ends_one_tick_before_proof",
      "description": "A stop mismatch beginning at 300 s clears at 410 s, one 10 s evaluator step before maturity, and never alarms.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 300,
            "value": false
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 410,
            "value": false
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "stop_mismatch_ends_at_proof_boundary",
      "description": "The stop mismatch clears exactly 120 s after it begins. Agreement on the maturity tick wins and no alarm is asserted.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 300,
            "value": false
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 420,
            "value": false
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "mature_start_alarm_recovers",
      "description": "A fail-to-start persists past its proof time, then clears immediately when independent proof arrives.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 700,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 590,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 600,
          "to_s": 690,
          "equals": true
        },
        {
          "output": "yFailToStart",
          "from_s": 700,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 590,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 600,
          "to_s": 690,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 700,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "mature_stop_alarm_recovers",
      "description": "An unexpected run persists past its proof time, then clears immediately when proof drops.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 300,
            "value": false
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 520,
            "value": false
          }
        ]
      },
      "expect": [
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 410,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 420,
          "to_s": 510,
          "equals": true
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 520,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 410,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 420,
          "to_s": 510,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 520,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "pending_direction_reversal_starts_fresh_lane",
      "description": "Command and status swap while fail-to-start is pending. The old lane resets and the unexpected-run lane must serve its complete independent timer.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 280,
            "value": false
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 280,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 390,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 400,
          "to_s": 2400,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 390,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 400,
          "to_s": 2400,
          "equals": true
        }
      ]
    },
    {
      "name": "mature_direction_reversal_hands_off",
      "description": "A mature fail-to-start reverses directly into unexpected-run. The old flag clears on the reversal tick, yFault has a real gap, and the opposite lane serves a fresh complete stop timer.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 400,
            "value": false
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 400,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFailToStart",
          "from_s": 300,
          "to_s": 390,
          "equals": true
        },
        {
          "output": "yFailToStart",
          "from_s": 400,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 510,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 520,
          "to_s": 2400,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 300,
          "to_s": 390,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 400,
          "to_s": 510,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 520,
          "to_s": 2400,
          "equals": true
        }
      ]
    },
    {
      "name": "two_short_start_mismatches_do_not_accumulate",
      "description": "Two commanded-on/unproved episodes are separated by agreement; elapsed time cannot transfer across the reset.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          },
          {
            "t": 580,
            "value": false
          },
          {
            "t": 900,
            "value": true
          },
          {
            "t": 1180,
            "value": false
          }
        ],
        "comp_status": false
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "two_short_stop_mismatches_do_not_accumulate",
      "description": "Two proven-on/uncommanded episodes are separated by agreement; elapsed time cannot transfer across the reset.",
      "inputs": {
        "comp_cmd": false,
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          },
          {
            "t": 400,
            "value": false
          },
          {
            "t": 900,
            "value": true
          },
          {
            "t": 1000,
            "value": false
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "command_chatter_never_matures",
      "description": "The final command chatters faster than either proof window while status is off; no continuous mismatch matures.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          },
          {
            "t": 400,
            "value": false
          },
          {
            "t": 600,
            "value": true
          },
          {
            "t": 700,
            "value": false
          },
          {
            "t": 900,
            "value": true
          },
          {
            "t": 1000,
            "value": false
          }
        ],
        "comp_status": false
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "status_chatter_never_matures",
      "description": "Independent proof chatters against a held-on command; every unproved interval clears before the start timer.",
      "inputs": {
        "comp_cmd": true,
        "comp_status": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 300,
            "value": false
          },
          {
            "t": 580,
            "value": true
          },
          {
            "t": 600,
            "value": false
          },
          {
            "t": 880,
            "value": true
          },
          {
            "t": 900,
            "value": false
          },
          {
            "t": 1180,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "unexpected_run_chatter_never_matures",
      "description": "Independent proof chatters on against a held-off command; every unexpected-run interval clears before the stop timer.",
      "inputs": {
        "comp_cmd": false,
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          },
          {
            "t": 400,
            "value": false
          },
          {
            "t": 600,
            "value": true
          },
          {
            "t": 700,
            "value": false
          },
          {
            "t": 900,
            "value": true
          },
          {
            "t": 1000,
            "value": false
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "upstream_enable_raw_false_positive",
      "description": "An upstream enable held true while downstream logic correctly withholds operation produces a raw fail-to-start alarm; this pins why only the final command is a valid binding.",
      "inputs": {
        "comp_cmd": true,
        "comp_status": false
      },
      "expect": [
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 290,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 300,
          "to_s": 2400,
          "equals": true
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 290,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 300,
          "to_s": 2400,
          "equals": true
        }
      ]
    },
    {
      "name": "command_echo_raw_blind_spot",
      "description": "A status echo follows the command while the device is mechanically stopped. The pair agrees and the graph stays silent, pinning why proof must be independent.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "manual_local_run_is_host_excluded",
      "description": "A locally commanded device is proven on while the BAS final command is off. The raw unexpected-run alarm is correct; approved service or hand operation is host NO_EVAL.",
      "inputs": {
        "comp_cmd": false,
        "comp_status": true
      },
      "expect": [
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 110,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 120,
          "to_s": 2400,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 110,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 120,
          "to_s": 2400,
          "equals": true
        }
      ]
    },
    {
      "name": "normal_start_proof_latency_just_inside_timer",
      "description": "A legitimate final compressor command precedes independent proof by 290 s, just inside the 300 s timer, and does not alarm.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 590,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "downstream_anti_cycle_behavior_stays_in_agreement",
      "description": "Upstream demand is intentionally absent from the graph: after the OEM anti-cycle interval, the final command and independent proof rise together and remain in agreement.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 590,
            "value": true
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 590,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "start_delay_just_beyond_timer",
      "description": "Proof arrives 310 s after command, one tick beyond the 300 s timer. Fail-to-start asserts exactly at 600 s and clears at 610 s.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 300,
            "value": true
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": false
          },
          {
            "t": 610,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 590,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 600,
          "to_s": 600,
          "equals": true
        },
        {
          "output": "yFailToStart",
          "from_s": 610,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 590,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 600,
          "to_s": 600,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 610,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "defrost_transition_final_pair_agrees",
      "description": "When the OEM final command represents a defrost compressor-off interval, command and proof drop together for 180 s and the proof rule stays in agreement.",
      "inputs": {
        "comp_cmd": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 600,
            "value": false
          },
          {
            "t": 780,
            "value": true
          }
        ],
        "comp_status": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 600,
            "value": false
          },
          {
            "t": 780,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "brief_defrost_off_with_upstream_command_is_host_excluded",
      "description": "If an upstream request remains true while OEM defrost logic stops the compressor for 180 s, the raw pair is mis-scoped. It stays below 300 s here, but the host must gate it or bind the downstream final command.",
      "inputs": {
        "comp_cmd": true,
        "comp_status": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 600,
            "value": false
          },
          {
            "t": 780,
            "value": true
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    },
    {
      "name": "multi_compressor_or_hides_lag_failure",
      "description": "An OR command and OR status both remain true because the lead compressor runs, hiding a failed lag-compressor request. The graph stays silent and documents the aggregation blind spot.",
      "inputs": {
        "comp_cmd": true,
        "comp_status": true
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yFailToStart",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        },
        {
          "output": "yUnexpectedRun",
          "from_s": 0,
          "to_s": 2400,
          "equals": false
        }
      ]
    }
  ]
}