Repository navigation
Releases: yroussev/camber
Release list
CAMBER v0.105.0
0.105: faithful CalTRACK 2.0 Daily, Billing and Hourly methods, an M&V names registry with the industry-standard names, a TOWT matching LBNL RMV2.0, sturdier M&V data handling, pump drift on hot-water loops, and four synthetic catalog entries with workbook exercises (#144–#149, #151–#180). Renames (#156, #163): mandv.caltrack.caltrack_savings and caltrack_savings_hourly were not CalTRACK-comparable and are now daily_changepoint_savings and towt_savings_hourly; fit_degree_day / DegreeDayModel are now fit_mean_temp_degree_day / MeanTempDegreeDayModel. The old names keep working with a DeprecationWarning until 1.0 and return the same numbers. The caltrack_daily_savings, caltrack_billing_savings and caltrack_hourly_savings functions implement the published methods and match eemeter 2.10 where asked (#156, #166, #167, #170, #171). Reports, legends and the dossier name models by their registry labels (#163, #174, #179). TOWT gains an unseen-bin fallback, optional non-negative predictions, fixed knots and recency weighting, and an RMV2.0 variant that matches the R reference to 1e-10 (#154, #155, #162, #165, #176–#178). M&V numbers can move on messy data: daily frames drop partial days, the fall-back DST hour is no longer double-counted, and implausible temperatures are screened at fit and prediction time (#157–#159, #168, #169); clean data gives the same numbers. best_model and ModelFitError say why no model could be fit (#160, #172, #173, #175), and seasonal_forecast warns on long horizons (#161). On the FDD side, the pump drift family runs on hot-water loops (#152), the pump DP-reset rules judge only running hours (#180), lbnl-chiller's pump rules read pump data (#164), the trust score checks the mixed-air order without airflow stations (#151), and plant-rule context is sharper (#148, #149, #153). Four synthetic entries (G36 VAV fleet, two-chiller plant, AHU maintenance, heat pumps) bring workbook exercises (#144–#147); answers in plant-chw-reset-pumping and plant-sensor-vs-equipment are re-recorded on running hours (#180).
Added
-
mandv.intervalfit.day_coverage(each day's covered share) andday_hours(each day's
length on the site's clock). -
The CalTRACK 2.0 Daily, Billing and Hourly methods (#156).
mandv.caltrack_dailyand
mandv.caltrack_hourlyimplement the published methods, rule by rule and with every constant
cited:caltrack_daily_savings,caltrack_billing_savingsandcaltrack_hourly_savings, each
returning the fitted model with aSavingsResultin aCalTRACKResult;- the
fit_caltrack_*functions; CalTRACKDailyModel,CalTRACKBillingModelandCalTRACKHourlyModel, each with
predict,as_dictandfrom_dict, so a baseline can be stored and projected later.
Daily and Billing apply CalTRACK's data sufficiency, usage-per-day and temperature rules, its
billing-period rules and its balance-point grid. They fit intercept-only, HDD, CDD and HDD+CDD
candidates, qualify them on degree days and signs, select by adjusted R² with eemeter's tie
order, and report CalTRACK's fractional savings uncertainty. Hourly fits one model per
calendar month, weighting the months either side at 0.5. Its occupancy comes from a
preliminary model's residuals, and its six fixed temperature bins are merged where sparse. An
hour of the week a month never saw predicts NaN and is counted.Defaults follow the CalTRACK documents.
eemeter_compat=Truereproduces eemeter 2.10 where
they differ, except eemeter 2.10's bug on unseen hours. A dev-only cross-check
(tests/test_caltrack_eemeter_xcheck.py, skipped without a separate eemeter 2.10.11
environment) matches eemeter to 3e-12 on synthetic cases. See docs/MANDV.md, "CalTRACK 2.0
methods". -
An M&V names registry (#163).
camber.mandv.namesgives every model kind, degree-day kind,
savings method and M&V function one row: its CAMBER code, the label reports show, the
industry-standard name, the citation, the implementing function and the known differences from
the standard (mv_name,label,model_label,citations). The "Method correspondence"
table ofdocs/MANDV.mdis generated from it (scripts/mv_names_doc.py, checked by the tests
andscripts/gates.sh), and a new "Naming and citations" section says how to cite a model by
its standard name and how to describe an ensemble by its components. -
M&V citations in
camber.references: ASHRAE Guideline 14-2014, the RP-1050 Inverse
Modeling Toolkit (Kissock, Haberl and Claridge 2003), PRISM (Fels 1986), TOWT (Mathieu, Price,
Kiliccote and Piette 2011), Granderson et al. 2015 (LBNL-187596), the LBNL M&V test-method
reports LBNL-187225 and LBNL-6963E, CalTRACK 2.0, IPMVP Core Concepts 2022 and the DOE SEP
50001 M&V Protocol 2019. New kindsPAPERandSTANDARD,MV_REFERENCES, theReference
fieldsauthors,year,venueanddoi, andReference.citation().docs/REFERENCES.md
lists them. -
mandv.degreeday.fit_mean_temp_degree_dayandMeanTempDegreeDayModel, the honest names of
the period-mean-temperature degree-day model (see Deprecated). -
best_modelsays why no model could be fit (#160). It raised a bareValueError("no model could be fit"). It now raisesmandv.models.ModelFitError, aValueError, whose message
lists every kind tried with its parameter count and why it was refused: the fit's own error,
n - p < 1with the counts, or a non-finite BIC. The list is also on.candidates.
best_model(..., return_candidates=True)returns(model, candidates), with each fitted
kind'sn,sse,bicandselected. The choice of model is unchanged. The billing
selector_mvform.best_billing_modelraises the sameModelFitErrorwhen neither the
change-point kinds nor the degree-day model can be fit, listing each with its reason. -
seasonal_forecastwarns past its horizon (#161). The forecaster is built for next-day
to next-week use. A newmax_horizon=(default 14 days,Noneto turn it off) issues a
forecast.ForecastHorizonWarningwhen the latest target lies further than that past the end
of history. The same warning, with the count, says when target intervals take the overall
mean because history never saw their time-of-week slot. The forecast is still returned.
backtestpassesmax_horizonthrough, so a hold-out longer than 14 days warns too.
docs/FORECAST.mddocuments the intended horizon. -
TOWT can predict hour-of-week bins the fit never saw, on request (#154).
TOWTModel.predict(..., unseen="fallback")fills such a row from the seen bin of the same hour
and occupancy class on the nearest day of the same day type, or else from the mean of that
hour's seen bins (NaN when the hour was never seen);return_counts=Truecounts each step.
TOWTAtIndex(unseen=),_design.design_rows(unseen=)and
towt_savings_hourly(unseen=)carry it through,avoided_energy_savingscaveats the count,
and coverage grades the filled rows unsupported. The default still raises. -
Optional non-negative predictions (#155).
clip_negative=TrueonTOWTModel.predict,
TOWTAtIndex,towt_savings_hourly,ChangePointModel.predictand
MeanTempDegreeDayModel.predictsets negative predictions to zero afterX @ betaand counts
them; the uncertainty stays on the unclipped projection. Off by default. -
A synthetic catalog entry for a G36 VAV fleet (#144).
synthetic-g36-vav-fleetis a VAV
air handler with six single-duct reheat boxes under Guideline 36 trim-and-respond SAT and
duct-static resets, hourly for three weeks of spring, as eight air handlers: a healthy fleet,
a rogue zone dragging the SAT reset, a blocked box holding duct static up, a capped fan
starving every box, a reset that ignores its requests, single-maximum reheat, overlapping
heating and cooling setpoints, and a warm zone that is not quite a rogue. Each air handler
trends its setpoints with the reset requests its boxes sent; each box its space temperature
and setpoints, airflow and minimum, damper and reheat valve. It is generated by CAMBER
(generatorg36_vav_fleet, seed 144), Apache-2.0, open, labelled and about 0.9 MB, with two
mappings and a run template that declares the served-by topology. No open dataset in the
catalog trends reset requests, so nine G36 zone-fleet rules had no case to fire on:
sat_/static_reset_effectiveness,static_rogue_zone_census,
sat_/static_cohort_starvation,cohort_airflow,cohort_space_temp,
reheat_minimization_g36andzones_heat_cool_census. The workbook exercise
zone-g36-fleetcomes with it, with an instructor key, a figure and answers pinned by the
test suite, and four variants that judge one air handler each.camber.fleetlabnow draws its
setpoint noise from the integer hash the synthetic entries use (moved to
camber._hashnoise), so its fleets are the same bytes on every machine; thefleet_fdd
benchmark is unchanged. -
TOWT with fixed knots, weights and the LBNL reference settings (#162).
fit_towt(..., knots=, knot_min_obs=, weights=): fixed temperature knots with unbounded outer
segments (Mathieu et al. 2011, Table 3), RMV2.0's outer-knot pruning, and weighted least
squares.towt_lbnl_config()returns the settings verified against LBNL's RMV2.0 1.1.0
(knots 40, 55, 65, 80, 90 °F, pruning under 20 observations, zero clip, recency weight
s² / (s² + Δt²)withs = 14days,lbnl_time_weights); RMV2.0's occupancy rule and its
ensemble of local fits are not part offit_towtbut offit_towt_rmv2(#165), and the
registry's TOWT row points there. Models with
these options serialiseopen_ends/weighted; older stored models load unchanged.
camber.referencesaddslbnl-rmv2. -
clip_negativeon the CalTRACK predicts (#171).CalTRACKHourlyModel.predict, and the
Daily and Billing predicts...
CAMBER v0.104.0
0.104: a safer camber serve, store locks on every write, store verify/normalize, coarse-trend caveats for start counts, pump drift gates that scale to the pump, and two synthetic plant cases (#134–#139, #141, #143, #150). Behaviour change (#134): camber serve now refuses cross-site browser requests (403); curl, scripts and health probes are unaffected, but a link to the viewer from another site no longer opens it. Deprecation (#134): a future release, 0.105 at the earliest, will make --auth token the default on loopback; pass --auth token or --auth none now to choose. Every write to a plain store now takes its single-writer lock, and --lock-timeout / CAMBER_LOCK_TIMEOUT set the wait (#135). camber store verify and camber store normalize check and rewrite parts into the 0.103 canonical layout (#136). boiler_short_cycle, compressor_short_cycle and chiller_staging say when a trend is too coarse to count starts, and a resampled run status no longer hides short cycling (#143). The pump and loop drift detectors size their gates to the pump, so small and constant-speed pumps are checked instead of declined; full-size results are unchanged (#150). Two new synthetic catalog entries bring the plant-boiler-cycling and plant-chiller-staging exercises (#137). Also: README images render on PyPI (#138), a flaky test (#139) and a catalog-rebuild race (#141) are fixed.
Added
camber serve --auth none(#134) chooses no authentication explicitly: the same open
server as leaving--authout, without the notice below.auth="none"is the Python form and
CAMBER_API_AUTH=nonethepython -m camber.api.serverform.- A
camber.api.serverentry indocs/API-STABILITY.md(#134): its public names, their
stability, and the planned change of theauthdefault. --lock-timeout SECONDSandCAMBER_LOCK_TIMEOUT(#135) set how long a command waits for a
store or dataset-cache lock that another process holds, oncamber datasets fetch|ingest|remove,
camber lab,camber interop openfdd ingestandcamber store migrate-partitions.0fails at
once, naming the holder. Without the flag, the environment variable sets the wait, else it is 30
seconds as before. A value that is not a number of seconds >= 0 is refused (exit 2).
camber retention applytakes--lock-timeouttoo, as the new name for--wait, which still
works; its default stays 0. In Python:ParquetStore(root, lock_timeout=…),ParquetStore.lock()
for holding the lock across a bulk load,lock_timeout=oningest_package, and
LabApp(lock_timeout=…).camber store verifyandcamber store normalize(#136).verifyreports, for a store or
one facility (--facility), whether each part file is in the canonical layout of 0.103 (the
camber.layoutmarker, no pandas metadata, one row group, rows sorted by the canonical key),
with counts of canonical, legacy (pre-0.103, no marker), edge and rollup parts, and lists any
part that fails to read. It takes no lock and changes nothing;--json; exit code 0 when every
part is readable, 1 when one is not, or with--strictwhen one is not canonical.normalize
rewrites the non-canonical parts, partition by partition, under the store lock
(--lock-timeout). It is a dry run unless--apply --yes, with--reasoninside a workspace,
where it is audited asstore.normalizeand covers the rollup stores too. Each part keeps its
name and exactly its rows (a rollup part keepscamber.rollup.covers). The new part is staged
inside the partition, read back and compared, then moved over the original, so a crash at any
point loses and duplicates no row and the next run finishes. It is idempotent, invalidates the
catalog and reports the bytes before and after. It leaves alone canonical parts, edge parts,
year-only parts (migrate-partitionsrewrites those), unreadable parts and facilities that do
not accept data or are under a legal hold, each with its reason, and refuses such a facility
named with--facility. In Python:ParquetStore.verify()andParquetStore.normalize().- The edge forwarder's parts stay content-addressed (#136), and
docs/EDGE-DEPLOY.mdsays
why. Canonical bytes would give a batch the forwarder re-sends after an upgrade a new key, so
its rows would land twice; and the landing checks each name against the sha256 of its bytes.
store verifycounts edge parts on their own andstore normalizeleaves them as they are. - Two more synthetic catalog entries (#137), for plant faults no open dataset shows.
synthetic-hw-plant-cyclingis a hot-water plant, every 15 minutes for three weeks of early
spring, as four plants: a well-tuned modulating boiler, an oversized on/off boiler that
short-cycles, a boiler that cycles a little too often, and a loop with three-way valves and a
constant-speed pump.synthetic-chw-plant-stagingis one chiller and its cooling tower, every
15 minutes for three summer weeks with a heat wave, as five plants: a well-staged chiller, an
oversized chiller that short-cycles, a chiller left on around the clock, a tower held at a fixed
85 °F and a tower reset with a high 80 °F floor. Both are generated by CAMBER (generators
hw_plant_cyclingandchw_plant_staging, seed 137), Apache-2.0, open, labelled, under 1 MB,
with a mapping and a run template. Until nowboiler_short_cycle,hw_plant_deltat,
hw_pump_dp_reset,chiller_stagingandcondenser_water_resethad no open-data case where
they flag a problem. - Workbook exercises
plant-boiler-cyclingandplant-chiller-staging(#137), with
instructor keys, a figure each and answers pinned by the test suite on the generated data. The
boiler exercise shows a short-cycling boiler's start count fall to a floor when the trend is
resampled to an hour, and a warn that depends on the boiler's own starts-per-day limit
(variants--hourlyand--10starts). The chiller exercise separates the two wayschiller_stagingfails, and shows a
condenser-water reset that passes the check but saves little (variant--slope05). The
workbook now has 22 exercises. pumpsimsizes the pump and runs constant-speed pumps (#150).simulate_caseand
make_casestakedesign_flow_gpm(flow at 100 % speed, default 1000 gpm), which scales flow,
power and their noise and leaves head, loop DP and ΔT alone, plusfixed_speed(a constant-speed
pump with aPUMP_STATUSof 1) andspeed_point=False(no speed column). The default pump's
frames are byte-identical to 0.103's.- The pump drift family's parameters are documented (#150) in
camber.rules.param_docsand
docs/THRESHOLDS.md, under "Pump and loop drift detectors": every constructor parameter of
pump_flow_drift,pump_head_drift,loop_deltat_drift,loop_dp_driftand
pump_power_drift, with its unit, range, basis and how to calibrate it.
Changed
-
camber serverefuses cross-site browser requests (#134), ascamber labdoes. A request
withSec-Fetch-Site: cross-site(or an unknown value), or with anOriginthat is nothttp://
orhttps://plus an allowedHostname, is 403 on every route and method, after the Host check
and before the token check.same-origin,same-siteandnonepass. Requests with neither
header pass, socurl, scripts, the image'sHEALTHCHECKand the Kubernetes/healthprobes
are unaffected, and so are the trend viewer's own requests. A link to the viewer from a page on
another site is now refused: open the URL directly. Behind a proxy that rewritesHost, list the
public name with--allow-host, since browsers send it inOrigin. -
One Host and Origin check for both servers (#134).
camber lab's own Host and Origin check
now uses the sharedHostAllowlistand cross-site check incamber._access, with its narrower
settings:127.0.0.1andlocalhostat the bound port,httporigins, andSec-Fetch-Site
same-originornone. The lab answers the same requests with the same errors; host names are
now matched without regard to letter case. -
Every write to a plain store takes the store lock (#135). In 0.103 only the dataset
catalog's writers locked a plain store. Now everyParquetStorewrite method holds the store's
single-writer lock (<store>/_lock, or the workspace's lock for a workspace store):
write_long,write_role_frame,register_facility,write_rollup(on the destination),
prune,drop_partition,drop_facilityandmigrate_partitions(apply=True). So do the
facility registry's changes on a plain store. The lock is re-entrant within a process.
camber interop openfdd ingest --store,camber store migrate-partitions --apply, the dataset
ingests andsynth_portfolio()take it once for the whole command, and the writes inside
re-enter it. Reads still take no lock. A plain store now gets a_lockfile on its first write.
A script that writes many times can holdwith store.lock():around the whole load, so the
lock is taken once; otherwise each write takes it on its own, which costs under a millisecond.
An ingest's staging area and a workspace's rollup stores take no lock of their own, because the
lock on the store they belong to already covers them. -
Start-counting rules say when a trend is too coarse to count starts (#143).
boiler_short_cycle,compressor_short_cycleandchiller_stagingtake a new parameter,
max_interval_min(default 15 minutes). On a trend coarser than that, a start count is a floor,
since a cycle shorter than the interval is invisible. A count at or abovemax_starts_per_day
keeps its verdict and gains a caveat saying so. Below the limit,boiler_short_cycleand
compressor_short_cycledecline (info, "cannot confirm on a 60-minute trend") when samples
show the equipment on for only part of an interval, because t...
CAMBER v0.103.0
0.103: numpy 2, a hardened camber serve, a better trend viewer and lab, reproducible store files, and a hot-water pump lockout check with a synthetic lab case (#122–#126, #128–#130, #132, #133). Breaking (#129): numpy 2.0 or newer is now required (pandas ≥ 2.2.2, pyarrow ≥ 16, matplotlib ≥ 3.8.4); pyarrow 26 needs numpy 2 but does not declare it. Deployment change (#128): camber serve now answers only the host names it is bound to, a defence against DNS rebinding; behind a proxy or ingress, add the forwarded name with --allow-host or CAMBER_API_ALLOWED_HOSTS, or those requests get 403. Token auth is opt-in (--auth token). The trend viewer shows the whole span with min/max thinning, a date range and brush-to-zoom, and says "local time" when no time zone is recorded (#122). The lab gains a disk check that matches the fetch, extras gating, Remove / From a folder / force re-ingest controls and evidence charts in its report (#123, #125). Stores and caches lock against a second writer, and a read-only shared cache works for a class (#124, #126). New store part files are byte-reproducible for the same inputs, CAMBER and pyarrow versions, and about 1.8x smaller; existing stores read unchanged (#130). hw_pump_summer_lockout checks hot-water pumps in warm weather, and a synthetic catalog kind brings the plant-lockout workbook exercise (#132, #133).
Added
camber serve --auth token: optional token auth, likecamber lab(#128). Every route
(/ui,/facilities,/points,/history,/about) then needs this run's access token. The
command prints a launch URL ending in?token=...and saves it in a launch file
serve-<port>.urlthat only you can read (0600, in a 0700 folder). Opening it sets an
HttpOnly; SameSite=Strictsession cookiecamber-serve-<port>and redirects (303) to the same
page without the token; scripts sendAuthorization: Bearer <token>. Without credentials a
request is 401 (403 for a wrong token); a bareGET /healthanswers only{"ok": true}, for
health checks.CAMBER_API_TOKEN(16 or more characters) fixes the token. Python:
make_server(..., auth="token", access_token=...)andserve(...)take the same arguments;
python -m camber.api.serverreadsCAMBER_API_AUTHandCAMBER_API_TOKEN. Off by default
in 0.103, so existing deployments keep working; a later release may make token auth the
default on loopback.camber serve --allow-host NAME(repeatable) andCAMBER_API_ALLOWED_HOSTS(#128), the
extraHostnames to answer for a proxy or DNS name: a bare name on any port,NAME:PORT
exactly.make_server(..., allowed_hosts=[...])is the Python form, and
camber.api.server.check_requestthe pure request check.- Single-writer locks on plain stores and dataset caches (#124). Two processes writing one
cache or one store, such as twocamber labs or a lab andcamber datasets ingest, no longer
corrupt each other. Every write to a dataset cache holds<cache>/_lock: a fetch that
downloads,ingest --from-dir, an ingest extracting archive members or recording an
acknowledgement, andremove. An ingest andremove --purge-storehold the store's lock:
<store>/_lockfor a plain store, or the workspace's own lock for a store in a portfolio
workspace, which is re-entered rather than doubled, so the lab's workspace ingest cannot
deadlock. It is the portfolio's kernel lock (flock,msvcrton Windows), released when its
process exits, so a crash leaves no stale lock. A second writer waits up to 30 seconds, then
stops withdataset cache … is locked by <pid>@<host> since <time>(orstore … is locked by …);camber datasetsexits 1. Reads take no lock. See
DATASETS.md. - A read-only shared cache (#124). A dataset cache the user cannot write, such as a course
folder shared read-only across a classroom, now works forcamber datasetsandcamber lab.
A fetch whose files are all present and match their pinned SHA-256 downloads and writes
nothing. An ingest reads the downloads in place and uses the archive members already extracted
there. Members still needed are extracted into a scratch folder beside the ingest's staging
area in the store, deleted when the ingest ends. Downloading,ingest --from-dirandremove
stop with the dataset cache … is read-only for this user and change nothing.
Using the lab describes setting one up for a class. - Trend viewer: date range, brush-to-zoom and a whole-span view (#122). The
/uitrend
viewer thatcamber serveandcamber labboth serve gains From / to date inputs,
Last 7 days / Last 30 days presets (counted back from the last stored sample) and
All. Brushing a panel now zooms to that span and reads it again at full resolution (up to
20,000 samples per series), and still selects its samples for theN selectedreadout; Zoom
out steps back through the spans viewed. A line under the controls gives the span shown, its
time-axis label, and how many samples were drawn out of how many stored. See
Visualization and the lab guide. /history?max_points=(#122). The read API's/historytakes an optionalmax_points, a
per-series budget: a longer series is cut intomax_points / 2equal time buckets, and each
keeps its minimum and maximum sample, so spikes, dips and flatlines survive the thinning. The
reply addssource_count(rows in the window before thinning),downsampled,max_points,
andfirst/last(the window's first and last stored timestamp). A request without
max_pointsreturns the same rows as before.time_axison/points(#122)./points?facility_id=…also returns how to label that
facility's time axis:timezone(ornull),local_label, andutc_label(nullwhen the
store records no time zone for the facility).camber lab: remove a dataset from the page (#123). Each row with anything on disk has a
Remove… button. After you type the dataset id, a job deletes the dataset's downloads and
extractions from the cache, ascamber datasets removedoes. A tick box also drops its
facilities from the store, as--purge-storedoes. The route isPOST /lab/jobs/remove, under
the lab's usual checks (access token, CSRF token, JSON only, catalog ids only), and needs
confirmequal to the id. In a portfolio workspace the purge follows the facility lifecycle:
it is refused, with the reason shown in the dialog, while a facility is under a legal hold or
is suspended, offboarding or archived, and it is checked again under the workspace lock.
Removals and purges are audited (lab.remove,lab.purge).camber lab: ingest a manual download from a folder (#123). Amanual: trueentry has a
From a folder… button that does whatcamber datasets ingest --from-dirdoes
(POST /lab/jobs/from-dir). The server treats the path as untrusted input. It must be an
absolute path (~is expanded) of at most 4096 characters, with no control character, to a
readable folder. A catalog file that links outside the folder is refused. The folder is only
read: each pinned file is verified (size and SHA-256), then copied or hard-linked into the
cache and ingested. A research-only entry needs its acknowledgement; in a workspace the ingest
is audited (lab.adopt).camber lab: force re-ingest (#123). A force re-ingest tick box next to Ingest
(already fetched), also used by From a folder…, re-ingests data whose inputs are
unchanged, which an ingest otherwise skips.camber labshows each dataset's optional extras (#123). A row lists its
requires_extras(for example needs xlsx). When an extra is not installed the badge turns
red with the install command, the row cannot be ticked, and the server refuses its fetch or
ingest (409) before anything is downloaded. Before, an Excel dataset downloaded in full and
then failed at ingest.- Evidence charts in the audit report (#125).
camber report(the default audit layout) and
the lab's report link now include a Finding evidence section. Each chart shows the
samples the rule judged, shaded where it flags them, drawn with the run's own rule instances
and data. To bound the report's size and build time, it draws the charts of the 12 worst
findings that have one (camber.report.audit.EVIDENCE_LIMIT; about 50 KB each) and says so
when there were more. The RCx layout (--layout rcx) still gives each issue its own page. compressor_short_cyclegets a recommended action and a Learn more link (#125). The
recommended action covers the compressor's minimum on/off timers, the stage differential and
where the calling sensor is mounted, with a site check for the walk-down. The link goes to the
measures chapter of PNNL's small/medium-sized building re-tuning course (PNNL-SA-92685), new in
camber.referencesaspnnl-small-retuning-ch3. It covers packaged units and their
thermostats.hw_pump_summer_lockout: the hot-water pump's warm-weather lockout (#132). PNNL's
heating-plant re-tuning locks out the hot-water pumps, not only the boiler, in warm weather; a
pump left running all summer with the boiler off passedboiler_summer_lockout. The new rule
reads when the pump runs (its status, else its speed above 5 %, else the loop flow) and
reports the share of its occupied running hours above the lockout, with the boiler check's
semantics and severity (warn at 5 %, fault at 20 %), plusmax_oat_running_fand
boiler_off_pct(the pump running with the boiler off). It shares the boiler's
summer_lockout_oat_f(default 65 °F): a config that sets it onboiler_summer_lockoutonly
passes it on, recorded in the findi...
CAMBER v0.102.0
0.102: follow-ups from 0.101, sample charts across the docs, a guide to the lab, and an access token on every lab route (#110–#121, #127). The suggester's unitless range check no longer overturns a strong name on a dirty series (#110); the air-economizer workbook exercise teaches the economizer low-limit lockout and the capstone adopts it (#111); and the RCx report ranks uncosted issues of equal severity by confidence before the issue key, so the capstone's stuck-damper issue ranks first again (#112). The docs site gains charts and screenshots rendered by CAMBER's own chart code (#113) and a step-by-step guide to camber lab (#121). Evidence charts shade exactly what each rule flags and name roles with their units (#114–#117, #119). A stuck supply-air sensor on a scheduled fan is now flagged (#118). Behaviour change (#120): free_cooling_missed no longer judges fan-off hours, so missed shares rise on units whose fan runs only when occupied; the air-economizer and capstone answers move with it. Security (#127): camber lab now requires a per-run access token on every route; open the URL it prints.
Added
- Sample charts and screenshots across the docs (#113). The docs site and README had no
images.scripts/docs_figures.pyrenders 36 figures and three README thumbnails into
docs/img/with CAMBER's own chart code: thecamber.chartsprimitives, the rules' evidence
hooks and the drift detectors' frozen-baseline bands. The data is deterministic synthetic data (camber.synth, theahusim,
driftsim,vavsimandpumpsimsimulators, the DCV simulator and small seeded generators);
no downloaded dataset is read. A fixed style, the Agg backend, a fixed dpi and size and PNGs
with no metadata keep reruns byte-stable on one machine. - One figure per chart pattern in Visualization (#113), next to its
code example: readiness ribbon, fault-annotated trend, load carpet (with a new short section),
quality dashboard, OAT scatter, diagnostic scatter, the fitted drift band, rule evidence, cohort
small multiples, M&V savings, load profile and load-duration curve, box by hour, and a new
CUSUM and energy-signature section. - A hero figure on the analytics pages (#113): free cooling, chiller, AHU, VAV and pump
drift, M&V (the change-point fit and the savings chart), sensor health and schedule inference. - The evidence chart each workbook exercise discusses (#113), with a caption that marks it as
a synthetic illustration rather than the exercise's dataset: the stuck outdoor-air damper's OA
fraction, the leaking cooling valve, the reheat penalty, chiller kW/ton, tower approach, the
change-point baseline and the rest. The exercises' text and answers are unchanged. - Screenshots (#113) of the RCx report, the site report, the
camber labcatalog page and
thecamber servetrend viewer, on a synthetic two-AHU site.--screenshotstakes them with
headless Google Chrome, serving on 127.0.0.1 and stopping the servers afterwards; it is
optional, as CI has no browser. - A small gallery in the README (#113): three thumbnails linking to the docs pages.
scripts/docs_figures.py --check(#113), run byscripts/gates.shand by
tests/test_docs_figures.py. It fails when an image referenced fromdocs/**/*.mdor
README.mdis missing or over 150 KB, when a docs image has no alt text or caption, when a
generated figure is missing, unreferenced or stale, or whendocs/img/exceeds 5 MB.- Using the lab, a step-by-step guide to
camber lab(#121), early in the
docs navigation and linked from the README, DATASETS.md, CLI.md and the workbook. A first
session on the openornl-frp-opsdataset (install, start, pick a dataset, Fetch & ingest,
trends, the report's top finding, the exercise, and the matching CLI commands), then how to
read the trend viewer and the report, where the cache and the store live and how to clean
them up, a classroom setup (pre-fetching for an offline room, manual downloads with
--from-dir, a portfolio workspace, what loopback-only means on a shared computer) and
troubleshooting.scripts/docs_figures.py --screenshotsadds five walkthrough screenshots;
unlike the other figures they show a real open dataset (credited in their captions), read
from a local copy through the verified--from-dirpath and never downloaded, and they are
skipped when no local copy is present.
Changed
-
Workbook
air-economizerteaches the economizer low-limit lockout (#111). A new step 5
has learners readfree_cooling_missed's warn on the fault-free unit (17.5 %). They then find
from the trends and the unit's sequence that 899 of its 965 missed hours (93 %) fall below the
documented 33.8 °F lockout, where the damper is held at its minimum by design. They set
low_limit_f33.8 and re-run: the fault-free unit reads ok (1.96 %), and the dampers stuck at
10 % and 25 % stay a fault (49.6 %). The exercise config leaves the low limit out on purpose,
and its_commentsays so. The declaration pins both readings, and the page (a new step and
question, with later ones renumbered) and the instructor key are updated. The stand-in now
models the lockout (four cold days below it), so both readings are also checked offline. -
Workbook
capstoneadopts the same low limit (#111), which intentionally changes its
answers.free_cooling_missedin the capstone config now setslow_limit_f33.8, with a
basis, to match the dataset template and the economizer exercise. On the real data the
control unit reads ok (2.0 %, was a 17 % warn) and loses its spurious "enable the economizer"
issue and walk-down item. The onset unit reads a warn at 21 % (was a 30 % fault): six healthy
months dilute a 1 July onset in a year-long share. Its damper issue keeps its cause heading
and confidence H, and it now has stronger cause evidence (commanded_open_pct89.9 % of 640
h, was 40.3 % of 673 h). With #112 it ranks first again: among
uncosted issues of one severity, confidence now decides the order before the issue key. Drift is
unchanged (-83, fault). The declaration, the page (questions 1 and 2, a caveat) and the key are
updated. -
RCx: uncosted issues of one severity rank by confidence before the issue key (#112).
link_findingsused to order uncosted issues of one tier by issue key, a fixed but arbitrary
tie-break. They now rank by input-trust confidence, H before M before L, and then by key.
Severity, conditional-last and costed-first by dollars are unchanged, and costed issues keep
their dollar order. This intentionally changes the RCx ranking where uncosted issues of one
tier differ in confidence. On the 15 catalog run templates (default subsets), 4 of 96 issues
change rank, in 2 reports, and each pair swaps. Inlbnl-ddahu,DMPRStuck_OA_100's
outdoor_air_fractionfault (H) moves from 2 to 1 aboveDMPRStuck_OA_0'sg36_afddfault
(M). Inornl-frp-ops,base_heating'snight_weekend_setbackfault (H) moves from 2 to 1
abovesb_heating'scompressor_short_cyclefault (M). Both reports keep their chosen week;
only its evidence score, which weights issues by 1/rank, changes. The RCx golden is unchanged:
its synthetic site has no uncosted tie with mixed confidence.docs/RCX-REPORT.mddescribes
the new order. -
Workbook
capstone: the damper issue ranks first again (#112). On the real data, the onset
unit's stuck-damper issue (an uncosted warn, confidence H) now ranks 1, above the onset unit's
supply-air reset and the control's static-pressure reset (uncosted warns, confidence M). It
ranked 3 in #111. The declaration asserts rank 1 in both modes. The page's question 1 asks what
puts the issue first when nothing is costed. The instructor key explains the confidence order,
lists the damper first in the walk-down, and rewords the common mistake about reading an
uncosted rank as a measure of size. -
The unitless range check no longer overturns a strong name on a dirty series (#110). On
the time-series path (the default when a series is passed, from 0.101), a point with no
declared unit is range-checked in every plausible unit. Two changes act on that check only:- an exact zero that the role cannot read in any plausible unit (a 0 °C room, 0 ppm CO2) is
a dropout and is left out; an all-zero series gives no range verdict; - when the role is the name's best lexical match and scores at least 0.6, the check keeps at
leastSTRONG_NAME_RANGE_FLOOR(0.75) of its score. A series with no reading inside the
bounds in any unit (a-999dead channel) is still demoted in full.
This intentionally changes suggestions when a series is passed without a unit. A declared
unit, theuse_timeseries=Falseopt-out and the name-only default are unchanged. At default
settings with the series passed, top-1 0.101 → 0.102:- held-out catalog names, real names, simulated LBNL sets, BTS anonymised: unchanged
(73.0, 95.7, 74.4, 46.2 %); - BTS Brick-class names (upper bound): 89.4 → 93.4 % (name alone 93.2 %);
- the five synthetic styles: 70.4-89.9 → 73.0-93.9 %;
ahu_03_supply_temp89.9 → 93.9 %
(name alone 93.9 %).
No pool loses top-1 or top-3. Before and after tables, and the 4 synthetic points that now
miss, are indocs/MAPPING-ASSIST.md. - an exact zero that the role cannot read in any plausible unit (a 0 °C room, 0 ppm CO2) is
-
camber.mapping_assist.STRONG_NAME_RANGE_FLOOR(new, provisional): the floor above, a
module constant likeWATER_AIR_PENALTY.
None of these figures is a gated benchmark.
Fixed
camber labneeds its access token on every route (#127, security). The lab binds
127.0.0.1, but every GET was unauthenticated and the page carried the CSRF token, so any
other account on a shared computer could load/lab, read the token, queue fetches and
ingests, and read the ingested data, reports and trends. Each run now makes...
CAMBER v0.101.0
0.101: a seasonal OA damper minimum, data-led point-role suggestions by default, and a
walk-down that follows the heading cause (#105–#109). g36_afdd takes a seasonal OA damper
minimum, oa_damper_min_by_month (#105). The lbnl-ddahu template uses it: 45 %, and 28 % in
June to August. The unit's winter heating hours return to OS#1 there, and its winter hours
idling at the minimum are no longer read as free cooling. #105 has a limit: FC6 still cannot
catch the dual-duct damper stuck shut (DMPRStuck_OA_0) on this data. Outside the summer that
run's damper command saturates open, so its hours are OS#5, where FC6 does not apply. The run is
still caught, through FC8, FC10, FC11 and FC12. The time-series point-role suggester gains a
setpoint guard and a range check for points with no unit (#106). Default change (#107):
suggest_roles, review_unmapped and review_bacnet now read the data whenever a series is
passed; use_timeseries=False opts out and gives the 0.100 output byte for byte. With a series
passed, top-1 rises against 0.100 on every evaluation pool. Against the name alone, pools with
strong names dip slightly on dirty series: BTS Brick-class names read 89.4 % with the series
against 93.2 % from the name alone, because site C's negative airflows and dropouts fail the
range check. docs/MAPPING-ASSIST.md advises use_timeseries=False, or cleaning the series,
where the names are already reliable. The RCx walk-down's equipment item now follows the cause
that heads the issue (#108), which changes the two re-headed catalog issues (lbnl-ddahu
DMPRStuck_OA_0, lbnl-sdahu damper_stuck_075). The open-fdd cross-check leaves the
per-verdict list out of its JSON by default (#109). It was re-run once on the merged code with
all three engines: only CAMBER's lbnl-ddahu verdicts change, as #105 reported, and every
engine's any-FC result is unchanged. The synthetic, fleet, LBNL, BDG2 and BDG2 savings benchmark
gates did not move.
Changed
-
The walk-down's equipment item follows the cause that heads the RCx issue (#108). Since
0.100 (#101) a member finding's equipment-level cause can head an issue, but the "Verify on
site" equipment item still followed the root finding.camber.walkdown.site_checkstakes
heading_causes={issue key: (rule, cause key)}(additive, provisional), and the RCx report
passes the cause it chose for each re-headed issue. That issue's equipment item is then the
member rule's template for that cause, with the member rule's references; an issue the root
heads keeps the root's item. This intentionally changes the Verify on site section for
re-headed issues only. On the 15 catalog run templates (default subsets), 2 of 98 issues
change, the two #101 re-headed:lbnl-ddahuDMPRStuck_OA_0andlbnl-sdahu
damper_stuck_075now ask for the outdoor-air damper's blades, linkage and actuator under a
full open and close command, not the minimum-position setting or the damper on a hot hour. The
RCx golden is unchanged (its synthetic site has no re-headed issue). -
open-fdd cross-check: the per-verdict list is left out of the JSON by default (#109).
run_crosscheck.pynow omits the per-verdict list and collapses the per-equipment "not
evaluated" reasons to counts unless--keep-verdictsis passed; a month-window run otherwise
writes about 200k more lines.--omit-verdictsis still accepted (it is the default). The
Markdown output is the same either way. The committed results were re-run at integration
with the new default, so none of them carries the per-verdict list. -
The
lbnl-ddahutemplate sets the unit's seasonal damper minimum (#105).g36_afddnow
runs withoa_damper_min45 andoa_damper_min_by_month28 in June to August (the documented
sequence positions), where the learned minimum was the 28 % summer position all year. Hourly,
every verdict holds: the fault-free andDMPRStuck_OA_100runs stay ok andDMPRStuck_OA_0
fault. The winter heating hours at the 45 % minimum return to OS#1, so FC6 judges 1,111 h on
the fault-free run (was 744 h; 0.09 %) and 1,273 h onDMPRStuck_OA_100(was 762 h; 0.08 %),
and FC5 and FC7 are evaluated there (367 h and 529 h, 0 %). Winter hours idling at the 45 %
minimum are no longer free cooling: the fault-free run's 4 h of FC9 hits go, and on
DMPRStuck_OA_0FC8 reads 100 % of 48 h (was 39.7 % of 121 h) and FC9 0 % (was 20.7 %).
FC6 still does not catchDMPRStuck_OA_0(1.96 % of 766 h, unchanged), which #105 set out
to do. Outside June to August that run's damper command saturates (median 100 %) while the hot
deck heats, or both coils run, so its hours are OS#5, where G36 does not apply FC6, and the
temperature balance there reads a median 17.7 % OA, within G36's 30-point tolerance of the
31.8 % minimum anyway.lbnl-sdahuis unchanged.g36_afddis not a scored target, so
camber datasets scoreand the gated benchmarks do not move. The open-fdd cross-check's CAMBER
verdicts onlbnl-ddahuchange (any-FC unchanged in both windows); docs/ECOSYSTEM.md has the
before/after. -
Default change: the point-role suggester reads the data when a series is passed (#107).
suggest_roles,review_unmappedandcamber.interop.bacnet.review_bacnetnow use the
time-series path (FeatureSuggester(use_timeseries=True)) when a series is passed for a token
and no suggester is given. This intentionally changes their output whenever a series is
passed, including the BACnet review path whenseries_by_nameis given. The new keyword
use_timeseries=Falseopts out and gives the 0.100 output byte for byte. Without a series the
output is unchanged, and so areroles_from_bacnetandvendor_aliases, which pass no
series. TheFeatureSuggesterclass default stays name-only. At default settings with the
series passed (no unit, no outdoor-air series), top-1 before → after:- held-out catalog names: 49.4 → 73.0 %;
- real names: 84.8 → 95.7 %;
- simulated LBNL sets: 73.1 → 74.4 %;
- BTS anonymised: 0.0 → 46.2 %;
- BTS Brick-class names (upper bound): 25.5 → 89.4 %;
- the five synthetic styles: 15-26 → 70-90 %.
On strong names the unitless range check costs a little against the name alone. With BTS
Brick-class names it is 89.4 % against 93.2 %, because site C's negative airflows are out of
range. The full table is indocs/MAPPING-ASSIST.md. -
Time-series suggester, step 1: held-out measurement and the two known fixes (#106). Both
fixes act only on the time-series path, which from #107 is the default when a series is
passed.-
Setpoint guard. When a point's name says setpoint (
setpoint,SP,STPT,
RMCLGSPT), the data ranks only the setpoint roles.robod'stemp_setpointstays
cool_sp, where the data alone had made itspace_temp. -
Range check without a unit. A point with no declared unit is now range-checked on the
time-series path. Before, it skipped the check. It is read in every plausible unit of the
role (the units the role templates try), and the best reading counts. A °C room temperature
passes, and a sentinel such as-999is demoted, as on the default path. -
Measured on the held-out catalog names with their data (
catalog_names.py --data, new;
89 of the 223 names have a usable open-tier series). Top-1 / top-3 %:- name only: 76.4 / 79.8;
- name plus data: 76.4 / 82.0 before, 75.3 / 80.9 after;
- the default suggester with the series passed: 49.4 / 50.6. Its range check reads a
unitless temperature as °F.
After the fixes the data helps 3 points and hurts 4. The 4 losses are refrigerated-case air
temperatures labelled as air-handler roles. Two of them, at about -3 °F, are below those
roles' physical bounds. -
Other evaluation pools (name plus data, top-1): real names 95.3 → 95.7 %, with 0 losses
(was 2); real names excluding in-sample names 89.1 → 90.7 %. Simulated LBNL sets and BTS are
unchanged. The synthetic names move by at most +0.1. Details and the helped and hurt lists
are indocs/MAPPING-ASSIST.md.
-
-
real_names.dataset_pointstakeskeep_seriesand returns each point's 15-minute series
with its profile. The evaluation scripts only.
None of these figures is a gated benchmark. The synthetic, fleet, LBNL, BDG2 and BDG2 savings
benchmark gates are not affected.
Added
g36_afddtakes a seasonal OA damper minimum (#105). A newoa_damper_min_by_month
parameter ({month: position %}, mirroringmin_oa_pct_by_month) overridesoa_damper_min
in the months it names, so the operating states judge each interval's OA damper against its own
month's minimum position: heating (OS#1) at it, free cooling (OS#2) beyond it. It needs
oa_damper_min, the position in the other months.run_g36_afddgains the same keyword and
G36Resulta trailingoa_damper_min_by_monthfield; the finding reports the metric only when
it is set, and a caveat names the seasonal minimum. The default isNone, so default outputs
are byte-identical. Param docs anddocs/THRESHOLDS.mdcover it.
CAMBER v0.100.0
0.100: the dual-duct unit's seasonal minimum, equipment causes heading RCx issues, and an
opt-in leak drift detector (#97–#104). g36_afdd FC6 takes a seasonal minimum OA (#97). The
lbnl-ddahu mapping adds its cold-deck SAT setpoint, so G36 FC9, FC11 and FC13 are evaluated
there (#98); the dataset re-ingests once, and the open-fdd cross-check was re-run on the merged
code. reheat_penalty takes a box_type that caps its fan-heat estimate by box (#99). A new
opt-in coil_leak_drift judges coil-valve leaks against the unit's own baseline (#100). Its
results are measured records, not gated. An RCx issue is now headed by a member finding's cause
when that cause names the equipment at fault (#101). The lbnl-ddahu and lbnl-sdahu
templates now run free_cooling_missed, which re-heads two catalog issues. The point-role
suggester reads more public naming conventions and keeps weather-station points outdoors
(#102). This intentionally changes the default suggester output. Setpoint-level grouping for the
sensor-health outlier read was measured and rejected (#103). Config runs resolve a facility's
time zone the way the read API does (#104). The synthetic, fleet, LBNL, BDG2 and BDG2 savings
benchmark gates did not move.
Changed
-
Dataset templates:
lbnl-ddahuandlbnl-sdahurunfree_cooling_missed(#101). Both
templates now run it at the documented 60 °F high limit, reading each unit's own OA damper
command and mixed-air temperature. Hourly,lbnl-ddahuDMPRStuck_OA_0reads warn (14.5 %
of free-cooling hours, cause: damper not delivering), and its fault-free andDMPRStuck_OA_100
runs read ok.lbnl-sdahualso sets the unit's documented 33.8 °F low-limit lockout
(low_limit_f, below), so 3,374 of its 5,515 sub-60 °F hours count as free-cooling weather.
Theredamper_stuck_010anddamper_stuck_025read fault (49.6 %), and
onset_damper_stuck_025anddamper_stuck_075read warn (21.1 % and 20.8 %), all with cause
damper not delivering. The fault-free andcoi_leakage_010runs read ok (1.96 % and 1.93 %).
Without the low limit they read warn (17.5 % and 18.3 %), because 93 % of their missed hours
fall below the lockout, where the sequence holds the damper at its minimum by design. The
template comment records both. The lbnl-sdahu RCx report has 11 issues, up from 8.
free_cooling_missedis not a scored target, socamber datasets scoreand the gated
benchmarks do not move. The other four air-handler templates do not run it, and each comment
says why.nuig-ahu101is 100 % outdoor air with no mixing box. Onlbnl-b59,ornl-frp-ops
andornl-frp-vavthe rooftop units trend no cooling-valve command, so the rule would decline. -
An RCx issue is headed by a member's cause when it names the equipment at fault (#101).
An issue's heading came from its chain's root finding, which can hide a more specific cause on
a member. A cause is equipment-level when it names a component that does not do what it is told:
a damper that does not deliver the outside air it is commanded to (free_cooling_missed,
damper_not_delivering), a reheat valve whose position does not follow its demand, a leaking
valve, a stuck actuator, or a drifting OA damper. The precedence: a root with an equipment-level
cause keeps the heading; otherwise the most upstream member with one heads the issue; otherwise
the root's cause, as before. The action, its title and its links stay the root's, and the page
says in one line which member named the cause. The executive summary's Issue column and the
causekey ofRcxReport.to_dict()issues follow the heading; each issue gainscause_rule,
the rule of the finding that names it. See docs/RCX-REPORT.md, "Which cause heads an issue".
On the catalog run templates (15 datasets, default subsets, 98 issues) two headings change,
both throughfree_cooling_missed, which thelbnl-ddahuandlbnl-sdahutemplates now run
(see Dataset templates below):lbnl-ddahuDMPRStuck_OA_0reads "Outdoor-air damper not
modulating (stuck low)" (was "Outside air below the ventilation minimum"), andlbnl-sdahu
damper_stuck_075reads "Outdoor-air damper not modulating (stuck part open)" (was "Economizer
open above the high limit"). No other heading changes. The RCx golden file is unchanged: its
synthetic site has no member with an equipment-level cause. -
lbnl-ddahumaps its cold-deck supply-air setpoint (#98).CSA_TEMPSPT(the
publisher's Brick model types it as a supply-air temperature setpoint of the cold deck; 55.0 °F
in every row) now maps tosupply_air_temp_sp, next to the cold-deckCSA_TEMP. Both roles are
the cold deck. The hot deck's own 90 °F setpoint is not mapped, and the mapping, template and
cross-check caveats say so. The dataset content hash changes, so stores re-ingest
lbnl-ddahuonce.g36_afddnow evaluates FC9, FC11 and FC13 on the unit.DMPRStuck_OA_0adds FC9 (20.7 %)
and FC11 (12.2 %) to the FC8, FC10 and FC12 it already flagged. FC13 reads 0 % on every run.
The fault-free run's FC9 hits fall on its 4 free-cooling hours, under the 24-hour floor, so
it staysok. No run's severity changes.- The open-fdd cross-check was re-run (
examples/openfdd_crosscheck/results/). Only
lbnl-ddahuverdicts change. Every engine's any-FC result per run is unchanged. The open-fdd
engines now evaluate FC7, FC9, FC11 and FC13 there: FC11 fires onDMPRStuck_OA_0in all four
open-fdd runs, and FC13 in the SQL engine and the pandas engine at its own defaults. In month
windows, the pandas engine at G36 tolerances goes from TPR 0.58 with FPR 5/12 to TPR 0.62 with
FPR 6/12, because FC9 fires on one fault-free month.docs/ECOSYSTEM.mdhas the before/after. - Workbook
air-sat-reset.supply_air_resetnow reads the flat cold-deck setpoint on
DDAHU__fault_free: "not reset (setpoint flat at ~55F)", where it read "no reset (SAT pinned
low at ~55 F regardless of OAT)" from the supply air before. The severity (warn), the
direction (flat) and the 66 % below the G36 target are unchanged. The instructor key, the
page, the exercise config comment and the stand-in follow, and a new check pins
sp_range_f0.0.
-
Unchanged. No gated benchmark key moves: the synthetic, fleet, LBNL, BDG2 and BDG2
savings benchmarks all hold. The LBNL benchmark's DDAHU family scoresoutdoor_air_fraction
only, which does not read the setpoint. -
Point-role suggester: vocabulary from public naming conventions (#102). This changes the
default name-only output. The 0.96 real-name evaluation showed most misses were vocabulary
gaps.FeatureSuggesternow reads:- new abbreviations:
AF,WH,STA,Enable,SS,PM/PMP,CHWP/HWP/CWP,
CT,SAF/SF,BOI,SW/RW/SWT/RWT/LWT/EWT,HWL/CHWL/
CDWL,HValve/CValve,HC/CC/LAT, presence, occupant,PIR, weather /WX,
and dry-bulb, wet-bulb and dew point; - run-together words (
OADMPR,RMCLGSPT) split into known abbreviations, and vowel-dropped
abbreviations (Sply).
Context rules use the same lexical score:
- a water point is not given an air-side role (a chilled-water flow is not
airflow); - a chiller, boiler or tower places a point on its loop;
- an unlocated temperature (
air_temperature) reads as a space temperature, not outdoor air; temp_setpointreads as a zone setpoint, notsupply_air_temp_sp;- a wet-bulb is not a dry-bulb
oat; - pump and tower-fan speeds no longer read as
supply_fan_speed; - a UUID or
PM2.5names nothing.
On a 6,546-case name × unit corpus, the top-1 role changed in 854 cases. The 0.95 golden cases
are unchanged. Module constantsWATER_AIR_PENALTY(0.4) andUNLOCATED_TEMP_PENALTY(0.6)
hold the two new multipliers. Results on the same inputs (examples/suggester_eval):- real names, pooled, name only: 82.5 → 94.3 % top-1 (in-sample: the vocabulary was chosen
from these misses); - held-out catalog names, name only (new
catalog_names.py, out of sample): 72.2 → 83.0 %; - BTS anonymised: unchanged.
- new abbreviations:
-
Weather-station guard on the time-series path (#102). With
use_timeseries=True, the data
ranks only the outdoor roles when a point's name places it outdoors. For a weather station
(weather,WX, meteo, the Synoptic_set_Nsuffix), it ranks onlyoat,outdoor_rhand
outdoor_co2. A wet-bulb needs the name to say so. The data no longer turns an outdoor
temperature into a wet-bulb or an outdoor humidity into a supply-air humidity. On real names,
the data's top-1 losses fell from 5 to 2. -
Not changed: the time-series path stays opt-in.
docs/MAPPING-ASSIST.mdrecommends
turning it on when series are passed. That needs maintainer sign-off.
The synthetic, fleet, LBNL, BDG2 and BDG2 savings benchmark gates are not affected.
Added
free_cooling_missedtakes an economizer low-limit lockout,low_limit_f(provisional).
Many sequences lock the economizer out below a low outdoor-air temperature and hold the OA
damper at its minimum, so mechanical cooling in that weather is not missed free cooling. With
low_limit_fset, hours below it are neither available nor missed. They are reported as
low_limit_excluded_hours(andlow_limit_cooling_hours, those with mechanical cooling
running), with a caveat.camber.freecooling.free_cooling_opportunitytakes the same
parameter through the same weather test and reportshours_low_limit_excluded. The RCx
report's economizer page passes the rule's value to it. The defaultNonekeeps both outputs
byte-identical, with none of the new metrics or fields.lbnl-sdahusets 33.8 °F.lbnl-ddahu
does not: no lockout is documented for that unit, and on its fault-free run the OA damper
modulates above its minimum...
CAMBER v0.99.1
0.99.1 patch: open-fdd-ingested facilities report their time zone (#96). The synthetic, fleet,
LBNL and BDG2 benchmark gates did not move.
Fixed
- The read API did not report the time zone of an open-fdd-ingested facility (#96).
camber interop openfdd ingestrecorded the site zone only in the facility'sopenfdd
provenance. Config runs found it there, but/facilitieslooked a zone up only on the registry
entry or through a catalogdataset_id, so these facilities had notimezoneand the trend
viewer labelled their site wall clock as UTC. The importer now also writes the zone to the
facility'smeta["timezone"]. The read API looks up a facility's zone in this order: the
explicittimezone, the catalog dataset zone, then theopenfddprovenance zone, so facilities
ingested with 0.99.0 report their zone without a re-ingest. An unchanged re-ingest, which is
skipped, also fills in the missing key. Facilities with no recorded zone are unchanged.
CAMBER v0.99.0
0.99: open-fdd interop and G36 heating at minimum OA (#22 items 1–4, #95). CAMBER and
open-fdd now meet at a file and process boundary, with
every result labelled by the engine and version that produced it. A reusable cross-check (#22
item 1) scores CAMBER's g36_afdd and open-fdd's pandas and SQL engines (commit 32a6d44, PyPI
4.4.9) on the same labelled LBNL air-handler data, and explains each difference from public code
and open data. An importer (#22 item 2) reads an open-fdd building package or historian layout
into a CAMBER store, so drift, M&V and sensor-trust checks can run on data collected at the
open-fdd edge. Columns map through a versioned role crosswalk (#22 item 4), and the results go
back as a draft findings-exchange JSON (#22 item 3). docs/ECOSYSTEM.md opens with an entry
point for open-fdd users. In g36_afdd, heating (operating state 1) now needs the OA damper at
its minimum, the companion of #94's free-cooling test (#95).
Added
-
G36 cross-check against current open-fdd (#22 item 1).
examples/openfdd_crosscheck
runs CAMBER'sg36_afddand open-fdd's two engines on the same labelled frames, pinned to
open-fdd commit32a6d44(PyPI 4.4.9). It works through files and processes only: the pandas
engine runs in its own venv as a subprocess, and the SQL engine runs asfdd_cliin a container
with no network and read-only mounts.The harness ships:
- a versioned role mapping (
role_map.json); - tolerance profiles (
profiles.json): open-fdd's defaults, and the G36 Table 5.16.14.7
tolerances CAMBER uses; - one normaliser per engine, recording each verdict's denominator;
- per-engine, per-FC scoring with Wilson intervals, where "not evaluated" is kept apart from
"not detected"; - synthetic probes that isolate single engine behaviours.
Results for all three engines on
lbnl-sdahu(full subset) andlbnl-ddahureplace the "not
yet re-compared" section ofdocs/ECOSYSTEM.md, which now also says where the 0.1.5
comparison no longer applies. CAMBER runs with each dataset's run-template parameters, so FC6
is evaluated onlbnl-sdahu(#94). Synthetic probes run throughfdd_cliconfirm which G36
tolerances the SQL tuning file cannot set (FC7, FC9, FC11, FC13–FC15). Offline tests run
without Docker or open-fdd:tests/test_openfdd_crosscheck.py. - a versioned role mapping (
-
CAMBER reads open-fdd data (#22 item 2, provisional). The new
camber.interop.openfddmodule reads an open-fdd building package (openfdd_package_v1, a
folder or a.zip) or open-fdd's historian Parquet layout. CAMBER's drift, M&V and
sensor-trust checks can then run on data collected at the open-fdd edge.- Boundary. It works through files and processes only. No open-fdd code is imported or
copied, and nothing is written back. - Required inputs.
read_package/read_historiantake the site time zone and the unit
system as required arguments, because a package carries neither and CAMBER will not guess. - Mapping. Columns map through the package's own map, which is authoritative. Every other
column is counted and reported with its reason: no name, unknown name, deliberately
unmapped, wrong equipment type, unusable unit or duplicate role. - Units and time. It handles UTC and local stamps, SI and IP units, declared units, and
0–1 vs 0–100 percent. It reads equipment classes from theequipTypestamps and the
inventory, never from ids.
- Boundary. It works through files and processes only. No open-fdd code is imported or
-
ingest_packageandcamber interop openfdd ingest(provisional). They write one facility
per building into a ParquetStore or a portfolio workspace.- Lifecycle. In a workspace the facility is registered through the lifecycle
(provisioning, oractivewith--activate), the write takes the workspace lock and is
audited (interop.openfdd.ingest), and a facility in any other state is refused. - Provenance. Recorded under
openfddon the registry entry: the source and schema
version, the sha256 of every file read, the crosswalk version and its pinned docs commit, the
zone, units and coverage. - Re-ingest. It is idempotent, and data is staged and swapped in.
--config-outwrites a
starting run config: rules the mapped roles can run, the package weather as the shared
outdoor temperature, and a daily M&V baseline per metered class.camber interop openfdd inspectdoes the same read without writing.
- Lifecycle. In a workspace the facility is registered through the lifecycle
-
A versioned open-fdd → CAMBER role crosswalk (#22 item 4, provisional). It ships as
camber/interop/openfdd/crosswalk.json, version 1, written from open-fdd's documented
Haystack-name and SQL-role vocabulary at a pinned commit. 50 of its 71 rows map; the other 21
are deliberate non-mappings, each with its reason.camber interop openfdd crosswalk [--json]
prints it. -
A findings-exchange JSON, draft 0.1 (#22 item 3, provisional).
findings_document,
run_findingsandcamber interop openfdd findings CONFIG --out FILEemit an engine-labelled
document that a separate process (an open-fdd agent or pipeline) reads back.- Every record carries
{engine: {name, version}}. declinedandnot_evaluatedare distinct statuses.magnitudenames its denominator.- Fields CAMBER does not yet produce are
null. - See docs/INTEROP-OPENFDD.md.
- Every record carries
Changed
- A store facility ingested from open-fdd knows its zone. A store-source config's
source.timezonenow defaults to the zone the facility was ingested in, as it already did for
catalog datasets. This is additive. A facility without anopenfddprovenance block is
unchanged. - G36 heating needs the OA damper at its minimum (#95).
g36_afddnow reads a fan-on hour
with the heating coil alone active as operating state 1 only when the OA damper is at its
minimum position, per the G36 §5.16.14 operating-state definitions. It uses the same learned or
configuredoa_damper_minandoa_damper_tolas the free-cooling test from #94. A heating
hour with the damper open beyond the minimum is state 5, where only FC1–FC4 apply. A missing
damper reading there leaves the hour unclassified. Without an OA damper point the valves-only
reading stays, and the caveat now names both states.- New outputs (provisional). The
G36Result.n_heating_above_min_oafield and the finding
metricheating_above_min_oa_hours. - Before/after. No change on
lbnl-sdahu(no heating coil),nuig-ahu101(no OA damper
point) or the synthetic G36 scenarios, and no gated benchmark moved. On the dual-duct
lbnl-ddahuthe hot deck heats while the OA damper command is well above the learned 28 %
minimum (median 55 % on the fault-free run). Between 885 and 1,520 heating hours per run move
from state 1 to state 5, so FC5 is no longer applicable on the two stuck-damper runs. No
run's flagged FCs or severity changed.
- New outputs (provisional). The
CAMBER v0.98.0
0.98: hardening from the workbook (#84-#93). Working through the 0.97 workbook on real data
showed where CAMBER's answers were wrong, silent or hard to tune. On the air side (#84) the damper
census and supply_air_control gate on trended occupancy, and leaking_valve can credit a unit's measured
fan heat. For terminal units and ventilation (#85) a new rule, actuator_stuck, finds a box's
damper or valve stuck against the zone's demand, reheat_penalty reads a valve's measured
position, a heating shortfall is graded on how often it happens as well as how deep it goes, and
cohort deviation gains opt-in grouping and normalisation. The central plant (#86) checks the
sign of the chilled-water reset, learns a pump's VFD floor, runs the boiler rules without a run
status, and accepts a declared drift reference. Sensor health (#87) reads a fan-less unit's
outliers per operating mode and names clipped readings, stray rows and scheduled status points.
The RCx report (#88) names each issue's cause, lists the checks that did not run, says what data
an M&V refusal needs, and ends the issues with a generated "Verify on site" checklist. More
rules link to the PNNL re-tuning material and ornl-frp-vav declares a scored detector (#89).
Every tunable threshold is documented with its basis and a way to calibrate it, and run configs
read from YAML as well as JSON (#90). Three follow-ups close small inconsistencies: one 60 °F
free-cooling high limit (#91), a site elevation for derived wet-bulbs (site_elevation_ft, #92),
and named fan-off hours with an opt-in duration fault in dcv_verification (#93).
Four maintainer decisions shape the benchmarks. S1: the LBNL benchmark reads the
leaking_valve parameters from the lbnl-sdahu template, which moves the SDAHU and pooled
leaking-valve keys (the fan heat was calibrated on a run that is also a scored negative;
docs/VALIDATION.md states the circularity). S2: faultlab's healthy chilled-water reset now
runs the right way, with no synthetic key moving. S3: actuator_stuck is a scored synthetic
scenario (46 scored rules, new actuator_stuck TPR/FPR keys). S4: drift accepts a declared,
never-stored reference (another unit or a known-good period). For sensor health, decision (a)
applies: only units with no fan signal are read per inferred operating mode, and fan-gated units
are unchanged. Every other benchmark is unchanged.
Added
roles_any_of(#86 item 4a, provisional). A rule may declareroles_any_of, a tuple of
role groups of which each needs at least one role present (a run status or a gas input). The
runners load every role of a group, skip the equipment when a group has none, and include the
present group roles in the sensor-health gate.camber.model.entities.missing_inputsis the
one test shared by the runners andrunnable_rules, andRunnable.missing_any_ofnames the
unmet groups. The mechanism alone leaves default outputs unchanged; the first rules to
declare groups are the boiler firing rules (see Changed, 098-plant-boiler).- Rules that did not run are listed (#88 item 3, #86 item 4b).
Registry.run,run_periods
andrun_fleettake an optionalskipped=list and append aRuleSkip(rule, equipment,
class, missing inputs, reasonmissing_inputs/no_data/no_verdict) for each equipment
a rule applies to but produced nothing on. A skip is recorded only when the rule applies: its
declared classes match the equipment's, or at least one of its inputs is present (a chiller
rule on an air handler is not applicable, not skipped). The default (None) records nothing,
so the benchmarks and faultlab are untouched.run_configcollects them in
RunResult.rules_skipped, adding a rule-level record when a configured rule found none of its
inputs on any equipment; they stay out offindings(andfindings.json, sodatasets score
decline counts do not move).- RCx report. Appendix A gains a "Checks not evaluated (missing inputs)" table (rule,
missing inputs, equipment: one row per rule and missing set, six units named, then "and N
more"), and the cover a "Checks not evaluated" row, both only when there is something to
list.no_verdictrecords are kept for later use but not listed.
- RCx report. Appendix A gains a "Checks not evaluated (missing inputs)" table (rule,
- An M&V refusal says what data is needed (#88 item 4). New
camber.mandv.sufficiency:
baseline_need(interval, n_have, min_n=...)returns the required, available and missing
amount, its unit, the calendar days it takes to collect, and a one-line text ("1,440 baseline
hours needed, 168 available: 1,272 more hours (about 53 days of data)").
caltrack_savingsandcaltrack_savings_hourlyraiseInsufficientBaseline, aValueError
whose message is byte-identical to before, with the gap on.need: too few days or hours,
missing hour-of-week bins, or thinly observed bins. The config's daily M&V decline for too few
days addsmetrics["data_needed"]to itsmv_baselineandmv_savingsfindings
(declined_reasonandsummaryunchanged), and the RCx "M&V and drift" section prints a
"Data needed" paragraph per meter. Hourly M&V has no config path, so its need is reported
through the exception only. Workbookcapstonepins the one-week refusal's need at 1,272 hours. - Tunable thresholds, documented (#90, provisional).
camber/rules/param_docs.pydocuments
every numeric, flag and enumerated constructor parameter of every built-in rule, including the
extra instances. Each entry gives the unit, a sensible range, the basis of the default
(standard: ...only where the code cites a section,public source: ...,CAMBER judgmentor
calibrated on ...) and how to calibrate it from your own data. Defaults are read from the
constructors, never copied.FIXEDlists thresholds that are still hard-coded, and a test fails
on any undocumented or stale parameter. camber rules params [RULE] [--json|--yaml]. Prints each parameter with its default,
basis and calibration hint, plus a ready-to-paste config snippet. The YAML snippet carries each
note as a comment beside its value.- Every built-in rule is tunable from a config.
make_ruleand a config'sparamsnow also
cover the extra instances (cohort_airflow,cohort_space_temp,sat_/static_reset_effectiveness,
sat_/static_rogue_zone_census,sat_/static_cohort_starvation); their identity arguments
(the cohort role, the reset kind) stay fixed.rule_factories()lists them. - YAML run configs (the
[yaml]extra,pyyaml>=6).camber run,report,explain,
ask,fleet,drift,mv,python -m camber.config, the portfolio migration and the edge
config read.yaml/.yml. Without PyYAML the command stops with an error that names the
extra. The loader types values as JSON does (dates,07:00,no/onand012stay
strings), so equivalent JSON and YAML configs give identical results, as a test asserts. JSON
stays the dependency-free default. camber datasets config --format yaml(and--out *.yaml) writes a template as YAML, with
its_commentnotes turned into comments;config_template(..., format=). Writing YAML needs
no extra.- Calibration provenance in findings. A rule entry may carry a
basismap
({"fan_heat_f": "calibrated on ..."}), which each finding of the rule records as
metrics["param_basis"]={param: {"value", "basis"}}. A misspelt parameter name is an
error. Existing configs are unaffected. - Docs.
docs/THRESHOLDS.mdis generated from the registry byscripts/thresholds_doc.py
(--checkis a test). The new guidedocs/TUNING.md, "Tuning thresholds with your own data",
covers calibrating on a known-good period, avoiding circular calibration, recording provenance
in config comments and re-checking against labelled data. Its worked examples use
lbnl-sdahuandnist-heatpump-fdd. Also updated: CLI.md, API-STABILITY (provisional), the
mkdocs nav, the README, and a "going further" pointer in the workbook index. - A declared, never-stored drift reference (#86 items 1 and 5, decision S4; provisional). A
drift.families[]entry may carry"reference": {"equip": ID}(score every other unit of the
class against a named healthy one, optionally with its own"period") or
"reference": {"period": [start, end]}(a known-good window of the same unit). The reference is
fitted in a scratch in-memory store on every run and never saved, so a run still cannot mint
its own baseline. Findings carrymetrics["baseline_source"](reference:<equip>or
period:<start>..<end>) and a caveat, and their summaries read "vs the reference ..." instead
of "vs frozen baseline". The reference unit declines asis_reference; a reference that cannot
serve a detector declines every target (reference_missing_inputs,reference_untrusted,
empty_reference). A section whose families all declare a reference needs nostoreand no
windows.DriftFamilyResult.referenceis new (also inas_dict); the drift report and
camber drift runname the reference. Nothing changes for configs without one. - The LBNL plant templates score the plant drift detectors.
lbnl-chiller.jsonand
lbnl-boiler.json(and theplant-cooling-tower/plant-boilerexercise configs) declare
PLANT__fault_freeas the reference for thetowerandboilerfamilies, and the catalog
declarescooling_tower_fan_effort_drift: [tower_fouling]and
boiler_efficiency_drift: [boiler_fouling]as scored targets. Throughcamber runthe verdicts
matchexamples/lbnl_fdd/plant_detectors.pyrun by run: tower fan effort 2/3 (fouling 065 and
080 at +17.8 / +10.9 fan %-points; 095 at +3.3 is under the floor), boiler efficiency 3/3
(+53.7 / +24.9 / +5.1 %; 095 clears the 5 % warn floor by 0.1 point), no false alarm on the
20 / 13 other runs. The scorer counts the reference run as a correct negative (0/21...
CAMBER v0.97.0
0.97: the PNNL re-tuning workbook (#79, #80, #81, #82, #83). A hands-on course that follows
PNNL's free Building Re-tuning training, run on the open dataset catalog in camber lab. Default
outputs are unchanged, and no gated benchmark moves.
Added
- The workbook framework (#79, provisional).
docs/workbook/holds a curriculum map from the
PNNL chapters and guides to exercises and datasets (index.md), an exercise page template
(_template.md: goal, Learn more, datasets and licence, setup in the lab and on the CLI,
steps, questions, what CAMBER shows, caveats, going further) and an instructor page with a
section per exercise (answer key, discussion points, common mistakes). The docs site has a new
Re-tuning workbook section, and the README a "Learn re-tuning with CAMBER" section.- Answer keys pinned by tests. Each exercise declares its datasets, configs, CLI commands and
expected answers (a finding present or absent on given equipment, a metric within a
tolerance, a label score, or a free-form check) intests/workbook/exercises/<id>.py, with a
synthetic stand-in shaped like each dataset. The offline tests run the exercise's configs on
the stand-in every time;pytest -m networkruns them on the real catalog data. A stale
answer fails and names the exercise, and the instructor page must quote every pinned figure. - Reference links from the registry. Workbook pages link PNNL only through reference-style
Markdown links labelled withcamber.referencesids;scripts/workbook_refs.pyprints the
definitions and--check(also a test) rejects an unknown id, a URL that differs from the
registry's, or a PNNL link made any other way. No mkdocs plugin is needed. - Catalog
exercisefield.suggested_analyses.exerciseis validated: a docs-relative
workbook page (workbook/<id>.md, optionally#<anchor>) or an https URL.camber datasets infoprints its published URL.camber labserves a relative link offline from the local
docs tree (/lab/docs/workbook/<id>.md, a script-free reading copy;--docs DIR, by default
the checkout'sdocs/), and links the published docs site otherwise. - Exercise config templates.
camber/datasets/configs/exercises/<id>.json(package data)
holds a tuned config per exercise where the dataset's default doesn't fit;
camber datasets config <dataset> --exercise <id>(and
datasets.config_template(..., exercise=)) writes it. - Worked example:
air-economizeronlbnl-sdahu: a damper stuck open is found by
outdoor_air_fractionandeconomizer_high_limit, one stuck near minimum only as missed free
cooling (free_cooling_missed), and the label score is read against the published labels.
- Answer keys pinned by tests. Each exercise declares its datasets, configs, CLI commands and
- Workbook: the air side (#80). Five exercises following PNNL re-tuning chapters 5 and 6 and
the AHU guides, each with an instructor key pinned by the answer-key tests:air-sat-reset(lbnl-sdahu,lbnl-ddahu,irish-ahu): is the supply-air temperature
reset, which way should it move with the outdoor air, how far below the Guideline 36 target
does it sit, and does the unit hold its setpoint.air-static-pressure(lbnl-sdahu,ornl-frp-vav): a flat duct static setpoint, and a
damper census over one real test day's ten boxes.air-heat-cool(lbnl-sdahu,lbnl-ddahu,irish-ahu): the published 10 % cooling-valve
leak (whichleaking_valve's 3 °F margin misses; the exercise teaches the comparison with
the fault-free run), both valves open on a dual-duct unit by design, and a real unit's coils.air-economizergains anirish-ahupart: the economizer rules on a real, unlabelled unit
with a documented 100 % outdoor-air period, split by period.air-scheduling(ornl-frp-ops): a 24/7 baseline against a night setback held by fan
cycling, compressor short-cycling, and the supply fan's night energy.- Exercise configs
air-sat-reset,air-sat-reset--lbnl-ddahu,air-sat-reset--irish-ahu,
air-static-pressure,air-static-pressure--ornl-frp-vavandair-heat-cool--lbnl-ddahu;
the catalog linkslbnl-ddahu,irish-ahuandornl-frp-opsto their exercises.
- Workbook: terminal units and ventilation (#81). Five exercises for PNNL chapter 7 and the
zone heating and cooling and minimum outdoor-air guides, each with an instructor key pinned by
offline stand-in tests and-m networktests on the real data:zone-reheat-overcooling(lbnl-fpu): a high minimum airflow that overcools and reheats,
against a stuck damper that floods the box (airflow_tracking,reheat_penalty,
overcooling_min_flow); the south-zone box is the one under test.zone-bad-box(ornl-frp-vav): the one box with a flat damper among ten, the SAT rogue-zone
census, the rooftop unit's airflow and static following the stuck box, and why a comfort rule
scores poorly as a stuck-damper detector (TPR 33 %, FPR 100 % on the published labels).zone-reheat-saturated(lbnl-fpu,ornl-frp-vav): a zone below its heating setpoint with
the reheat demand saturated (reheat_capacity_shortfall), a heating shortfall rather than
overcooling; and a real building whose electric reheat is not trended, where no reheat verdict
is possible.zone-dcv(finnish-dcv,b4b-windesheim,sdu-ou44): a documented DCV law, a verdict
that depends on which CO₂ sensor you trust, and the honest "not judged".zone-min-oa(lbnl-b59): the system-level 62.1 Ventilation Rate Procedure with stated
assumptions (public sources only), measured OA several times the requirement, and CO₂ and
DCV as second opinions.- Four exercise config templates (
zone-reheat-overcooling,zone-reheat-saturated,
zone-reheat-saturated--ornl-frp-vav,zone-min-oa); the catalog entrieslbnl-fpu,
ornl-frp-vav,finnish-dcv,b4b-windesheim,sdu-ou44andlbnl-b59link their
exercise.
- Workbook: the central plant (#82). Five exercises on the open LBNL plant datasets, each with
a page, an instructor key, a tuned config and a synthetic stand-in:plant-chiller-efficiency(lbnl-chiller): a generic kW/ton ceiling flags the healthy plant;
the ceiling calibrated from the fault-free run finds the severe chiller fouling and the tower
bypass, and the label score shows its false alarms.plant-cooling-tower(lbnl-chiller): a controlled tower hides fouling in fan effort rather
than approach; condenser-water reset;condenser_bypass_leakon the stuck bypass.plant-chw-reset-pumping(lbnl-chiller): chilled-water reset, a low delta-T that is a
property of the constant-flow primary loop, a flat DP setpoint, and a VFD floor above the pump
rule's fixed near-minimum band.plant-boiler(lbnl-boiler): why the lockout, short-cycle and delta-T rules stay silent on
a plant whose boiler status is an enable, a pump that runs all year, and boiler fouling that no
point-in-time rule sees.plant-sensor-vs-equipment(lbnl-chiller): where each sensor bias shows up (kW/ton, the
secondary pump, a sensor-offset attribution) and why the score counts it as a negative.- The
lbnl-chillerandlbnl-boilercatalog entries link their exercise.
lbnl-chillermapping (#82). The secondary loop's lead pump speed and its DP and DP
setpoint now map tochw_pump_speed,chw_diff_pressandchw_diff_press_sp, so
chw_pump_dp_resetcan run on the plant. Re-ingest (--force) to pick them up. The dataset
template's rules do not read them, so its findings are unchanged.- Workbook: data quality, energy charting, M&V and the capstone (#83, provisional). Four
exercises, each with its page, instructor key and answer-key test (stand-in offline, real data
under-m network):data-trend-quality(nuig-ahu101,lbnl-b59,irish-ahu): the core points each unit
trends, then the sensor-health layer on real faults -- a copied return-air point, mixed-air
sensors that fail the flow balance, room CO2 clipped at full scale, a meter held over two
weekends, a gap-filled CO2 stretch -- and an "untrusted" supply air that is a well-controlled
sensor.data-energy-charting(bdg2,valladolid-uva): weekday and weekend profiles,
out-of-hours base load, load factor and the daily change-point baselines as the
weather-dependence chart.mv-baselines(cofactor-drammen,valladolid-uva,bdg2): G14 acceptance and SEP
50001 §6.4.1 validity, forecast against backcast, SEP chaining, a chain across the spring 2020
school closure with a non-routine adjustment, and bill-only M&V on synthetic bills cut from
an open meter (days-weighted fit, bases chosen from the bills, calendarized). Four exercise
configs:mv-baselines,mv-baselines--forecast,--backcastand--covid.capstone(lbnl-sdahu,ornl-frp-ops): the RCx report on the fault-onset splice, a
walk-down checklist built from its recommended actions, conditional issues and default
assumptions, a re-tuning plan, verification by drift (a first-half baseline scored on the
second half), and a before-and-after scheduling test where hourly M&V is refused for lack of
baseline data. Exercise configcapstone.- Catalog
exerciselinks:nuig-ahu101todata-trend-quality,bdg2to
data-energy-charting,valladolid-uvaandcofactor-drammentomv-baselines.
Fixed
- Workbook network tests (test harness only).
pytest -m networknow ingests each run's
subset: the plant exercises that needfullget their own sibling store, so the default and
full ingests oflbnl-chillerno longer replace each other. A dataset already in the store is
ingested again when its subset or mapping has changed (for example the newlbnl-chillerpump
roles). A manual download (`lbnl-...