Packaged / DX equipment FDD
0.5 extends the vendor-neutral rule library beyond built-up AHUs and central plants to the packaged
and refrigerant-side equipment that dominates small–mid commercial stock: rooftop units (RTU),
heat pumps / VRF, DOAS/ERV, fan-coil units (FCU), and refrigerant-side chiller degradation. As
always, rules key off Roles (not vendor tags), each ships a synthetic fixture, and each is
accuracy-scored in the synthetic benchmark.
flowchart LR
roles["Roles (compressor_status, filter_diff_press, cond_approach_temp, ...)"] --> tmpl["Equipment templates (RTU / HeatPump / DOAS / FCU)"]
tmpl -- "completeness gating" --> rules
subgraph rules["camber.rules.builtin — DX detectors"]
r1[compressor_short_cycle]
r2[compressor_staging]
r3[heatpump_defrost]
r4[filter_fouling]
r5[chiller_approach_fouling]
end
r1 --> find[Findings]
r2 --> find
r3 --> find
r4 --> find
r5 --> find
find --> bench["synthetic benchmark (VALIDATION.md)"]
Role-keyed DX detectors, gated by per-unit completeness, fan into Findings scored by the benchmark.
New roles
Status/stage and refrigerant-side signals (all with PHYSICAL_BOUNDS + a Haystack hint):
| Role | Meaning |
|---|---|
compressor_status |
DX compressor running (1) / off (0) |
compressor_stage |
active DX cooling stage (0,1,2,…) |
condenser_fan_status |
condenser/outdoor fan running |
heat_stage |
active gas/electric heating stage |
reversing_valve_cmd |
heat-pump mode: heating (1) / cooling (0) |
filter_diff_press |
differential pressure across the air filter (inH2O) |
supply_air_humidity / return_air_humidity |
air-side relative humidity (%) |
cond_approach_temp / evap_approach_temp |
chiller approach temperatures (°F) |
New equipment templates
RTU, HeatPump (VRF), DOAS (ERV via optional humidity roles), and FCU (now a distinct template,
not the VAV alias). Completeness validation gates which rules can run per unit — e.g. HeatPump
requires a reversing-valve command; DOAS requires outdoor-air flow.
New rules
compressor_short_cycle— DX compressor firing in short bursts (starts/day past a min-off-time ceiling). Reuses the generic on/off start counter. Flag:max_starts_per_day. On a trend coarser thanmax_interval_min(15 minutes) the count is a floor: a verdict below the ceiling declines toinfowhen the resampled status shows the compressor on for part of an interval (see TIME-HANDLING.md).compressor_staging— unstable multi-stage DX (excess stage changes/day). Flag:max_changes_per_day. Since 0.106 only changes while the compressor runs count: a start or a stop (to or from stage 0) iscompressor_short_cycle's to judge.heatpump_defrost— excess heat-pump defrost / reversing-valve cycling (iced coil or faulty defrost termination). Flag:max_reversals_per_day. On a resampled valve, a bin part-way between heating and cooling counts the fewest changes it allows (two for a defrost inside a heating spell).
Both rules take max_interval_min (default 5 minutes, since 0.106): a defrost or a short stage
averages away in a coarser bin, so the count is a floor. A count at or above the limit keeps its
verdict with a caveat; below it, samples part-way between two states decline the verdict to
info ("cannot confirm on a 15-minute trend").
- filter_fouling — air filter at/above its change-out differential pressure (wasted fan energy,
starved airflow). Flag: change_dp_inwc (default ~1.0 inH2O).
- chiller_approach_fouling — condenser/evaporator approach-temperature degradation (tube
fouling / low charge), the refrigerant-side indicator that needs no refrigerant-pressure
instrumentation. Flags: cond_design_f, evap_design_f.
Every rule returns info (not a false fault) when its required role is absent, is registered in
camber.rules.builtin, and runs unchanged across any building once its points are mapped.
Refrigerant-side DX and heat-pump rules (0.93)
Provisional (GitHub issues #40 and #6). Three detectors read a DX unit's or heat pump's refrigerant circuit and indoor coil. Each has two modes:
- Target mode (
analyze, the ordinarycamber run): the unit's median in cooling operation is compared with a manufacturer target. Passtargets={"subcooling_f": 10}, or per equipment{"RTU-*": {...}}. Without a target, only wide universal limits apply. - Baseline mode (
analyze_periods, the newdxdrift family,camber drift): the unit is compared with its own frozen fault-free period at matched conditions. The normalizer is outdoor air plus return air for an air-cooled unit, or load in tons for a water-cooled machine. The finding reports the current median residual against both a degF floor and a sigma floor.
Rows count only in cooling operation: compressor on, reversing valve in cooling and supply air below return, whichever of those points are mapped. The inputs can be controller-reported or derived from pressures and line temperatures (see Refrigerant properties).
| Rule | Reads | Verdicts |
|---|---|---|
dx_refrigerant_charge (built-in) |
liquid subcooling; superheat as corroboration (metric="superheat" for a fixed-orifice unit) |
undercharge, overcharge |
dx_indoor_airflow (built-in) |
evaporator temperature split, return minus supply, matched on return air and the return dew point (return_air_dewpoint_temp, or computed from return RH) |
airflow_low, airflow_high; a narrowed split with subcooling down is reported as capacity_low (charge), not high airflow |
discharge_superheat_drift (dx family, #6) |
discharge superheat, two-sided, plus the family's CUSUM for a sustained shift | rose: starved compressor (low charge, restriction); fell: liquid reaching it (overcharge, floodback) |
Without targets the universal limits are 2-25 °F of subcooling (below 2 °F the valve is fed flash gas) and an 8-28 °F split. The magnitude floors are screening-grade and were characterized on the NIST heat pumps below. They are constructor arguments.
Equipment classes. A new dx family covers split and packaged AC and condensing units that are
not heat pumps (DX, SPLIT_SYSTEM, CONDENSING_UNIT, ACCU, ...). dx_refrigerant_charge runs on
heat_pump, dx and air_handler (an RTU's DX section). dx_indoor_airflow runs on heat_pump
and dx only: a chilled-water AHU's split follows its valve, not its airflow.
Scored on nist-heatpump-fdd
The data are 56 scoreable labelled runs: two residential R-410A split heat pumps with TXVs, each on
two line sets, tested in steady state. The 17 mislabelled EF files, the 6 CF_50 files and the 2
JUNK files are excluded as the catalog documents. Each test file (one steady-state test,
269 files) was scored against the fault-free files of the same unit and line set, leaving the file
itself out (baseline mode, one averaged row per test point). The rates are Wilson 95% intervals.
| Detector | Target faults | TPR | FPR (fault-free files) | Fires on other faults |
|---|---|---|---|---|
dx_refrigerant_charge |
under- / overcharge (incl. doubles) | 86/95 = 91% [83-95] | 7/90 = 8% [4-15] | 34/84 = 40% |
dx_indoor_airflow |
low / high indoor airflow (incl. doubles) | 20/93 = 22% [14-31] | 2/90 = 2% [1-8] | 2/86 = 2% |
discharge_superheat_drift |
under- / overcharge | 30/95 = 32% [23-41] | 5/90 = 6% [2-12] | 27/84 = 32% |
What the numbers say:
- Charge. Every overcharge file (16/16) and 70 of 79 undercharge files are caught. All 34 of its firings on other faults are condenser-blockage tests, alone or with an airflow fault, and mostly on the 16 SEER unit, whose subcooling falls 4-9 °F with a blocked coil exactly as it does with undercharge. A charge verdict is therefore a prompt to check the charge and the condenser coil. Scored per run, with fault-free runs split into two halves by file, it catches 23/24 charge runs at 0/8 false alarms. The run template's target mode, with targets taken from the fault-free runs and so in-sample, catches 24/24 at 0/4.
- Indoor airflow. At 15% or more airflow reduction (EF ≤ 85%) it catches 18/44 files. At 5-14% (0/22) and on increased airflow (2/27) the split change sits inside the fault-free scatter. Most of the labelled airflow tests are double faults with undercharge or condenser blockage, which pull the split the other way. It almost never fires on a non-airflow fault (2%).
- Discharge superheat (#6). Weaker than subcooling on these TXV units, confirming #6's original
finding that it is the less sensitive signal. Undercharge raises it (30/79 files); overcharge
barely moves it (0/16). Two of its five fault-free false alarms are the two files that run
with negative suction superheat (
fault-free-points-not-steady-cooling).
All seven charge false alarms are on the 14 SEER unit. Three are files with essentially no subcooling: the two 2016-03-15 files in that data issue, and a two-point long-line-set file at -1.1 °F. The other four sit 3.2-4.3 °F below their reference, just past the 3 °F floor. They are left as published and count against the rule.
Water-source heat pumps with three points (0.93)
Water-to-air heat pumps in a school or office are often trended with only discharge air, fan
status and zone temperature: no compressor status, no reversing valve and no refrigerant point.
rules.heatpump_ops_rule.infer_hp_mode reads the mode from the discharge air against the zone:
15 °F or more above means heating, 10 °F or more below means cooling. A mapped compressor status
and reversing valve take precedence. Three rules follow, all on the heat_pump class:
hp_mode_vs_need: occupied hours spent cooling a room already below its heating setpoint, or heating one above its cooling setpoint (changeover, thermostat location, setpoints).hp_capacity_shortfall: the room outside its band. When the unit was already working in the right mode, the verdict is capacity (unit size, airflow, source-loop temperature, refrigerant). When the unit was idle or running the other way, the verdict is control (thermostat, setpoint, schedule, a stuck changeover), and the summary gives the split. This is the heat-pump counterpart of the VAV "zone below setpoint with reheat saturated" rule, which is for terminal boxes only. Whenhp_room_imbalanceruns in the same config and finds a unit's room fighting, a capacity or mixed verdict on that unit becomesroom_fight(the severity stands,verdict_alonekeeps the old verdict): the cause is the fight, not the unit's size.hp_room_imbalance(fleet): two or more units serving one room that fight (one heats while another cools), split the work very unevenly, or whose zone sensors disagree. Rooms come from the rule'srooms={room: [units]}or from a topology whose parent of each unit is its room.
Setpoints are the mapped heat_sp / cool_sp. Otherwise a stated default band of 68-76 °F applies,
with a caveat.
Defrost (0.106). With the mode read from the compressor and the reversing valve, a cooling run
of at most max_defrost_min (15 minutes) with heating on both sides is an air-source unit's
defrost, and all three rules count it as heating. hp_mode_vs_need reports the occupied defrost
time as defrost_hours; heatpump_defrost judges how often a unit defrosts.
Source loop. source_loop_deltat (class family source_loop: GEO_LOOP, GROUND_LOOP, ...)
reads a heat-pump loop through the new generic roles source_loop_supply_temp,
source_loop_return_temp, source_loop_diff_press and source_loop_pump_speed. These are kept
apart from the chilled-water and tower roles, whose rules would misread such a loop. While the
pumps run, it flags a loop that carries almost no heat. The warn condition is a |ΔT| under 1 °F for
half the pumping time with a 90th percentile below half the ~10 °F design difference; the fault
condition is 80% of the time with a 90th percentile below a quarter of it. That pattern is
overpumping: constant speed, no differential-pressure reset, or heat-pump isolation valves left
open.
The faultlab scenarios for the five new single-equipment rules (dx_refrigerant_charge,
dx_indoor_airflow, hp_mode_vs_need, hp_capacity_shortfall, source_loop_deltat) were signed
off as gated synthetic keys for 0.93 (see VALIDATION.md). hp_mode_vs_need and
hp_capacity_shortfall decline equipment with a heating or cooling valve and no compressor or
reversing-valve signal: that is a hydronic coil unit, and on a terminal box with no recorded class
the out-of-reheat case belongs to reheat_capacity_shortfall.