Skip to content

Latest commit

 

History

19 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Heathrow's Breaking Point

A capacity stress-testing engine that discovers which airport constraint fails first

This project models Heathrow as a chain of nine connected operational constraints (not a single capacity number), forecasts demand at 15-minute resolution, converts demand into wait times and breach risk, and answers a specific operational question:

If another 2,000 passengers or 15 flights enter tomorrow's schedule, where does the operation break first — and what is the least disruptive response?

Dashboard overview

All data is synthetic — generated to be realistic in scale and structure, not sourced from Heathrow Airport Ltd's actual operational data. Where a calibration choice was made (e.g. capacity sizing, service rates), it's documented in code and in this README, not hidden.


What's in this project

src/
  config.py                        Airport structure: terminals, 9 constraints, capacities
  pipeline.py                      Master orchestration — run this to reproduce everything

  simulation/
    schedule_generator.py          Synthetic flight schedule with realistic diurnal demand
    demand_model.py                Converts flights -> 15-min interval demand per constraint
    calibrate.py                   Reproducible capacity calibration (documents the tuning)

  forecasting/
    historical_data.py             Synthetic historical demand (seasonality + trend + noise)
    quantile_forecast.py           Hierarchical quantile forecasting (expected/busy/extreme)

  queueing/
    erlang.py                      Erlang C multi-server queueing model
    wait_time_engine.py            Applies queueing across the full demand grid

  montecarlo/
    shock_simulation.py            Weather / delay / staff-absence Monte Carlo simulation

  optimization/
    staffing_optimizer.py          PuLP constraint optimisation — staff reallocation
    schedule_shift.py              Finds the smallest flight-timing change that resolves a breach

  narrative/
    bottleneck_detector.py         Explainable bottleneck detection & plain-English narrative
    stakeholder_negotiation.py     Competing stakeholder requests, evaluated against the model

  reporting/
    excel_workbook.py              Capacity-planning workbook (editable assumptions, RAG heatmap,
                                    risk register, scenario comparison — real Excel formulas)
    dashboard.py                   Interactive control-room-style HTML dashboard
    briefing.py                    One-page senior briefing + stakeholder-specific summaries
    pdca_cycle.py                  4-week continuous improvement (Plan-Do-Check-Act) simulation

Outputs (in outputs/)

File What it is
heathrow_dashboard.html Self-contained interactive dashboard — open in any browser
Heathrow_Capacity_Planning_Workbook.xlsx Editable Excel workbook: assumptions, RAG heatmap, risk register, scenario comparison
senior_leadership_briefing.md One-page briefing, every figure traced to model output
stakeholder_briefings.md Separate framing for airlines, security, finance
pdca_four_week_log.csv Simulated 4-week forecast-vs-actual improvement cycle

Dashboard (full view)

Full dashboard

Excel Workbook

The capacity-planning workbook ships with live formulas (Quick Capacity Check recalculates as you edit assumptions), conditional-formatted RAG heatmaps, a ranked risk register, and a baseline-vs-stress scenario comparison. Verified zero formula errors via LibreOffice recalculation.

Assumptions — editable scenario levers Assumptions sheet

Quick Capacity Check — live formula recalculation Quick capacity check

RAG Capacity Heatmap — peak utilisation by hour, every terminal/process RAG heatmap

Operational Risk Register — ranked by severity score Risk register

Scenario Comparison — baseline vs stress test Scenario comparison

How to reproduce

pip install numpy pandas scipy scikit-learn statsmodels plotly openpyxl networkx pulp

python -m src.pipeline                          # runs baseline + stress scenario (~65s)
python -c "import pickle; from src.pipeline import run_baseline_and_stress; \
           pickle.dump(run_baseline_and_stress(mc_trials=150), open('cache/scenario_results.pkl','wb'))"

python src/reporting/excel_workbook.py
python src/reporting/dashboard.py
python src/reporting/briefing.py
python src/reporting/pdca_cycle.py               # ~2 min — refits forecaster 4x

Tests

pip install pytest
python -m pytest tests/ -v

10 tests covering: demand mass-conservation, non-negativity, Erlang C queueing correctness (wait time monotonicity, instability, resource sensitivity), and quantile-forecast ordering (expected ≤ busy ≤ extreme, never negative).


How this actually works (and where I messed up)

The demand model

Flights don't show up as passengers all at once, they trickle in. If your flight's at 6:30am, you're not all walking up to check-in at 6:30am, you're showing up between 4:00 and 5:00. So instead of dumping passenger counts straight onto a flight time, I built "offset profiles" for each of the 9 constraints (check-in, security, border control, baggage, stands, ground handling, PRM assistance, and the two runway processes): little curves that say this many people show up this many minutes before/after the flight.

I also added a realistic aircraft mix (regional/narrowbody/widebody, each with their own typical load factor) and terminal shares roughly matching real Heathrow proportions, because "every flight has 150 people" felt lazy.

One thing I'm actually proud of: I checked that passengers don't get created or lost in this whole spreading-out process. Total check-in demand should equal exactly (departure passengers × the fraction who actually need a desk instead of just doing it on their phone). It matched to the decimal. Small thing, but it's the kind of bug that's very easy to introduce and very annoying to notice later.

The time I broke the entire airport

So here's a fun one. My first pass at capacity numbers left basically everything over capacity, some processes were running at 460% utilisation. Four. Sixty. Percent. The airport was just... permanently on fire, all day, every day.

Which is a problem, because the whole point of this project is "show me where it breaks first." If everything's already broken, that question doesn't mean anything anymore.

Turned out I'd made two dumb assumptions: I had border control staffed like it was 1995 (nobody using e-gates), and I assumed everyone physically queues at check-in, when in reality most people check in on their phone and only show up at a desk if they're dropping a bag. Fixed both, and suddenly the baseline runs tight, like 82-95% utilisation, which is what a real busy airport actually feels like, with runway capacity as the one thing that's genuinely maxed out.

Which, kind of amusingly, is also true of the real Heathrow. It only has two runways and that's famously its biggest constraint. I didn't plan that, it just fell out of doing the modelling properly. I'll take it.

Forecasting

Gradient-boosted quantile regression (via scikit-learn) trained on day-of-week, time-of-day, terminal, process, and what demand looked like 7 days ago. It spits out three numbers per cell: expected, busy, and extreme (P50/P90/P99, if you like your stats formal). I double-checked that "extreme" is never lower than "expected," because quantile models can absolutely do that to you if you're not careful, and it would be pretty embarrassing.

Queueing

Erlang C, basically the actual maths for "if this many people arrive per hour and I have this many desks, how long do they wait." I treat each 15-minute window as its own little steady-state queue, which is the standard simplification everyone in this space uses because modeling the true second-by-second queue dynamics is a nightmare nobody has time for. Runways get their own simpler treatment since they're one shared airport-wide resource, not a "6 lanes, pick one" situation.

Monte Carlo

I run 150-200 randomized "what if tomorrow actually goes like this" simulations: bad weather, a wave of delayed flights, a chunk of staff calling in sick, and see how often each part of the airport breaches. So instead of saying "security will be over capacity," the model can say "there's a 91% chance security breaches between 6:45 and 7:30," which is a much more useful thing to tell a human.

The two "what do we actually do about it" tools

I built two: one reallocates a fixed pool of staff hours toward the worst moments of the day (using actual constrained optimization, via PuLP, not vibes), and one searches for the smallest possible fix, literally "what's the fewest flights I can nudge, by the fewest minutes, to make this breach go away." That second one is the one that produces sentences like "moving 3 flights by 25 minutes clears the breach," which is exactly the style of answer this whole project was supposed to produce.

Stakeholders arguing with each other

When an airline asks for more slots, I don't script the answer, I actually run it through the model and see what breaks. Most of the time it's runway capacity, which is a hard wall no amount of extra check-in staff can fix. Where it is fixable (security, check-in, assistance), the model proposes the smallest real fix instead of just saying no.

The thing I want to be upfront about, not bury in a footnote

In the 4-week improvement loop, some of the "worst gap" numbers (20-25%) are really just noise from small sample sizes, like if only 4 aircraft are using a stand that hour, one extra flight swings the percentage a lot even though nothing meaningful actually happened. It's not fake, but it's also not a real signal you should act on without a human glancing at it first. I'd rather say that plainly than have someone find it themselves and wonder what else I glossed over.

About

Simulates a busy airport hour-by-hour to find exactly which part breaks first under pressure, then suggests the smallest fix. Built with forecasting, queueing theory, and optimisation.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages