MARTA Delay Predictor - Real-Time ML for Atlanta Transit
Ingest the GTFS-realtime feed every 30 seconds, train XGBoost nightly on the rolling 30-day window, serve predictions in under 500ms.
Role: ML Engineer + Full Stack Developer. Team: Individual. Status: Complete. Year: 2026.
Overview
Problem. MARTA's own app tells you when a bus is "scheduled" but nothing useful about when it will actually show up. The GTFS-realtime feed has the raw signal, but consuming it end-to-end is work nobody in the city has done publicly.
Approach. Ingest the GTFS-realtime stream every 30 seconds into Supabase with PostGIS. Join against the static schedule to compute observed delays. Train XGBoost nightly on the rolling 30-day window. Serve predictions through Cloud Run. Visualize with Mapbox GL.
Outcome. Live demo dashboard runs against a seeded snapshot of the MARTA network so the visualization is always on even when the public GTFS-realtime feed drops. A GTFS-realtime ingester in backend/app/ingestion/gtfs_realtime.py writes vehicle_positions and arrivals into Cloud SQL and is ready for a Cloud Run job; the dashboard's live-vs-seed toggle is wired to the marta-realtime-gtfs-demo PostHog flag. Migrated the ingestion layer from Railway to Supabase Edge Functions mid-project for cost, which simplified the topology.
Lessons. The static GTFS join is the unglamorous core. Everything interesting (predictions, dashboards) depends on it being correct. The most time went into timezone normalization and trip-update deduplication, not on the ML.
Architecture
- Ingestion: Supabase Edge Functions pulling GTFS-realtime every 30s
- ML: XGBoost delay regressor, nightly retrain on rolling 30d window
- Backend: FastAPI serving inference, PostGIS for spatial joins
- Frontend: React + Mapbox GL JS with live vehicle overlays
- Deploy: Cloud Run (backend), Vercel (frontend)
Live → GitHub → Conference deck →
Key features
- GTFS-realtime ingestion every 30 seconds via Supabase Edge Functions
- Static GTFS + realtime join for observed delay computation
- XGBoost delay regressor with nightly retrain on 30-day rolling window
- Per-stop delay predictions served in under 500ms
- Live Mapbox GL dashboard with vehicle position overlays
- Recorded seed window so demos work without live feed access
Technical details
- Ingestion: Supabase Edge Functions (Deno), 30s cron
- Database: Supabase PostgreSQL + PostGIS for spatial joins
- ML: XGBoost 2.x regressor, nightly retrain pipeline
- Backend: FastAPI with async inference endpoints
- Frontend: React 18 + Mapbox GL JS
- Deploy: Cloud Run (backend), Vercel (frontend)
Challenges
- Timezone normalization across schedule vs realtime feeds
- Trip-update deduplication when the feed re-emits the same update
- Modeling sparse ridership at low-traffic stops without overfitting
- Migrating ingestion from Railway to Edge Functions without dropping events
Tags: Python, FastAPI, XGBoost, PostGIS, Supabase Edge Functions, Mapbox, React