PYTHON · ML · CLOUD · Commercial property portfolio, 62 buildings
GridWatchAnomaly Detection on Building Energy Telemetry
Every one of 1,480 half-hourly meters across a 62-building commercial portfolio was recording, and nothing was reading them. A fault surfaced when a quarterly bill looked high — typically 60 to 90 days after a chiller had started running through the night. I built unsupervised anomaly detection on the telemetry that raises a ranked, costed alert to the facilities team within hours.
- €318k annualised energy waste identified across the portfolio
- 4.2 h median detection time, from 60+ days
- 1,480 half-hourly meters monitored continuously
01 — Context
The problem.
Half-hourly data existed for every meter and was effectively unread. With 1,480 series there was no realistic way for a facilities team to eyeball them, and the few thresholds someone had configured were static — so they fired constantly in January and never in June. Most alerts were ignored, which is the normal end state for an alert nobody trusts.
The failures being missed were mundane and expensive: schedules left on after a bank holiday, a stuck valve, simultaneous heating and cooling, a chiller cycling all night in an empty building.
02 — Method
The approach.
The build, in the order it happened.
-
A baseline per meter, not per portfolio
Each meter gets its own expected profile from a seasonal-trend decomposition conditioned on day type, occupancy schedule and heating and cooling degree days. Anomaly means departure from that meter’s own expectation, which is the only definition that survives a portfolio this varied.
-
Two detectors, deliberately different
A residual-based statistical detector catches sustained level shifts; an isolation forest over shape features catches profile changes that leave the daily total unchanged — the overnight-running case that a total-consumption check misses entirely.
-
Ranked by euros, not by z-score
Every alert carries an estimated cost per day using the site’s actual tariff and standing charges. The facilities team works a list ordered by money, which is what made them work the list at all.
-
Closed the loop on outcomes
Each alert is closed with a cause code by the engineer who attended. Those codes tune the detectors and, after ten weeks, let false positives be suppressed by pattern rather than by raising a global threshold.
03 — Outcome
What changed.
- Median detection time fell from over 60 days to 4.2 hours from the onset of an anomaly.
- €318k of annualised waste identified in the first two quarters, of which €241k was remediated within the period.
- Alert precision improved from 21% at launch to 68% by week twelve as cause codes fed back into the detectors.
- The single largest find was a chiller running unoccupied overnight in one building for an estimated 11 weeks, at roughly €640 a week.
04 — The numbers
Median time to detect an energy anomaly.
Tracked as hours, from Baseline through Wk 14 — low 4.2, high 1512.
05 — Screens
What it looks like in use.
One meter against its own baseline. The overnight shoulder is the chiller — invisible in the daily total, obvious in the profile.
The alert queue, ordered by euros per day. Cause codes from closed alerts are what lifted precision from 21% to 68%.
06 — Stack & role
Built with.
- Python (Pandas, scikit-learn, statsmodels)
- BigQuery
- Cloud Functions
- SQL
- Looker Studio
- Role
- Consulting data scientist — detection design, deployment, FM handover
- Duration
- 13 weeks
- Client
- Commercial property portfolio, 62 buildings
- Period
- 2026
07 — Questions
The questions I get asked about this one.
There were no labelled faults to train on — that was the whole problem. Unsupervised detection plus engineer-supplied cause codes bootstraps the labels, and a supervised layer becomes viable once enough have accumulated.
Heating and cooling degree days are covariates in each meter’s baseline, so a cold snap raises the expectation instead of raising 1,480 alerts.
Ranking by cost and closing every alert with a cause code. An alert with a euro figure attached gets attended; a z-score does not.