Project 3 · Portfolio
Timetable Stress-Test Simulator
A "what if?" machine for a real bus route — change the timetable, and the app plays out a whole day of buses hundreds of times, with realistic hold-ups along the way, to show how many buses would still turn up on time.
View the live demo
What it does
The app is built around one real bus route in Leeds — First Leeds' route 56, using its real published timetable. You pick a day (weekday, Saturday or Sunday), a direction, and how many minutes apart the buses run. You can also add extra stops to see what that would cost.
The app then replays the day hundreds of times, each time adding small random delays at every stop — traffic lights, busy stops, slow boarding. This "roll the dice many times" method is called a Monte Carlo simulation. It shows the share of buses that arrive on time, how punctual each main stop is, how late buses get on a normal day and on a bad day, and a map of the route.
You can choose where the delays come from: an Estimated model (the default, based on the England-wide average) or a Real model, built from about 100,000 real GPS positions of buses on this route, recorded over ten days in September 2026. The page always shows which model is in use, so an estimate is never passed off as real data.
Why it matters
Changing a real timetable is slow, expensive and affects thousands of passengers — so it helps to test an idea on a computer first. Adding a stop sounds harmless, but every stop adds a little time, and small delays pile up along a route.
The real data also told a story of its own: this route is less punctual than the England average. Measured straight from the GPS readings, about 60 to 71% of departures from the main stops were on time, compared with the 80% England-wide figure (outside London) that the default model assumes. Punctuality also falls the further a bus travels along the route.
Who it helps
For transport planners, councils and bus companies, it is a quick, safe way to ask "what happens if…?" before changing a timetable for real. For passengers and the curious, it explains in plain numbers why buses run late and where on the route it gets worse. For anyone reviewing this work, it shows an honest approach to data: real measurements and estimates are kept apart and clearly labelled, and the app lists its own known limits.
Under the hood
Technology used, and why
Every tool below was picked for a specific reason, not out of habit — each card explains it in plain terms.
Python
The programming language the whole app is written in — the standard choice for working with data and running simulations.
pandas & NumPy
pandas reads the timetable tables; NumPy rolls the random delays. Together they replay a whole day of buses hundreds of times in seconds.
Streamlit
Turns the simulation straight into a web page with buttons and sliders, without building a separate website front end from scratch.
Altair charts & pydeck map
Altair draws the "how late buses get along the route" chart; pydeck draws the route map on OpenStreetMap tiles — no paid map account needed.
Bus Open Data Service (GTFS)
The government's official, free source of bus timetables. GTFS is the standard format they come in, so the app uses the real published times, not made-up ones.
zoneinfo & tzdata
GPS readings use world time (UTC) but timetables use UK time, which shifts by an hour twice a year. These convert between the two so lateness is measured correctly.
pytest
Automatic checks for the tricky maths. For the riskiest parts the tests were written first, before the code, so they prove the code does what it should.
Docker & Docker Compose
Packages the whole app so it runs the same way everywhere, with a built-in health check and an automatic restart if it ever stops.
How it's built
Architecture
Shown here as three diagrams at different levels of detail — first how the data is prepared before anyone visits, then what happens when you use the app, and finally how the two delay models plug in.
1. Getting the data ready (done once, in advance)
Two sources are boiled down into small files that ship with the app. The app never connects to a live database while you use it.
flowchart LR
accTitle: Data preparation diagram
accDescr: The official timetable file from the Bus Open Data Service is cut down to route 56 only and saved as small timetable files. Separately, a saved copy of real bus GPS positions is compared with the timetable to measure real lateness, which is saved as a real delay file. Both sets of files ship with the app.
D1A["Official timetable
(Bus Open Data)"] --> D1B["Cut down to
route 56 only"]
D1B --> D1C["Small timetable
files"]
D1D["Saved copy of real
bus GPS positions"] --> D1E["Measure real
lateness per stop"]
D1E --> D1F["Real delay
files"]
D1C --> D1G["Shipped inside
the app"]
D1F --> D1G
classDef io fill:#fde8d7,stroke:#c2410c,stroke-width:2px,color:#1f2937
classDef proc fill:#dcfce7,stroke:#15803d,stroke-width:2px,color:#1f2937
class D1A,D1D,D1G io
class D1B,D1C,D1E,D1F proc
In plain terms: the real timetable and real GPS readings are tidied up once, in advance, so the app is fast and self-contained.
2. The big picture — from your choices to the results
One pass through the app, start to finish. Every change you make re-runs the whole simulation.
flowchart LR
accTitle: End-to-end simulation flow diagram
accDescr: You choose the day, direction, gap between buses and any extra stops. The app builds that day's timetable. The simulation engine replays the day hundreds of times with random delays. The results are counted. You see the share on time, a chart and a map.
D2A["You choose day,
direction, gap, stops"] --> D2B["App builds that
day's timetable"]
D2B --> D2C["Engine replays the
day hundreds of times"]
D2C --> D2D["Results counted
across all replays"]
D2D --> D2E["You see % on time,
chart and map"]
classDef io fill:#fde8d7,stroke:#c2410c,stroke-width:2px,color:#1f2937
classDef proc fill:#dcfce7,stroke:#15803d,stroke-width:2px,color:#1f2937
class D2A,D2E io
class D2B,D2C,D2D proc
Each replay runs seven buses; the first is a "warm-up" bus and is left out of every figure, so the results aren't skewed by an empty road at the start.
3. Where the delays come from
Zooming in on the "Engine replays the day" box above: the engine asks a delay model how much time to add at each stop. You choose which one.
flowchart LR
accTitle: Delay model choice diagram
accDescr: The engine needs a delay for each stop. If you chose the Estimated model, delays are drawn from the England-wide average. If you chose the Real model, delays come from what was measured on this route. Either way the page labels which model was used.
D3A["Engine needs a
delay at each stop"] --> D3B{"Which delay
model?"}
D3B -->|Estimated| D3C["England-wide
average delays"]
D3B -->|Real| D3D["Delays measured
on this route"]
D3C --> D3E["Results labelled with
the model used"]
D3D --> D3E
classDef step fill:#dcfce7,stroke:#15803d,stroke-width:2px,color:#1f2937
classDef check fill:#e0e7ff,stroke:#4338ca,stroke-width:2px,color:#1f2937
class D3A,D3C,D3D,D3E step
class D3B check
The Estimated model is a sensible starting point but is not measured on this route; the Real model is, but rests on about ten days of data. That is why the page always says which one you are looking at.
See it running for real
This isn't a mock-up — it's a live simulator, built on a real bus route's real timetable and real GPS data.
View the live demoTimetable data: information taken from the bus open data digital service internet site.