Inspiration
Every winter a named storm sweeps across Britain, and every winter the story is the same: lines down, homes dark for days, and crews working through a queue nobody outside the network operator can see. In the 60 hours of Storm Darragh (December 2024), one distribution operator alone logged 2,101 separate power cuts. Storm Éowyn followed seven weeks later.
We wanted to answer two questions that usually live behind closed doors:
- Where does a storm actually hurt the grid, and how does local damage cascade into regional blackouts?
- If you had money to reinforce a handful of lines before the storm, which ones would buy the most resilience per pound?
Nobody hands you a map of the GB transmission network, a history of every power cut, and a weather archive in one place. So we built all three.
What it does
GridGuard is an interactive storm simulator and decision tool for the Great Britain transmission grid.
- A real map of the grid. 303 substations and 367 transmission branches at 132–400 kV, built from OpenStreetMap routes with sampled terrain elevation. It is zoomable, pannable and scrubbable hour by hour.
- Real storms, replayed. Eight named storms from 2024–2026 (Bert, Darragh, Éowyn, Amy, Claudia, Bram, Goretti, Austen), driven by hourly Met Office UKV gusts. You can also run synthetic windstorms: a single peak, persistent winds or two peaks.
- Physics-based cascades. Wind topples pylons, the DC power flow recomputes, and overloaded lines trip in waves. Crews then repair damage over several days when the wind allows it.
- Real outages overlaid. Over 127,000 scraped unplanned power cuts appear as they happened, so you can see whether the simulated damage lines up with where the lights actually went out.
- An XGBoost outage-risk layer. A model trained on 11.5 years of fault reports and ERA5 weather shows each line corridor's hourly risk of a weather-caused fault.
- Mitigation planning. You choose suggested or custom line reinforcements and repair priorities. GridGuard re-runs the same storm with the same random seed and reports paired loss reductions, recovery times, benefit/cost ratios and break-even events.
How we built it
1. Graphing the entire national grid
There is no public, routable graph of the GB transmission network. We built one from the open uk-grid-atlas (OpenStreetMap power lines):
- We clustered OSM line endpoints within 2 km into substations and merged parallel ways into multi-circuit branches.
- We contracted series chains and kept the largest connected component, which covers about 88% of the atlas's route length.
- We sampled every route every ~2 km for terrain elevation, and estimated pylon count from route length and typical span.
2. A serious collection of power-cut data
Most operators publish only live outages that disappear once fixed, so history has to be scraped and rebuilt. We pulled together:
| Source | What we did | Records |
|---|---|---|
| National Grid (NGED) live feed | Replayed 44,208 git-scraper snapshots (one every ~20 min) and inferred start and end times from when each incident appeared and disappeared | 82,969 |
| UK Power Networks | Git-scraper history, planned work filtered out | 37,184 |
| Outage-Watch (SSEN, SPEN, NPg, ENWL, WPD, UKPN) | Hourly multi-operator scrape | 7,674 |
| SSEN NaFIRS fault register | 2015–2026 faults with cause, equipment and voltage | 69,237 |
| GDELT news | UK power-cut headlines from 2017 onward, geocoded against GeoNames and matched to nearby outages | 131 |
Every feed reports location differently. Many give only a list of affected postcodes, so we ported the NearMyPostcode decoder to Python. It reproduces the official JavaScript exactly on 2,200 test lookups, and we use it to turn postcode lists into coordinates. Planned maintenance was stripped using each operator's own flags (PLANNED_FLAG, FaultType=PSI, PPC… refs and so on) and message text. All times were normalised to UTC.
The result is 127,827 unplanned outages across seven operators in one schema, plus a refined register of 197,955 fault reports for model training.
3. Open-source weather
- Met Office UKV (2 km) hourly gusts via the Open-Meteo API for the eight storm replays.
- ERA5 reanalysis (2015–2026) for model training, fetched on demand along every route.
- We audited both against independent NOAA station gusts. ERA5 reached a correlation of $r = 0.88$ during Storm Éowyn.
4. The physics: wind → pylons → lines → cascade
Local gust is scaled up on high ground using the UK National Annex to BS EN 1991-1-4 altitude factor, where $A$ is site altitude and $z$ is tower height:
$$c_{\text{alt}} = 1 + 0.001\,A\left(\frac{10}{z}\right)^{0.2}$$
Each pylon has a lognormal fragility curve against its local gust $g$:
$$P_{\text{pylon}}(g) = \Phi!\left(\frac{\ln(g / g_{50})}{\beta}\right)$$
Hazards add up along a route. A line whose sample point $k$ carries $n_k$ pylons fails within the hour with probability
$$P_{\text{line}} = 1 - \exp!\Big(-\sum_k n_k\,\big[-\ln(1 - P_{\text{pylon}}(g_k))\big]\Big)$$
When a line falls, we solve the DC power flow $\mathbf{B}\,\boldsymbol{\theta} = \mathbf{P}$ with sparse matrices. Any line with $|f_{ij}| > \bar{f}_{ij}$ trips, and the process repeats until the network settles. Islands without enough generation shed load. Over a multi-day storm, a limited number of crews repair lines only while the local gust is below $60\ \text{km/h}$, with lognormal repair times (median 8 h). Unserved energy is accumulated as
$$E_{\text{unserved}} = \sum_t \sum_{b} \text{shed}_{b,t}\,\Delta t$$
5. Choosing reinforcements fairly
Reinforcing a line multiplies its hazard rate by $0.25$ and raises its rating by 30%. A greedy selector picks lines on a separate 24-storm training bank. Every plan is then scored on held-out storms that share weather and random draws, so the comparison is paired and you can trust the differences:
$$\Delta = \frac{1}{N}\sum_{s=1}^{N}\Big(E^{(s)}{\text{no action}} - E^{(s)}{\text{plan}}\Big)$$
These differences feed editable cost assumptions, which produce benefit/cost ratios and break-even storm counts.
6. XGBoost corridor-risk model
For every branch $i$ and hour $t$, the model predicts the probability that a new weather-caused overhead-line fault is reported inside the line's 5 km corridor during $[t, t+1\,\text{h})$.
- Data: 7,388,184 line-hours with 6,985 positive hours, a base rate of 0.095%. There is no oversampling.
- Features: route max and 95th-percentile gust, 3-hour gust history, rain, temperature, length, voltage and elevation. Crosswind is the gust component across each 2 km segment, where $\phi$ is the wind direction and $\alpha$ the segment bearing: $$g_\perp = \max_{\text{segments}} g \cdot \lvert\sin(\phi - \alpha)\rvert$$
- Constraints: wind features are monotone increasing, because stronger wind should never lower risk.
- Validation: a strictly chronological split (fit, tune, calibrate on the first 75% of hours; test on the last 25%), with whole storms kept on one side and boundary rows purged.
- Calibration: Platt scaling on the model margin $m$, so $\hat{p} = \sigma(a\,m + b)$.
On the untouched 2023–2026 test period:
| Model | Log loss | Average precision |
|---|---|---|
| XGBoost (calibrated) | 0.00650 | 0.076 |
| Wind-only logistic | 0.00702 | 0.043 |
| Constant rate | 0.00853 | 0.001 |
That is 24% lower log loss than the constant baseline and an average precision 70× the event rate. On lines outside the training area, the model's ranking still separates corridors that did and didn't see faults during Darragh with AUC 0.75.
Stack
Python with NumPy, SciPy (sparse solves), pandas and XGBoost, served by Flask. The frontend is React and Vite with a hand-rolled SVG map: every frame is drawn from the simulation clock, with no animation library. Data comes from OpenStreetMap, Open-Meteo (ERA5 and UKV), NOAA ISD, GDELT, GeoNames and the ONS Postcode Directory.
Challenges we ran into
- No labels for the thing we most wanted to predict. No public dataset says "transmission line X failed at time T". The refined register has zero verified asset failures, and only 24 weather-caused 132 kV overhead faults in 11.5 years. We retargeted the model to an honest area-level question instead: fault risk in a line's corridor.
- Models can be right and still dangerous. When we fed the corridor-risk probabilities into the simulator as line-failure probabilities, Darragh failed 165 of 367 lines with 0% recovery. The engineering model failed none, which matches reality. Distribution faults are not transmission failures, so the ML output became a separate map layer, and the simulator actively rejects it as line-failure input.
- Scraped feeds lie by omission. Live-only feeds have gaps, so a missing incident doesn't prove there was no outage. We inferred start and end bounds from snapshot timing (about ±20 min), split incidents that vanished for more than 3 hours into new episodes, and only trained where the register is continuous.
- Every operator speaks a different dialect. Postcodes in one feed, points in another, local time here and UTC there, and five different ways of flagging planned work.
- Rate limits. GDELT throttles hard and Open-Meteo has daily quotas, so every fetcher had to be resumable and cache-aware.
- Storms under-predicted. The model sees 545 expected events against 950 observed in storm hours. Leaf-on autumn storms like Amy hit trees in full leaf, and our features had no notion of season.
- Merging four parallel branches (map, multi-day simulation, data refinement and ML) into one coherent app in a weekend.
Accomplishments that we're proud of
- A routable graph of the whole GB transmission grid built from open data, with terrain-aware fragility.
- 127,827 unplanned outages reconstructed from tens of thousands of scraper snapshots across seven operators, plus a Python postcode decoder that matches the official JavaScript on all 2,200 test lookups.
- An XGBoost model that beats wind-only and constant baselines on three years of untouched future data.
- Honesty built into the product. Every plan comparison is paired and held out, assumptions are labelled in the UI, and the app refuses to misuse its own ML output.
- Full replays of eight real storms, with real outages appearing on the map hour by hour.
What we learned
- Data engineering is most of the work. Scraping, deduplicating, geocoding and time-aligning outages took longer than the simulator and the model combined.
- Choose the target the data can support. Asking a well-posed area-level question gave a model we can trust. Forcing a transmission-failure label would have given one we couldn't.
- Validate like the future is unknown. Chronological splits, purged boundaries and storms kept whole separate a real result from leakage.
- Paired comparisons matter. Comparing plans on identical storms and seeds turns noisy Monte Carlo output into decisions you can defend.
- Small physical priors go a long way. Monotone wind constraints and the altitude factor make both the model and the simulator behave sensibly at the extremes.
View the code on GitHub!
Go to the "hybrid-merge" branch https://github.com/TimothyKa100/OxHacks-Weather-Power-Grid-Simulation
Built With
- era5
- javascript
- numpy
- openstreetmap
- python
- react
- vite
- xgboost
Log in or sign up for Devpost to join the conversation.