Skip to content

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 camber package.

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 from from 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.*_rule diagnostic modules as import targets: they are discovered through camber.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=), the DatasetEntry.manual / manual_instructions fields, the catalog fields manual, manual_instructions, requires_extras ("xlsx", "brick"), licence_check.expect / json_path, a run's sheet and group: "brick", ingest.brick (file, member, equip_classes) and the mapping-file keys equipment / equipment_classes; the xlsx extra; and BrickPointMapping.owner / owner_class plus camber.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 (including state_dir, manifest, migrate and legacy_sites), PortfolioLocked, LifecycleError, STATES, TRANSITIONS, DELETING, DEFAULT_POLICY, allowed_actions, transition, find_workspace, is_workspace, and the camber portfolio / camber facility commands. The on-disk formats are versioned and read back-compatibly: _portfolio.json with its schema_version, _audit.ndjson, the registry-v2 fields, state/<fid>/manifest.json with its schema_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 openfdd commands.

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-only kernel= on the savings functions, and the as_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 trailing period, model_window, sep_terms, df, enpi_uncertainty and uncertainty_terms), METHODS, SAME_LENGTH_TOLERANCE_DAYS, and the new trailing MethodResult fields;
  • the whole of camber.mandv.sep;
  • camber.mandv.adjustments: NonRoutineAdjustment, StaticFactorAdjustment, IndicatorFit, AdjustedResult (with its trailing enpi, enpi_uncertainty, unadjusted_enpi and links), WaterfallStep, ConfoundedAdjustment, EcmSchedule, DEFAULT_SETTLE_DAYS, VALIDITY, check_validity, apply_adjustments (and its keyword-only reporting_index=, links= and schedule=), 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) and camber.charts.adjustment_waterfall;
  • the config keys mv[].method, kernel, intermediate_period, normal_year, validity, adjustments, ecm_dates, settle_days and materiality_threshold, the mv_savings metrics they add, and the mv_method_proposal finding.

  • 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_provenance and the rest of its __all__), camber.mvrun and camber.report.mv;
  • the additive surfaces they needed: the keyword-only model_types= of BaselineStore, the trailing BaselineRecord.provenance (left out of as_dict when empty, so drift baseline files are unchanged), the class attributes LIST_KEY / SCHEMA, the keyword-only provenance= / accepted_by= of freeze and provenance= of accept_new_normal; IndicatorFit.from_dict (and adjustment_from_dict now rebuilding fit); AdjustedResult.baseline_version; MethodProposal.fitted; the keyword-only windows= of sequential_chain; camber.config.run_mv_config; camber.charts.chained_cusum_plot;
  • the on-disk state/<fid>/mv_baselines.json ({"schema": 1, "mv_baselines": [...]}), the mv_baselines retention class and manifest kind, the config keys mv[].rebaseline and the top-level mv_store, the mv_trigger finding and the baseline_version, partial and triggers metrics on mv_savings;
  • the config keys mv[].model ("change_point" | "cp_driver"), drivers, occupied_weekdays and holidays, and the model_form, drivers and driver_coef metrics they add to mv_baseline (0.90; the change-point + driver form of phase 21c on the config path);
  • the camber mv run | freeze | list | propose | rebaseline | adjust | report commands and their mv.freeze, mv.rebaseline and mv.adjust audit actions.

  • Site time zone (0.90.1; #56): camber.tsparse.TimezoneWarning and check_timezone, the timezone= / strict_timezone= keywords on parse_timestamps, io.load_csv, realio.load_point / load_status / load_equipment and the wide, long, per-point CSV, SQL, OPC-UA and BACnet adapters, the EquipRef.timezone / strict_timezone fields, and the config keys source.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.caveats and the short_baseline metric of mv_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 the weather_provenance, isd_missing_years, power_coverage_end and power_cell series attributes. The bias-correction method may change.
  • False-verdict and sensor-trust fixes (0.90.1; #57, #58), all additive:
  • min_unoccupied_run_pct= on setback.analyze_setback and NightWeekendSetback, and the trailing SetbackResult.unoccupied_to_occupied_ratio / min_unoccupied_run_pct;
  • the trailing SATResetComplianceResult fields mean_abs_error_f, pct_above_g36_target, mean_above_gap_f and n_warm, track_gap_f= on SupplyAirResetCompliance, and the optional rule attribute equip_classes that Registry.run declines other classes by;
  • sensorhealth.STUCK_HOURS, sensorhealth.frame_checks, stuck_hours= on sensor_trust, the trailing SensorTrust fields longest_flat_hours, stuck_intervals, first_valid, window_coverage, n_state_changes and frame_checks, and the flags late_start, never_changes, fractional_status, implausible_fan_off, status_speed_mismatch and all_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 and attrs["billing"] that daily_energy_vs_temp returns for billing input, and the trailing billing / n_days fields of NonRoutineResult and billing / n_periods fields of StepChangeResult and StepChangesResult (in as_dict only 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) and camber.rules.applicability (RULE_EQUIP_CLASSES, ROLES_SUFFICE, rule_equip_classes): which equipment family each built-in rule applies to. Registry.run / run_periods decline a recognised class outside a rule's families and caveat an unrecognised one; run_fleet leaves such equipment out of the batch and records metrics["_class_excluded"]. The families, spellings and per-rule classification may change;
  • the config topology section, camber.topology_infer.topology_from_config, CHILD_COLUMNS / PARENT_COLUMNS, and RunResult.topology / topology_source;
  • CHWSupplyTracking (chw_supply_tracking), RUN_STATUS_ROLE and TEMPERATURE_GATE_CAVEAT in camber.rules.chwplant_rule; analyze_chw_tracking, CHWTrackingResult, chiller_running, chw_tracking_mask, analyze_chw_plant(running=) and the trailing CHWPlantResult.run_source in camber.chwplant; the run_source metric of chw_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 the chwst_reset_direction, flow_mode, flow_cv and design_deltaT_min_f metrics, and the trailing CHWPlantResult.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 trailing CHWPumpResult fields near_min_band_pct / near_min_source / vfd_floor_pct and 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 trailing Issue fields upstream_causes / downstream, and the scope_equips metric 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 new fdd_g36 names (MODE_DELAY_MIN, ALARM_DELAY_MIN, AVG_WINDOW_MIN, FC_OMIT_NO_HEATING, FC_OMIT_NO_COOLING, G36Thresholds.fc14_fan_heat) and the trailing G36Result fields. A built-in rule's equip_classes attribute (g36_afdd, supply_air_reset_compliance) is its RULE_EQUIP_CLASSES entry, the air-handler family;
  • camber.freecooling.integrated_economizer_mask with ECON_DAMPER_MIN_PCT, ECON_OAF_MIN_PCT, ECON_MIN_DELTA_F, and camber.setpoint_reset (classify_setpoint_reset, #63); the thresholds may be retuned;
  • sources=True on the RCx report's OAT-source helper is internal; the per-source OAT rows and oat_equips / scope_equips metrics on sensor_drift:oat findings 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_mask with CHILLER_POWER_RUN_FRAC, GAS_FIRING_FRAC and PLANT_GATE_NONE; in camber.sensorhealth, PLANT_GATED_ROLES, PLANT_SETTLE, plant_gates, frame_sensor_health(plant_gate=), sensor_trust(run_gate=, run_gate_source=), the trailing SensorTrust.run_gate and the not_running flag; and (0.111, #297) CW_FLOW_RUN_FRAC, PLANT_GATE_INPUTS, untrusted_roles(gate_frame=), SensorTrust.run_coverage and the logged_while_running flag; 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_temp and cw_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 the boiler / tower drift 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 rule camber.rules.ventilation_rule.VentilationSystemVRP (ventilation_system_62_1, not auto-registered) and the config ventilation section. 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_balance trust flags and their frame_checks entries (#16);
  • SATResetResult.direction and the reset_direction / sp_wrong_direction metrics of supply_air_reset (#65);
  • Evidence.masks, link_findings(part_mask_for=) and UpstreamCause.unit_hours (#67).
  • Billing M&V and weather fallbacks (0.92; #64), all additive:
  • days-weighted fits: weights= on mandv.models.fit_model / best_model, mandv.stats.fit_stats, regression_tests / model_regression_tests and mandv.adjustments.estimate_nre_indicator; mandv.models.fit_weights; days= on avoided_energy_savings, forecast_savings, backcast_savings and mandv._design.projection_variance, days_baseline= / days_reporting= on chained_savings, days= on select_method; the fit records' weight_scale (serialised only when set) and the trailing IndicatorFit.weight_scale / noise_scale;
  • BillingSeries.from_csv, BillingSeries.merge_estimated and the trailing BillingSeries.merged field; energy_vs_temp now sets attrs["billing"];
  • camber.mvbilling (billing_label, load_bills, billing_oat, billing_findings) and the config keys of an mv entry with bills (bills.*, name, oat, base_f, min_coverage, min_bills), a config with no source when it has no equipment, and the billing metrics of mv_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 keys fallbacks, stations[].offset_correction, hour_of_day_f, hour_basis and rmse_after_monthly_f; the RCx oat_reference.fetch values auto, isd and open_meteo with cache_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, Unit and UnitSystem. The alias table and the ambiguous-unit list may grow;
  • the config block units (system, area), an mv entry's units, bills.heat_content / bills.enthalpy, report.benchmark.unit, and the per-unit price.electricity / price.gas forms (EnergyPrice.from_dict);
  • the metrics energy_unit, meter_unit and unit_system on mv_* findings under a unit system, and waste_energy / energy_unit from annotate_costs(units=);
  • units= / heat_content= / enthalpy= / energy_unit= on mandv.sep.primary_energy and aggregate_energy_types, with the trailing unit / delivered_units fields of PrimaryEnergy and FacilitySEP (serialised only when set);
  • bps.site_eui_units, BillingSeries.converted, mvbilling.billing_units and the trailing Benchmark.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_KEYS and SCHEMA. 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.region and bills.meter_type, the metric energy_factor on billing mv_* findings, mvbilling.billing_conversion, and the trailing UnitSystem.factor_set / UnitSystem.region fields.
  • 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_BANDS and DEFAULT_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, StatePrice and EIA_FUELS;
  • in camber.energy_factors: the kind argument of factor_sets (its default keeps the 0.92 conversion-only list), get_reference_set, load_reference_set, ReferenceSet and PRICE_BAND_METER_TYPES, and the price_band and eui_reference document 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_check and bills.scale_override and report.benchmark. property_type, the unit_scale finding (and scale_override metric), mvbilling.billing_scale_check, bps.site_eui_plausibility, and datasets._ingest.meter_scale_warning with 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 parameters unoccupied_heat_sp_f, unoccupied_cool_sp_f, min_setback_depth_f and max_hold_duty_pct; the same keywords on camber.setback.analyze_setback (max_hold_duty as a 0..1 fraction there); the trailing SetbackResult fields (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; the coil_sensor_fan_heat and fan_speed_thr keywords of camber.leakvalve.analyze_leak_valves, whose fan_heat_f default moved from 1 F to 2 F and is now an allowance on the heating side only; the trailing LeakValveResult fields (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_temp and cool_coil_leaving_temp (#42);
  • camber.rules.reheat_capacity_rule.ReheatCapacityShortfall (reheat_capacity_shortfall, built-in, terminal boxes only; #44), its parameters, metrics and AIRFLOW_SHORT_RATIO. Its thresholds are screening-grade and may be retuned; its faultlab scenario 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_mask and DEFAULT_ECON_HIGH_LIMIT_F; camber.rules.iaq_rule.CO2VentilationSystem (co2_ventilation_system, built-in fleet rule); the CO2Ventilation parameters exclude_economizer, oa_damper_min_pct, econ_high_limit_f and the trailing CO2VentilationResult fields econ_hours_pct, over_vent_econ_pct, over_vent_all_pct (#38);
  • the DemandControlledVentilation / DcvSystemVerification parameters full_outdoor_air and stratify_hour, assess_dcv(stratify_hour=), the trailing DcvResult fields lift_basis and demand_lift_pooled, and the oa_segments metric (#37);
  • the SimultaneousHeatCool parameters dehumidification, reheat_lift_f, dewpoint_margin_f, humid_rh_pct, fault_pct, warn_pct; analyze_ahu(simul_classes=) and the trailing AHUResult.simul_class_pct (#41). cool_coil_leaving_temp is 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 / StoreEquipRef key refrigerant, and the roles liquid_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_deltat and discharge_superheat_drift (modules camber.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 the dx / source_loop equipment and drift families.
  • M&V days and holiday calendars (0.93; #68), all additive and provisional:
  • mandv.intervalfit.repeated_hour_weights and the keyword-only timezone= of daily_energy_vs_temp and rate_to_energy; a source's timezone (or a catalog store's local_timezone) now also sets daily mv day 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_calendar and mv[].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_rate and the volume_flow unit kind; the mv[] keys heat_content and meter_type for a trended gas volume flow;
  • the trailing MeterChain.units and the units key of its as_dict under a unit system;
  • build_fleet_report(eui_unit=, units=) and the trailing FleetReport.eui_unit;
  • carbon.factor_per and per-unit factor specs in carbon.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 trailing IsdStation.icao, and the keyword-only privacy= of nasa_power_url, open_meteo_url, nominatim_url, geocode, fetch_nasa_power, fetch_open_meteo, oat_reference, oat_reference_open_meteo, plus privacy= / audit= / purpose= of oat_reference_blended and oat_reference_auto, and place= of oat_reference_auto; the provenance keys weather_privacy, weather_provenance["privacy"] and weather_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=) and RcxOptions.weather;
  • Portfolio.add_facility(private=), Portfolio.set_private, the registry field private and the audit action facility.private;
  • the config keys private and weather (privacy, precision_deg), weather and place in an mv entry's oat and in report.rcx.oat_reference; the audit file weather_audit.ndjson and its record fields; the CLI camber weather audit and camber facility private, and camber 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) and camber.mandv.billsteps (BillStepRule, scan_statistic, bill_steps, DEFAULT_MIN_RUN, DEFAULT_THRESHOLD);
  • the trailing RebaselinePolicy.bill_steps field (in as_dict only when set) and the config key mv[].rebaseline.bill_steps;
  • the keyword-only degree_day= of mandv.methods.select_method, and the heating_base_f / cooling_base_f / p keys of a degree-day model's summary in its models;
  • the mv_method_proposal metrics model_criterion and degree_day_candidate (billing entries with base_f: "auto"); the n_bills, bases, ranking and tried keys of a billing meter's rebaseline window in camber mv propose --json.
  • Portfolio lifecycle, offboarding to purge (added in 0.95, provisional, #18 step 3): Portfolio.audit and Portfolio.note_edge_device (the public audit and edge-device note used by camber.edge), Portfolio.offboard, archive, restore, purge (each apply=False by default; purge needs confirm= equal to the facility id), Portfolio.export, bundles and recover; Portfolio.transition now runs offboard / archive / restore. On disk: the export bundle layout under archive/<fid>/<bundle_id>/ with manifest.json ("schema": "camber.bundle/1") and manifest.sha256; the registry fields offboarding and archive; the tombstone fields purged_at and purge_pending; the _swap-* / _trash-* crash-recovery names; the audit actions facility.offboard|archive|restore|purge|export and portfolio.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_SCHEMA and the policy document it describes ("schema": "camber.retention/1", from Portfolio.retention_policy()), Portfolio.set_retention, set_retention_override, hold, release_hold, apply_retention; the data class weather_audit in DEFAULT_POLICY; the rollup stores rollups/hourly|daily/ (columns value and n); ParquetStore.partitions, drop_partition, migrate_partitions and migrated_files (the year=Y/_migrated.json record); the audit actions retention.set|override|hold|release|apply|incomplete and store.migrate_partitions. CLI: camber retention show|set|override|hold|release|apply and camber store migrate-partitions (exit code 75 when the lock is held). Stable surface changed additively: ParquetStore now writes year=/month= partitions and still reads year-only ones; a full read_long returns a month column alongside year.
  • Edge lifecycle (0.95; #18 step 5), all additive and provisional:
  • the modules camber.edge.landing (ACCEPTING_STATES, NON_ACCEPTING_STATES, QUARANTINE_PREFIX, CATEGORIES (with duplicate), 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) and camber.edge.bucket_rules (PROVIDERS, BUCKET_CLASSES, DAYS_PER_MONTH, DAYS_PER_YEAR, rule_days, normalize_policy, policy_from_portfolio, bucket_lifecycle_rules and its layout=, LAYOUTS, class_prefixes_from_policy);
  • in camber.edge.spool: Spool.compact, Spool.lock, Spool.retire, Spool.retirement, Spool(lock_timeout=), CompactResult and SpoolRetired (also exported from camber.edge); the journal record op: "mark" and the optional attempts field of an enqueue record; the spool files _lock and retired.json;
  • EdgeConfig.device_id (config key device_id, env CAMBER_EDGE_DEVICE_ID); the forwarder's year=/month= object keys and the month field of its batch manifest;
  • the workspace directory quarantine/ and its *.quarantine.json records; the registry field edge_devices; the audit actions edge.land, edge.reconcile.quarantine, edge.quarantine.release, edge.quarantine.discard and edge.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 constants LAB_HOST, DEFAULT_PORT, BODY_LIMIT and TOKEN_HEADER; the HTTP routes GET /lab, /lab/catalog, /lab/jobs[/<id>], /lab/reports/<fid> and POST /lab/jobs/fetch|ingest, /lab/jobs/<id>/cancel, with their JSON bodies; the audit actions lab.fetch, lab.acknowledge and lab.ingest; and the camber lab command. The page's markup and the catalog-view JSON may change with the exercises. Also additive: the internal via= of camber.datasets._ops.fetch_dataset (the ledger records "lab fetch"), and the /ui?facility_id= deep link of the live viewer. camber serve and camber.api.server.dispatch are unchanged and stay GET-only. Since 0.102 (#127) every lab route needs the access token (the launch URL's ?token=, its camber-lab-<port> session cookie, or Authorization: Bearer); added, also provisional: LabApp(access_token=...), LabApp.access_token, LabApp.launch_url() and camber.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_html and links_text. Reference ids are stable; titles, the rule map and verified_on dates are data and change as the publisher moves documents. Also additive and provisional in 0.96: Recommendation.references (and the references key of action_plan_rows rows and of each RCx issue in RcxReport.to_dict()), the DEFAULT_PARAMS keys dcv_*, chw_low_dt_warn_pct and pump_near_*_warn_pct, AuditReport.title / is_std211() / display_title() / to_html_document(), camber.report.audit.html_document and REPORT_CSS, the config keys report.title and report.ecms, the RCx section id reading, and camber.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, the title and cause keys of each RCx issue in RcxReport.to_dict(), DEFAULT_PARAMS["econ_stuck_low_oaf_pct"], and the free_cooling_missed metrics missed_cause (damper_not_delivering / economizer_not_commanded / undetermined, None with nothing missed), commanded_open_pct, commanded_open_hours, commanded_open_oaf_median_pct and missed_damper_cmd_median_pct.
  • The bts catalog entry and the brick_streams adapter (0.96; #75), additive and provisional: the catalog id bts and its facilities ds-bts-a|b|c; the ingest keys of "adapter": "brick_streams" (sites with model / index / series / source_timezone / local_timezone, index.id / index.class, stream_predicate, series_format: "bts_pickle", equip_classes, site_equip, hold) and a subset's groups of site keys; the provenance keys site, 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-only use_timeseries=, oat= and model= of FeatureSuggester and oat= / profile= of FeatureSuggester.suggest; the RoleSuggestion.basis value timeseries. 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 timezone key of a /facilities row (and of ReadAPI.facilities()), present only when the facility's zone is recorded, and the viewer's UTC toggle. The count units in camber.api.ui.role_units (occupancy persons, 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_factories and make_rule accepting the extra instances (cohort_airflow, sat_reset_effectiveness, ...); the camber rules params [RULE] [--json|--yaml] command and its JSON shape; the format= keyword of camber.datasets.config_template and camber datasets config --format json|yaml; a rule entry's basis map and the param_basis finding metric it adds; .yaml / .yml run configs through camber.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 (Haystack heating valve sensor; not exported as a Brick point); the fan_heat_f= keyword of ReheatPenalty (None, a number of °F, or "auto"); the finding metrics valve_signal, valve_divergence_share (reheat_penalty, overcooling_min_flow) and fan_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 metrics flat_runs, stuck_share, contradicted_share, value, tier, reason, driver, roles); the CohortDeviation keywords group_by_topology, normalise, reference, design_max and tail and the summary="variability" value; in camber.charts.cohort, the tail= keyword of cohort_deviation and cohort_deviation_from_values; the advisory default DEFAULT_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; the mode= / mode_source= keywords of sensor_trust and mode= of frame_sensor_health (and what "auto" infers); the SensorTrust fields mode_source, mode_outlier_frac, main_start, main_end, n_stray and clipped (and its dict keys); the flags clipped, stray_lead and stray_tail; the role argument of gapfill_signature and its scheduled_days / n_schedule_patterns metrics. 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_afdd parameters oa_damper_min, oa_damper_tol and occupancy_gate and the metrics oa_damper_min, oa_damper_min_source, idle_at_min_oa_hours, occupancy_gate and unoccupied_hours; in camber.fdd_g36, OA_DAMPER_TOL, OA_MIN_LEARN_N, the oa_damper_min= / oa_damper_tol= keywords of classify_os, the oa_damper_min=, oa_damper_tol= and occupied= keywords of run_g36_afdd, and the trailing G36Result fields oa_damper_min, oa_damper_min_source, n_idle_at_min_oa and n_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 G36Result field n_heating_above_min_oa and the g36_afdd metric heating_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 (plus oa_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_rule key of each RCx issue in RcxReport.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 its cause key filled) with the member's cause; a config run on a store source takes the facility's zone as /facilities reports it, so an explicit timezone on the registry entry, or a per-site zone in the dataset block, now also sets the run's zone (daily mv day lengths, a shared_oat file's clock) where it did not before.
  • Walk-down follows the heading cause (0.101; #108), additive and provisional: the heading_causes= keyword of camber.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 (its rule and references are 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 of ReheatPenalty (None, "single_duct", "parallel" or "series"), the constant camber.rules.reheat_rule.BOX_TYPES, and the finding metrics box_type, fan_lift_f and fan_heat_closed_f (present only when box_type is set).
  • Coil-valve leak drift (0.100; #100), additive, opt-in and provisional: the drift detector camber.rules.coil_leak_rule.CoilLeakDrift (rule name coil_leak_drift; its constructor parameters, module constants, coil_leak_* metrics and the model kinds coil_leak_cool / coil_leak_heat); the drift.families[] keys coil_leak and coil_leak_params (ahu family only); the coil_leak= / coil_leak_params= keywords of camber.driftrun.build_drift_suite and refit_baselines and camber.driftrun.COIL_LEAK_COILS; the coil_leak_drift:<coil> signal of diagnose_ahu_drift; and in camber.rules.param_docs, DRIFT_PARAM_DOCS, DRIFT_EXEMPT, documented_drift_rules and drift_rule_params. Without coil_leak nothing changes.
  • Point-role suggester vocabulary and weather-station guard (0.100; #102), additive and provisional: the camber.mapping_assist constants WATER_AIR_PENALTY and UNLOCATED_TEMP_PENALTY, and the example script examples/suggester_eval/catalog_names.py. Behaviour change: the default name-only FeatureSuggester reads more abbreviations and applies water-medium and unlocated-temperature rules, so some names get a different suggestion. With use_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_afdd parameter oa_damper_min_by_month and its metric of the same name (present only when it is set); the oa_damper_min_by_month= keyword of camber.fdd_g36.run_g36_afdd and the trailing G36Result field oa_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_unmapped and camber.interop.bacnet.review_bacnet build FeatureSuggester(use_timeseries=True) when a series is passed for a token and no suggester is given. Their new keyword use_timeseries (None by default: on when a series is passed) opts out with False, which reproduces the 0.100 output. Without a series the output is unchanged. The FeatureSuggester class 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 example real_names.dataset_points takes keep_series, and catalog_names.py takes --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=) and ParquetStore.lock(timeout=None); the lock_timeout= keywords of camber.interop.openfdd.ingest_package and camber.lab.LabApp; and the environment variable CAMBER_LOCK_TIMEOUT. The default of FacilityRegistry(lock_timeout=) is now None (the store-lock default, 30 s unless the variable says otherwise). Behaviour change: every ParquetStore write method and the facility registry of a plain store hold the store's single-writer lock (<store>/_lock), so a plain store gets a _lock file on its first write and a second process writing the same store waits, then gets StoreLocked.
  • Store verify and normalize (0.104; #136), additive and provisional: ParquetStore.verify() and ParquetStore.normalize() (both in camber.store._normalize, with check_part), their report shapes, the audit action store.normalize, and the CLI camber 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_coverage and day_hours; the keyword-only min_coverage= of daily_energy_vs_temp, daily_changepoint_savings, detect_non_routine, detect_step_change and detect_step_changes; the config key mv[].min_coverage; the frame attrs dropped_days, min_coverage, n_temp_screened, n_duplicate_stamps and n_repeated_hour_copied; the trailing result fields n_days_dropped / n_temp_screened of the three non-routine results (in as_dict only when non-zero), and the mv_baseline metrics of the same names (present only when non-zero);
  • the keyword-only timezone= of hourly_energy_vs_temp, daily_changepoint_savings and towt_savings_hourly (and of their deprecated old names);
  • the private module mandv._temperature (screen_temperature); the keyword-only temp_bounds= of fit_model, best_model, fit_towt, fit_mean_temp_degree_day and the intervalfit frame builders; the trailing n_temp_screened field of ChangePointModel, TOWTModel and MeanTempDegreeDayModel (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 behind camber mcp: McpServer (handle, handle_line), serve, serve_stdio, build_context, launch, LaunchError and the constants SUPPORTED_VERSIONS, MODERN_VERSIONS, LEGACY_VERSIONS, TOOL_ERROR_CODES, LIST_TTL_MS, CACHE_SCOPE, MAX_MESSAGE_BYTES; the camber mcp options --store, --allow, --allow-all-active, --config and --min-trust; the mcp extra (empty). Added in 0.111 (#292): WorkerPool, the workers= argument of serve / serve_stdio, McpServer.handle(..., defer=True) with ToolCall and McpServer.finish, the tool error code busy (in TOOL_ERROR_CODES) and the options --max-concurrent and --max-queued. Behaviour change: camber mcp answers 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, the NOTICE: lines and the _meta keys camber/facts, camber/banners, camber/truncated and camber/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.

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:

  1. Announce. In some MINOR release X.Y, the name starts emitting a DeprecationWarning that says the version it will be removed in and what to use instead. It keeps working unchanged. This is wired with the @deprecated decorator / warn_deprecated() helper in the private camber._deprecation module.
  2. 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.x line is not removed before 2.0.
  3. 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.py holds 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, from 0.9.3) keep the surface clean.

Support

  • Python: the versions in requires-python and 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.