Engine-only deployment and external IoT pipelines
Library-only (
pip install open-fdd): See the engine documentation — Getting started, Column map & resolvers, and Engine-only & IoT (same topics as below, scoped to pandas integrators).
Some integrators already operate data collection (historians, MQTT, proprietary BAS exports) and modeling / semantics (warehouse schemas, optional Brick elsewhere). Open-FDD’s --mode engine and the pandas YAML engine let you add FDD without adopting the full stack.
Package names: The rules code lives under
open_fdd.engine. The repo’s optionalopenfdd-enginepackage (openfdd_engine) is a thin re-export around the same API — not a different engine. See The optional openfdd-engine package for a comparison table and Docker vs library paths.
What --mode engine starts (Docker)
From the repo root:
./scripts/bootstrap.sh --mode engine
This brings up TimescaleDB, the fdd-loop service, and weather-scraper — see Modular architecture for the matrix. There is no API, React, or BACnet scraper in this slice by default.
When it fits
- You will load aligned time-series into Postgres (same DB the platform uses) and let the existing loop consume rules + data model from the normal Open-FDD deployment pattern.
- You want weather-augmented or scheduled FDD in the same containerized runtime as production Open-FDD.
When something else fits better
- Your data lives in Snowflake / BigQuery / a lake and you only need rule evaluation on batches or streams: use the Python library path below (no Docker).
Library path — same YAML, any DataFrame
The rule runner is open_fdd.engine.runner.RuleRunner. It loads the same .yaml rule files as the platform (type: bounds|flatline|expression|hunting|oa_fraction|erv_efficiency, inputs, params, etc.). Authoring references:
- Expression rule cookbook
- Examples under
examples/my_rules/in the repo
Minimal integration pattern
- Build a pandas
DataFramewhose columns are your sensor traces (and optionaltimestamp). - Point
RuleRunnerat a directory of.yamlfiles or passrules=[...]dicts. - Call
run(df, timestamp_col=..., skip_missing_columns=True, column_map={...}). - Read boolean
*_flagcolumns (and optional rolling persistence — same parameters as in-platform).
column_map — when your modeling layer uses Brick class URIs or tags but dataframe columns are different (e.g. temp_sa vs Supply_Air_Temperature_Sensor), pass the same column_map concept the platform uses after SPARQL resolution. See RuleRunner.run docstring in open_fdd/engine/runner.py.
Bring your own column_map or resolver
Integrators own the bridge from their naming (warehouse columns, Haystack refs, another graph) into what RuleRunner expects:
-
Plain dict (most common) — Build
column_map: dict[str, str](rule input / Brick-class key → actual DataFrame column name) and pass it toRuleRunner.run(..., column_map=column_map). No TTL required on your side if you already know the columns. - Brick TTL in library code — If you have a Brick
.ttlfile (or the same shape as Open-FDD’s model), reuse the platform logic:from pathlib import Path from open_fdd.engine.column_map_resolver import BrickTtlColumnMapResolver column_map = BrickTtlColumnMapResolver().build_column_map(ttl_path=Path("path/to/model.ttl"))Same behavior as
resolve_from_ttlwhen the file exists; requiresopen-fdd[brick]/ rdflib. - Custom
ColumnMapResolver— Implement theColumnMapResolverprotocol (build_column_map(*, ttl_path: Path) -> dict[str, str]) with your own lookup (REST, SQL, manifest file, etc.). You may ignorettl_pathif your source is elsewhere. For forked platform code,run_fdd_loop(..., column_map_resolver=your_resolver)swaps mapping for the DB loop; the stock Dockerfdd-loopdoes not set this — it keepsBrickTtlColumnMapResolver.
Priority / policy: There is no automatic “ontology priority” in the engine — you supply one column_map per run (or one resolver that returns it). Ambiguity (e.g. multiple Haystack matches) should be resolved before calling RuleRunner with a strict dict.
Types live in open_fdd.engine.column_map_resolver and are re-exported from openfdd_engine (shim only). More context: The optional openfdd-engine package, GitHub #122 (resolver RFC).
Manifest file + composite priority (workshop / gap-fill)
load_column_map_manifest(path)— reads.jsonor.yaml/.yml. Accepts either a flatstr → strobject orcolumn_map:nested mapping.ManifestColumnMapResolver(path)— same map via theColumnMapResolverprotocol;build_column_mapignoresttl_path(manifest is the source of truth for that resolver).FirstWinsCompositeResolver(r1, r2, ...)— runs each resolver in order; the first resolver to define a key wins (e.g.BrickTtlColumnMapResolver()thenManifestColumnMapResolver("extras.yaml")so TTL fills most keys and the manifest only adds missing ones). This is the supported pattern for ontology-style priority without unsafe dynamic imports.
Config-driven resolver class names (e.g. loading a Python import path from env) are not supported on purpose — easy to turn into import injection. Compose resolvers in code or a thin startup script.
Examples: examples/column_map_resolver_workshop/ — demo_one_shot.py (manifest + RuleRunner), demo_multi_ontology_illustration.py (illustrative Brick / Haystack / DBO / 223P key shapes).
Install
pip install -e ".[dev]" # from open-fdd clone, or
pip install open-fdd # when using a published version that includes the engine
The repo also contains an optional openfdd-engine tree (packages/openfdd-engine/) that re-exports the same API; pip install open-fdd is the supported public install — use open_fdd.engine after install. More detail: The optional openfdd-engine package.
Standalone playground (optional)
An in-repo example folder is included for workshops and quick starts:
examples/engine_iot_playground/withREADME.md,rules/*.yaml,data/RTU11.csv,run_demo.py, andRTU11_engine_tutorial.ipynb.
That pattern is not a second engine — it is the same code path as production rules, without Docker.
Summary
| Approach | You bring | Open-FDD provides |
|---|---|---|
--mode engine | Postgres feed + ops for containers | fdd-loop, weather worker, DB |
RuleRunner in Python | DataFrame + YAML directory | Identical rule YAML semantics on pandas |
| Full stack | BACnet / UI needs | Collector + model + engine together |
For mode overview and service list, start at Modular architecture.