SQL anomaly detection
Open-FDD ships two anomaly-style layers. Do not conflate them:
| Layer | What it is | Where you see it | Tuners |
|---|---|---|---|
| Overview SQL anomaly screening | Statistical self-baseline (rolling Z-score / robust) on mapped sensors | Overview → Anomaly screening (SQL) | Request series params (not Lab rule sliders) |
Registry rules (SV-*, PID-HUNT-1, WX-1) |
Deterministic physics / quality / hunting screens in DataFusion SQL | Lab → SQL FDD Rules → Run all / FDD Plots · plant health matrices | Lab sliders from sql_rules/registry.yaml |
Neither layer is ML or peer-fleet scoring. Peer / self-baseline expansion beyond the Overview screen remains optional follow-on work.
1. Overview SQL anomaly screening (Wave M / soft-OPEN)
Purpose: Flag equipment × sensor roles whose recent samples deviate from their own recent distribution — useful when hard physics limits are still “in range” but the point is behaving oddly.
Engine: DataFusion over historian Parquet (POST /api/analytics/sql-anomaly, query_version: sql-anomaly-v1).
UI: React Overview section data-testid="overview-sql-anomaly" — tabulated only (no Plotly).
Hub flag:
| Env | Default | Meaning |
|---|---|---|
OPENFDD_SQL_ANOMALY_SCREENING |
on when unset | Set 0 / false / off on live OT hubs that should skip the scan |
| Status | — | GET /api/analytics/sql-anomaly/status → { enabled, mode } |
Screened roles
Only columns present in the building’s historian are screened:
oa_t, mat, sat, zone_t, zone_rh, duct_static, oa_h
(Package maps must expose these SQL roles — see package authoring / Haystack → SQL.)
Methods
series.method |
Behavior |
|---|---|
zscore (default) |
Rolling mean / stddev over window_rows; flag when \|z\| > z_threshold |
robust |
Per-equipment median + population σ (MAD-style soft fallback); same threshold on robust z |
Request tuners (series object)
Overview currently posts the defaults below. Agents / API clients may override:
| Key | Type | Default | Clamp / notes |
|---|---|---|---|
window_rows |
u32 | 24 |
Clamped 3–168 (hours-ish samples; depends on poll) |
z_threshold |
f64 | 3.0 |
Clamped 1–10 |
method |
string | "zscore" |
"robust" for the median/σ path |
transition_events |
bool | true |
Counts normal→anomaly edges into anomaly_events |
Output columns: equipment_id, role (Haystack-ish), method, score (max |z|), threshold, anomaly_hours (≈ flagged sample count), anomaly_events, last_at.
Example:
curl -sS -X POST "$OPENFDD_API_BASE/api/analytics/sql-anomaly" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"building_id": "ACME",
"query_version": "sql-anomaly-v1",
"series": {
"window_rows": 48,
"z_threshold": 2.5,
"method": "zscore",
"transition_events": true
}
}'
There is no Lab slider strip for this analytics path today — tune via Overview defaults or the API series body. After Update this rule on registry rules, Overview still refreshes its own anomaly table on building / refresh token only.
2. Sensor validation rules (SV-*) — Lab tuners
These are production FDD rules in the SQL registry (also pandas cookbook twins). They feed fault hours, plant/VAV health matrices, and FDD Plots — not the Overview Z-score table.
Tune in Lab (A–Z rule menu). Sliders write session_config / rule params; Update this rule then re-run so Reports / FDD Plots refetch (RULES_UPDATED). Units follow the Lab unit system (°F canonical in SQL; metric convert at query).
| Rule | Detects | Primary Lab tuners |
|---|---|---|
| SV-RANGE | Sample outside physical hard range (temp / RH / duct static catalogs) | range_scale_temperature, range_scale_humidity, range_scale_pressure, confirm_seconds, coverage / startup / operational gate |
| SV-FLATLINE | Stuck / frozen sensor (Δ ≤ tol over window) | flatline_tol, flatline_hours, confirm_seconds, gates |
| SV-SPIKE | One-sample jump beyond type limit | spike_scale, spike_scale_temperature, spike_scale_humidity, spike_scale_pressure, confirm_seconds |
| SV-STALE | All modeled analogs flat over window (feed likely dead) | stale_hours, stale_tol, confirm_seconds |
| SV-RATE (alias SV-SLEW) | Sustained implausible rate (°F/h-class) vs span / gap | sensor_span, max_gap_hours, steady fault rate, confirm_seconds |
Shared gate knobs on most sweeps: minimum_active_coverage_pct, startup_delay_min, require_operational_gate, confirm_seconds (confirm = consecutive seconds before a fault sticks).
Full equations + copy-paste SQL stubs: DataFusion SQL cookbook → Sensor validation. Catalog metadata: P0 rule catalog.
3. Control hunting — PID-HUNT-1
| Detects | Rolling lookback where a 0–100% actuator (damper / valve / fan cmd) shows large total variation and span |
| Lab tuners | window_hours (default 1 h), change_deadband_pct, minimum_span_pct, total_variation_fault_pct, plus shared gates / confirm_seconds |
| Cookbook | PID-HUNT-1 |
Distinct from SV-SPIKE / SV-RATE (sensor path) and from ASHRAE FC4 hunting where that FC rule is also enabled.
4. Weather spike — WX-1
| Detects | Outdoor-air temperature sample-to-sample jump |
| Lab tuners | spike_limit (°F, default 16), confirm_seconds |
| Kind | weather equipment / site OAT role |
Related to spike screening but scoped to OAT on weather equipment — not the Overview rolling Z-score table.
5. How the pieces fit
Package map (Haystack → SQL roles)
│
▼
Historian Parquet
│
├── Overview POST /api/analytics/sql-anomaly ← statistical screen
│ (window_rows, z_threshold, method)
│
└── POST /api/fdd/run (registry SV-*, PID-HUNT-1, WX-1, …)
↑
Lab sliders / session_config / confirm_min
Health matrices on Overview use registry SV-* / hunting flags when present; a clean sensor matrix (rows: [] on /api/analytics/sensor-faults) means no SV-* faults in window — it does not mean the Z-score table is empty.
6. Ops notes
- Prefer disable Overview SQL anomaly on OT-heavy Railway hubs (
OPENFDD_SQL_ANOMALY_SCREENING=0) if the scan contends with large ACME historians; keep LabSV-*for durable faults. - Synthetic-59 includes dedicated equipment cases for flatline / range / rate / spike / stale / PID hunt — use those soaks before trusting Lab defaults on a live site.
- Pandas oracle twins live under Pandas cookbook → Sensor validation for offline notebooks (
pip install "open-fdd[oracle]").