LLM-powered transit advice, grounded in real-time MBTA analytics.
Raw transit feeds overwhelm both riders and language models. This project shows that if you run a traditional analytics pipeline first and feed an LLM only the distilled, trip-specific signal — plus alerts from multiple sources — you get commuter guidance that is concise, actionable, and far less prone to hallucination.
Research write-up: Towards Smarter Transit Advice: LLMs Grounded in Real-Time Analytics — Yashna Meher, Questrom School of Business, Boston University.
MBTA V3 API data + a rider's start/end station + multi-source alerts → analytics first, then LLM summarization → a personalized bulletin.
For the trip North Station → Park Street on the Green Line at 4 PM, the pipeline turns thousands of raw records into this:
Every claim is grounded in computed analytics (e.g. Park Street is 39.4% on-time, avg delay 3.3 min → add a 20+ minute buffer) or a verified alert — never invented.
The paper benchmarks four ways of using an LLM on live transit data. The winner is clear:
| # | Approach | Result |
|---|---|---|
| 4.1 | Dump raw historical data into the LLM | ❌ Overwhelmed; vague, generic summaries |
| 4.2 | Run analytics first, then feed insights | ✅ Concise, accurate, actionable |
| 4.3 | Feed only /alerts data |
🟡 Good, but no operational context |
| 4.4 | Analytics + multi-source alerts | ⭐ Best — precise and context-rich |
Takeaway: the LLM is a synthesis layer on top of analytics, not a replacement for it. Grounding in verified numbers is what makes the output trustworthy.
| Stage | File | What it does |
|---|---|---|
| 1 · Collect | src/collect_data.py |
Polls /schedules, /predictions, /vehicles, /alerts every 30 s (GTFS-RT best practice) → per-line CSVs |
| 2 · Analyze | src/analytics.py |
Matches predictions to schedules → per-stop headway, delay, on-time rate, peak hours |
| 3 · Summarize | src/summarize.py |
Grounds an LLM in the analytics + multi-source alerts → the rider bulletin |
pip install -r requirements.txt
# Stage 3 — reproduces the paper's grounded bulletin, no API key needed
python src/summarize.py
# Stage 2 — prints the stop-by-stop reliability report
python src/analytics.pysummarize.py runs fully offline using the committed snapshots in
sample_data/. To call a real LLM instead:
export OPENAI_API_KEY=sk-... # or put it in a .env file (see .env.example)
python src/summarize.py --liveTo collect fresh data yourself (Stage 1):
export MBTA_API_KEY=... # free at https://api-v3.mbta.com/
python src/collect_data.py --line Green-E --minutes 60├── src/
│ ├── collect_data.py # Stage 1 — 30s API poller
│ ├── analytics.py # Stage 2 — stop-by-stop reliability metrics
│ └── summarize.py # Stage 3 — LLM summarization (offline mock by default)
├── sample_data/
│ ├── analytics_report.txt # Stage 2 output snapshot (LLM input A)
│ └── alerts_multisource.md # official + social + event alerts (LLM input B)
├── outputs/
│ ├── trip_bulletin.md # final LLM bulletin
│ └── trip_bulletin.svg # rendered visual
└── docs/
├── paper.pdf # the research write-up
├── proposal.pdf # project proposal
└── architecture.png # system diagram (Figure 1)
The raw dataset was gathered by polling the MBTA V3 API every 30 seconds in Google
Colab across several days (Green Line E/D, focused on the North Station → Park Street
trip), then exported to CSV/Excel for offline analysis. Those raw files are not
committed (large, and easily regenerated with src/collect_data.py). The committed
sample_data/analytics_report.txt is the
distilled Stage 2 output from the full multi-day run, which is what feeds Stage 3.
Fine-tune open-weight models (e.g. LLaMA) to match frontier models on transit tasks;
extend to multi-agent architectures that generate new alerts for riders and
operators; and generalize the "analytics → LLM synthesis layer" pattern to other
analytics-heavy domains such as finance and supply chain. See docs/paper.pdf.
MIT © 2025 Yashna Meher
