Skip to content

Repository files navigation

Transit Ledger ATL

Should a Georgia Tech student trust the bus right now — or call the Uber?

MARTA's app tells you when a bus is scheduled. It doesn't tell you that Route 20 is running 31 minutes late today, or that half the scheduled buses never showed up on GPS ("ghost buses"). Transit Ledger ATL polls MARTA's live GTFS-RT feeds every minute into TimescaleDB, computes per-route reliability in real time, and gives riders a straight verdict: take the bus / take an Uber / coin flip — plus an AI campus briefing regenerated by a durable Render Workflow.

Built solo in 24h at Hack RenderATL by a GT student who has been ghosted by the 46 one too many times.

Architecture

MARTA GTFS-RT (.pb, every 60s)
        │  Render Cron Job
        ▼
Tiger Cloud (TimescaleDB)          Render Workflows
  hypertables: vehicle_positions,    campusBriefing task
    trip_delays (226k rows/10min)      ├─ gatherStats   → Tiger caggs
  continuous aggregate:                ├─ writeBriefing → Gemini (retried)
    route_reliability_15m              └─ storeBriefing → briefings table
  columnar compression after 2h
        │
        ▼
Render Web Service (Express + vanilla JS)
  · Bus-vs-Uber verdict engine (/api/commute)
  · Gemini function-calling chat over live SQL (/api/chat)
  · Live fleet map colored by route on-time %
  · Campus briefing panel (reads workflow output)

Track usage

Tiger Data

  • Two hypertables ingesting ~2,900 GPS points + ~226,000 delay samples every 10 minutes.
  • A continuous aggregate (route_reliability_15m) keeps 15-min on-time % per route precomputed — the dashboard chart and every verdict read from it lag-free, no GROUP BY at request time.
  • Columnar compression on vehicle_positions after 2 hours keeps the free instance small.
  • Ghost-bus detection joins live GPS against the static GTFS timetable in SQL.

Render Workflows

  • workflows/index.ts defines a durable task graph with @renderinc/sdk/workflows: campusBriefing fans into gatherStats → writeBriefing → storeBriefing subtasks, with per-task retry/backoff (Gemini free tier is flaky — retries are load-bearing).
  • The web service triggers runs via render.workflows.startTask() (POST /api/briefing/run).
  • Plus the whole app deploys from render.yaml: web service + every-minute cron poller.

Gemini API

  • Function-calling chat: Gemini gets 5 tools (route reliability, ghost buses, headway gaps, worst routes, commute verdict) and answers questions like "can I make it to Georgia Tech by 9:30?" with real SQL-backed numbers.
  • Workflow briefing: Gemini turns live Tiger stats into a 120-word campus commute briefing with concrete advice.

Run locally

cp .env.example .env   # DATABASE_URL (Tiger) + GEMINI_API_KEY
npm install
npm run migrate
npm run poll:loop      # terminal 1 — poller every 30s
npm run web            # terminal 2 — http://localhost:3000

Without RENDER_API_KEY/RENDER_WORKFLOW_SLUG, the briefing button falls back to inline generation — same pipeline, no durability.

Deploy

  1. Push to GitHub → Render New > Blueprint → this repo (web + cron from render.yaml).
  2. New > Workflow → this repo, start command npm run workflows (Blueprints don't support workflow services yet).
  3. Set env vars: DATABASE_URL (all three), GEMINI_API_KEY (web + workflow), RENDER_API_KEY + RENDER_WORKFLOW_SLUG (web).

Env vars

Var Notes
DATABASE_URL Tiger Cloud connection string
GEMINI_API_KEY Google AI Studio
GEMINI_MODEL optional, default gemini-3.1-flash-lite
RENDER_API_KEY triggers workflow runs from the web service
RENDER_WORKFLOW_SLUG slug of the dashboard-created workflow service

Definitions

  • On-time = |delay| ≤ 5 min vs static GTFS schedule (MARTA RT sends absolute times, not delays — we compute them).
  • Ghost bus = a trip on today's timetable that started ≥8 min ago but has never appeared on GPS.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages