CAMBER capabilities reference
A single index of what CAMBER does, grouped by the building-analytics layers, with the key API, the option flags that tune each capability, the module, and the standard it cites. Deeper write-ups are linked where they exist.
Everything is vendor-neutral via the Role model, dependency-light (stdlib + numpy / pandas /
pyarrow / matplotlib), and clean-room (every method cites a public standard; every rule ships a
synthetic fixture).
flowchart TD
src["BAS sources (CSV/SQL/Haystack/Modbus/MQTT/BACnet/OPC-UA)"] --> ingest["Ingest (SourceAdapter)"]
ingest --> model["Semantic model (Role + MappingProvider + entities)"]
model --> fdd["FDD (rules.Registry)"]
model --> mv["M&V (mandv change-point / TOWT)"]
model --> stream["Streaming (OnlineFDD / OnlineCusum / forecast)"]
model --> domain["Domain analytics (comfort/carbon/GEB/ventilation)"]
fdd --> triage["Triage + lifecycle (rules.triage / faultlifecycle)"]
triage --> advisory["Advisory (aso / actionplan / scorecard)"]
mv --> report["Reporting (AuditReport / fleet)"]
advisory --> report
domain --> report
report --> platform["Platform (store / api / integrate / plugins / charts)"]
The capability families, all speaking the vendor-neutral Role model.
Ingest
Adapters normalize any source to named point series on a common time grid (SourceAdapter:
point_names / load_points / units). See INGEST-PROTOCOLS.md and
SECURITY.md.
- CSV —
ingest.csv_perpoint.PerPointCsvAdapter(a folder of per-point files),ingest.csv_wide.WideCsvAdapter(one wide table), andingest.csv_long.LongCsvAdapter(timestamp,point,valuehistorian shape). Flags:resample,profile. - Vendor formats — one shared multi-format timestamp parser (
tsparse: ISO / US / EU-dayfirst / BAS / epoch / Excel-serial) + value/status coercion (coerce: thousands, null tokens, On/Off/Open/ Closed/Fault status) + named vendor profiles (ingest.profiles: niagara_n4/metasys/webctrl/tracer/ desigo).load_csv(..., profile=…). See INGEST-FORMATS.md. - Project-Haystack —
ingest.haystack.HaystackAdapterover an injectable transport (http_json_transport, orclient_transportto wrap a maintained client). Flags:range_str,resample. - SQL / historian —
ingest.sql.SqlSource/read_pointsover any PEP-249 connection. Flags:ts_col/point_col/value_col/unit_col,where. - Network protocols (read-only) — Modbus (
[modbus]), MQTT/Sparkplug ([mqtt]), BACnet incl. experimental BACnet/SC ([bacnet]), OPC-UA ([opcua]). Read-only by construction; historian-first posture. Per-adapter flags documented in INGEST-PROTOCOLS.md. - Data quality —
ingest.quality.assess(coverage, gaps, flatline, outliers, duplicate timestamps, composite score) andclean. Also reads two-regime structure (n_regimes/regime_threshold/regime_outlier_frac) so a duty-cycled point — a BTU meter, a lead pump, a status — is not scored as broken for cycling; a split is claimed only on mass and separation and temporal coherence, so a randomly-railed sensor is never mistaken for a schedule. Pooledoutlier_frackeeps its meaning and is never masked. Flags:expected_freq,drop_outliers,regime_aware(off by default —sensorhealthopts in per role, since scoring a duty cycle as normal is the direction that could mask a fault). - Time / DST —
timegrid:interval_hours,regularize(sort + de-duplicate the DST fall-back hour),localize(tz-localize resolving DST ambiguous/nonexistent),dst_anomalies.load_csvde-duplicates timestamps by default. See TIME-HANDLING.md.
Semantic model
- Roles + mapping —
model.roles.Rolevocabulary;model.mapping.MappingProvider(alias + pattern → role).resolve.resolve(equip, roles)assembles a role-named frame. Flags:resample. - Entities + completeness —
model.entities(Site/Equip/Point) with equipment-template completeness validation. - Brick interop —
interop.mapping_from_brick/roles_from_brick(import);interop.to_brickandinterop.site_to_ttl/site_from_ttl(export + whole-site round-trip). Flags:backend(auto/rdflib/minimal; rdflib via the[brick]extra). - Project-Haystack semantics —
interop.haystack_tags/equip_haystack_tags(role→tags export) and, new in 0.6,interop.role_from_tags/roles_from_haystack/mapping_from_haystack(tag→role import) — closing the round-trip to Brick-level parity (every role recovers). - ASHRAE 223P —
interop.semantic223.site_to_223/site_from_223map a site (roles + equipment) to/from a 223P-shaped RDF subset (connections, medium, points;ROLE_TO_223quantity-kinds, broadened in 0.6 to the full plant/DX/refrigerant side; every role with a physical quantity maps, and the status/command roles are documented as intentionally unmapped). Flags:profile(minimal/full),include_relations. See ONTOLOGY.md.
FDD — fault detection & diagnostics
Rule engine (rules.base.Registry, rules.builtin.builtin_registry); each rule consumes a
role-frame and returns a Finding. Run with registry.run(name, equip_refs, mapping, min_trust=…).
- Air-side (G36 + PNNL Re-tuning) — simultaneous heat/cool, reheat (penalty + G36 minimization),
SAT reset, overcooling (min-flow + severity), economizer / OA-fraction (incl. under-ventilation),
night/weekend setback, duct-static, zone census, and unmet-setpoint hours (
unmet_setpoint_hours— occupied space temp outside the heating/cooling band, the operator-facing comfort/capacity metric). Per-rule flags (e.g.threshold,min_oa_pct,occupied_only,tol_F). - Occupancy. The schedule-based rules (
night_weekend_setback,overcooling_min_flow,overcooling_severity,reheat_penalty,zones_heat_cool_census,outdoor_air_fraction,reheat_minimization_g36since 0.106, the DCV rules) take a trendedOCCUPANCYpoint as the truth — it replaces the schedule — and otherwise usestart_hour/end_hour/occupied_days(default Mon–Fri 07–18, a generic office assumption;camber.schedules.effective_occupied_mask). Since 0.98damper_censusreads each box's trended occupancy the same way (schedule fallback), andsupply_air_controlgates its fan-on hours on a trended occupancy point when one exists, with no schedule fallback (occupancy_gate); both report which gate applied in anoccupancy_gatemetric. - Fan-on only.
outdoor_air_fractionjudges fan-on samples by default (fan_gate=True: fan status, else fan speed, else airflow —camber.schedules.fan_on_mask): with the fan stopped the mixing-box temperatures read still air, not a mix. A unit with no fan signal is judged ungated and the finding says so. Itsmin_oa_pctis the unit's own design minimum (confirm it against the sequence — a 10 % minimum damper position can be a 1.6 % OA fraction), andmin_oa_pct_by_monthsets a seasonal one. - Terminal air temperatures. At a VAV/CAV box
SUPPLY_AIR_TEMPis the box discharge (downstream of its reheat coil) and the entering primary air is mapped toMIXED_AIR_TEMP.reheat_penaltyjudges "reheat into cold supply" on the entering air; with only the discharge mapped the count is a lower bound (reheat warms it), so it is caveated and never reported as a confident ok. It also cross-checks the valve against the discharge-air rise (#63): a valve at ≥ 90 % with a median rise under 5 °F is declined (nothing counted or costed), and a shut valve with a ≥ 10 °F rise on a quarter of its samples is caveated. Where a box trends both the reheat demand and the measured position (0.98, #85), map the position asheat_valve_position:reheat_penaltyandovercooling_min_flowthen judge the heat delivered from the position (a valve stuck shut is 0 % open, not a reheat penalty), record it invalve_signal, and caveat a demand ≥ 90 % while the position reads ≤ 5 % on a quarter of the full-demand samples as a stuck or failed valve. On fan-powered boxesfan_heat_f(°F, or"auto": the median closed-valve lift, clipped to 0–8 °F) raises both valve-vs-discharge bounds by the box fan's own heat.box_type(0.100, #99:"single_duct","parallel","series") sets the caps on that estimate. A series box's fan always runs and mixes in plenum air, so its no-rise allowance is capped at 10 °F, while the big-rise allowance stays at 8 °F. The estimate is learned from the closed-valve samples, and a higher cap there would absorb a passing valve's heat. - Not overcooling.
overcooling_severityexcludes morning recovery (WARMUP, else the firstrecovery_hoursof each occupied block) and fan-off free-floating, and reports a space below setpoint with its reheat ≥reheat_saturated_pctopen as a heating shortfall. The shortfall grade is the lesser of its depth tier and its share tier (0.98, #85:shortfall_share_pct, warn from 5 %, fault from 20 % of occupied samples at leastwarndeep), so a few deep cold hours a year gradeinfo;share_pctapplies the same gate to the overcooling tiers (off by default).overcooling_min_flowdeclines withoutAIRFLOW+AIRFLOW_SP(it can't test "at minimum"). - Minimum vs active airflow setpoint (0.106, #186).
airflow_spis the box's active airflow setpoint (what the static-pressure census and the cohort rules read); its minimum has its own role,airflow_min_sp, whichreheat_minimization_g36,zones_heat_cool_census,reheat_penaltyandovercooling_min_flowprefer. With onlyairflow_spmapped they read it as the minimum, as before, unless it swings with load while the airflow tracks it: thenreheat_minimization_g36and the census infer the minimum (its 5th percentile), say so, and the per-box rule rates no higher than warn.reheat_minimization_g36does not count G36's high heating loop (the zone heating loop,zone_heat_loop, above 50 %; without it the valve at ≥ 90 % or the discharge ≥ 15 °F above the space) as a violation: a dual-maximum box raises its airflow there by design.zones_heat_cool_censusrates the share of occupied hours with one zone heating while another cools (pct_hours_mixed); one box heating above its minimum is the per-box rule's. - Held setback (0.93, #43). A unit scheduled off at night still cycles on to hold its
zones at the setback temperature, so
night_weekend_setbackno longer reads runtime alone: when the runtime test fails, a fan that cycles (mean duty belowmax_hold_duty_pct, 90 %, in the unoccupied hours it runs) while the zone sits at setback reads "effective (fan cycling to hold)". The zone isspace_temp, else the return air while the fan runs; the setback is the trendedheat_sp/cool_spin unoccupied hours, else the configuredunoccupied_heat_sp_f/unoccupied_cool_sp_f, else (heating only) a zonemin_setback_depth_f(3 °F) below its occupied temperature. A fan running every unoccupied hour stays "MISSING", and the 5 % runtime floor (#57) still decides first. - Leaking valves and fan heat (0.93, #42).
leaking_valveallows for the supply fan's heat (fan_heat_f, default 2 °F, G36's ΔT_SF) before calling a heating leak, gives a cooling leak no credit for it, judges fan-on samples only when a fan signal is mapped, and judges each coil on its own leaving-air sensor (heat_coil_leaving_temp/cool_coil_leaving_temp) when one is trended rather than on the supply air downstream of the fan (coil_sensor_fan_heat=Trueon a blow-through unit). - Out of reheat (0.93, #44).
reheat_capacity_shortfall(terminal boxes only) flags a zone more thantol_f(1.5 °F) below its heating setpoint while its reheat valve is at or abovereheat_saturated_pct(90 %) -- a capacity or airflow problem, not tuning. Occupied samples, less morning recovery, warm-up and fan-off samples; warn at 5 %, fault at 20 % of them, after 10 h. The setpoint isheat_sp, else the config'sheat_sp_f(one value, or per box); withairflow+airflow_spthe finding says whether the box was short of air or of heat. - Dehumidification with reheat (0.93, #41).
simultaneous_heat_coolsets apart the both-open hours that read as dehumidification with reheat: the fan running, the air leaving the cooling coil (COOL_COIL_LEAVING_TEMP) at least 2 °F below the supply air (the heat is added after the coil) and at or within 2 °F of the entering dew point (fromOAT+OUTDOOR_RHandRETURN_AIR_TEMP+RETURN_AIR_HUMIDITY, the lower of the two; else a return humidity of 55 % or more). A coil leaving above the entering dew point is dry, so reheat after it still counts. Partial evidence (reheat after the coil but no humidity, or high humidity but no coil-leaving temperature) is a caveat and caps the finding atwarn, never a fault. The rule parameterdehumidificationdeclares the sequence (trueaccepts reheat after the coil unless a dew point shows it dry;falsecounts every both-open hour). A unit with none of these signals is judged as before, with a caveat. - Status duty. Event-logged status points are resampled to their time-weighted duty
(
camber.realio.load_status(how="duty")), so a runtime verdict such as setback does not change with the resample interval;how="any"keeps the old "on at any moment" bins. Ingest and a run's read from a store resample status the same way, and the start-counting rules count the fewest starts the duties allow, declining a below-limit verdict on a coarse trend that hid starts (0.104, #143; TIME-HANDLING.md). - Central plant & hydronic — chiller kW/ton efficiency, chiller staging + multi-chiller fleet
over-staging, cooling-tower approach, condenser-water reset, CHW/HW pump (riding-curve + VFD-min),
CHW reset + low-ΔT, boiler summer-lockout + short-cycle, hot-water pump warm-weather lockout
(
hw_pump_summer_lockout, 0.103). Flags include design targets (design_kw_per_ton,max_starts_per_day, …). - Control stability —
control_hunting: flags a modulating output (valve/damper) that reverses direction excessively (unstable loop) by counting reversals per observed hour beyond a deadband (percent of stroke; data gaps are not counted as calm time). A trend too coarse to show the warn rate (15-min data can show at most 4 reversals/hr) is declined with a caveat, never called stable. Flags:warn_per_hr,fault_per_hr,deadband,gap_factor.supply_air_control: flags supply-air temperature that fails to track its setpoint (control/capacity fault; fan-on hours only, and since 0.98 only occupied ones when the unit trends occupancy). Flags:tol_F,warn_pct,fault_pct,occupancy_gate("trended"default,"schedule","off").airflow_tracking: flags measured VAV airflow that fails to track its setpoint (stuck/undersized damper, failed actuator, starvation, bad flow sensor); since 0.107 fan-on hours only when a fan status or speed is mapped, and occupied ones when occupancy is trended. Flags:tol_frac,abs_tol,warn_pct,fault_pct. - Stuck actuator —
actuator_stuck(0.98, #85; terminal boxes and fan coils): a damper or valve (roles: damper, heating-valve position else demand, cooling valve) that holds one position over occupied, fan-on hours is judged against what the zone asked for. A run of at leastmin_flat_hoursis contradicted (can fault) when the damper is shut through occupied hours with no airflow (againstAIRFLOW_SPormin_airflow; with neither, the occupied mode alone, caveated), part open while the zone runswarm_margin_fover its cooling setpoint, fully open while the zone sitssatisfied_margin_fbelow it, or flat while its demand twin moves; it is unexplained (warn at most) when one value holdswhole_day_shareof a day while the demand, airflow setpoint, a setpoint or the zone temperature moves; anything else is consistent (a box at its minimum in a mild zone, a damper saturated open on a hot day). Theornl-frp-vavdataset declares it as its detector. - Peer/cohort —
cohort.CohortDeviation(fleet rule): flags a unit running unlike its peers on a role (robust z of a mean/peak/load-shape summary). Shipped instancescohort_airflow,cohort_space_temp; construct your own for any role. Flags:k,summary,min_cohort, and since 0.98 (#85, opt-in):group_by_topology(compare only units behind the same air handler),normalise("design_max": by design airflow;"reference": by each unit's declared known-good twin or period),summary="variability"andtail("low"/"high"). Size normalisation alone cannot isolate a stuck box: onornl-frp-vavonly the box's own fault-free reference, or its damper's variability on the low tail, separated it. Since 0.106 (#186)min_abs_dev(1 °F forcohort_space_temp) keeps a tight cohort from flagging a small real difference, and acohort_airflowcohort whose spread passesmax_spread(0.3: box sizes or loads differ) readsinfo, notok, when nothing stands out. - Economizer / free cooling —
economizer_high_limit(excess OA above the high limit — not locked out; judged on measured OA/supply airflow when trended, else the MAT/RAT balance, else the damper; samples still below return air are left to a differential changeover; with no configuredmin_oa_pctonly excess above a generous 50 % bound is scored and OA that is excess only against a generic 20 % minimum is declined, since it depends on the unknown design minimum),free_cooling_missed(mechanical cooling — cooling valve aboveactive%, default 5 % — ran while OAT was cool enough for free; durations in hours; hours already on ~100 % outside air are an integrated economizer, not missed — the RCx economizer page's test, shared ascamber.freecooling.integrated_economizer_mask; 0.98 records why:missed_causetells a damper commanded open that did not deliver outside air from an economizer never commanded open; 0.102 judges fan-on hours only, from fan status, else speed, else airflow, and runs ungated, saying so, on a unit with none of them),static_pressure_reset(duct-static setpoint that doesn't trim with demand; a one-time step is reported as a step, not a reset — seecamber.setpoint_reset). Flags:high_limit_f,min_oa_pct,min_damper,differential,active,min_range_inwc. - Packaged / DX & refrigerant-side (
docs/FDD-DX.md) —compressor_short_cycleandcompressor_staging(RTU/DX cycling + staging),heatpump_defrost(excess reversing-valve cycling),filter_fouling(filter ΔP at/above change-out), andchiller_approach_fouling(condenser/evaporator approach-temperature degradation — refrigerant-side fouling without refrigerant-pressure sensors). New equipment templates: RTU, HeatPump/VRF, DOAS, FCU. - Drift-detection families — catches equipment degrading over time against its own frozen,
load-normalized baseline (the "is this slowly getting worse than it used to be, at matched load?"
question), all on one shared engine (
camber.chillerbaseline/camber.chillerdrift), each pairing a period statistic with a streaming sustained-shift CUSUM alarm and honest threshold labels (screening-grade magnitude floors vs. provisional-untuned CUSUM timing): - Chiller (CHILLER-DRIFT.md) — condenser/evaporator approach, liquid-line subcooling (charge), suction superheat (evaporator feed), condenser-water range.
- Pump / hydronic (PUMP-DRIFT.md) — the distribution side at matched duty.
- AHU / air-side (AHU-DRIFT.md) — supply fans, coils, filters, duct-static
control, with a per-AHU co-movement roll-up (
diagnose_ahu_drift) that names the locus. - VAV / zone-terminal (VAV-DRIFT.md) — the box's damper, airflow tracking, and
reheat coil, with a per-box roll-up (
diagnose_vav_drift) disambiguating box vs upstream starvation. Each family ships a physics simulator (ahusim/condensersim/evaporatorsim/vavsim) for ROC validation;camber.driftvalidationcalibrates thresholds against labelled data. Period rules, run viaRegistry.run_periodswith a frozen baseline store. Driven from a config'sdriftsection orcamber drift run|report|freeze|list(camber.driftrun) — one page per run viareport.drift_report_html. Scoring is read-only toward the store; onlyfreezecreates a reference and only the attributedaccept_new_normalmoves one, and an equipment nothing could evaluate is listed as not evaluated rather than diagnosed steady. See CLI.md. - Trim-and-Respond / G36 reset analytics (TR-RESET.md) — asks whether the plant's
setpoint-reset logic does what ASHRAE Guideline 36 intends (
camber.g36_resetengine). Five detectors:supply_air_reset_compliance(SAT held colder than the §5.16.2.2 OAT→SAT target — a reheat opportunity),sat/static_reset_effectiveness(does the reset setpoint actually trim and respond to its zone requests, or is it stuck / not-responding / not-trimming / diverging?), andsat/static_rogue_zone_census(fleet rules finding the one zone monopolizing the requests and dragging the whole reset). Screening / opportunity-grade. Since 0.106 (#186) a slow but correct reset under modest demand is not called unresponsive, the finding lists every failure mode, the static census lists a zone that is short of cooling asoverloaded_zonesrather than a rogue, the SAT censuses count G36's tier-1 request when a zone trendszone_cool_loop, and the cohort-starvation summary names every starved air handler. - G36 §5.16.14 engine —
fdd_g36.run_g36_afddscores AHU fault conditions FC1–FC15 with operating-state gating (an interval with a missing valve command is unclassified — no FC is scored there — rather than read as free cooling; unsorted/duplicate timestamps are sorted and de-duplicated first); cross-validated vs open-fdd 0.1.5, re-compared with open-fdd 4.4.9 on labelled LBNL runs (ECOSYSTEM.md), and accuracy-scored in the synthetic harness (VALIDATION.md). Since 0.91 it applies the G36 time filters: evaluation only while the supply fan runs (a run with no fan signal is declined), ModeDelay (30 min) after a fan start or zone-group mode change, AlarmDelay (30 min) persistence, and 5-minute averaging. It also scores cooling-only AHUs, with the heating-coil tests omitted. The registered ruleg36_afddruns it from a config (rules: ["g36_afdd"]). It gives one finding per AHU with every FC's hours and rate, and those findings flow intocamber runand the RCx report. MAT and SAT stand in for the cooling-coil entering and leaving temperatures only on an AHU without a heating coil. Otherwise FC14 and FC15 are declined. FC8/FC9 hours that coincide with a confirmed FC14 are attributed to FC14, so a passing valve is reported as a leak, not under the free-cooling labels. Since 0.98 (#94) free cooling (OS#2) needs the OA damper open beyond its minimum position, not just both valves shut: a fan-on hour at minimum OA with both coils idle (a deadband hour, an unoccupied recirculation run) is OS#5, where only FC1-FC4 apply. The minimum isoa_damper_min, learned by default from the unit's mechanical-cooling hours at minimum OA. Since 0.99 (#95) heating (OS#1) likewise needs the OA damper at that minimum: a heating hour with the damper open beyond it is OS#5. Since 0.101 (#105)oa_damper_min_by_monthgives a sequence with a seasonal minimum position its own minimum per month. Unoccupied operation is evaluated, as in G36;occupancy_gate="trended"limits the evaluation to a trended occupancy point's occupied hours. Parameters:heating_coil,min_oa_pct(enables FC6, the test for a damper stuck open at minimum OA),oa_damper_min,oa_damper_min_by_month,oa_damper_tol,occupancy_gate,mode_delay_min,alarm_delay_min, and the screening-gradewarn_pct/fault_pct. Like every air-handler rule it declines a VAV box, heat pump, fan coil or plant (camber.rules.applicability), and an FC13 it reports is linked to a chilled-water plant short of setpoint in the same hours (chw_supply_tracking) as the likely upstream cause. - Sensor health / data trust —
sensorhealth(physical bounds, cross-sensor consistency, per-role trust roll-up +trusted_rolesgate),sensordrift(bias / drift / tracking vs a reference — fetch one withweather_source.oat_reference(NASA POWER by lat/lon),oat_reference_for(by address, via a keyless geocoder), oroat_reference_isd(station-precise, NOAA/ISD); see WEATHER.md),mapping_confidence. The runner'smin_trustflag makes a rule decline when its inputs aren't trusted. Cross-sensor and provenance checks: flow-based mixing balance, copied points, gap-fill signatures, cross-unit identity, zone-vs-outdoor CO2, percent-mapped-as-airflow; see SENSOR-HEALTH.md. - Prioritization & lifecycle —
rules.triage:rank_findings(severity, or a magnitude/cost key),group_findings(root-cause grouping),FaultRegister(new/ongoing/resolved across runs). Persistent, cross-process:faultlifecycle.FaultLifecycle— a fingerprint-keyed fault store with an assignment/status workflow, SLA/aging tracking, and atomic JSON persistence. Flags:magnitude_key,actionable_only,reopen_on_recurrence,auto_resolve_absent. - Fault economics —
fault_economics: per-fault annual $ impact → rank by money. Flags:params(assumptions),models,min_severity(viarank_by_cost). Triage-gradeDEFAULT_MODELScover simultaneous heat/cool, reheat, chiller/tower/pump efficiency, duct-static, boiler cycling, and the Trim-and-Respond reset family (SAT-below-target and static/SAT reset-not-trimming → reheat/fan waste;not_responding/stuck/divergesare comfort/indeterminate → uncosted by design). Drift findings are also uncosted by design — a drift is a leading recommission indicator, not a spend (its magnitude is a condition-space residual, not a priceable energy quantity). Every estimate carries itsbasis+assumptionsand returns uncosted (never a fabricated figure) when the sizing it needs is missing. - Ventilation (ASHRAE 62.1) —
ventilation.assess_62_1(Ventilation Rate Procedure: required vs delivered OA, deficit) andassess_dcv(is OA raised when CO₂/occupancy is high, judged on occupied, non-economizing samples viaeconomizer_active_mask; the CO₂ lift taken within the hour of day since 0.93), with theVentilationRateProcedure/DemandControlledVentilationrules, theDcvSystemVerificationfleet rule (zone CO₂ joined to the serving air handler's OA via the served-by topology) andRole.OA_AIRFLOW(the OA damper judges where the flow is missing; a declared 100 % outdoor-air unit falls back to supply airflow or fan speed). CO₂ adequacy (co2_ventilation, and the fleet twinco2_ventilation_system) leaves economizer-mode hours out of its over-ventilation verdict (iaq.economizer_mode_mask) and, since 0.107, judges a trended occupancy point's occupied hours in place of the assumed schedule. Flags:space_typevsrp/ra,ez,aggregate,co2_setpoint,oa_floor,min_modulation,min_lift_ppm. See VENTILATION.md. - Accuracy + CI gating —
eval.benchmark+validation.metrics_with_ci(Wilson CIs), andeval.check_against_baselineto gate accuracy (TPR/FPR/diagnosis) against a committed baseline in CI (--json/--gate/--tol/--update-baseline). See VALIDATION.md. - Validation dossier —
camber.dossier/camber validateaggregates all four validation tracks (synthetic, generated-fleet, real-data FDD, real-data M&V) into one text/HTML/JSON credibility artifact: live-recomputed pure tracks + cited real-data results with Wilson CIs and honest boundaries. See VALIDATION.md.
Sequence-of-Operations conformance
soo — a declarative clause engine (gated predicates over roles, JSON-authorable) measuring
operated-vs-designed behavior as a conformance %, with soo_library (packaged ASHRAE G36 clauses).
Flags: persistence window, per-class spec.
M&V — measurement & verification
See MANDV.md. Change-point models (mandv.models, 2P–5P + zero variants), a TOWT
variant (mandv.towt, after LBNL's Mathieu et al. 2011), fit statistics + G14 fractional savings uncertainty (mandv.stats), CUSUM
(mandv.cusum), weather normalization (mandv.weather), normalized annual savings
(mandv.normalized), non-routine adjustment (mandv.nonroutine), Option-B retrofit isolation
(mandv.retrofit_isolation), the CalTRACK 2.0 Daily, Billing and Hourly methods (mandv.caltrack_daily, mandv.caltrack_hourly), a degree-day
baseline on period-mean temperature (mandv.degreeday, HDD/CDD regression with an auto-fit balance
point; the variable-base form on bills is mandv.basetemp), IPMVP Option A
(mandv.option_a, measured Δparameter × stipulated duty), and Option D-style modeled savings (mandv.rc_model, a
1R1C or 2R2C grey-box calibrated to metered energy — grid-τ + OLS, G14-gated — run as-found vs
as-corrected for a modeled pre-implementation saving, with multi-zone stacked-OLS calibration and
an optional EnergyPlus cross-validator (interop.energyplus); see OPTION-D.md).
CAMBER now
covers IPMVP Options A/B/C/D. Flags: confidence, exclude_non_routine, model kinds, aggregate,
balance_point, interval.
Streaming / online
Incremental monitors for a live BAS feed — bounded state, O(1)–O(window) per sample. See STREAMING.md and FORECAST.md.
- Online M&V —
mandv.online.OnlineCusum(incremental tabular CUSUM of savings/waste vs a baseline model → savings-erosion alarm) andRollingAnomaly(rolling median/MAD residual z-score). Flags:limit,slack,window,k,min_samples. - Online FDD —
rules.online.OnlineFDD: sliding trailing-window rule evaluation emitting aTransitiononly on a verdict change (no per-sample re-alert), per-equipment isolation. Flags:window,eval_every,min_samples,emit_ok. - Forecasting + learned-normal anomalies —
forecast.seasonal_forecast(time-of-week shape + additive drift, no ML dep),backtest(MAE / MAPE / CV(RMSE) honesty check),forecast_anomalies(robust residual band → FDD signal). Flags:drift_window,k,test_frac.
Commissioning (RCx / MBCx)
rcx: functional_test (FPT pass-rate), before_after (MBCx persistence across an intervention
date), track_measures (measure register → verified/regressed/inconclusive/insufficient).
Money & compliance
- Tariffs —
tariff(URDB-shaped: TOU energy + tiers, TOU/flat demand, ratchet, fixed → monthly + annual bill),tariff.validate_bill(vs actual invoices, MAPE + per-month status),interop.openei(URDB fetch),[tariff]PySAM bridge. Flags:tol_pct. - ECM finance —
finance: payback, NPV, IRR, SIR with escalation / O&M / salvage. - Demand & peak —
demand: peak + drivers, load factor, baseload, night/weekend baseload anomaly, peak-shave $ value. Flags:near_peak_frac,start_hour/end_hour,target_kw. - BPS compliance —
bps:site_eui,emissions_intensity,assess_bps/assess_eui(compliant?, margin, penalty exposure). Limits are caller-supplied (no hard-coded legal values).
Grid-interactive (GEB) & carbon timing
Beyond using less energy — quantify shifting and shedding load, and the carbon cost of when power is used. Advisory analytics (read-only toward the BAS). See GEB.md and CARBON.md.
- Demand response & flexibility —
geb.demand_response(shed kW/kWh/% + rebound vs a baseline),geb.flexibility(sheddable load above baseload, peak-to-average headroom). Flags:rebound_hours,baseload_pct. - Load timing —
geb.carbon_aware_shift(CO₂ saved shifting load dirty→clean hours) andgeb.operation_score(load timing vs a price/carbon signal, rearrangement-inequality bounds). - Hourly / marginal Scope-2 —
carbon_hourly.hourly_emissions(time-varying factor → co2e, effective factor, timing premium) andmarginal_vs_average(load-shift value uses marginal; reporting uses average). Flags:unit_kg_per_kwh. - OpenADR export —
interop.openadr.to_openadr_report: map ademand_responseresult to an OpenADR-3.0-shaped report payload for a DR program.
Domain analytics
comfort (Std-55 PMV/PPD), iaq (CO₂ ventilation adequacy), cost, carbon, water (irrigation
/ cooling-tower / leak), loadprofile, pv (+ interop.pvlib_bridge, [pv]),
interop.psychro (PsychroLib, [psychro]), lighting. Plus:
- Schedule inference — schedule.detect_schedule / compare_schedule: the actual weekly
operating schedule from interval load vs a stated one (setback opportunity). SCHEDULE.md.
- Change-point detection — changedetect.detect_level_shifts: when a signal's mean shifts
(MBCx persistence/regression). CHANGEDETECT.md.
- Free-cooling opportunity — freecooling.free_cooling_opportunity: missed economizer hours →
recoverable kWh/$. FREECOOLING.md.
- Load disaggregation — disaggregate.disaggregate_load: baseload / weather / other split.
DISAGGREGATE.md.
Advisory & synthesis
Read-only, human-in-the-loop layers on top of the findings:
- ASO — aso.recommend / recommend_findings: an actionable finding → a suggested setpoint/
sequence change, grounded (cites the rule + G36/PNNL), never a BAS command. ASO.md.
- Action plan — actionplan.build_action_plan: findings + fault_economics ($/yr) + aso,
ranked worst-dollars-first; embeds in the audit report + config runs. ACTIONPLAN.md.
- Health scorecard — scorecard.build_scorecard: per-category scores + an overall A–F grade.
SCORECARD.md.
AI-assist (advisory, provider-agnostic)
Dependency-light, advisory-only, read-only toward the BAS. The LLM path is fully provider-agnostic — no vendor named, no SDK, no network (an AST guard enforces it) — and everything works with no LLM wired via deterministic fallbacks.
- Assisted point mapping —
mapping_assist.suggest_roles/review_unmapped: suggest roles for unmapped tags (a human-confirmed review list; never mutates aMappingProvider).FeatureSuggester(numpy baseline: string + unit + physical-range fit), optionalMLSuggester([ml]extra, scikit-learn, no pretrained weights), andLLMSuggester(agent seam; proposals validated + re-scored deterministically). See MAPPING-ASSIST.md. - Grounded explanation & Q&A —
agent.explain/agent.ask: cited, plain-language explanations and NL Q&A over the findings/costs/recommendations/scorecard/completeness/history/mapping. AContextof citableFacts (order-stable ids), number-traceability verification (agent.check), a deterministic template fallback, and an injectedcomplete(prompt, **opts)seam (client_from_callable). See AGENT.md. - Portfolio triage —
agent.facts_from_fleetbuilds grounded facts from areport.FleetReport(per-building EUI / faults / recoverable $), andbuild_context(fleet=…)makes a multi-site context, soask/explainanswer portfolio-wide ("which building is worst?").
Storage
store.ParquetStore — entity-keyed, hive-partitioned (site/year) Parquet with tag-filtered reads,
rollups, retention pruning, year-partition pruning + column projection + cached catalog. See
SCALE.md. Flags: read_long(columns=…, start/end), rollup(freq, agg),
prune(before_year).
Reporting, integration & API
- Audit —
report.AuditReport(ASHRAE/ACCA Standard 211, text/HTML) with prioritized findings. - Portfolio rollup —
report.build_fleet_report(cross-sectional EUI benchmark + fault rollup, ranked by recoverable $). Flags:price,loads,peer_median_eui,top_n. - Outbound —
integrate: CMMS work-orders (finding_to_ticket/findings_to_tickets→ neutral dict), notifiers (dispatch_findingsoverwebhook_transport/email_transport, withslack_payload/teams_payloadformatters; severity filter + fingerprint dedupe), and findings/ metrics export (export_findings: CSV / Parquet / JSON). All opt-in, from the findings layer — never writing to the BAS. Flags:channel,min_severity,dedupe,dry_run,format,flatten_metrics. See INTEGRATIONS.md. - Charts + dashboard — the full visualization pattern catalog A–J: readiness ribbon (A),
fault-annotated multi-trend (B), load carpet (E), data-quality dashboard (I), OAT cloud-shape
scatter (D,
oat_scatter), templated diagnostic scatters (G,diagnostic), rules as a chart engine (J,evidence— every rule renders its own proof), cohort small-multiples (C,cohort), M&V savings with uncertainty (H,savings), load profiles / load-duration curves (F,loadprofile_chart), plus the legacy scatters/CUSUM/energy-signature.report.build_dashboardassembles them into one self-contained HTML (matplotlib inlined, no web framework), embeds each finding's evidence (rules=), and offers a brush-able inline-SVG scatter (interactive=True). Flags:sections,rank_by,top_n,normalize,rules,evidence,interactive. See VISUALIZATION.md. - Read-only API + live web UI —
api.server(camber serve <store>orpython -m camber.api.server <store> [port]): GET/about/health/facilities/points/history, plus a live vanilla-JS dashboard at/ui(facility/equip/role selectors + a synchronized multitrend, brush-linked viawindow.CAMBER, polling for fresh data; a date range and brush-to-zoom over a min/max-thinned whole span, 0.103). Read-only, localhost-bound, CSP-locked, no framework; aHostallowlist against DNS rebinding and token auth (--auth token, 0.103; the loopback default since 0.107,--auth noneopts out). Env:CAMBER_STORE/CAMBER_API_HOST/CAMBER_API_PORT/CAMBER_API_ALLOWED_HOSTS/CAMBER_API_AUTH/CAMBER_API_TOKEN. See VISUALIZATION.md.
Orchestration & distribution
- Config-driven runs —
config: one JSON config (source → mapping → equipment → rules → report) runs a whole analysis:python -m camber.config run.json. - CLI — the
camberconsole script:run/report/explain/ask/fleet/charts/validate/serve/drift/edgesubcommands, with a vendor-neutral--llm-cmdseam for the agent. See CLI.md. - Plugins —
plugins: third-party rules / ingest adapters / report formats discovered via Python entry points (camber.rules/camber.adapters/camber.reports) or registered in-process, duck-typed against the existing protocols with per-plugin error isolation. See PLUGINS.md. - Distribution & deployment — slim multi-stage Docker image + compose bundle
(DOCKER.md), PyPI (
camber-toolkit) + GHCR via the tag-driven release workflow, CI on 3.10/3.11, and reference Kubernetes / conda-recipe manifests (deploy/). See DEPLOY.md.
See also: ARCHITECTURE.md, ECOSYSTEM.md (fork-vs-depend analysis), and the ROADMAP.