FCU-0007 — Simultaneous heating and cooling commands
| Status | verified — engine e2ff2f8, cxf:fnv1a128:4b59bb37f9d6537178e60d22896cd2d6, 2026-08-20 |
| Severity | 2 |
| Method | rule |
| Phase | 2 |
| Category | CRITICAL_WASTE |
| Confidence | HIGH |
| Estimation | QUALITATIVE_ONLY |
| G36 | — |
| Clusters | — |
| Suppresses | — |
| Suppressed by | — |
| Related | FCU-0004, FCU-0005 |
| Playbooks | simultaneous-hc, fcu-faults |
| Source | HVAC FDD Reference v1.0 ch.12 and library AHU-0016 — ordinary simultaneous heating/cooling command overlap is an actionable control-waste signature; Library-authored FCU command-level adaptation; no cited source publishes the shipped 10% thresholds or 300 s duration as portable values |
| Operating states | normal automatic FCU heating or cooling when simultaneous cooling-plus-reheat is not an intended humidity-control sequence |
Preconditions (host-enforced): htg_vlv_cmd and clg_vlv_cmd must be physical 0-100% valve commands for heating and cooling coils on the same FCU, with known scaling and fail positions. Exclude valve exercise, freeze protection, commissioning tests, and intentional dehumidification/cooling-plus-reheat. If simultaneous conditioning is a designed mode, provide a host mode gate or do not instantiate this rule. Inputs must be fresh and aligned; unmet obligations are NO_EVAL, not healthy.
Points: htg_vlv_cmd, clg_vlv_cmd
Outputs:
yFault— True after both same-FCU valve commands remain strictly above their active thresholds for sustained_durationyHeatingActive— Diagnostic sub-condition flag; true while the heating command is strictly above threshold. False never means NO_EVALyCoolingActive— Diagnostic sub-condition flag; true while the cooling command is strictly above threshold. False never means NO_EVAL
Parameters:
| Name | Default | Unit | CXF path | Description |
|---|---|---|---|---|
heating_active_threshold | 10.0 | % | heatingActive.t | ADOPTED_TUNABLE material heating-command threshold; equality is clear. Commission above leakage, actuator minimum, and command noise. |
cooling_active_threshold | 10.0 | % | coolingActive.t | ADOPTED_TUNABLE material cooling-command threshold; equality is clear. Commission above leakage, actuator minimum, and command noise. |
sustained_duration | 300.0 | s | persist.delayTime | LIBRARY_PRECEDENT continuous overlap duration; verify against normal FCU mode transitions and sampling. |
Description
This rule detects a same-FCU heating-valve command and cooling-valve command materially open together. It reports a command conflict, not physical valve position or coil heat transfer. FCU-0004/0005 remain useful for distinguishing unintended thermal effect with a nominally closed command.
Detection Logic
heating_active = htg_vlv_cmd > heating_active_threshold
cooling_active = clg_vlv_cmd > cooling_active_threshold
yHeatingActive = heating_active
yCoolingActive = cooling_active
yFault = (heating_active AND cooling_active) sustained for sustained_duration
Both comparisons are strict. TrueDelay(delayOnInit=true) starts only while
both subconditions are true; either command clearing resets the timer and a
mature alarm clears immediately.
Possible Diagnoses
- Heating and cooling PID loops overlap or lack an interlock
- Occupancy, mode, or setpoint transition leaves both outputs active
- BAS priority-array override holds one command open
- Wrong point scaling or commands bound from different FCUs
- Intentional dehumidification/reheat, freeze, or exercise mode not host-gated
Energy Impact
Simultaneous commands can add and remove heat at the same terminal, increasing plant and fan energy. Commands alone do not prove valve position, water flow, or thermal transfer, so this card makes no portable savings claim.
Emissions Impact
Scope 1 and/or 2 effects depend on the serving heating and cooling plants. Quantify only after measuring delivered heat and avoided plant input energy.
Deviations
- Thresholds and duration are library defaults. The source supports the signature, not universal 10% or 300 s values; commission all three.
- Intentional cooling-plus-reheat is host-excluded. The graph has no mode input and intentionally alarms on that raw overlap when the host gate is absent.
- CLU-01 is unchanged. Its AHU-0016 trigger cannot causally clear a local FCU command conflict under the cluster contract; shared workflow is represented by the simultaneous-hc playbook instead.
- Leak rules are not suppressed. A command conflict and physical heat transfer with a closed command are different evidence and can co-occur.
- No empirical FPR or TPR is claimed. No current harness exposes both physical same-FCU valve commands without inference; validation is synthetic only.
Test Vectors
12 scenarios, clock step 60 s over 720 s.
| Scenario | Description |
|---|---|
both_closed | Ordinary idle operation keeps both direction flags and the alarm clear. |
heating_only | A material heating command without cooling is valid single-mode operation. |
cooling_only | A material cooling command without heating is valid single-mode operation. |
both_exactly_at_threshold | Strict comparisons keep equality clear on both lanes. |
one_just_above_one_at_threshold | One active lane cannot mature a simultaneous-command fault. |
both_just_above_sustained | One representable increment above both thresholds matures only after persistence. |
overlap_ends_one_step_before_persistence | A conflict ending one evaluator step before 300 s never matures. |
mature_overlap_recovers_when_cooling_clears | A mature fault clears immediately when either material command clears. |
two_short_overlaps_reset | Two sub-duration conflicts separated by either valve clearing do not accumulate time. |
initialization_overlap_serves_full_delay | delayOnInit prevents an immediate alarm when the evaluator starts inside a conflict. |
heating_reset_then_cooling_overlap | Clearing the heating lane resets the timer before a later overlap. |
intentional_dehumidification_raw_case | The raw graph alarms on intentional cooling-plus-reheat; the host must exclude that mode or not instantiate this rule. |
vectors.json
{
"schema": "cxf-library/vectors/v1",
"clock": {
"step_s": 60,
"horizon_s": 720
},
"scenarios": [
{
"name": "both_closed",
"description": "Ordinary idle operation keeps both direction flags and the alarm clear.",
"inputs": {
"htg_vlv_cmd": 0.0,
"clg_vlv_cmd": 0.0
},
"expect": [
{
"output": "yHeatingActive",
"from_s": 0,
"to_s": 720,
"equals": false
},
{
"output": "yCoolingActive",
"from_s": 0,
"to_s": 720,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 720,
"equals": false
}
]
},
{
"name": "heating_only",
"description": "A material heating command without cooling is valid single-mode operation.",
"inputs": {
"htg_vlv_cmd": 40.0,
"clg_vlv_cmd": 0.0
},
"expect": [
{
"output": "yHeatingActive",
"from_s": 0,
"to_s": 720,
"equals": true
},
{
"output": "yCoolingActive",
"from_s": 0,
"to_s": 720,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 720,
"equals": false
}
]
},
{
"name": "cooling_only",
"description": "A material cooling command without heating is valid single-mode operation.",
"inputs": {
"htg_vlv_cmd": 0.0,
"clg_vlv_cmd": 40.0
},
"expect": [
{
"output": "yHeatingActive",
"from_s": 0,
"to_s": 720,
"equals": false
},
{
"output": "yCoolingActive",
"from_s": 0,
"to_s": 720,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 720,
"equals": false
}
]
},
{
"name": "both_exactly_at_threshold",
"description": "Strict comparisons keep equality clear on both lanes.",
"inputs": {
"htg_vlv_cmd": 10.0,
"clg_vlv_cmd": 10.0
},
"expect": [
{
"output": "yHeatingActive",
"from_s": 0,
"to_s": 720,
"equals": false
},
{
"output": "yCoolingActive",
"from_s": 0,
"to_s": 720,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 720,
"equals": false
}
]
},
{
"name": "one_just_above_one_at_threshold",
"description": "One active lane cannot mature a simultaneous-command fault.",
"inputs": {
"htg_vlv_cmd": 10.1,
"clg_vlv_cmd": 10.0
},
"expect": [
{
"output": "yHeatingActive",
"from_s": 0,
"to_s": 720,
"equals": true
},
{
"output": "yCoolingActive",
"from_s": 0,
"to_s": 720,
"equals": false
},
{
"output": "yFault",
"from_s": 0,
"to_s": 720,
"equals": false
}
]
},
{
"name": "both_just_above_sustained",
"description": "One representable increment above both thresholds matures only after persistence.",
"inputs": {
"htg_vlv_cmd": 10.1,
"clg_vlv_cmd": 10.1
},
"expect": [
{
"output": "yHeatingActive",
"from_s": 0,
"to_s": 720,
"equals": true
},
{
"output": "yCoolingActive",
"from_s": 0,
"to_s": 720,
"equals": true
},
{
"output": "yFault",
"from_s": 0,
"to_s": 240,
"equals": false
},
{
"output": "yFault",
"from_s": 360,
"to_s": 720,
"equals": true
}
]
},
{
"name": "overlap_ends_one_step_before_persistence",
"description": "A conflict ending one evaluator step before 300 s never matures.",
"inputs": {
"htg_vlv_cmd": 25.0,
"clg_vlv_cmd": [
{
"t": 0,
"value": 25.0
},
{
"t": 240,
"value": 0.0
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 720,
"equals": false
},
{
"output": "yHeatingActive",
"from_s": 0,
"to_s": 720,
"equals": true
},
{
"output": "yCoolingActive",
"from_s": 0,
"to_s": 180,
"equals": true
},
{
"output": "yCoolingActive",
"from_s": 240,
"to_s": 720,
"equals": false
}
]
},
{
"name": "mature_overlap_recovers_when_cooling_clears",
"description": "A mature fault clears immediately when either material command clears.",
"inputs": {
"htg_vlv_cmd": 25.0,
"clg_vlv_cmd": [
{
"t": 0,
"value": 25.0
},
{
"t": 420,
"value": 0.0
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 240,
"equals": false
},
{
"output": "yFault",
"from_s": 360,
"to_s": 360,
"equals": true
},
{
"output": "yFault",
"from_s": 420,
"to_s": 720,
"equals": false
},
{
"output": "yHeatingActive",
"from_s": 0,
"to_s": 720,
"equals": true
},
{
"output": "yCoolingActive",
"from_s": 0,
"to_s": 360,
"equals": true
},
{
"output": "yCoolingActive",
"from_s": 420,
"to_s": 720,
"equals": false
}
]
},
{
"name": "two_short_overlaps_reset",
"description": "Two sub-duration conflicts separated by either valve clearing do not accumulate time.",
"inputs": {
"htg_vlv_cmd": 25.0,
"clg_vlv_cmd": [
{
"t": 0,
"value": 25.0
},
{
"t": 180,
"value": 0.0
},
{
"t": 300,
"value": 25.0
},
{
"t": 480,
"value": 0.0
}
]
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 720,
"equals": false
}
]
},
{
"name": "initialization_overlap_serves_full_delay",
"description": "delayOnInit prevents an immediate alarm when the evaluator starts inside a conflict.",
"inputs": {
"htg_vlv_cmd": 30.0,
"clg_vlv_cmd": 30.0
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 240,
"equals": false
},
{
"output": "yFault",
"from_s": 360,
"to_s": 720,
"equals": true
},
{
"output": "yHeatingActive",
"from_s": 0,
"to_s": 720,
"equals": true
},
{
"output": "yCoolingActive",
"from_s": 0,
"to_s": 720,
"equals": true
}
]
},
{
"name": "heating_reset_then_cooling_overlap",
"description": "Clearing the heating lane resets the timer before a later overlap.",
"inputs": {
"htg_vlv_cmd": [
{
"t": 0,
"value": 25.0
},
{
"t": 180,
"value": 0.0
},
{
"t": 360,
"value": 25.0
}
],
"clg_vlv_cmd": 25.0
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 600,
"equals": false
},
{
"output": "yFault",
"from_s": 720,
"to_s": 720,
"equals": true
}
]
},
{
"name": "intentional_dehumidification_raw_case",
"description": "The raw graph alarms on intentional cooling-plus-reheat; the host must exclude that mode or not instantiate this rule.",
"inputs": {
"htg_vlv_cmd": 35.0,
"clg_vlv_cmd": 60.0
},
"expect": [
{
"output": "yFault",
"from_s": 0,
"to_s": 240,
"equals": false
},
{
"output": "yFault",
"from_s": 360,
"to_s": 720,
"equals": true
},
{
"output": "yHeatingActive",
"from_s": 0,
"to_s": 720,
"equals": true
},
{
"output": "yCoolingActive",
"from_s": 0,
"to_s": 720,
"equals": true
}
]
}
]
}