Crowdsourced speed audit of Greater Malé public transport. Riders record trips
on their own phones; recorded speed is compared against per-road speed limits;
aggregates across many trips build the evidence base. Built from
RTL-Speed-Audit-PRD.md (Phase 1 PWA + cloud sync).
npm install
npm run dev # http://localhost:3008
npm run build # static export (never run while `next dev` is running — shared .next)Try it without a bus: tick Demo mode on the home screen — it replays a route with simulated GPS, speeding periodically.
- RTL Bus — tag one of the 11 RTL Greater Malé routes (Routes 1–5, 8–12, Vilimalé), optional fleet number.
- Taxi / Other (motorbike, joy ride…) — no route tag; the trip logs wherever the vehicle goes.
- Logs GNSS at 1 Hz (fixes worse than 20 m accuracy dropped, < 2.5 m/s treated as stationary).
- Every fix is judged against the limit for the road it's on: nearest corridor segment within 50 m, else the island zone (Malé 30, Hulhumalé 30, Vilimalé 25). Over-limit = speed > limit + 5 km/h margin.
- Offline-first: fixes buffer in IndexedDB; trips upload when online (if sync is configured).
- Privacy: pseudonymous contributor id, first/last 60 s of every trip deleted, per-trip delete, write-only cloud key.
Geometry and limits in lib/route.ts are approximate. Only the Sinamalé
Bridge 50 km/h is publicly reported; everything else awaits the field survey
(PRD 7.3). Do not publish findings from this dataset.
Trips upload through /api/sync (a Vercel function) into Neon Postgres — the
phone never holds a database credential. Tables are auto-created on the first
upload.
- Deployed on Vercel; connect the Neon integration to the project so
DATABASE_URLis injected (Vercel dashboard → Storage/Integrations → Neon). - For local dev, copy
.env.local.exampleto.env.localwith your Neon connection string. - Run the views in
db/schema.sqlonce in the Neon SQL editor for trend queries:segment_rates(per-segment speeding rate) andhourly_rates(by hour of day and vehicle), both excluding demo trips.
Without a DATABASE_URL the app still works fully offline; the trips page
reports "Cloud sync: off".
Phase 2: map-matching validation, analyst dashboard. Phase 3: Capacitor wrapper for background logging. Phase 4: remaining routes, taxi attribution.