Systems/Routes & stops
04 / 06

Routes & stops

Schedule as minute-of-day

SchemaRoutesStops

Overview

A route has a name, a color, an active flag, and an ordered array of stops. Each stop has a name, lat/lng, scheduled minute-of-day, plus brand-rich fields — tagline (one prose sentence), houseStyle (a wine-style line), hoursText (free-form opening hours), and heroImageUrl with attribution. The scheduledMinuteOfDay field is the secret sauce.

How it works

1

Route insertion: `POST /admin/routes` accepts the route fields plus an array of stops. The API generates IDs, computes the order from array position, and writes both in a single transaction.

2

ETAs derived from minute-of-day: the rider's Today page combines scheduledMinuteOfDay with today's date in the route's timezone to build a real timestamp, which feeds the ETA banner.

3

Live vehicle progress: as the vehicle pings positions, we compute the nearest un-passed stop by haversine distance plus the route's polyline projection. The 'next stop' becomes the basis for the ETA.

4

Stop edits propagate immediately — there is no caching layer between the API and the catalog. Rider apps pick up changes on next screen focus via React Query's staleTime of 60000.

5

Inactive routes are filtered out of the assignments picker and the rider catalog, but their historical reservations stay intact (we soft-archive, not hard-delete).

Key decisions

Minute-of-day, not a timestamp

A route that runs every day at 9am isn't tied to any specific date. Storing a full timestamp would force us to materialize one row per day, or do tricky modulo math. An integer 0–1439 makes the recurrence trivial — combine with today's date when you need a real timestamp.

Stops own prose, not just coordinates

A wine-country shuttle stop is a destination, not a coordinate. The tagline, houseStyle, hoursText, and heroImageUrl fields let each stop tell a story on the rider's StopDetailSheet. The data is heavier than a stop on a city bus would be — and that's the whole point.