API stability & deprecation policy
This document defines what CAMBER promises about its API: what counts as public, what
those version numbers mean, and how a public name is changed or removed. It is the contract
behind the 1.0 release.
Until 1.0, CAMBER is in the 0.x range, where Semantic Versioning
explicitly allows anything to change between releases. This policy describes the guarantees
that take effect at 1.0; the releases before it work to make them true before they are
promised.
What is public
A name is public if and only if it does not start with an underscore, and it lives in a module whose name does not start with an underscore, under the
camberpackage.
Concretely, the public API is:
- Every non-underscore function, class, and constant in a non-underscore module — whether you
reach it through a package (
from camber.rules import builtin_registry) or by its module path (from camber.mandv.towt import fit_towt,from camber.ingest.quality import assess). - Each package and module also defines
__all__, its curated surface: the names you get fromfrom camber.x import *, the names featured in the docs, and the recommended way to import.__all__is a convenience and a curation, not a narrowing — a non-underscore name reachable by its module path is public even if it is not lifted into a package__all__.
The top-level camber namespace is intentionally minimal: it exposes only __version__. Import
from the subpackages and modules, not from camber directly.
What is not public (may change or vanish without notice)
- Anything whose name starts with
_(functions, classes, constants, methods, attributes). - Any module whose name starts with
_(e.g.camber._deprecation). - The individual
camber.rules.*_rulediagnostic modules as import targets: they are discovered throughcamber.rules.builtin_registry(), which is public and stable. The set of shipped rules and their behavior is part of the product; the module path you'd import a rule class from is not a promised import surface. - Internal structure of returned objects beyond their documented fields, private attributes, and the exact text of messages, logs, and reprs.
- The optional-integration bridges under
camber.interop.*that wrap third-party libraries (PySAM, pvlib, psychrolib, better-lbnl, rdflib, …): CAMBER's own wrapper functions are public, but where they surface an upstream type or option, that part follows the upstream library's compatibility, not CAMBER's.
Provisional surfaces
A few public modules are marked provisional: they follow the naming rule above and are locked by the snapshot test, but their names and signatures may still change in a MINOR release (with a CHANGELOG entry, without a deprecation window) until they are declared stable.
camber.datasets(added in 0.86) -- the open dataset catalog:DatasetEntry,FetchResult,IngestResult,catalog,get,fetch,ingest,status,remove,config_template,score. The catalog's content (catalog.json: entries, subsets, pinned checksums) is data, not API, and changes whenever a publisher reissues a file or an entry is added. Added in 0.89, also provisional:ingest(..., accept_noncommercial=, from_dir=), theDatasetEntry.manual/manual_instructionsfields, the catalog fieldsmanual,manual_instructions,requires_extras("xlsx","brick"),licence_check.expect/json_path, a run'ssheetandgroup: "brick",ingest.brick(file,member,equip_classes) and the mapping-file keysequipment/equipment_classes; thexlsxextra; andBrickPointMapping.owner/owner_classpluscamber.interop.brick.part_parents_from_brick. Reports built from store frames pick up the dataset provenance stamped on the frame (DataFrame.attrs), an internal mechanism, not API.camber.portfolio(added in 0.86) -- the portfolio workspace and facility lifecycle:Portfolio(includingstate_dir,manifest,migrateandlegacy_sites),PortfolioLocked,LifecycleError,STATES,TRANSITIONS,DELETING,DEFAULT_POLICY,allowed_actions,transition,find_workspace,is_workspace, and thecamber portfolio/camber facilitycommands. The on-disk formats are versioned and read back-compatibly:_portfolio.jsonwith itsschema_version,_audit.ndjson, the registry-v2 fields,state/<fid>/manifest.jsonwith itsschema_version, and the migration redirect stubs. See PORTFOLIO.md.camber.interop.openfdd(added in 0.99, #22). The open-fdd importer and findings JSON:- readers and ingest:
read_package,read_historian,ingest_package,package_config,suggested_rules,openfdd_meta; - the crosswalk:
load_crosswalk,crosswalk_table,Crosswalk,CrosswalkRow; - results:
ColumnMapping,OpenFddEquipment,OpenFddPackage,OpenFddIngestResult; - findings:
findings_document,run_findings,FINDINGS_SCHEMA,FINDINGS_SCHEMA_VERSION; - and the
camber interop openfddcommands.
Three formats are versioned data that may change in a minor release: the crosswalk
(crosswalk.json, crosswalk_version), the findings-exchange schema (0.1-draft) and the
openfdd provenance block on a facility's registry entry. A crosswalk change bumps its
version, which re-ingests on the next run. The additive config change is provisional too: a
store facility ingested from open-fdd defaults source.timezone to the zone it was ingested
in. See INTEROP-OPENFDD.md.
- The RCx report (added in 0.88): camber.report.rcx -- RcxOptions, RcxReport
(to_html, to_dict, slots), build_rcx_report, select_week, WeekChoice, load_notes,
notes_template, P3_FAMILIES, WEEK_MODES; PLANT_FAMILIES and select_week(roles_for=)
are provisional additions in 0.91 (#32) -- and the issue layer in
camber.rules.triage: Issue, SensorCause, Confidence, link_findings,
finding_confidence, sensor_causes, issue_totals, SHARED_ROLES. The HTML layout, the
notes-file schema and the report.rcx config keys may change with the grounded-prose work. The
additive pieces that shipped with it are stable in shape: the RunResult fields registry,
frame_for, refs, data_sources, config and base_dir; data_sources= on
build_site_report / build_dashboard; gate= on sensor_trust / frame_sensor_health;
gate= on g36_reset.sat_reset_compliance; schedules.fan_on_mask; charts.template_violations;
charts.box_by_hour; the fan_gate / denom_min_f parameters and n_masked_* / fan_gate
/ failure_mode metrics on the OA and SAT-reset rules; OAFractionResult.masked;
FleetReport.cost_estimated; and the --layout flag. See RCX-REPORT.md.
- Facility-keyed identity (added in 0.86, part of the lifecycle work). These keyword
arguments are additive and optional, and each call behaves exactly as before without them:
facility_id= / legacy_sites= on FaultLifecycle and BaselineStore (and their load),
facility_id= on FaultLifecycle.update, the camber.integrate.tickets and
camber.integrate.export functions, and FaultRegister.update. The same holds for the new
record fields FaultRecord.facility_id / aliases and BaselineRecord.facility_id /
aliases, and for the ticket fields facility_id / legacy_fingerprint. The new config keys
facility_id, workspace and faults, and the RunResult fields facility_id, workspace
and faults, are also additive. Their semantics may still be refined with the lifecycle work.
Records now ignore unknown keys when they are read back, so a newer file loads in this
version.
- M&V foundations for rebaselining (added in 0.87; issue #21, phase 21a):
camber.mandv.methods(MethodResult,backcast_savings),camber.mandv.nonroutine.detect_step_changes/StepChangesResult/StepChange,camber.mandv.stats.regression_tests/RegressionTests/sep_validity/Validity/model_regression_tests/logical_signs, the keyword-onlykernel=on the savings functions, and theas_dict()/from_dict()model serialisation format. Later phases (SEP chaining and method selection, adjustments, versioned baselines) may reshape them. - M&V SEP methods and adjustments (issue #21, phases 21b and 21c; one flow: method, then adjustments, then result):
camber.mandv.methods:forecast_savings,standard_conditions_savings,chained_savings,sequential_chain,select_method,MethodProposal,ChainLink(with its trailingperiod,model_window,sep_terms,df,enpi_uncertaintyanduncertainty_terms),METHODS,SAME_LENGTH_TOLERANCE_DAYS, and the new trailingMethodResultfields;- the whole of
camber.mandv.sep; camber.mandv.adjustments:NonRoutineAdjustment,StaticFactorAdjustment,IndicatorFit,AdjustedResult(with its trailingenpi,enpi_uncertainty,unadjusted_enpiandlinks),WaterfallStep,ConfoundedAdjustment,EcmSchedule,DEFAULT_SETTLE_DAYS,VALIDITY,check_validity,apply_adjustments(and its keyword-onlyreporting_index=,links=andschedule=),estimate_nre_indicator,nra_from_isolation,propose_adjustments,is_material,adjustment_from_dict,NRA_METHODS,STATIC_METHODS;camber.mandv.multivariable(ChangePointDriverModel,fit_cp_driver_model) andcamber.charts.adjustment_waterfall;-
the config keys
mv[].method,kernel,intermediate_period,normal_year,validity,adjustments,ecm_dates,settle_daysandmateriality_threshold, themv_savingsmetrics they add, and themv_method_proposalfinding. -
M&V versioned baselines and rebaselining (issue #21, phase 21d; #48):
- the whole of
camber.mandv.rebaseline(MVBaselineStore,RebaselinePolicy,Trigger,DeclaredChange,StaticFactorChange,assess_triggers,propose_rebaseline,new_baseline_window,BaselineWindow,RebaselineProposal,version_segments,mv_provenanceand the rest of its__all__),camber.mvrunandcamber.report.mv; - the additive surfaces they needed: the keyword-only
model_types=ofBaselineStore, the trailingBaselineRecord.provenance(left out ofas_dictwhen empty, so drift baseline files are unchanged), the class attributesLIST_KEY/SCHEMA, the keyword-onlyprovenance=/accepted_by=offreezeandprovenance=ofaccept_new_normal;IndicatorFit.from_dict(andadjustment_from_dictnow rebuildingfit);AdjustedResult.baseline_version;MethodProposal.fitted; the keyword-onlywindows=ofsequential_chain;camber.config.run_mv_config;camber.charts.chained_cusum_plot; - the on-disk
state/<fid>/mv_baselines.json({"schema": 1, "mv_baselines": [...]}), themv_baselinesretention class and manifest kind, the config keysmv[].rebaselineand the top-levelmv_store, themv_triggerfinding and thebaseline_version,partialandtriggersmetrics onmv_savings; - the config keys
mv[].model("change_point"|"cp_driver"),drivers,occupied_weekdaysandholidays, and themodel_form,driversanddriver_coefmetrics they add tomv_baseline(0.90; the change-point + driver form of phase 21c on the config path); -
the
camber mv run | freeze | list | propose | rebaseline | adjust | reportcommands and theirmv.freeze,mv.rebaselineandmv.adjustaudit actions. -
Site time zone (0.90.1; #56):
camber.tsparse.TimezoneWarningandcheck_timezone, thetimezone=/strict_timezone=keywords onparse_timestamps,io.load_csv,realio.load_point/load_status/load_equipmentand the wide, long, per-point CSV, SQL, OPC-UA and BACnet adapters, theEquipRef.timezone/strict_timezonefields, and the config keyssource.timezone/source.strict_timezone. - M&V fixes (0.90.1; #51, #55, #59):
lag1_autocorrelation(period_start=, period_end=)and its monthly adjacency window,ExtrapolationPolicy.min_points_outside,mvrun.baseline_window_check,DegreeDayModel.caveatsand theshort_baselinemetric ofmv_baseline. The thresholds may be retuned. - Weather fallback (0.90.1; #53):
oat_reference_blended,oat_reference_isd(fallback=, power_transport=),power_grid_cell,isd_catalog_end,WeatherCacheMiss,POWER_GRID_DEG,fetch_isd(on_missing_year=),fetch_nasa_power(snap_to_cell=),cached_transport(offline=, should_cache=),cached_bytes_transport(offline=),nasa_power_url(time_standard=), and theweather_provenance,isd_missing_years,power_coverage_endandpower_cellseries attributes. The bias-correction method may change. - False-verdict and sensor-trust fixes (0.90.1; #57, #58), all additive:
min_unoccupied_run_pct=onsetback.analyze_setbackandNightWeekendSetback, and the trailingSetbackResult.unoccupied_to_occupied_ratio/min_unoccupied_run_pct;- the trailing
SATResetComplianceResultfieldsmean_abs_error_f,pct_above_g36_target,mean_above_gap_fandn_warm,track_gap_f=onSupplyAirResetCompliance, and the optional rule attributeequip_classesthatRegistry.rundeclines other classes by; sensorhealth.STUCK_HOURS,sensorhealth.frame_checks,stuck_hours=onsensor_trust, the trailingSensorTrustfieldslongest_flat_hours,stuck_intervals,first_valid,window_coverage,n_state_changesandframe_checks, and the flagslate_start,never_changes,fractional_status,implausible_fan_off,status_speed_mismatchandall_points_frozen. The limits and thresholds may be retuned.-
M&V billing-period series (added in 0.90.1; #54): the whole of
camber.mandv.billing(BillingSeries,as_billing_series,is_billing_like,daily_weather), the billing columns andattrs["billing"]thatdaily_energy_vs_tempreturns for billing input, and the trailingbilling/n_daysfields ofNonRoutineResultandbilling/n_periodsfields ofStepChangeResultandStepChangesResult(inas_dictonly for billing input). -
Equipment classes, plant tracking and plant links (0.91; #61, #62), all additive:
camber.model.equipclass(EQUIP_FAMILIES,equip_family,family_matches) andcamber.rules.applicability(RULE_EQUIP_CLASSES,ROLES_SUFFICE,rule_equip_classes): which equipment family each built-in rule applies to.Registry.run/run_periodsdecline a recognised class outside a rule's families and caveat an unrecognised one;run_fleetleaves such equipment out of the batch and recordsmetrics["_class_excluded"]. The families, spellings and per-rule classification may change;- the config
topologysection,camber.topology_infer.topology_from_config,CHILD_COLUMNS/PARENT_COLUMNS, andRunResult.topology/topology_source; CHWSupplyTracking(chw_supply_tracking),RUN_STATUS_ROLEandTEMPERATURE_GATE_CAVEATincamber.rules.chwplant_rule;analyze_chw_tracking,CHWTrackingResult,chiller_running,chw_tracking_mask,analyze_chw_plant(running=)and the trailingCHWPlantResult.run_sourceincamber.chwplant; therun_sourcemetric ofchw_plant_reset. The tracking threshold and severity bands may be retuned;- 0.98 (#86), provisional:
CHWPlantReset(design_deltaT_min_f=, expected_reset_sign=, flow_mode=, constant_flow_cv=)with thechwst_reset_direction,flow_mode,flow_cvanddesign_deltaT_min_fmetrics, and the trailingCHWPlantResult.flow_cv/n_flow;CHWPumpDPReset/HWPumpDPReset(near_min_pct=, floor_tol_pct=),analyze_chw_pump(near_min_pct="auto", floor_tol_pct=),camber.chwpump.learn_vfd_floor, the trailingCHWPumpResultfieldsnear_min_band_pct/near_min_source/vfd_floor_pctand the pump rules' metrics of the same names; - in
camber.rules.triage:UpstreamCause,PLANT_CAPACITY_RULES,SAT_HIGH_RULES,G36_SAT_HIGH_FCS,is_sat_high,link_findings(topology=, plant_overlap_min=), the trailingIssuefieldsupstream_causes/downstream, and thescope_equipsmetric that scopes a sensor-drift cause to the units reading that sensor; - the G36 engine as a rule:
camber.rules.g36_rule.G36AFDD(g36_afdd, #60) and its finding metrics (fc,flagged_fcs,worst_fc,fan_gate, ...); the newfdd_g36names (MODE_DELAY_MIN,ALARM_DELAY_MIN,AVG_WINDOW_MIN,FC_OMIT_NO_HEATING,FC_OMIT_NO_COOLING,G36Thresholds.fc14_fan_heat) and the trailingG36Resultfields. A built-in rule'sequip_classesattribute (g36_afdd,supply_air_reset_compliance) is itsRULE_EQUIP_CLASSESentry, the air-handler family; camber.freecooling.integrated_economizer_maskwithECON_DAMPER_MIN_PCT,ECON_OAF_MIN_PCT,ECON_MIN_DELTA_F, andcamber.setpoint_reset(classify_setpoint_reset, #63); the thresholds may be retuned;-
sources=Trueon the RCx report's OAT-source helper is internal; the per-source OAT rows andoat_equips/scope_equipsmetrics onsensor_drift:oatfindings are provisional. -
Plant detectors and the plant run gate (0.92; #13, #14, #15, #66), all additive and provisional:
- the run gate (#66):
camber.schedules.plant_run_maskwithCHILLER_POWER_RUN_FRAC,GAS_FIRING_FRACandPLANT_GATE_NONE; incamber.sensorhealth,PLANT_GATED_ROLES,PLANT_SETTLE,plant_gates,frame_sensor_health(plant_gate=),sensor_trust(run_gate=, run_gate_source=), the trailingSensorTrust.run_gateand thenot_runningflag; and (0.111, #297)CW_FLOW_RUN_FRAC,PLANT_GATE_INPUTS,untrusted_roles(gate_frame=),SensorTrust.run_coverageand thelogged_while_runningflag;link_findings(shared_scope=)/sensor_causes(shared_scope=); the chilled-water plant rules'run_source="power"fallback (POWER_GATE_CAVEAT); the RCx report's OAT peer check without a reference; - roles
gas_input_rate,cond_entering_water_tempandcw_bypass_valve, and their Brick mappings; camber.rules.boiler_efficiency_rule(BoilerEfficiencyDrift),camber.rules. tower_fan_effort_rule(CoolingTowerFanEffortDrift),camber.rules.condenser_bypass_rule(CondenserBypassLeak, built-in) and their threshold constants, which are screening-grade and may be retuned;camber.plantdrift(PlantDriftDiagnosis,diagnose_boiler_drift,diagnose_tower_drift) and theboiler/towerdrift families;camber.faultlab.PENDING_SCENARIOS(scenarios awaiting sign-off as gated synthetic keys; empty in 0.92 and again in 0.93, whose six new scenarios were signed off and promoted).- Air-side correctness (0.92; #16, #17, #65, #67), all additive and provisional:
- system-level ASHRAE 62.1 VRP (#17): in
camber.ventilation,VentZone,SystemVrpRequirement,SystemVrpResult,system_outdoor_air,simplified_ev,estimate_oa_cfm,assess_system_62_1,zones_from_records,load_vent_zones,DEFAULT_EZ_COOLING,DEFAULT_EZ_HEATING; the fleet rulecamber.rules.ventilation_rule.VentilationSystemVRP(ventilation_system_62_1, not auto-registered) and the configventilationsection. The defaults (D = 1 without a system population, Ez 1.0 / 0.8, the 2 F estimate error) and the finding metrics may change; - the
copied_signal/mixing_balancetrust flags and theirframe_checksentries (#16); SATResetResult.directionand thereset_direction/sp_wrong_directionmetrics ofsupply_air_reset(#65);Evidence.masks,link_findings(part_mask_for=)andUpstreamCause.unit_hours(#67).- Billing M&V and weather fallbacks (0.92; #64), all additive:
- days-weighted fits:
weights=onmandv.models.fit_model/best_model,mandv.stats.fit_stats,regression_tests/model_regression_testsandmandv.adjustments.estimate_nre_indicator;mandv.models.fit_weights;days=onavoided_energy_savings,forecast_savings,backcast_savingsandmandv._design.projection_variance,days_baseline=/days_reporting=onchained_savings,days=onselect_method; the fit records'weight_scale(serialised only when set) and the trailingIndicatorFit.weight_scale/noise_scale; BillingSeries.from_csv,BillingSeries.merge_estimatedand the trailingBillingSeries.mergedfield;energy_vs_tempnow setsattrs["billing"];camber.mvbilling(billing_label,load_bills,billing_oat,billing_findings) and the config keys of anmventry withbills(bills.*,name,oat,base_f,min_coverage,min_bills), a config with nosourcewhen it has noequipment, and the billing metrics ofmv_baseline/mv_savings(billing,weighted_by_days,n_bills,n_report_bills,g14_interval,hdd_total,cdd_total,oat_source,bills_dropped,estimated_merged,estimated_dropped,mean_bill_days);- in
camber.weather_source:FALLBACKS,open_meteo_url,open_meteo_transport,fetch_open_meteo,oat_reference_open_meteo,oat_reference_auto,oat_reference_blended(fallbacks=, meteo_transport=, diurnal=, station_offsets=),oat_reference_isd(fallback="open_meteo"), and the provenance keysfallbacks,stations[].offset_correction,hour_of_day_f,hour_basisandrmse_after_monthly_f; the RCxoat_reference.fetchvaluesauto,isdandopen_meteowithcache_dir/offline. The correction method and its thresholds may be retuned. - Energy units (0.92; #69), all additive and provisional:
- the module
camber.energy_units: the factor tables,parse_unit,parse_heat_content,convert,energy_factor,to_kwh,convert_power,power_factor,energy_unit_of_rate,convert_eui,eui_factor,convert_rate,format_energy,UnitandUnitSystem. The alias table and the ambiguous-unit list may grow; - the config block
units(system,area), anmventry'sunits,bills.heat_content/bills.enthalpy,report.benchmark.unit, and the per-unitprice.electricity/price.gasforms (EnergyPrice.from_dict); - the metrics
energy_unit,meter_unitandunit_systemonmv_*findings under a unit system, andwaste_energy/energy_unitfromannotate_costs(units=); units=/heat_content=/enthalpy=/energy_unit=onmandv.sep.primary_energyandaggregate_energy_types, with the trailingunit/delivered_unitsfields ofPrimaryEnergyandFacilitySEP(serialised only when set);bps.site_eui_units,BillingSeries.converted,mvbilling.billing_unitsand the trailingBenchmark.unit.- Energy conversion factor sets (0.92; #69), all additive and provisional:
- the package
camber.energy_factors:factor_sets,get_factor_set,load_factor_set,validate_factor_set,resolve_unit,resolve_meter_type,factor_for,to_kbtu,reference_markdown,FactorSet,FactorEntry,Conversion,EnergyFactorWarning,UNIT_KEYSandSCHEMA. The factor-set JSON schema (camber.energy_factors/1), the unit keys, the meter-type aliases and the bundled sets may grow. A published set's numbers change only to correct a transcription error; a new edition is a new set; - the config keys
units.factor_set/units.regionandbills.meter_type, the metricenergy_factoron billingmv_*findings,mvbilling.billing_conversion, and the trailingUnitSystem.factor_set/UnitSystem.regionfields. - Unit-scale plausibility (0.92; #71), all additive and provisional:
- the module
camber.unit_scale:check_bills,check_series,check_eui,parse_scale_override,fuel_group,kbtu_per_unit,Evidence,UnitScaleCheck,SCALES,DEFAULT_PRICE_BANDSandDEFAULT_EUI_REFERENCE. The evidence weights, the combination rule, the intensity bounds and the step test may be retuned; - the module
camber.interop.eia:fetch_state_price,eia_price_url,eia_transport,StatePriceandEIA_FUELS; - in
camber.energy_factors: thekindargument offactor_sets(its default keeps the 0.92 conversion-only list),get_reference_set,load_reference_set,ReferenceSetandPRICE_BAND_METER_TYPES, and theprice_bandandeui_referencedocument kinds. The bundled bands and policy factors are CAMBER screening policy and may be revised; the transcribed ENERGY STAR medians change only to correct a transcription; - the config keys
bills.scale_checkandbills.scale_overrideandreport.benchmark. property_type, theunit_scalefinding (andscale_overridemetric),mvbilling.billing_scale_check,bps.site_eui_plausibility, anddatasets._ingest.meter_scale_warningwith the BDG2 ingest warnings.
- Setback, leaking valve and reheat capacity (0.93; #42, #43, #44), all additive and provisional:
night_weekend_setback's held-setback test (#43): the constructor parametersunoccupied_heat_sp_f,unoccupied_cool_sp_f,min_setback_depth_fandmax_hold_duty_pct; the same keywords oncamber.setback.analyze_setback(max_hold_dutyas a 0..1 fraction there); the trailingSetbackResultfields (setback_basis,unoccupied_duty_when_running_pct,zone_temp_source,zone_temp_unoccupied_f,zone_temp_occupied_f,unoccupied_heat_sp_f,unoccupied_cool_sp_f,held_side) and the matching finding metrics. The hold thresholds may be retuned;leaking_valve's constructor (#42):fan_heat_f(default 2 F, G36's ΔT_SF),delta_thr_f,valve_closed_thr,coil_sensor_fan_heat; thecoil_sensor_fan_heatandfan_speed_thrkeywords ofcamber.leakvalve.analyze_leak_valves, whosefan_heat_fdefault moved from 1 F to 2 F and is now an allowance on the heating side only; the trailingLeakValveResultfields (fan_heat_f,fan_gated,hw_basis,chw_basis,hw_median_delta_f,chw_median_delta_f) and the matching finding metrics;- the roles
heat_coil_leaving_tempandcool_coil_leaving_temp(#42); camber.rules.reheat_capacity_rule.ReheatCapacityShortfall(reheat_capacity_shortfall, built-in, terminal boxes only; #44), its parameters, metrics andAIRFLOW_SHORT_RATIO. Its thresholds are screening-grade and may be retuned; itsfaultlabscenario is a gated synthetic key since the 0.93 sign-off.- Ventilation and heat/cool hardening (0.93; #37, #38, #41), all additive and provisional:
camber.iaq.economizer_mode_maskandDEFAULT_ECON_HIGH_LIMIT_F;camber.rules.iaq_rule.CO2VentilationSystem(co2_ventilation_system, built-in fleet rule); theCO2Ventilationparametersexclude_economizer,oa_damper_min_pct,econ_high_limit_fand the trailingCO2VentilationResultfieldsecon_hours_pct,over_vent_econ_pct,over_vent_all_pct(#38);- the
DemandControlledVentilation/DcvSystemVerificationparametersfull_outdoor_airandstratify_hour,assess_dcv(stratify_hour=), the trailingDcvResultfieldslift_basisanddemand_lift_pooled, and theoa_segmentsmetric (#37); - the
SimultaneousHeatCoolparametersdehumidification,reheat_lift_f,dewpoint_margin_f,humid_rh_pct,fault_pct,warn_pct;analyze_ahu(simul_classes=)and the trailingAHUResult.simul_class_pct(#41).cool_coil_leaving_tempis the same role as #42's. - Refrigerant properties and DX / heat-pump rules (0.93; #39, #40, #6), all additive and provisional:
- the module
camber.refrigerant(saturation curves, the pressure-to-subcooling / superheat / approach transforms,derive_refrigerant_roles), the config /EquipRef/StoreEquipRefkeyrefrigerant, and the rolesliquid_line_temp,suction_line_temp,discharge_line_temp,liquid_line_pressure,discharge_superheat_temp,return_air_dewpoint_temp,source_loop_supply_temp,source_loop_return_temp,source_loop_diff_press,source_loop_pump_speed. A fit's coefficients may be refined against the reference equations of state; - the rules
dx_refrigerant_charge,dx_indoor_airflow,hp_mode_vs_need,hp_capacity_shortfall,hp_room_imbalance(fleet),source_loop_deltatanddischarge_superheat_drift(modulescamber.rules.dx_charge_rule,dx_airflow_rule,heatpump_ops_rule,source_loop_rule,dx_discharge_superheat_rule), their threshold constants (screening-grade, may be retuned),camber.dxdrift(DX_DETECTORS,diagnose_dx_drift) and thedx/source_loopequipment and drift families.
- M&V days and holiday calendars (0.93; #68), all additive and provisional:
mandv.intervalfit.repeated_hour_weightsand the keyword-onlytimezone=ofdaily_energy_vs_tempandrate_to_energy; a source'stimezone(or a catalog store'slocal_timezone) now also sets dailymvday lengths;- the package
camber.calendars(HolidayCalendar,public_holidays,calendar_info,load_calendar_csv,register_calendar,registered,bundled,SCHEMA), its JSON schema (camber.calendars/1) and bundled files, and the CSV calendar format. Bundled years may grow; a date changes only to follow its official source; - the config keys
mv[].holiday_calendarandmv[].break_calendar, and the"break_day"driver;mandv._mvform.with_base_dir(private module). - Energy-units follow-ups (0.93; #70), all additive and provisional:
energy_units.VOLUME_FLOW_UNITS,quantity_of_rateand thevolume_flowunit kind; themv[]keysheat_contentandmeter_typefor a trended gas volume flow;- the trailing
MeterChain.unitsand theunitskey of itsas_dictunder a unit system; build_fleet_report(eui_unit=, units=)and the trailingFleetReport.eui_unit;carbon.factor_perand per-unit factor specs incarbon.emissions(factors=).
- Weather privacy guardrails (0.94; #73), all additive and provisional:
- the module
camber.weather_privacy(PRIVACY_MODES,DEFAULT_PRECISION_DEG,MIN_PRECISION_DEG,POWER_GRID,AUDIT_FILE,OFFLINE_HELP,PrivacyViolation,OfflineViolation,WeatherPolicy,resolve_policy,WeatherContext,weather_context,grid_snap,coarsen,coarsening_note,check_url,redact_url,WeatherAudit,read_weather_audit,weather_audit_path,default_weather_dir,guarded_transport,CITY_TABLE,resolve_city). The city table's content is data and may grow; - in
camber.weather_source:isd_url,resolve_place, the trailingIsdStation.icao, and the keyword-onlyprivacy=ofnasa_power_url,open_meteo_url,nominatim_url,geocode,fetch_nasa_power,fetch_open_meteo,oat_reference,oat_reference_open_meteo, plusprivacy=/audit=/purpose=ofoat_reference_blendedandoat_reference_auto, andplace=ofoat_reference_auto; the provenance keysweather_privacy,weather_provenance["privacy"]andweather_place; interop.eia.fetch_state_price(privacy=, audit=, purpose=),interop.openei.fetch_urdb_rate(privacy=, audit=, purpose=),unit_scale.check_bills(privacy=, audit=),mvbilling.billing_scale_check(prep=)andRcxOptions.weather;Portfolio.add_facility(private=),Portfolio.set_private, the registry fieldprivateand the audit actionfacility.private;- the config keys
privateandweather(privacy,precision_deg),weatherandplacein anmventry'soatand inreport.rcx.oat_reference; the audit fileweather_audit.ndjsonand its record fields; the CLIcamber weather auditandcamber facility private, andcamber facility add --private.
- Bill-based M&V follow-ups (0.95; #74), all additive and provisional:
- the modules
camber.mandv.billwindow(BillWindow,RANKING,new_bill_window) andcamber.mandv.billsteps(BillStepRule,scan_statistic,bill_steps,DEFAULT_MIN_RUN,DEFAULT_THRESHOLD); - the trailing
RebaselinePolicy.bill_stepsfield (inas_dictonly when set) and the config keymv[].rebaseline.bill_steps; - the keyword-only
degree_day=ofmandv.methods.select_method, and theheating_base_f/cooling_base_f/pkeys of a degree-day model's summary in itsmodels; - the
mv_method_proposalmetricsmodel_criterionanddegree_day_candidate(billing entries withbase_f: "auto"); then_bills,bases,rankingandtriedkeys of a billing meter's rebaselinewindowincamber mv propose --json.
- Portfolio lifecycle, offboarding to purge (added in 0.95, provisional, #18 step 3):
Portfolio.auditandPortfolio.note_edge_device(the public audit and edge-device note used bycamber.edge),Portfolio.offboard,archive,restore,purge(eachapply=Falseby default;purgeneedsconfirm=equal to the facility id),Portfolio.export,bundlesandrecover;Portfolio.transitionnow runsoffboard/archive/restore. On disk: the export bundle layout underarchive/<fid>/<bundle_id>/withmanifest.json("schema": "camber.bundle/1") andmanifest.sha256; the registry fieldsoffboardingandarchive; the tombstone fieldspurged_atandpurge_pending; the_swap-*/_trash-*crash-recovery names; the audit actionsfacility.offboard|archive|restore|purge|exportandportfolio.recover. CLI:camber facility offboard|archive|restore|purge|export|bundles. - Retention and month partitions (added in 0.95, provisional, #18 step 4):
camber.portfolio.RETENTION_SCHEMAand the policy document it describes ("schema": "camber.retention/1", fromPortfolio.retention_policy()),Portfolio.set_retention,set_retention_override,hold,release_hold,apply_retention; the data classweather_auditinDEFAULT_POLICY; the rollup storesrollups/hourly|daily/(columnsvalueandn);ParquetStore.partitions,drop_partition,migrate_partitionsandmigrated_files(theyear=Y/_migrated.jsonrecord); the audit actionsretention.set|override|hold|release|apply|incompleteandstore.migrate_partitions. CLI:camber retention show|set|override|hold|release|applyandcamber store migrate-partitions(exit code 75 when the lock is held). Stable surface changed additively:ParquetStorenow writesyear=/month=partitions and still reads year-only ones; a fullread_longreturns amonthcolumn alongsideyear.
- Edge lifecycle (0.95; #18 step 5), all additive and provisional:
- the modules
camber.edge.landing(ACCEPTING_STATES,NON_ACCEPTING_STATES,QUARANTINE_PREFIX,CATEGORIES(withduplicate),LandedKey,parse_landed_key,already_migrated,facility_status,route_key,read_key_listing,reconcile),camber.edge.quarantine(QUARANTINE_DIR,RECORD_SUFFIX,quarantine_root,list_quarantine,land,quarantine_reconciled,release,discard),camber.edge.decommission(DecommissionResult,default_device_id,decommission,record_retirement) andcamber.edge.bucket_rules(PROVIDERS,BUCKET_CLASSES,DAYS_PER_MONTH,DAYS_PER_YEAR,rule_days,normalize_policy,policy_from_portfolio,bucket_lifecycle_rulesand itslayout=,LAYOUTS,class_prefixes_from_policy); - in
camber.edge.spool:Spool.compact,Spool.lock,Spool.retire,Spool.retirement,Spool(lock_timeout=),CompactResultandSpoolRetired(also exported fromcamber.edge); the journal recordop: "mark"and the optionalattemptsfield of anenqueuerecord; the spool files_lockandretired.json; EdgeConfig.device_id(config keydevice_id, envCAMBER_EDGE_DEVICE_ID); the forwarder'syear=/month=object keys and themonthfield of its batch manifest;- the workspace directory
quarantine/and its*.quarantine.jsonrecords; the registry fieldedge_devices; the audit actionsedge.land,edge.reconcile.quarantine,edge.quarantine.release,edge.quarantine.discardandedge.decommission; - the bucket-rules input schema and the reconciliation report's JSON keys;
- the CLI
camber edge compact | decommission | reconcile | land | quarantine | record-retirement | bucket-rules.
camber.lab(added in 0.96, provisional, #77) -- the loopback-only catalog UI:LabApp,LabError,dispatch_lab,make_lab_server,serve_lab,Job,JobQueue,JobCancelled, and the constantsLAB_HOST,DEFAULT_PORT,BODY_LIMITandTOKEN_HEADER; the HTTP routesGET /lab,/lab/catalog,/lab/jobs[/<id>],/lab/reports/<fid>andPOST /lab/jobs/fetch|ingest,/lab/jobs/<id>/cancel, with their JSON bodies; the audit actionslab.fetch,lab.acknowledgeandlab.ingest; and thecamber labcommand. The page's markup and the catalog-view JSON may change with the exercises. Also additive: the internalvia=ofcamber.datasets._ops.fetch_dataset(the ledger records"lab fetch"), and the/ui?facility_id=deep link of the live viewer.camber serveandcamber.api.server.dispatchare unchanged and stay GET-only. Since 0.102 (#127) every lab route needs the access token (the launch URL's?token=, itscamber-lab-<port>session cookie, orAuthorization: Bearer); added, also provisional:LabApp(access_token=...),LabApp.access_token,LabApp.launch_url()andcamber.lab.announce.
camber.references(added in 0.96, provisional, #78) -- the linked-reference registry:Reference,REFERENCES,RULE_REFERENCES, the kind constants (GUIDE,TRAINING,TOOL_GUIDE,REPORT,PROJECT,RELATED_TOOL,KINDS),reference,references_for,reference_ids_for,references_for_findings,reference_urls,links_htmlandlinks_text. Reference ids are stable; titles, the rule map andverified_ondates are data and change as the publisher moves documents. Also additive and provisional in 0.96:Recommendation.references(and thereferenceskey ofaction_plan_rowsrows and of each RCx issue inRcxReport.to_dict()), theDEFAULT_PARAMSkeysdcv_*,chw_low_dt_warn_pctandpump_near_*_warn_pct,AuditReport.title/is_std211()/display_title()/to_html_document(),camber.report.audit.html_documentandREPORT_CSS, the config keysreport.titleandreport.ecms, the RCx section idreading, andcamber.api.ui.role_units. The text of recommendations and report titles is presentation, not API: it follows the finding's cause and may be reworded. Additive and provisional in 0.98 (#88):Recommendation.cause, thetitleandcausekeys of each RCx issue inRcxReport.to_dict(),DEFAULT_PARAMS["econ_stuck_low_oaf_pct"], and thefree_cooling_missedmetricsmissed_cause(damper_not_delivering/economizer_not_commanded/undetermined,Nonewith nothing missed),commanded_open_pct,commanded_open_hours,commanded_open_oaf_median_pctandmissed_damper_cmd_median_pct.
- The
btscatalog entry and thebrick_streamsadapter (0.96; #75), additive and provisional: the catalog idbtsand its facilitiesds-bts-a|b|c; the ingest keys of"adapter": "brick_streams"(siteswithmodel/index/series/source_timezone/local_timezone,index.id/index.class,stream_predicate,series_format: "bts_pickle",equip_classes,site_equip,hold) and a subset'sgroupsof site keys; the provenance keyssite,streams_listed,series_files,streams_without_file,streams_by_status,streams_mapped,streams_ingested,mapped_without_file,duplicate_roles_split,unmapped_classes; the equipment ids<class>_<id prefix>and<equip>-<n>. - Time-series point-role evidence (0.96; #45), additive and provisional: the module
camber.mapping_timeseries(SeriesProfile,profile_series,RoleTemplate,ROLE_TEMPLATES,UNIT_SCALES,template_scores,ProfileModel,PROFILE_FEATURES,blend,INFORMATIVE_NAME,TS_WEIGHT_UNINFORMATIVE,TS_WEIGHT_INFORMATIVE); the keyword-onlyuse_timeseries=,oat=andmodel=ofFeatureSuggesterandoat=/profile=ofFeatureSuggester.suggest; theRoleSuggestion.basisvaluetimeseries. The template contents and the blend weights may be retuned in a minor release.
- The trend viewer's site time (0.96), additive and provisional: the
timezonekey of a/facilitiesrow (and ofReadAPI.facilities()), present only when the facility's zone is recorded, and the viewer's UTC toggle. The count units incamber.api.ui.role_units(occupancypersons, request and stage roles) are display data and may be extended.
- Tunable thresholds and YAML configs (0.98; #90), all additive and provisional:
camber.rules.param_docs(PARAM_DOCS,ParamDoc,BASIS_KINDS,EXEMPT,DELEGATES,FIXED,RuleParam,rule_params,documented_rules,describe,config_snippet,snippet_yaml,render_text,json_value);camber.rules.builtin.rule_factoriesandmake_ruleaccepting the extra instances (cohort_airflow,sat_reset_effectiveness, ...); thecamber rules params [RULE] [--json|--yaml]command and its JSON shape; theformat=keyword ofcamber.datasets.config_templateandcamber datasets config --format json|yaml; a rule entry'sbasismap and theparam_basisfinding metric it adds;.yaml/.ymlrun configs throughcamber.config.load_config(the[yaml]extra) and the loader's JSON-like typing. Entry texts (basis, calibration hints, ranges) are documentation and may change in any release; defaults are the rules' and follow the rules.
- Reheat valve position vs demand (0.98; #85), additive and provisional: the role
heat_valve_position(Haystackheating valve sensor; not exported as a Brick point); thefan_heat_f=keyword ofReheatPenalty(None, a number of °F, or"auto"); the finding metricsvalve_signal,valve_divergence_share(reheat_penalty,overcooling_min_flow) andfan_heat_f(reheat_penalty).
- Stuck actuators and cohort options (0.98; #85), additive and provisional: the rule
actuator_stuck(camber.rules.actuator_stuck_rule.ActuatorStuck, its parameters, tiers and metricsflat_runs,stuck_share,contradicted_share,value,tier,reason,driver,roles); theCohortDeviationkeywordsgroup_by_topology,normalise,reference,design_maxandtailand thesummary="variability"value; incamber.charts.cohort, thetail=keyword ofcohort_deviationandcohort_deviation_from_values; the advisory defaultDEFAULT_PARAMS["reheat_valve_divergence_share"].
- Sensor-health modes, stray rows, clipping and schedules (0.98; #87), all additive and
provisional:
camber.sensorhealth.clipped_at_limit; themode=/mode_source=keywords ofsensor_trustandmode=offrame_sensor_health(and what"auto"infers); theSensorTrustfieldsmode_source,mode_outlier_frac,main_start,main_end,n_strayandclipped(and its dict keys); the flagsclipped,stray_leadandstray_tail; theroleargument ofgapfill_signatureand itsscheduled_days/n_schedule_patternsmetrics. The thresholds behind them are module constants and may be retuned.
- G36 free cooling needs an open economizer (0.98; #94), additive and provisional: the
g36_afddparametersoa_damper_min,oa_damper_tolandoccupancy_gateand the metricsoa_damper_min,oa_damper_min_source,idle_at_min_oa_hours,occupancy_gateandunoccupied_hours; incamber.fdd_g36,OA_DAMPER_TOL,OA_MIN_LEARN_N, theoa_damper_min=/oa_damper_tol=keywords ofclassify_os, theoa_damper_min=,oa_damper_tol=andoccupied=keywords ofrun_g36_afdd, and the trailingG36Resultfieldsoa_damper_min,oa_damper_min_source,n_idle_at_min_oaandn_unoccupied. Behaviour change: with an OA damper point, an interval with both coils inactive and the damper at or below its minimum is now OS#5, not OS#2, and a missing damper reading there is unclassified.
- G36 heating needs the OA damper at its minimum (0.99; #95), additive and provisional: the
trailing
G36Resultfieldn_heating_above_min_oaand theg36_afddmetricheating_above_min_oa_hours. Behaviour change: with an OA damper point, an interval with the heating coil alone active and the damper open beyond its minimum (plusoa_damper_tol) is now OS#5, not OS#1, and a missing damper reading there is unclassified.
- RCx heading cause and the facility time zone (0.100; #101, #104), additive and provisional:
the
cause_rulekey of each RCx issue inRcxReport.to_dict()(the rule of the finding whose cause heads the issue). Behaviour changes: an RCx issue whose member finding names the equipment at fault, while its root does not, is headed (and itscausekey filled) with the member's cause; a config run on a store source takes the facility's zone as/facilitiesreports it, so an explicittimezoneon the registry entry, or a per-site zone in the dataset block, now also sets the run's zone (dailymvday lengths, ashared_oatfile's clock) where it did not before.
- Walk-down follows the heading cause (0.101; #108), additive and provisional: the
heading_causes=keyword ofcamber.walkdown.site_checks({issue key: (rule, cause key)}). Behaviour change: in the RCx report's "Verify on site" section, an issue headed by a member's cause gets that member's equipment item (itsruleandreferencesare the member rule's) instead of the root's.
- Fan-heat cap by box type (0.100; #99), additive and provisional: the
box_type=keyword ofReheatPenalty(None,"single_duct","parallel"or"series"), the constantcamber.rules.reheat_rule.BOX_TYPES, and the finding metricsbox_type,fan_lift_fandfan_heat_closed_f(present only whenbox_typeis set).
- Coil-valve leak drift (0.100; #100), additive, opt-in and provisional: the drift detector
camber.rules.coil_leak_rule.CoilLeakDrift(rule namecoil_leak_drift; its constructor parameters, module constants,coil_leak_*metrics and the model kindscoil_leak_cool/coil_leak_heat); thedrift.families[]keyscoil_leakandcoil_leak_params(ahufamily only); thecoil_leak=/coil_leak_params=keywords ofcamber.driftrun.build_drift_suiteandrefit_baselinesandcamber.driftrun.COIL_LEAK_COILS; thecoil_leak_drift:<coil>signal ofdiagnose_ahu_drift; and incamber.rules.param_docs,DRIFT_PARAM_DOCS,DRIFT_EXEMPT,documented_drift_rulesanddrift_rule_params. Withoutcoil_leaknothing changes.
- Point-role suggester vocabulary and weather-station guard (0.100; #102), additive and
provisional: the
camber.mapping_assistconstantsWATER_AIR_PENALTYandUNLOCATED_TEMP_PENALTY, and the example scriptexamples/suggester_eval/catalog_names.py. Behaviour change: the default name-onlyFeatureSuggesterreads more abbreviations and applies water-medium and unlocated-temperature rules, so some names get a different suggestion. Withuse_timeseries=True, the data ranks only outdoor roles for a point whose name places it outdoors. The vocabulary, the multipliers and the guard may be retuned in a minor release.
- G36 seasonal OA damper minimum (0.101; #105), additive and provisional: the
g36_afddparameteroa_damper_min_by_monthand its metric of the same name (present only when it is set); theoa_damper_min_by_month=keyword ofcamber.fdd_g36.run_g36_afddand the trailingG36Resultfieldoa_damper_min_by_month. Without it nothing changes.
- Point-role suggester defaults to the data when a series is passed (0.101; #106, #107),
provisional. Behaviour change:
camber.mapping_assist.suggest_roles,review_unmappedandcamber.interop.bacnet.review_bacnetbuildFeatureSuggester(use_timeseries=True)when a series is passed for a token and no suggester is given. Their new keyworduse_timeseries(Noneby default: on when a series is passed) opts out withFalse, which reproduces the 0.100 output. Without a series the output is unchanged. TheFeatureSuggesterclass default stays name-only. On the time-series path, a name that says setpoint lets the data rank only setpoint roles, and a point with no declared unit is range-checked in every plausible unit. The examplereal_names.dataset_pointstakeskeep_series, andcatalog_names.pytakes--data. The guards and the default may be retuned in a minor release.
- Store lock wait (0.104; #135), additive and provisional:
ParquetStore(root, lock_timeout=)andParquetStore.lock(timeout=None); thelock_timeout=keywords ofcamber.interop.openfdd.ingest_packageandcamber.lab.LabApp; and the environment variableCAMBER_LOCK_TIMEOUT. The default ofFacilityRegistry(lock_timeout=)is nowNone(the store-lock default, 30 s unless the variable says otherwise). Behaviour change: everyParquetStorewrite method and the facility registry of a plain store hold the store's single-writer lock (<store>/_lock), so a plain store gets a_lockfile on its first write and a second process writing the same store waits, then getsStoreLocked. - Store verify and normalize (0.104; #136), additive and provisional:
ParquetStore.verify()andParquetStore.normalize()(both incamber.store._normalize, withcheck_part), their report shapes, the audit actionstore.normalize, and the CLIcamber store verify|normalize. - M&V day coverage, the hourly repeated hour and the temperature screen (0.105; #157, #158, #159), additive and provisional:
mandv.intervalfit.day_coverageandday_hours; the keyword-onlymin_coverage=ofdaily_energy_vs_temp,daily_changepoint_savings,detect_non_routine,detect_step_changeanddetect_step_changes; the config keymv[].min_coverage; the frameattrsdropped_days,min_coverage,n_temp_screened,n_duplicate_stampsandn_repeated_hour_copied; the trailing result fieldsn_days_dropped/n_temp_screenedof the three non-routine results (inas_dictonly when non-zero), and themv_baselinemetrics of the same names (present only when non-zero);- the keyword-only
timezone=ofhourly_energy_vs_temp,daily_changepoint_savingsandtowt_savings_hourly(and of their deprecated old names); - the private module
mandv._temperature(screen_temperature); the keyword-onlytemp_bounds=offit_model,best_model,fit_towt,fit_mean_temp_degree_dayand theintervalfitframe builders; the trailingn_temp_screenedfield ofChangePointModel,TOWTModelandMeanTempDegreeDayModel(serialised only when non-zero).
Behaviour changes, on by default: a daily frame drops days whose meter data cover less
than 90 % of the day (min_coverage=0 keeps them); temperatures outside -40 to 140 °F are
treated as missing by every baseline fit (temp_bounds=(-inf, inf) keeps them); a TOWT row
without a temperature predicts NaN instead of its time-of-week level; hourly_energy_vs_temp
without a zone averages duplicate timestamps into one hour (it summed energy) and warns with
TimezoneWarning, and with timezone= returns a tz-aware local index. Clean data, with whole
days and plausible temperatures, gives the same numbers as before.
- CalTRACK 2.0 methods (0.105; #156), additive and provisional: the modules
mandv.caltrack_daily and mandv.caltrack_hourly and every public name in them, including
caltrack_daily_savings, caltrack_billing_savings, caltrack_hourly_savings, the fit_*
functions, CalTRACKDailyModel, CalTRACKBillingModel, CalTRACKHourlyModel and their
as_dict formats. Also mandv.CalTRACKResult (defined in the private mandv._caltrack), the
eemeter_compat= switch and the pytest marker eemeter_oracle. The defaults follow the
published CalTRACK 2.0 documents, and eemeter_compat=True follows eemeter 2.10. A default may
change in a minor release if a CalTRACK document is found to say otherwise, with a CHANGELOG
entry. The renamed daily_changepoint_savings / DailyNMECResult and towt_savings_hourly /
TOWTHourlyResult keep the stability of the names they replace. The new trailing field
TOWTHourlyResult.model is provisional and is not in as_dict.
- M&V names registry (0.105; #163), additive and provisional: the module
camber.mandv.names (MVName, MV_NAMES, GROUPS, mv_name, label, model_label,
citations); in camber.references the kinds PAPER and STANDARD, MV_REFERENCES, the
trailing Reference fields authors, year, venue and doi, and Reference.citation();
camber.mandv.rc_model.OPTION_D_BASIS; and camber.mandv.degreeday.fit_mean_temp_degree_day /
MeanTempDegreeDayModel (the old names are deprecated, below). The row set and its wording may
change; the kind codes in data do not. Behaviour changes: the M&V report's Model, Method
and Degree-day kind columns, the mv_baseline summary, the energy-signature and OAT-scatter
legends and titles show registry labels ("3P cooling", not "3PC"); OptionDSavings.basis of a
claimed saving reads "Option D-style modeled savings (calibrated 1R1C grey box;
pre-implementation estimate)". Metrics and serialised models keep the kind codes.
- Mixed-air order check in trust (0.105; #151), additive and provisional: the trust flag
mixing_order and its frame_checks entry ("check": "mixing_temperature_order"), and the
keyword-only gate= of sensorhealth.frame_checks. Behaviour change: on a unit without
both airflow stations, a mixed-air temperature outside [OAT, RAT] on fan-on samples is capped
at "suspect" (see docs/SENSOR-HEALTH.md). The thresholds may be retuned in a minor release.
- G36 zone-fleet edges (0.106; #186), additive and provisional: the roles airflow_min_sp
(a box's minimum airflow setpoint; Haystack discharge air flow min sp), zone_heat_loop and
zone_cool_loop (a zone's heating and cooling loop outputs, %), none exported as a Brick
point; the reheat_minimization_g36 keywords dual_max_valve=, dual_max_dat_rise=,
start_hour=, end_hour=, occupied_days= and its metrics reheat_high_loop_pct,
min_flow_basis, high_loop_evidence, occupancy; the zones_heat_cool_census metrics
pct_hours_mixed, avg_zones_cooling_only, avg_zones_heating_when_mixed and the
camber.zones.zone_states columns n_cooling_only, mixed; the reset-effectiveness fields
and metrics failure_modes, n_demand_judged, n_idle_judged; the static rogue census
metrics overloaded_zones, overloaded_share; the cool_loop_col= keyword of
rogue_zone_census and cohort_starvation; the CohortDeviation keywords min_abs_dev=,
max_spread=, the min_abs_dev= keyword of cohort_deviation and
cohort_deviation_from_values, and the cohort metrics min_abs_dev, cohort_spread.
Behaviour changes: zones_heat_cool_census rates on pct_hours_mixed (different zones
heating and cooling) instead of pct_hours_any_both; reheat_minimization_g36 no longer
counts G36's high heating loop as a violation and reads a trended occupancy point; the
not-responding and not-trimming shares count only the cycles where the T&R trajectory had left
that end; a static rogue that is short of cooling moves from rogues to overloaded_zones;
cohort_space_temp needs a 1 °F difference from the median, and cohort_airflow reads info
instead of ok when its cohort spreads past 0.3.
- TOWT reference settings, unseen bins, clipping and fit errors (0.105; #154, #155, #160,
#162, #172, #175), additive and provisional: the fit_towt keywords knots=,
knot_min_obs= and weights=; mandv.towt.towt_lbnl_config(), its TOWTReferenceConfig
and mandv.towt.lbnl_time_weights; the TOWTModel.predict keywords unseen= ("raise", the
default, or "fallback"; mandv.towt.UNSEEN_MODES), clip_negative= and return_counts=, carried by TOWTAtIndex,
towt_savings_hourly and ChangePointModel.predict (clip_negative=), and the counts they
return; and mandv.models.ModelFitError, the ValueError subclass a fit with no usable points
raises, whose message names what the fit saw. The message text is not API.
- TOWT (LBNL RMV2.0 variant) (0.105; #165, #176, #177, #178), additive and provisional: the
module mandv.towt_rmv2 and every public name in it (fit_towt_rmv2, TOWTRMV2Model with
predict, as_dict and from_dict, RMV2_UNSEEN_MODES, interval_of_week,
rmv2_centres, rmv2_occupied_intervals); the registry row towt_rmv2 and the reference
lbnl-rmv2. Its defaults follow RMV2.0 1.1.0 and may change, with a CHANGELOG entry, if that
reference is found to differ.
- Condenser-water flow, chilled-water loops and chiller capacity (0.106; #183, #184),
additive and provisional: the role cw_flow; camber.model.equipclass.is_chw_loop; the
chiller_staging_fleet parameter capacity_kw (a number or an {equip: kW} map) and its
metrics capacity_source, n_loops_excluded, running_hours, multi_hours and
sample_interval_min; the load_source value cw_heat_rejection of
cooling_tower_fan_effort_drift and the metric tower_approach_load_source of
cooling_tower_approach_drift; the run_source and n_judged metrics of
chiller_approach_fouling; and the optional scored key of a benchmark record, which
camber.eval.benchmark reads. Breaking (#183): chiller_staging_fleet's
n_running_hours and n_multi_hours are renamed n_running_samples and n_multi_samples,
with no aliases. Behaviour changes: camber datasets score scores each rule only on the
equipment it ran on; a chilled-water loop runs by its pumps.
- Fan-gated filter and hunting rules (0.106; #181, #182), additive and provisional: the
fan_gate parameter of filter_fouling and control_hunting (default on) and their metric
fan_gate; filter_fouling's fault_factor parameter and its metrics n_judged,
n_masked_fan_off and fault_dp_inwc; control_hunting's resolution_margin parameter and
its metrics fan_on_hours and fault_reachable. Behaviour change: both judge fan-on
samples, and control_hunting declines (info) a threshold the trend cannot reach.
- Heat-pump trend interval and defrost (0.106; #185), additive and provisional: the
max_interval_min parameter of heatpump_defrost and compressor_staging and their metrics
interval_min and n_partial; the max_defrost_min parameter of hp_mode_vs_need,
hp_capacity_shortfall and hp_room_imbalance (heatpump_ops_rule.MAX_DEFROST_MIN, 15
minutes) and hp_mode_vs_need's metric defrost_hours; hp_capacity_shortfall's verdict room_fight and its metrics
verdict_alone, deferred_to, room, heating_opposite_pct and cooling_opposite_pct.
- Edge row digests and the upload inbox (0.107; #142), additive and provisional: the module
camber.edge.row_digest (rows_digest, file_rows_digest, PartitionRows,
DIGEST_VERSION, INDEX_NAME); the quarantine reason duplicate; the inbox= keyword of
camber.edge.landing.route_key; the edge config key part_layout (canonical or legacy;
env CAMBER_EDGE_PART_LAYOUT), the forwarder's part_layout= and
camber.edge.forwarder.PART_LAYOUTS; and the manifest fields part_layout and
rows_sha256. The partition cache _edge_rows.json is a private file. Behaviour change:
the forwarder writes canonical parts, and the landing quarantines a re-sent month whose rows
it already holds.
- RCx report as PDF (0.107; #197), additive and provisional: RcxReport.to_pdf(path=None),
the pdf extra and camber report --layout rcx --out FILE.pdf. The PDF's layout follows the
HTML and may change with it.
- Excess outside-air cost (0.107; #195), additive and provisional: the fault_economics
estimate for outdoor_air_fraction and economizer_high_limit findings; the
EquipmentLoad fields supply_airflow_cfm, design_oa_cfm and heat_pump_cop; the rules'
oa_cost_* metrics and outdoor_air_fraction's min_oa_source; in camber.oafraction,
excess_oa_penalty, humidity_ratio, moist_air_enthalpy, SENSIBLE_BTUH_PER_CFM_F,
ENTHALPY_BTUH_PER_CFM, HEATING_RISE_F and HEATING_VALVE_PCT; and the rule attribute
roles_cost. Behaviour change: outdoor_air_fraction's min_oa_pct defaults to None
(the generic 20 %, judged as before, but not priced).
- RCx generated prose and fact index (0.107; #194), additive and provisional: the module
camber.report.rcx_prose (load_prose, prose_prompts); camber.report.rcx.load_prose and
prose_prompts; RcxOptions.prose (config report.rcx.prose); RcxReport.facts, prose
and prose_enabled, and the facts and prose keys of RcxReport.to_dict(); the prose=
keyword of build_rcx_report (its client= now writes prose); the
require_cite_each_sentence= keyword of camber.agent.verify.check and context-qualified
[F1@ctx] citations; and camber report --prose, --prose-prompts and --llm-cmd. The fact
kinds, id letters and slot scopes may change in a minor release.
- RCx zone chains (0.107; #196), additive and provisional: camber.rules.triage.ZONE_CHAINS,
ZONE_ROGUE_LINKS, ZoneLink, zone_chain_gaps, Issue.zone_links and the zone_chains
key of Issue.to_dict(). The chain pairs may change in a minor release. Behaviour change:
with a declared served-by topology, box findings join their air handler's issue.
- Gates for valves, DX units and towers (0.107; #199, #201, #202), additive and provisional:
simultaneous_heat_cool's metric valve_coverage_pct and
camber.rules.simul_hc.VALVE_COVERAGE_CAVEAT_PCT; the decline reasons no_valve_data,
no_run_gate, no_operating_signal and never_running; cooling_tower_approach's metric
run_gate and the run_mask= keyword of analyze_cooling_tower_approach. Behaviour
changes: each rule declines (info) where it used to judge every sample.
- Air-side gates and occupancy (0.107; #198, #200), additive and provisional:
airflow_tracking's abs_tol parameter (cfm, default 0: off) and its metrics fan_gate and
occupancy_gate; the occupied= keyword of camber.iaq.analyze_co2_ventilation and the
occupancy_basis metric of co2_ventilation and co2_ventilation_system. Behaviour
changes: airflow_tracking judges fan-on samples (and occupied ones when occupancy is
trended); the CO2 rules use a trended occupancy point in place of the assumed schedule.
- Dataset writes in a workspace (0.107; #212, #213, #218), additive and provisional: the
reason= keyword of camber.datasets.ingest and remove, the CLI flags datasets ingest
--reason and datasets remove --reason, --yes and --confirm ID, and the audit actions
datasets.ingest, datasets.remove and datasets.purge; the leftovers and
empty_facilities keys of the store verify report. camber.datasets._lifecycle and
camber.store._swap are private. Behaviour changes: inside a workspace an ingest or a
purge follows the facility lifecycle and refuses a facility under a legal hold or not
provisioning / active, a forced re-ingest included; datasets config --out refuses a
dataset that is not ingested.
- M&V report fixes (0.107; #208, #209, #210), additive and provisional:
NormalizedSavings.n_excluded, MeterChain.meter_unit and the model_label field of the
freeze and rebaseline plan rows.
- Legal holds and audit under the store primitives (0.108; #250, #251, #252, #255, #256),
additive and provisional: the reason= keyword of ParquetStore.drop_facility, prune and
drop_partition (FacilityRegistry.remove(reason=) now defaults to None); the audit actions
store.drop_facility, store.prune and store.drop_partition; the recover action
archive_blocked_legal_hold; the deletes list of a restore plan from archived.
Behaviour changes, inside a portfolio workspace only: those four primitives need a reason
and refuse a facility under a legal hold (LifecycleError), and drop_facility and remove
also one that is not provisioning / active; FacilityRegistry.register (and
so a store write's metadata) refuses to change private, display_name, archive,
offboarding or edge_devices; interop.openfdd.ingest_package refuses a changed or forced
re-ingest of a held facility, and its state refusal is now a LifecycleError (still a
ValueError); a config run with a faults fold, and a drift or M&V baseline write, refuse an
archived or purged facility (a hold alone does not), whose report outputs are no longer
recorded in its manifest;
restore empties the live trees its bundle does not list and, under a hold, refuses one that
would delete a live file; Portfolio.rename refuses another facility's id, name or past
display name; recover leaves a held facility's hot data. A plain store is unchanged.
- Ontology names (0.108; #253, #254, #257), provisional. Behaviour changes: to_brick,
site_to_ttl and site_to_223 percent-encode every character of a name outside
[A-Za-z0-9_-] in its local name and add an rdfs:label with the exact name (Brick); Brick
part nodes are per equipment (bldg:<equip>.Cooling_Coil). The Brick and 223P readers decode
percent-encoded local names. roles_from_haystack and topology_from_haystack read Haystack
JSON v3 and Hayson markers and refs.
- Edge landing row checks and store dedupe (0.108; #228, #229, #230, #233), additive and
provisional: camber.edge.row_digest.PartitionView and read_rows;
camber.edge.landing.row_duplicates and the row_duplicates and duplicate_copies keys of
the reconcile report; camber.edge.quarantine.remove_row_duplicates and the audit action
edge.reconcile.dedupe; the CLI flag edge reconcile --yes; the landing category conflict
and the replaces, replaced, revised and conflicts_with fields of a land row; the replaces key
of the land report. Behaviour changes: route_key signs accepted uploads into _inbox/
by default (inbox=False keeps the old layout); the landing replaces a stored part whose rows
a new part holds in full and quarantines a conflicting overlap; an empty directory is an empty
store for store verify, normalize and migrate-partitions, as for camber serve.
- Plant rule fixes (0.108; #234, #235, #236, #237, #240), additive and provisional:
camber.chillerstaging.chiller_power_running; the run_frac keyword of
analyze_chiller_staging and analyze_chiller_staging_fleet and the run_source and
run_threshold_kw fields of their results (run_threshold_kw on ChillerStagingResult only);
the min_power_kw and run_frac parameters of chiller_staging and chiller_staging_fleet
and their run_source metric (run_threshold_kw too on chiller_staging);
LoadBaseline.load_source; the decline reason load_source_mismatch of
cooling_tower_approach_drift and cooling_tower_fan_effort_drift, and their metrics
tower_approach_baseline_load_source and baseline_load_source; control_hunting's metric
not_evaluated_signals; BoilerCyclingResult.n_days_observed, the gap_factor= keyword of
analyze_boiler_cycling, and the n_days_observed metric of boiler_short_cycle and
compressor_short_cycle. Behaviour changes: min_power_kw defaults to None (size
relative) in place of 2 kW; the short-cycle rates are per observed day; heatpump_defrost
and compressor_staging read a rate over at least one day.
- Edge forwarder, spool, sinks and ingest protocols (0.108; #242–#249 and the rolling-window
merge), additive and provisional: camber.edge.sink.sink_metadata, SINK_METADATA_KEYS,
is_permanent_error, is_permanent_status and PermanentSinkError;
camber.edge.spool.read_retirement, Spool(dead_letter_after=), Spool.stuck,
Spool.dead_letters, Spool.requeue_dead, the SpoolEntry fields rejects and
last_error, the DrainResult fields rejected and dead, and the journal ops dead and
requeue; the BatchResult fields already_spooled, rejected and dead;
camber.edge.config.CONFIG_KEYS, the EdgeConfig fields timezone and dead_letter_after,
the config keys timezone, dead_letter_after, sink.timeout, source.timezone and
source.strict_timezone, and CAMBER_EDGE_TIMEZONE; camber.edge.row_digest.merge_parts;
camber.edge.quarantine.QuarantineCollision, the landing category quarantine_collision,
the merged field of a land row, the merges and errors keys of the land report and
the merges / merged / errors details of the edge.land audit records, and the errors
key of the reconcile --apply report; the CLI command edge requeue (--key, --apply)
and the flags edge status --json and --limit; Bacpypes3Client(trend_records=),
BacnetClientConfig.trend_records, read_trend_log(address=, max_records=),
read_present_value(address=), and the BacnetPoint fields device_instance and
address; HaystackAdapter(timezone=, strict_timezone=), parse_his_grid(timezone=,
strict_timezone=), Series.attrs["unit"] on its result, and
camber.ingest.haystack.haystack_tz. Behaviour changes: a landed part that agrees with
the stored parts it overlaps is merged with them; a permanent sink rejection is counted and
dead-lettered instead of blocking the spool; SDK sinks send a compact metadata subset; the
spool skips a batch already pending; the edge config warns about unknown keys (refused since
0.109) and refuses bad values and
more secret shapes; BacnetSource refuses duplicate point names and discovery_to_points
renames objects that share a name; parse_his_grid returns a tz-naive wall-clock index;
edge land isolates per-object errors and exits 1 when one failed; edge selftest uses a
temporary spool; a 401 / 403 from the sink is transient, stops the cycle and is never
dead-lettered (camber.edge.sink.AUTH_STATUSES, is_auth_status, is_auth_error,
Spool.auth_failing, DrainResult.auth_failed, the auth_failing and config_warnings
keys of edge status --json, and EdgeConfig.warnings).
- Zone-fleet rule fixes (0.108; #238, #239, #241), additive and provisional:
reheat_minimization_g36's min_reheat_hours parameter and its metric n_reheat_samples;
the min_present_frac parameter of sat_ and static_cohort_starvation and of
camber.g36_reset.cohort_starvation, their metric group_skipped_cycles and the
CohortStarvationResult.group_skipped_cycles field; zones_heat_cool_census's
min_heating_frac, max_driver_zones and persistent_frac parameters and its metrics
heating_frac_when_mixed, persistent_heating_zones, driver_zones,
pct_hours_mixed_without_drivers and severity_capped. Behaviour changes:
reheat_minimization_g36's n_reheat_hours counts hours, not samples, and the rule reports
info below min_reheat_hours; the cohort censuses skip cycles with too few zones reporting;
the heating/cooling census caps a result one or two boxes drive at warn.
- G36 fleet generator version 3 and the RCx cause-chain entry (0.108; #226), additive and
provisional: the catalog entry synthetic-g36-rcx-chains (generator g36_rcx_chains, its
run template and the workbook configs rcx-cause-chains and rcx-cause-chains--no-topology),
the mapping synthetic_g36_chains_ahu.json, and the fault labels starved_by_air_handler,
warm_by_air_handler, stuck_damper, sat_too_warm, sat_too_cold and static_too_low.
The generator modules are private; what an entry generates may change with its generator
version. Behaviour change: synthetic-g36-vav-fleet generates version 3, so its data and
every answer recorded on it change; its blocked inlet is on VAV4__rogue_static.
- Sensor references for any point in the RCx report (0.108; #267), additive and
provisional: report.rcx.references ({equip: {role: spec}}, a spec being {"equip": ...}
with an optional "role", or {"csv": ...} with time_col / value_col, plus an optional
label and the compare_to_reference thresholds bias_warn, bias_fault, drift_warn,
drift_fault, min_correlation, min_samples, min_drift_days) and
RcxOptions.references. Each comparison is a sensor_drift:<role> finding on the unit, scoped
to it (metrics["scope_equips"], metrics["reference"]), shown in the data section's
reference table. references["*"]["oat"] is an alias of oat_reference (giving both is an
error).
- Cost-only inputs and money at risk (0.108; #268), additive and provisional:
camber.rules.triage.Issue.cost_conditional_on and Issue.cost_at_risk, the
n_cost_conditional key of issue_totals and of the RCx kpis, and the issue dict's
cost_conditional_on. Behaviour change: a sensor cause on a role a member's rule reads
only to price its finding (roles_cost) moves the issue's dollars to the at-risk total; the
verdict, the confidence and conditional_on are unchanged.
- The SAT-reset target named by its source (0.108; #269), additive: the
supply_air_reset_compliance metrics target_source ("g36", "site_sequence" or
"configured"), pct_below_target, target_median and pct_above_target. The pre-0.108
keys pct_below_g36_target, g36_target_median and pct_above_g36_target carried the same
values as aliases in 0.108 and are removed in 0.109 (#272). Behaviour change: the finding's summary and
the cost basis name the source ("vs configured target", sat-below-configured-target->reheat,
sat-below-site-sequence-target->reheat); a G36-default map reads as before. The excess-OA
estimate's assumptions on a missing-minimum decline now carry the airflow_source it would
use, so Appendix B shows it.
- The synthetic entries synthetic-ahu-excess-oa and synthetic-ahu-sensor-bias (0.108,
Track C), additive and provisional: their generators (ahu_excess_oa, ahu_sensor_bias),
run templates, mappings and the workbook configs air-excess-oa-cost (and its --no-min and
--design-oa variants) and data-sensor-conditional (and --no-reference), and the fault
labels excess_oa and sat_sensor_bias.
- The *_g36_target SAT-reset metric aliases removed (0.109; #272), breaking: the
supply_air_reset_compliance metrics no longer carry pct_below_g36_target,
g36_target_median or pct_above_g36_target; read pct_below_target, target_median and
pct_above_target (same values, since 0.108). The cost estimate and the cause-chain linking
read only pct_below_target, so a finding saved before 0.108 with only the old key is no
longer priced or linked as supply air too cold. The SATResetComplianceResult fields of
camber.g36_reset keep their names.
- The edge forwarder's high-water mark and strict edge config (0.109; #266, #271), additive
and provisional: the module camber.edge.high_water (HighWater, read_high_water,
config_fingerprint, HIGH_WATER_FILE) and its state file <spool>/high_water.json;
Forwarder(high_water=, high_water_id=); the BatchResult fields unchanged and
advanced; the high_water key of edge status --json; a source's load_points(since=),
passed when the source accepts it. Behaviour changes: a poll reads each point from the
month of its last acknowledged observation and sends only the months with a newer or revised
observation (high_water=False keeps the pre-0.109 full read); an unknown edge-config key is
refused with a ValueError (deprecated in 0.108 with a one-minor window). Removed:
EdgeConfig.warnings and the config_warnings key of edge status --json, which only carried
the unknown keys.
- No-persist config runs (0.110; #276), additive and provisional: the persist= keyword of
camber.config.run_config and run_config_file, the RunResult.persisted field, the
"persisted" key of RunResult.faults, and the CLI flag --no-persist of camber run and
camber report. camber._nopersist is private. Behaviour change: none for a persisting run
(the default).
- The capability registry (0.110; #275), new and provisional: the package
camber.capabilities (Caps, CallContext, CapabilityResult, CapabilityError,
ERROR_CODES, Capability, PromptArg, Prompt, Registry, default_registry,
research_only, run_config_for), the error codes, the built-in capabilities facilities and
points with their input and output schemas, and the _cap_<name>.py module convention.
Names, schemas and error codes may change in a minor release while the MCP (0.110) and A2A
(0.112) adapters settle. camber.capabilities._schema (the JSON Schema subset validator) and
_policy are private.
- The first read-only capability set and the built-in prompts (0.110; #278), additive and
provisional: the capabilities coverage, sensor_trust, rule_run, finding_detail,
rcx_report, mv_summary, drift, dataset_search, dataset_entry, rule_params and
check_citations with their names, input and output schemas, fact-id contexts
(F<n>@<capability>) and the finding_id format (<rule>@<equip>, #<n> on repeats); the
prompts triage_ahu, rcx_executive_summary, explain_finding, check_savings_claim,
data_quality_review, compare_equipment, sensor_trust_review and plan_site_visit with
their arguments. Schemas, fact texts and prompt wording may change in a minor release while
the MCP and A2A adapters settle. The modules (_cap_*, _common) are private. Behaviour
change: the data of a CapabilityError a handler raises is now path-redacted like its
message, and an invalid_params refusal by the input schema keeps its JSON pointers
(/facility_id: ...) instead of redacting them as paths. Registry.register refuses an
output_schema whose root is not "type": "object", and Registry.get (so Registry.call)
raises the new code unknown_capability for an unknown name instead of not_found. Fact ids
carry a per-call token in their context (F<n>@<capability>-<6 hex>), added by
Registry.call. Changed in 0.111 (#295): the token (call_token) also covers the run
config's content, the trust floor and the store generation, so ids change. Changed in 0.111 (#301): the error code unavailable (in ERROR_CODES
and camber.mcp.TOOL_ERROR_CODES) for a store read that fails, which was internal; the
DATE arguments (start, end, a drift baseline / current) accept only calendar-valid
months, days, hours and minutes; the facilities row gains the optional unavailable field.
- StoreReadError (0.111; #301), additive and provisional:
camber.store.parquet_store.StoreReadError, an OSError that ParquetStore.read_long (so read_role_frame, points
and every read) raises when stored data cannot be read, with facility_id, retryable and
reason. Behaviour change: such a read used to raise pyarrow's ArrowInvalid (a
ValueError) or a bare FileNotFoundError; a read of one facility now lists only that
facility's partition directory, retries once when a listed part file has vanished, and returns
an empty frame when the facility's directory is gone.
camber.mcp(added in 0.110, provisional, #277) -- the Model Context Protocol server behindcamber mcp:McpServer(handle,handle_line),serve,serve_stdio,build_context,launch,LaunchErrorand the constantsSUPPORTED_VERSIONS,MODERN_VERSIONS,LEGACY_VERSIONS,TOOL_ERROR_CODES,LIST_TTL_MS,CACHE_SCOPE,MAX_MESSAGE_BYTES; thecamber mcpoptions--store,--allow,--allow-all-active,--configand--min-trust; themcpextra (empty). Added in 0.111 (#292):WorkerPool, theworkers=argument ofserve/serve_stdio,McpServer.handle(..., defer=True)withToolCallandMcpServer.finish, the tool error codebusy(inTOOL_ERROR_CODES) and the options--max-concurrentand--max-queued. Behaviour change:camber mcpanswers messages concurrently (replies may come out of order), sends no reply to a cancelled call, and stops the calls still running when stdin closes instead of finishing them. The wire behaviour follows the MCP revisions it speaks (2026-07-28, 2025-11-25, 2025-06-18); the CAMBER-specific parts of a result -- the two text blocks, theNOTICE:lines and the_metakeyscamber/facts,camber/banners,camber/truncatedandcamber/error-- are provisional too.
The read API server: camber.api.server
The HTTP server behind camber serve (#134 records its surface). Its public names and their
stability:
| Name | Since | Stability |
|---|---|---|
dispatch(api, method, path, query) |
0.1 | stable: the pure router, (status, body) |
make_server(store, *, host, port, ...) |
0.1 | stable; the keywords below are newer |
serve(store, *, host, port, ...) |
0.1 | stable; same arguments as make_server |
ReadAPIHandler |
0.1 | stable as a class name; its methods are internal |
make_server / serve keywords allowed_hosts=, auth=, access_token= |
0.103 | provisional |
check_request(access, method, path, query, headers) |
0.103 | provisional: None or (status, body, headers) |
ALLOWED_HOSTS_ENV ("CAMBER_API_ALLOWED_HOSTS") |
0.103 | provisional |
check_request's access argument is the server's server.access object, whose class is
private; its attributes are not API. The routes (/, /about, /health, /ui, /facilities,
/sites, /points, /history) and their JSON fields follow the read API in camber.api.read.
camber.api.read.BadQuery (0.107, #216; also exported from camber.api, provisional) is the
ValueError subclass a malformed start, end, limit or max_points raises; it is the only
400 from dispatch. Any other failure is a 500 whose body is {"error": "internal error"} (no
detail), with the detail logged to stderr. make_server / serve refuse a port outside 0
to 65535 with ValueError (0.107, #217).
The request checks are behaviour, not names: the Host allowlist (0.103) and the cross-site check
(0.104) may tighten in a minor release, with a CHANGELOG entry.
Default change for auth (0.107, #221; announced in 0.104, #134). auth=None (the default)
now means "token" when the server is bound to loopback (127.0.0.1 and the rest of
127.0.0.0/8, localhost, ::1), and "none" on every other bind, 0.0.0.0 and ::
included. Before 0.107 it meant "none" everywhere, with a deprecation notice on loopback from
0.104. The same holds for camber serve without --auth and for python -m camber.api.server
with CAMBER_API_AUTH unset. To keep an open loopback server, pass auth="none" (--auth none,
CAMBER_API_AUTH=none). Code that passes auth explicitly, and every non-loopback bind, is
unaffected. With token auth, server.access.launch_url() is the URL with the token, and
camber serve prints it and saves it in the launch file.
Deprecated
Currently deprecated names and code paths, each with its replacement. Each emits a
DeprecationWarning when it is used.
| Deprecated | Since | Removed in | Use instead |
|---|---|---|---|
Site-keyed fault and baseline records (fingerprint sha1(site, equip, rule/kind)), read through the compatibility path of a FaultLifecycle / BaselineStore opened with facility_id= |
0.86 | 2.0 | camber portfolio migrate (re-keys them to facility_id once, on disk) |
The ticket field legacy_fingerprint (present only when facility_id= is passed) |
0.86 | 2.0 | the facility-keyed fingerprint |
mandv.caltrack.caltrack_savings (a daily change-point baseline, not CalTRACK Daily) |
0.105 | 1.0 | mandv.caltrack.daily_changepoint_savings (same numbers); for CalTRACK Daily, mandv.caltrack_daily.caltrack_daily_savings |
mandv.caltrack.NMECResult |
0.105 | 1.0 | mandv.caltrack.DailyNMECResult |
mandv.caltrack.caltrack_savings_hourly (a pooled TOWT, not CalTRACK Hourly) |
0.105 | 1.0 | mandv.caltrack.towt_savings_hourly (same numbers); for CalTRACK Hourly, mandv.caltrack_hourly.caltrack_hourly_savings |
mandv.caltrack.HourlyNMECResult |
0.105 | 1.0 | mandv.caltrack.TOWTHourlyResult |
camber.mandv.degreeday.fit_degree_day |
0.105 | 1.0 | fit_mean_temp_degree_day (same arguments and result) |
camber.mandv.degreeday.DegreeDayModel |
0.105 | 1.0 | MeanTempDegreeDayModel (stored models keep "type": "DegreeDayModel" and read back unchanged) |
Stores opened without a facility (plain stores outside a portfolio workspace) are not deprecated in this release and do not warn: nothing there can be migrated to a facility id yet. Keying by the site string will be deprecated for them too once facility ids are the default outside workspaces. That change will be announced here with its own window.
What the version number means
From 1.0.0, CAMBER follows Semantic Versioning MAJOR.MINOR.PATCH:
| Part | Bumped when | Your code |
|---|---|---|
PATCH (1.0.0 → 1.0.1) |
Backward-compatible bug fixes only. | Keeps working. |
MINOR (1.0.0 → 1.1.0) |
New public API added; existing public API unchanged. New deprecations may be announced. | Keeps working. |
MAJOR (1.0.0 → 2.0.0) |
A public name is removed or changed incompatibly. | May need changes — see the CHANGELOG and the deprecation warnings you'll have seen since the last major. |
"Backward-compatible" is judged against the public surface defined above. Bug fixes that change a genuinely wrong result are allowed in a MINOR/PATCH even though output changes — correctness is not a frozen contract. Documented numerical results validated against a standard (the FDD/M&V benchmarks) are guarded separately by the benchmark CI gate.
Recommended pin
camber-toolkit>=1.0,<2
Pin the major. Minor and patch upgrades within a major are safe by this policy.
Deprecation policy
A public name is never removed or changed incompatibly without a deprecation period. The lifecycle:
- Announce. In some MINOR release
X.Y, the name starts emitting aDeprecationWarningthat says the version it will be removed in and what to use instead. It keeps working unchanged. This is wired with the@deprecateddecorator /warn_deprecated()helper in the privatecamber._deprecationmodule. - Window. The name keeps working, warning, for at least one full minor release and
until the next MAJOR — whichever is longer. In practice a name deprecated during a
1.xline is not removed before2.0. - Remove. The name is removed only in a MAJOR release, listed under Removed in the CHANGELOG.
stateDiagram-v2
[*] --> Public
Public --> Deprecated : announce, DeprecationWarning
Deprecated --> Deprecated : keeps working and warns
Deprecated --> Removed : MAJOR release only
Removed --> [*]
note right of Deprecated : at least one full minor, and until the next MAJOR
A public name is never removed without a full warning window; removal happens only at a MAJOR.
So: to stay current, run your test suite with deprecation warnings visible
(python -W error::DeprecationWarning to make them hard failures), and act on them before the
next major. You will never be surprised by a removal you weren't warned about a full release
line in advance.
A deprecated object carries a machine-readable __deprecated__ = {"since", "remove_in", "use"}
attribute for tooling, and its docstring gains a deprecation note.
How this is enforced
tests/test_public_api.pyholds a committed snapshot of the entire public surface (tests/public_api_snapshot.json). Adding or removing any public name fails the test until the snapshot is regenerated — so a change to the promised surface is always a deliberate, reviewed act, never an accident. The test also asserts every__all__name resolves and that no underscore-prefixed name leaks into an__all__.- The lint gate (ruff, since
0.9.1) and type gate (mypy, from0.9.3) keep the surface clean.
Support
- Python: the versions in
requires-pythonand the CI matrix (currently 3.10–3.13). Dropping a Python version is a MINOR-release change announced in the CHANGELOG. - Dependencies: NumPy / pandas / pyarrow / matplotlib within the ranges in
pyproject.toml. NumPy 2 is required from 0.103.0; NumPy 1.x is not supported. Raising a dependency floor is a MINOR-release change announced in the CHANGELOG.