Inspiration

I left my friend at a stop one night because the app said his bus was coming. No worries, I thought. It took 30 minutes. He stood there alone, stressed, watching his phone die, waiting on a bus that the schedule kept promising. That night stuck with me. Transit apps just repeat whatever the agency feed says, and nobody checks whether the feed is telling the truth. Torontonians even have a name for the worst case: the ghost bus, marked "arriving" and then it just never comes. So I built the app that checks.

What it does

GhostBus (ghostbus.tech) is a transit app for Toronto and the GTA that attaches evidence to every claim. It covers ten transit systems on one 3D voxel map, plans trips across agencies with ranked alternatives, tracks vehicles live, and shows connection odds as percentages computed from real observed delays, with the sample size disclosed. When it does not have data, it says "schedule only, not enough live history yet" instead of inventing confidence like most apps do.

How we built it

React and Vite on the front, Fastify and Postgres (PGlite) on the back, GTFS static plus GTFS realtime feeds, a Three.js voxel city rendered inside MapLibre, deployed on a small VPS. The core is a delay engine that measures lateness against our own copy of the published schedule instead of trusting the feed, then runs every observation through honesty gates: coverage thresholds, circuit breakers, and quarantine audits. Nothing publishes unless it clears them. Built with Claude Code too.

Challenges we ran into

The TTC feed cannot even say "late": it publishes no delay field, and our decoder was silently inventing zeros, so 300,000 collected rows were worthless and we rebuilt the measurement ourselves. The realtime feed and the schedule do not share IDs, so we had to statistically learn the mapping from vehicle geometry. A nightly cleanup bug quietly erased a day of learned trust at 4 a.m. Postgres compiled to WebAssembly has no background log cleaner, and our server came within two days of a full disk. Every one of these looked like working code and was only found by measuring production.

Accomplishments that we're proud of

Coverage of ten GTA transit systems, verified stop by stop. A delay engine that earned 200,000 real observations and refuses to publish what it cannot back. Cross-agency trip planning where every leg carries its own evidence. A location system that never silently substitutes a fake position, which is the failure that started this project. And a working, deployed product a judge can open right now. Additionally, I also had to apply for a Metrolinx API key which said it could take upto 10 business days but thankfully I was able to get cleared really early which saved a lot of time.

What we learned

Honesty is an architecture, not a disclaimer. Every serious bug we hit was the system confidently repeating something false, which is exactly what riders live with. The fix was always the same: show your evidence, or say you do not have any. We also learned to verify against real data instead of trusting reports, including our own.

What's next for GhostBus

Fare data, seeded from agency sources so prices meet the same evidence standard as arrival times. Crowding indicators from the occupancy data agencies already publish. More regions, since the engine now adapts to any GTFS feed. And sharing the accountability ledger with rider advocacy groups, because the evidence we collect is most useful in the hands of the people holding agencies to their own schedules. Expanding to North America was my original plan but I realized that was unrealistic for this time period but I still want to expand so I will keep improving.

Built With

Share this project:

Updates