AHU-0016 — Simultaneous heating and cooling
| Status | verified — engine e2ff2f8, cxf:fnv1a128:31aa24f4af35117444e0227b5a021b99, 2026-08-17 |
| Severity | 2 |
| Method | rule |
| Phase | 1 |
| Category | CRITICAL_WASTE |
| Confidence | HIGH |
| Estimation | DIRECT_MEASUREMENT |
| G36 | — |
| Clusters | CLU-01 |
| Suppresses | — |
| Suppressed by | — |
| Related | AHU-0004, AHU-0020, AHU-0025 |
| Playbooks | simultaneous-hc |
| Source | HVAC FDD Reference v1.0 §9, AHU-0016; PNNL-27338 retuning measures (EEM-38) |
| Operating states | all |
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:
| Name | Default | Unit | CXF path | Description |
|---|---|---|---|---|
htg_vlv_threshold | 5.0 | % | htgThr.t | Minimum heating valve position considered open |
clg_vlv_threshold | 5.0 | % | clgThr.t | Minimum cooling valve position considered open |
alarm_delay | 900.0 | s | persist.delayTime | Continuous 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):
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
- Control sequence error — heating and cooling loops fighting (missing interlock or overlapping deadbands)
- Stuck heating valve (mechanically open)
- Stuck cooling valve (mechanically open)
- Incorrect valve wiring (normally-open vs normally-closed)
- 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 = 0on bothGreaterThresholdblocks), like the reference. If command signals chatter around the threshold in practice, sethper site rather than raisingt. persist.delayOnInit = true(Modelica/CDL default isfalse). 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.
| Scenario | Description |
|---|---|
normal_heating | Reference vector: heating only, no fault |
normal_cooling | Reference vector: cooling only, no fault |
both_open | Reference vector: both valves open; fault asserts after alarm_delay (900 s) |
both_below_threshold | Reference vector: both valves cracked but under threshold |
deadband | Reference vector: both valves closed |
threshold_edge | Exactly at threshold is not open (strict > comparison) |
transient_overlap | Overlap 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
}
]
}
]
}