Fan-coil and standalone zone-controller rules

Open-FDD ships nine FCU-* rules for fan coils and standalone DDC zone controllers. They run in the DataFusion product engine and in the external open_fdd.rules pandas oracle. Per-rule equations, parameter tables, and the shipped SQL / pandas are in the DataFusion SQL cookbook and the Pandas cookbook. This page is the unit-boundary and equipment-kind deep dive.

Registry equipment kinds are zone_other, general, and ahu (sql_rules/registry.yaml and CookbookRule in open_fdd/rules/cookbook_catalog.py). Stamps zone_other, zone, fcu, fanCoil, fanCoilUnit, and the standalone-DDC aliases canonicalize to zone_other (edge/src/equipment_types.rs).

Haystack tags stack on one record: an equip carries equip + fanCoilUnit + zone together, and each point carries its own markers (zone + air + temp + sensor, discharge + air + temp, heating + valve + cmd, and so on). Open-FDD consumes that stack as one stamp plus the flattened point-role map (zone-air-temp, discharge-air-temp, heating-valve, …). The equipment id is only the historian key. Master versus follower is the stamp (zone_other versus resolved kind general) together with whether zone-air-temp is mapped. Detail: Zone terminals.

Rule Screening condition Confirm
FCU-SENSOR-NULL Own zone SP exists and zone temperature is null for at least 90% of the window 0
FCU-HTG-COIL Fan on, heating ≥80%, SAT − zone <5.4°F 15 min
FCU-CLG-COIL Fan on, cooling ≥80%, heat shut, SAT ≥ zone 15 min
FCU-VALVE-PASS-HTG Fan on, both valves shut, SAT − zone >5.4°F 15 min
FCU-VALVE-PASS-CLG Fan on, both valves shut, zone − SAT >5.4°F 15 min
FCU-DAMPER-POS Fan on, command ≥15%, feedback >15 points low 15 min
FCU-CO2-DAMPER Fan on, valid CO₂ >1000 ppm, damper command <10% 15 min
FCU-DEADBAND Cooling SP − heating SP <1°C 0
FCU-MODE-CYCLE At least four heat/cool valve mode transitions in the window 0

Equipment kinds

Controller Stamp (equipType / equipment_type) Registry kind
Sensor-owning fan coil or standalone zone controller (master) zone_other, zone, fcu, fanCoil, fanCoilUnit, or a standalone-DDC alias zone_other
Follower with no zone sensor of its own registry kind general (see below) general
Stamped air handler that also carries these roles ahu and the AHU aliases ahu

A fan coil’s stamp stays on the zone_other row. Stamping it ahu sends it down the air-handler path. ahu remains on the FCU registry list so an air handler can run this family when the roles are mapped. Modeling detail: Zone terminals, FCU, and unit ventilators.

canonical_kind in edge/src/equipment_types.rs resolves the master stamps above to zone_other. It does not list the token general. Product results call kind_for, which returns zone_other, ahu, or another canonical kind. The registry still lists general so a follower kind matches the family where that kind is actually resolved (the pandas oracle maps a GENERAL type to general). Pair a setpoint-only follower with the sensor-null behavior below rather than an equipment-id test.

FCU-SENSOR-NULL treats a missing zone_t column as zero fault hours. The runner rewrites sql_rules/fcu_sensor_null.sql in that case so a follower that only publishes a setpoint is not a dead-sensor fault. A present zone_t column that is null still runs the coverage test. The pandas twin returns an all-false mask when zone-air-temp is not a column.

Roles

Haystack tags are the preferred haystack_point_to_role arms in crates/fdd_core/src/columns.rs. The full registry table is SQL rules → Haystack map.

SQL role Haystack tag Used by
zone_t zone-air-temp sensor-null (optional), coils, passing valves
zone_air_temp_sp zone-air-temp-sp FCU-SENSOR-NULL (required)
sat discharge-air-temp coils, passing valves
htg_valve_pct heating-valve heating coil, both passing-valve rules, cooling coil, mode cycle
clg_valve_pct cooling-valve cooling coil, both passing-valve rules, mode cycle
damper_cmd damper-cmd damper position, CO₂ damper
damper_pct damper damper position
zone_co2 zone-co2 CO₂ damper
cooling_sp cooling-sp (alias zone-cooling-sp) deadband
heating_sp heating-sp (alias zone-heating-sp) deadband
fan_status fan-status fan proof (optional)
fan_cmd fan-cmd fan-proof fallback (optional)

Commands accept either 0–1 or 0–100 values (> 1.0 divides by 100). Fan proof prefers status. CO₂ below 300 ppm is treated as invalid rather than a ventilation fault. Valve-shut tests use a fixed 0.05.

Fan-proof screening difference

DataFusion (sql_rules/fcu_*.sql): when fan_status is not null, on means fan_status > 0.05; otherwise normalized fan_cmd > 0.10.

Pandas (_fcu_fan_on): when fan-status is non-null, as_bool treats a numeric status as on above 0.5; null samples fall back to fan-cmd > 0.10.

That cut is one reason the family remains sql_screening.

Unit boundary

Canonical FDD temperatures are °F. sat and zone_t are in TEMPERATURE_ROLES (crates/fdd_core/src/units.rs). On a metric or SI session the DataFusion engine reads them from history_si, converted to °F, so coil and passing-valve deltas are °F (default 5.4°F = 3°C).

cooling_sp, heating_sp, and zone_air_temp_sp are not temperature roles. history_si does not convert them. FCU-DEADBAND compares cooling_sp − heating_sp in site-native °C (deadband_c, default 1). Package authors must not map Fahrenheit setpoints into cooling_sp or heating_sp.

The pandas runner drops zone-air-temp-sp, cooling-sp, and heating-sp from the Fahrenheit quality pass for every FCU-* rule so those ranges do not null the pass-through columns before FCU-DEADBAND or FCU-SENSOR-NULL.

Loading and evidence

The files live in sql_rules/ and are registered in sql_rules/registry.yaml; no site registry fork or OPENFDD_SQL_RULES_DIR override is needed. Registry parity_status is sql_screening. Focused predicate fixtures cover pandas behavior, and DataFusion SQL is compiled and executed by Rust rule tests. That is not mask or duration proof.

Soft-OPEN for this family closes only after tip and field stress on issue-mapped gates (mask and duration versus the pandas oracle). Cookbook CI and a green docs build do not close it. There is no FCU gate in the stress scripts yet; adding that stub is a later change, not part of the cookbook pages. The dashboard_wired field remains false while these rules are outside the maintained Overview summaries; the Results and SQL FDD surfaces still expose their registry entries.