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

AHU-0016 — Simultaneous heating and cooling

Statusverified — engine e2ff2f8, cxf:fnv1a128:31aa24f4af35117444e0227b5a021b99, 2026-08-17
Severity2
Methodrule
Phase1
CategoryCRITICAL_WASTE
ConfidenceHIGH
EstimationDIRECT_MEASUREMENT
G36
ClustersCLU-01
Suppresses
Suppressed by
RelatedAHU-0004, AHU-0020, AHU-0025
Playbookssimultaneous-hc
SourceHVAC FDD Reference v1.0 §9, AHU-0016; PNNL-27338 retuning measures (EEM-38)
Operating statesall

Preconditions (host-enforced): Equipment is in occupied mode or supply fan is running. Host gates evaluation; when unmet, the verdict is NO_EVAL, not healthy.

Points: htg_vlv_cmd, clg_vlv_cmd

Outputs:

  • yFault — True while both valves have been open beyond their thresholds for at least alarm_delay

Parameters:

NameDefaultUnitCXF pathDescription
htg_vlv_threshold5.0%htgThr.tMinimum heating valve position considered open
clg_vlv_threshold5.0%clgThr.tMinimum cooling valve position considered open
alarm_delay900.0spersist.delayTimeContinuous fault persistence required before the alarm asserts

Description

The heating coil valve and cooling coil valve are both commanded open beyond their respective thresholds at the same time. The air stream is being heated and then cooled (or vice versa) — energy is simultaneously added and removed with no occupant benefit, so the entire overlap is waste. One of the most common and highest-impact faults in commercial buildings; the trigger rule for cluster CLU-01.

Detection Logic

yFault = (htg_vlv_cmd > htg_vlv_threshold)
     AND (clg_vlv_cmd > clg_vlv_threshold)
     sustained continuously for alarm_delay

Block graph (rule.cxf.jsonld):

AHU-0016 block graph

TrueDelay implements the alarm persistence: yFault asserts only after the combined condition has held continuously for alarm_delay seconds, and any interruption restarts the timer. The comparison is strict (>), so a valve sitting exactly at its threshold does not count as open. persist sets delayOnInit = true so a condition already present at engine start still waits out the full delay (engine-verified: the Modelica default of false passes an initially-true input through immediately, which would fire the alarm on the first tick after a controller restart).

Possible Diagnoses

  1. Control sequence error — heating and cooling loops fighting (missing interlock or overlapping deadbands)
  2. Stuck heating valve (mechanically open)
  3. Stuck cooling valve (mechanically open)
  4. Incorrect valve wiring (normally-open vs normally-closed)
  5. Poorly tuned PID loops with overlapping deadbands

Energy Impact

CRITICAL_WASTE, HIGH confidence, DIRECT_MEASUREMENT. Waste is directly computable from live points and design capacities: waste_kw = htg_vlv_cmd/100 × ahu_htg_capacity_kw + clg_vlv_cmd/100 × ahu_clg_capacity_kw. Savings range 10–30% of AHU thermal energy (PNNL-27338, EEM-38). Present in the majority of buildings; wastes energy in all seasons.

Emissions Impact

Scope 1 + 2 (gas heating waste + electric cooling waste), DIRECT_EMISSIONS, HIGH confidence. Typical range 2,000–15,000 kg CO₂e/yr. Avoided-emissions basis: marginal operating emissions rate (MOER).

Deviations

  • The reference card’s precondition (“equipment in occupied mode or fan running”) is declared in frontmatter for host enforcement rather than encoded in the block graph. The CXF computes the pure fault condition; gating its evaluation (and the NO_EVAL verdict when data is bad or the unit is off) is the host’s responsibility, matching the open-control engine’s status-blind design.
  • Threshold comparisons use zero hysteresis (h = 0 on both GreaterThreshold blocks), like the reference. If command signals chatter around the threshold in practice, set h per site rather than raising t.
  • persist.delayOnInit = true (Modelica/CDL default is false). The reference is silent on startup behavior; we require the persistence window even when the condition pre-exists at load, to avoid instant alarms after controller restarts.

Notes

Fix the trigger first: resolving this rule typically clears the CLU-01 member faults (mode mismatch, lockout issues) within 24–48 hours. Remote fix succeeds ~70% of the time (interlock/deadband/override corrections) at $0 cost.

Test Vectors

7 scenarios, clock step 60 s over 1800 s.

ScenarioDescription
normal_heatingReference vector: heating only, no fault
normal_coolingReference vector: cooling only, no fault
both_openReference vector: both valves open; fault asserts after alarm_delay (900 s)
both_below_thresholdReference vector: both valves cracked but under threshold
deadbandReference vector: both valves closed
threshold_edgeExactly at threshold is not open (strict > comparison)
transient_overlapOverlap shorter than alarm_delay never alarms; TrueDelay resets on release
vectors.json
{
  "schema": "cxf-library/vectors/v1",
  "clock": {
    "step_s": 60,
    "horizon_s": 1800
  },
  "scenarios": [
    {
      "name": "normal_heating",
      "description": "Reference vector: heating only, no fault",
      "inputs": {
        "htg_vlv_cmd": 45.0,
        "clg_vlv_cmd": 0.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1800,
          "equals": false
        }
      ]
    },
    {
      "name": "normal_cooling",
      "description": "Reference vector: cooling only, no fault",
      "inputs": {
        "htg_vlv_cmd": 0.0,
        "clg_vlv_cmd": 60.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1800,
          "equals": false
        }
      ]
    },
    {
      "name": "both_open",
      "description": "Reference vector: both valves open; fault asserts after alarm_delay (900 s)",
      "inputs": {
        "htg_vlv_cmd": 20.0,
        "clg_vlv_cmd": 30.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 840,
          "equals": false
        },
        {
          "output": "yFault",
          "from_s": 960,
          "to_s": 1800,
          "equals": true
        }
      ]
    },
    {
      "name": "both_below_threshold",
      "description": "Reference vector: both valves cracked but under threshold",
      "inputs": {
        "htg_vlv_cmd": 3.0,
        "clg_vlv_cmd": 4.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1800,
          "equals": false
        }
      ]
    },
    {
      "name": "deadband",
      "description": "Reference vector: both valves closed",
      "inputs": {
        "htg_vlv_cmd": 0.0,
        "clg_vlv_cmd": 0.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1800,
          "equals": false
        }
      ]
    },
    {
      "name": "threshold_edge",
      "description": "Exactly at threshold is not open (strict > comparison)",
      "inputs": {
        "htg_vlv_cmd": 5.0,
        "clg_vlv_cmd": 5.0
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1800,
          "equals": false
        }
      ]
    },
    {
      "name": "transient_overlap",
      "description": "Overlap shorter than alarm_delay never alarms; TrueDelay resets on release",
      "inputs": {
        "htg_vlv_cmd": 20.0,
        "clg_vlv_cmd": [
          {
            "t": 0,
            "value": 30.0
          },
          {
            "t": 600,
            "value": 0.0
          }
        ]
      },
      "expect": [
        {
          "output": "yFault",
          "from_s": 0,
          "to_s": 1800,
          "equals": false
        }
      ]
    }
  ]
}