Writing SQL rules
PostgreSQL is the production FDD source. The SQLite pack is generated from it for smaller NF hosts. Keep every rule site-neutral and use SI values at the rule boundary.
Where inputs and results live
Rules read per-run scratch tables (history, weather, …) built from the input contract. In the Full edition the
scratch history is built from the view fdd.input_sample(site_id, point_key, ts, value) (SI values) joined to
fdd.point, and results are upserted into fdd.rule_result; tuning revisions live in fdd.rule_config (the Full edition’s Tuning tab
stores per-site and per-equipment sensor-sweep limits there in SI, rule id SV-LIMITS). All
three tables are in sql/fdd/schema/core.sql, which every application using Open-FDD shares byte for byte. An
application provides only its own fdd.input_sample view with the signature declared under sources in
sql/fdd/input_contract.json.
Rule contract
The FDD engine loads a scoped, typed history table with one equipment and timestamp per row and one SI column per
role. weather contains as-of weather data. equipment contains model and operational context. A rule query receives
those tables and its tuned parameters, then returns one row per equipment with its equipment_id and declared output
columns. Sensor sweeps use the long-form sensor relation and return one row per assessed sensor series.
Rules must not read NF tables directly, perform unit conversion, or infer scales from values. Missing, unknown, or unusable input remains unknown. Do not convert it to a healthy zero. Keep operational gates and coverage requirements explicit in the SQL.
Add or change a rule
- Edit or add
sql/fdd/postgres/rules/<rule>.sql. - Declare the rule, equipment kinds, required and optional roles, parameters, units, confirmation time, and output
columns in
sql/fdd/registry.yamlandsql/fdd/tuning_catalog.json. - Add any new canonical role in
sql/fdd/role_map.jsonand its scale/type insql/fdd/input_contract.json. -
Render the rule and build the catalog and both packs with these commands.
python3 scripts/nf/build_rule_catalog.py python3 scripts/nf/build_sql_pack.py python3 scripts/nf/gen_rule_docs.py python3 scripts/nf/gen_cookbook.py - Add a deterministic fixture that exercises the fault and healthy path. Check output columns, missing-data behavior, confirmation edges, operational gating, and relevant timeline rows.
- Run the focused engine and parity tests below. Document a deliberate difference in
sql/fdd/KNOWN_DIFFERENCES.mdand the parity registry.
Roles and units
Use the canonical role names in role_map.json. Equipment kinds restrict where a rule runs. Required roles determine
whether the rule can run. Optional roles refine proof or context. The input contract fixes each role’s numeric scale
and database type. Use °C for temperature, Pa for pressure, L/s for flow, kW for power, kWh for energy, %RH on a
0–100 scale, fractions on 0–1, and status values on 0–1. SI conversion occurs at ingest. Rules contain SI thresholds
and never convert values.
Tuning
In the Full edition, each rule view on the FDD tab embeds inline Tuners for per-rule parameters and sensor-sweep role limits (SV-LIMITS). Operators edit in display units (°F, Δ°F, inH2O, cfm, gpm) with reversible boundary conversion, preview the validated override, and save append-only SI revisions in fdd.rule_config scoped to site or site:<site>:equip:<equip>. Rollback restores only the selected rule’s fields without disturbing sibling overrides. From the CLI, python sql/fdd/render_rule.py SV-RANGE --params '{"confirm_min":10}' renders standalone SQL.
Verification
node --test nf-app/tests/engines.test.mjs nf-app/tests/sv-sweeps.test.mjs
python -m pytest tests/parity -q
python scripts/nf/build_rule_catalog.py --check
python scripts/nf/build_sql_pack.py --check
python scripts/nf/gen_rule_docs.py --check
python scripts/nf/gen_cookbook.py --check
The rule catalog is the compact index. The SQL cookbook lists parameters, roles, units and outputs and links each SQL file. See the units convention for the conversion boundary. To run a rule on your own database see Using SQL rules outside Open-FDD.