The raw files and the generated bundles answer different questions. upstream-stops.json contains 125 stop records in this checkout. The generated bus payload contains 85 stops because the build deduplicates coordinates, removes two known placeholder categories, and only keeps stops that the route catalog can place in an ordered service sequence.
That reduction is intentional. A raw stop record is evidence about a named place. The route catalog is the source of the order in which a bus service visits those places. The geometry file is evidence about how to draw the service between them. Calling all three “the route data” hides the decisions that happen between input and artifact.
The bus side starts with the raw stop response and route-catalog.json. The jeepney builder follows a different path: it takes the checked-in jeepney-routes.geojson, loads the OSM place snapshot, derives landmarks, and writes the generated jeepney bundle. The jeepney route-inference tool is not part of that production bundle build. It produces candidate geometry for review in a separate output directory.
How a bus stop gets an identity
The builder does not use the stop's name alone as its ID. It combines a slugified name with latitude and longitude rounded to five decimal places. That lets the same named place appear at different coordinates without silently collapsing into one record, while repeated copies of the same coordinate can share an identity.
This is the relevant part of transform.ts:
const stopId = generateStopId( stop.name, stop.latitude, stop.longitude,)if (!stopsMap.has(stopId)) { stopsMap.set(stopId, { name: stop.name, location: stop.location, lat: stop.latitude, lng: stop.longitude, routeRefs: new Set(), })}The later catalog pass maps each ordered name to this index and adds the route reference to the stop. A stop therefore has both a stable generated identity and the directional services that use it. Matina Crossing, for example, is not just a point on a map. Its routeRefs describe which catalogued services may use that point.
The transform also excludes records named Waiting Shed and records whose location is Testing Purposes. Those filters are small, but they explain why the raw count is not the bundle count. When a name in the catalog cannot be resolved to a retained raw record, it is not silently invented as a stop.
Current snapshot
| Artifact | Current evidence | Role | Limitation |
|---|---|---|---|
upstream-stops.json | 125 raw records | Coordinates, names, locations, and source route labels | A snapshot, not a live feed |
route-catalog.json | 9 route families and AM/PM sequences | Route identity and ordered topology | Curated local data; metadata says reviewer is not assigned |
bus-routes.geojson | 18 features, each marked source: "road-routed" | Canonical directional map geometry | Generated from the current stop topology and router output |
bus-bundle.generated.ts | Version 2026-08-28.958321, 233,896 bytes | Application payload | Build artifact for this checkout |
jeepney-routes.geojson | 52 LineString features | Checked-in jeepney line input | Feature properties are only route name and color |
jeep-bundle.generated.ts | Version 2026-08-28.6078fa, 824,911 bytes | Application jeepney payload with derived landmarks | Metadata says reviewStatus: "unreviewed" |
The bus bundle also records SHA-256 fcbb0834e24523865011a2d97f33901a82011f3a2ffa228b543c78d3dfd149b5; the jeepney bundle records 70a917c635fcf79a1e5c7acab315059e538be8bfbfc2600fc3a2d1c36d32ceb9. The generated timestamps and versions should be treated as provenance for the artifact, not as service freshness.
The shared metadata records dataDate: "2026-08-10", validationStatus: "passed", reviewStatus: "unreviewed", and warns riders to confirm the route and fare with the operator. The current fare-rules file is empty, so these pages do not present a fare table as though it were populated.
Validation is part of the build
The bus schemas make direction and route identity explicit. A route ID must match R followed by three digits, a route reference must match R123:AM or R123:PM, and the reference must agree with its two component fields. The geometry validator also checks that every route exists in the catalog, has at least two ordered stops, stays inside the expanded stop bounds, keeps each ordered stop within 250 metres of the line, and does not backtrack by more than 500 metres along the geometry.
The tests make the failure boundary concrete. R999:AM is rejected because R999 is not in the catalog. A duplicate route reference is rejected before it can become two indistinguishable map features. A bad coordinate or a mismatched routeRef is rejected by schema validation. See route-geometry.test.ts and route-geometry.ts.
The build boundary is build-bus-bundle.ts. It parses the raw response, catalog, metadata, fares, and canonical geometry before writing the generated module. A missing geometry reference can still produce an explicitly ordered-stop fallback for application use, but that fallback is not relabelled as road geometry.
The same provenance rule applies to the map. Transit catalog and map explains the consumer path; jeepney route data and inference explains why the jeepney inputs need a different review boundary.