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

RTU-0011 — RTU supply-air temperature tracking failure

Statusverified — engine e2ff2f8, cxf:fnv1a128:0ae66df187840b9fde91f9796ee3082a, 2026-08-20
Severity3
Methodrule
Phase2
CategoryCOMFORT_ENERGY
ConfidenceMEDIUM
EstimationQUALITATIVE_ONLY
G36
Clusters
Suppresses
Suppressed by
RelatedRTU-0001, RTU-0002, RTU-0003, RTU-0004, RTU-0007, RTU-0008, RTU-0009, RTU-0010
Playbooksrtu-compressor-refrigerant
SourceVeronica, Automatically Detecting Faulty Regulation in HVAC Controls, HVAC&R Research 19(4), 2013, pp.412-422, DOI 10.1080/10789669.2013.789369 — regulated variables are checked against user allowance bands; semantic evidence, not this graph’s thresholds; Library CHW-0007 and HW-0010 — strict mirrored tracking-error topology with operating-premise gating and persistent alarm; Library-authored packaged-DX adaptation; no cited source publishes the shipped 2 K and 900 s combination as portable
Operating statesnormal automatic RTU heating or cooling after airflow and active mechanical delivery are established and the active SAT target has settled

Preconditions (host-enforced): sat and sat_sp must be the same RTU’s discharge temperature and final active mode-specific target, not an occupied default or cooling-only constant during heating. sf_status must prove stable supply airflow; comp_status or htg_status must represent active mechanical conditioning. Exclude startup, defrost, post-heat fan delay, mode/setpoint changes, demand response, low-ambient protection, and OEM capacity or safety limits. Suspend or smooth evaluation across normal staged/cycling-DX off intervals. Points must be healthy, fresh, aligned, and in degC. yConditioningActive false means NO_EVAL; true does not prove fan status or the other host obligations.

Points: sat, sat_sp, sf_status, comp_status, htg_status

Outputs:

  • yFault — True after proven fan operation and mechanical conditioning remain outside either side of the active SAT band for sustained_duration
  • yTooWarm — Diagnostic direction flag; true while evaluable SAT is strictly more than tracking_error above setpoint. False never means NO_EVAL
  • yTooCold — Diagnostic direction flag; true while evaluable SAT is strictly more than tracking_error below setpoint. False never means NO_EVAL
  • yConditioningActive — Evaluability subcondition; true when compressor or heating proof is active. False means NO_EVAL; true does not establish every host precondition

Parameters:

NameDefaultUnitCXF pathDescription
tracking_error2.0KtooWarmRaw.t, tooColdRaw.tADOPTED_TUNABLE symmetric settled SAT allowance; equality is clear. Commission above combined sensor uncertainty, cycling ripple, and normal deadband.
sustained_duration900.0spersist.delayTimeLIBRARY_PRECEDENT continuous out-of-band duration; commission above normal mode, staging, and setpoint-settling dynamics.

Description

This rule reports an RTU whose discharge temperature remains materially outside its final active target while the supply fan and mechanical heating or cooling are proven active. Warm/cold outputs identify the observed direction, not the failed component or commanded mode.

Detection Logic

error               = sat - sat_sp
conditioning_active = comp_status OR htg_status
running_conditioning = sf_status AND conditioning_active
too_warm            = error > tracking_error
too_cold            = -error > tracking_error

yConditioningActive = conditioning_active
yTooWarm = running_conditioning AND too_warm
yTooCold = running_conditioning AND too_cold
yFault = running_conditioning AND (too_warm OR too_cold),
         sustained for sustained_duration

RTU-0011 block graph

Comparisons are strict and direction flags are immediate. One TrueDelay(delayOnInit=true) follows the warm/cold OR, so a direct sampled direction handoff preserves age while any in-band or non-running tick resets it.

Possible Diagnoses

  1. Active setpoint misbound, stale, overridden, or not delivered locally
  2. Insufficient heating/cooling capacity, failed stage, or abnormal cycling
  3. Low supply airflow, dirty filter/coil, or failed fan proof
  4. Economizer or outdoor-air damper introducing the wrong air condition
  5. Refrigerant charge, condenser airflow, compressor, or heat-section fault
  6. Sensor bias or normal OEM limit/transition omitted from host gating

Energy Impact

A tracking fault may increase compressor, heating, or fan runtime and can miss temperature or humidity targets. The sign alone does not establish waste; no energy is inferred from temperature error without a causal model.

Emissions Impact

Scope 1 and/or 2, qualitative. Quantify only after isolating the cause and measuring affected fuel or electrical input against a defensible baseline.

Deviations

  • The 2 K / 900 s pair is adopted library logic. The sources support SAT tracking as a diagnostic, not portable packaged-unit thresholds.
  • Confidence is MEDIUM rather than the brief’s proposed HIGH. Cycling DX, active-setpoint semantics, airflow, OEM limits, and transitions require commissioned host gates before the same signature is causal.
  • Mechanical status is an OR, not a mode table. Heating-only, cooling-only, or both-active raw states are detectable; the host validates the actual mode.
  • Direction handoff preserves persistence. A sampled warm-to-cold transition remains continuously out of band; an actual in-band sample resets the timer.
  • Proof faults remain related, not suppressors. Current metadata cannot suppress tracking only for RTU-0010’s fail-to-start direction.
  • No empirical FPR or TPR is claimed. The current harness lacks a defensible final active RTU SAT setpoint plus aligned mechanical-status mapping.

Test Vectors

15 scenarios, clock step 60 s over 1500 s.

ScenarioDescription
fan_off_with_large_errorTemperature error without proven supply airflow is not evaluable.
fan_on_without_mechanical_conditioningA running fan alone does not establish the mechanical-delivery premise.
active_on_setpointStable mechanical operation at the active target is healthy.
cooling_status_too_warmCooling proof with SAT above its target drives the warm diagnostic and matures the alarm.
heating_status_too_coldHeating-only proof with SAT below target exercises the independent cold direction.
exact_warm_error_is_clearThe +2 K boundary is clear under the strict comparison.
exact_cold_error_is_clearThe -2 K boundary is clear under the mirrored strict comparison.
both_conditioning_statuses_active_raw_caseThe graph intentionally treats either or both mechanical statuses as active; host mode validation remains external.
normal_dx_off_cycle_resets_timerA normal compressor-off interval removes evaluability and resets an immature error episode.
violation_ends_one_step_before_persistenceAn error ending one evaluator step before 900 s never matures.
mature_warm_fault_recoversA mature alarm and its direction clear immediately when SAT returns to band.
two_short_error_episodes_resetTwo sub-duration violations separated by an in-band interval cannot accumulate persistence.
initialization_violation_serves_full_delaydelayOnInit requires the full tracking interval after evaluator startup.
sampled_direction_handoff_preserves_persistenceA direct sampled warm-to-cold handoff stays continuously outside the symmetric band and preserves timer age.
setpoint_step_raw_transientAn unexcluded active-setpoint step can create raw error, but this short case resets before alarm maturity.
vectors.json
{
  "schema": "cxf-library/vectors/v1",
  "clock": {
    "step_s": 60,
    "horizon_s": 1500
  },
  "scenarios": [
    {
      "name": "fan_off_with_large_error",
      "description": "Temperature error without proven supply airflow is not evaluable.",
      "inputs": {
        "sat": 20.0,
        "sat_sp": 15.0,
        "sf_status": false,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yConditioningActive",
          "from_s": 0,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        }
      ]
    },
    {
      "name": "fan_on_without_mechanical_conditioning",
      "description": "A running fan alone does not establish the mechanical-delivery premise.",
      "inputs": {
        "sat": 20.0,
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": false,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yConditioningActive",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        }
      ]
    },
    {
      "name": "active_on_setpoint",
      "description": "Stable mechanical operation at the active target is healthy.",
      "inputs": {
        "sat": 15.0,
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yConditioningActive",
          "from_s": 0,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        }
      ]
    },
    {
      "name": "cooling_status_too_warm",
      "description": "Cooling proof with SAT above its target drives the warm diagnostic and matures the alarm.",
      "inputs": {
        "sat": 17.1,
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yConditioningActive",
          "from_s": 0,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 1500,
          "equals": true
        }
      ]
    },
    {
      "name": "heating_status_too_cold",
      "description": "Heating-only proof with SAT below target exercises the independent cold direction.",
      "inputs": {
        "sat": 12.9,
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": false,
        "htg_status": true
      },
      "expect": [
        {
          "output": "yConditioningActive",
          "from_s": 0,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 1500,
          "equals": true
        }
      ]
    },
    {
      "name": "exact_warm_error_is_clear",
      "description": "The +2 K boundary is clear under the strict comparison.",
      "inputs": {
        "sat": 17.0,
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        }
      ]
    },
    {
      "name": "exact_cold_error_is_clear",
      "description": "The -2 K boundary is clear under the mirrored strict comparison.",
      "inputs": {
        "sat": 13.0,
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        }
      ]
    },
    {
      "name": "both_conditioning_statuses_active_raw_case",
      "description": "The graph intentionally treats either or both mechanical statuses as active; host mode validation remains external.",
      "inputs": {
        "sat": 17.1,
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": true,
        "htg_status": true
      },
      "expect": [
        {
          "output": "yConditioningActive",
          "from_s": 0,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 1500,
          "equals": true
        }
      ]
    },
    {
      "name": "normal_dx_off_cycle_resets_timer",
      "description": "A normal compressor-off interval removes evaluability and resets an immature error episode.",
      "inputs": {
        "sat": 18.0,
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": [
          {
            "t": 0,
            "value": true
          },
          {
            "t": 600,
            "value": false
          },
          {
            "t": 900,
            "value": true
          }
        ],
        "htg_status": false
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 540,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 600,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yTooWarm",
          "from_s": 900,
          "to_s": 1500,
          "equals": true
        }
      ]
    },
    {
      "name": "violation_ends_one_step_before_persistence",
      "description": "An error ending one evaluator step before 900 s never matures.",
      "inputs": {
        "sat": [
          {
            "t": 0,
            "value": 18.0
          },
          {
            "t": 840,
            "value": 15.0
          }
        ],
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 780,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 840,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        }
      ]
    },
    {
      "name": "mature_warm_fault_recovers",
      "description": "A mature alarm and its direction clear immediately when SAT returns to band.",
      "inputs": {
        "sat": [
          {
            "t": 0,
            "value": 18.0
          },
          {
            "t": 1080,
            "value": 15.0
          }
        ],
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 1020,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 1080,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 1020,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 1080,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        }
      ]
    },
    {
      "name": "two_short_error_episodes_reset",
      "description": "Two sub-duration violations separated by an in-band interval cannot accumulate persistence.",
      "inputs": {
        "sat": [
          {
            "t": 0,
            "value": 18.0
          },
          {
            "t": 480,
            "value": 15.0
          },
          {
            "t": 720,
            "value": 18.0
          },
          {
            "t": 1200,
            "value": 15.0
          }
        ],
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        }
      ]
    },
    {
      "name": "initialization_violation_serves_full_delay",
      "description": "delayOnInit requires the full tracking interval after evaluator startup.",
      "inputs": {
        "sat": 18.0,
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        }
      ]
    },
    {
      "name": "sampled_direction_handoff_preserves_persistence",
      "description": "A direct sampled warm-to-cold handoff stays continuously outside the symmetric band and preserves timer age.",
      "inputs": {
        "sat": [
          {
            "t": 0,
            "value": 18.0
          },
          {
            "t": 480,
            "value": 12.0
          }
        ],
        "sat_sp": 15.0,
        "sf_status": true,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 420,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 480,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 420,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 480,
          "to_s": 1500,
          "equals": true
        },
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 1500,
          "equals": true
        }
      ]
    },
    {
      "name": "setpoint_step_raw_transient",
      "description": "An unexcluded active-setpoint step can create raw error, but this short case resets before alarm maturity.",
      "inputs": {
        "sat": 15.0,
        "sat_sp": [
          {
            "t": 0,
            "value": 15.0
          },
          {
            "t": 300,
            "value": 12.0
          },
          {
            "t": 720,
            "value": 15.0
          }
        ],
        "sf_status": true,
        "comp_status": true,
        "htg_status": false
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooWarm",
          "from_s": 0,
          "to_s": 240,
          "equals": false
        },
        {
          "output": "yTooWarm",
          "from_s": 300,
          "to_s": 660,
          "equals": true
        },
        {
          "output": "yTooWarm",
          "from_s": 720,
          "to_s": 1500,
          "equals": false
        },
        {
          "output": "yTooCold",
          "from_s": 0,
          "to_s": 1500,
          "equals": false
        }
      ]
    }
  ]
}