Skip to content

CAMBER architecture

CAMBER is organized as a pipeline with supporting layers around it. The design goal is vendor-neutrality: raw BAS points are mapped to a small vocabulary of roles, and everything downstream is written against roles, so one rule set runs on any building once its tags are mapped.

raw BAS/meter data
  → [ingest]    source adapters → named point series on a common time grid
  → [model]     roles + mapping + entity model (what each stream means)
  → [resolve]   discover equipment, assemble role-named frames
  → [rules]     FDD rule engine        [mandv]  M&V / change-point engine
  → [report]    Std-211 audit deliverables
flowchart LR
  raw["raw BAS/meter data"] --> ingest["ingest (source adapters)"]
  ingest -- named point series --> model["model (roles + mapping + entities)"]
  model --> resolve["resolve.discover / resolve()"]
  resolve -- role-named frame --> rules["rules (FDD Registry)"]
  resolve -- role-named frame --> mandv["mandv (M&V / change-point)"]
  rules -- Findings --> report["report (Std-211 audit)"]
  mandv --> report
  ingest -.-> store["store (ParquetStore)"]
  store -.-> resolve
  interop["interop (Brick/Haystack)"] -.-> model
  rules --> integrate["integrate (tickets/notify)"]
  store -.-> api["api (read-only HTTP)"]
  report --> charts["charts (visuals)"]

The role-named frame is the lingua franca: everything downstream of resolve is written against roles, not vendor tags.

Around the pipeline: store/ (persistence), interop/ (Brick/Haystack), integrate/ (tickets/notifications), api/ (read API), charts/ (visuals).

Layers (and where they live)

Ingest — camber/ingest/

Source adapters that normalize any input to named point series on a common time grid: csv_perpoint (one file per point), csv_wide (tabular), haystack (a Project-Haystack hisRead client behind an injectable transport). quality.py scores per-point data quality (robust outliers, flatline/gap detection) with an auditable cleaning trail. units.py normalizes 0–1 vs 0–100 position signals.

Semantic model — camber/model/

  • roles.py — the vendor-neutral Role vocabulary (the heart of the design).
  • mapping.py — MappingProvider: alias/regex tables that map a source's tags to roles (a config file per building, not code).
  • entities.py — Site/Equip/Point entities, equipment templates, and completeness validation (which analytics a building's instrumentation can support).

Resolve — camber/resolve.py

discover() finds equipment of a class; resolve() loads the requested roles into a single role-named DataFrame (the unit every rule consumes). Occupancy filtering and percent-unit normalization happen here.

FDD — camber/rules/ (+ the math modules)

rules/base.py defines the Rule protocol (roles_required, analyze() -> Finding) and a Registry runner. Each rule is a thin adapter over a math module (ahu.py, reheat.py, oafraction.py, chwplant.py, fdd_g36.py, …). Rules are gated by the roles present, so an under-instrumented building is skipped with a reason rather than crashing. rules/triage.py ranks findings by impact and tracks their lifecycle (new/ongoing/resolved).

M&V — camber/mandv/

Change-point inverse models (models.py: 2P–5P + heating/cooling-zero), the schedule-aware TOWT model (towt.py), fit statistics + savings uncertainty (stats.py), CUSUM (cusum.py), weather normalization (weather.py), and rate/energy-aware resampling (resample.py, intervalfit.py).

Domain analytics

comfort.py (Std-55 PMV/PPD), cost.py (utility cost), carbon.py (emissions), water.py (irrigation/cooling-tower/leak), loadprofile.py (peak/load shape), pv.py / lighting.py (on-site systems), eval.py (FDD-accuracy harness).

Reporting — camber/report/

audit.py assembles ASHRAE/ACCA Standard 211 deliverables (benchmark + ECM table + prioritized findings) to text/HTML.

Storage — camber/store/

ParquetStore: a tidy long-form, hive-partitioned (site/year) Parquet store keyed to the entity model, with tag-filtered reads, rollups, and retention pruning.

Interop — camber/interop/

brick.py derives a role mapping from a Brick (.ttl) model; export.py emits Haystack tags / a Brick model from the entity model (round-trips).

Integration & API — camber/integrate/, camber/api/

integrate/tickets.py turns findings into CMMS-ticket records with a pluggable notifier. api/ is a read-only HTTP facade over the store (/sites, /points, /history).

Conventions

  • Role-named frames are the lingua franca between layers: a DataFrame whose columns are Role enum members.
  • Findings (rules/base.Finding) are the structured output of a diagnostic (rule, equip, severity, metrics, summary).
  • Synthetic-first tests: each diagnostic/model has a synthetic fixture proving detection/fit; real public datasets are exercised in examples/.