feat(adapter): add Helsinki (HSL) GTFS adapter - #166
Open
ciotlosm wants to merge 1 commit into
Open
Conversation
New adapter package @n3ary/gtfs-adapter-helsinki for the
n3ary transit family. Adapts HSL's daily GTFS feed plus
the standard GTFS-RT protobuf feeds (vehicle positions, trip
updates, service alerts) at realtime.hsl.fi.
Why this is simpler than the cluj adapter:
- HSL publishes a complete, daily-regenerated GTFS zip
(no Transitous seed + Tranzy reconciliation needed)
- HSL publishes standard GTFS-RT protobuf (no HFP conversion
-- the worry going in -- the GTFS-RT surface is the
public realtime API)
- HSL's open data is public (no API key, no rate limit)
- HSL ships route_color + route_text_color per route already
(no per-feed color computation needed)
- HSL has no per-feed networks (no _route_tags / network
derive pipeline)
Adapter surface:
/ingest - DEFAULT_GTFS_URL, REQUIRED_SECRETS (empty),
DEFAULT_AGENCY_ID, ingestBuild(). Single GET
against https://gtfs.hsl.fi/gtfs.zip, returns
the bytes. No zip-content verification (the
static pipeline catches malformed GTFS via the
SQL load errors; a fetch-verify round-trip would
add a zip-reader dep for marginal value).
/static - staticExtension(feedConfig) -- adds _neary_config
for the app's timing-aware travel-time math, no-op
fillComputedColumns. Exports hslRouteType +
computeHslBrandColor for the app's category chip
rendering. HSL's pathway_mode=1 -> metro is
handled by hslRouteType (HSL's metro lines use
route_type=0 historically, with pathway_mode as
the real signal).
/rt - helsinkiQuirk (called by the generic gtfs-rt
proxy per fetched FeedMessage). Two HSL-specific
fixes:
1. Synthesize a trip_id for VehiclePosition
and TripUpdate entities when HSL omits one.
HSL's real-time feed uses the composite
route_id + start_date + start_time +
direction_id instead of trip_id (per
https://hsldevcom.github.io/gtfs_rt/). The
static feed regenerates trip_ids on every
publish, so the composite is the only stable
join key. Format:
hsl:<route_id>:<start_date>:<start_time>:
<direction_id>.
2. Split the HSL <operator>/<vehicle> vehicle
id. Sets vehicle.label to the bare vehicle
number while keeping vehicle.id as the full
composite (so consumers can filter by
operator).
Plus splitHslVehicleId / syntheticHslTripId
helpers, exported for tests.
44 unit tests, all green. pnpm check clean. pnpm build
emits a dist/ tree (gitignored).
Separate PRs still needed in:
- n3ary/gtfs feeds/helsinki/config.json
- n3ary/gtfs-publisher feeds/helsinki/config.json
This PR is the adapter only; the feed configs wire it in
and the publisher's feeds.json can be regenerated against
the new package.
This was referenced Aug 23, 2026
ciotlosm
added a commit
to n3ary/gtfs-publisher
that referenced
this pull request
Aug 23, 2026
Wires the new @n3ary/gtfs-adapter-helsinki adapter (n3ary/ gtfs-adapters#166) into the static pipeline. Mirrors the cluj-napoca config shape: - id: helsinki - source: adapter-driven, publisher @n3ary/gtfs-adapter-helsinki - secrets: empty (HSL is public open data, no API key) - license: CC BY 4.0 (HSL open data, attribution required) - smoke: tripIdPattern matches the synthetic hsl:<route>: <date>:<time>:<dir> format the adapter's RT quirk produces - timing: HSL-defaulted speeds (metro + tram + bus backbone means peak ~22 km/h, higher than cluj's 14 km/h). Peak windows 7-9 and 15:30-17:30. Night 23:00-05:30. Dwell 25s. Sister PR: n3ary/gtfs-adapters#166 (the adapter itself) and a follow-up in n3ary/gtfs-publisher to add the RT config.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a new adapter package,
@n3ary/gtfs-adapter-helsinki, for HSL's (Helsinki Region Transport) GTFS feed. One of the per-feed adapters undergtfs-adapters/adapters/, alongside the existing cluj-napoca.Why this is simpler than the cluj adapter
realtime.hsl.fi. No HFP conversion needed (worried about this going in — turned out HSL's public API is standard GTFS-RT).route_color+route_text_colorper route. No per-feed color computation._route_tags/ network derive pipeline.Adapter surface
/ingestingestBuild(). Single GET againsthttps://gtfs.hsl.fi/gtfs.zip, returns the bytes. No zip-content verification (the static pipeline catches malformed GTFS via the SQL load errors).REQUIRED_SECRETSis empty./staticstaticExtension(feedConfig)— adds_neary_configfor the app's timing-aware travel-time math, no-opfillComputedColumns. ExportshslRouteType+computeHslBrandColorfor the app's category chip rendering. HSL'spathway_mode=1 → metrois handled byhslRouteType(HSL's metro lines useroute_type=0historically, withpathway_modeas the real signal)./rthelsinkiQuirk(called by the generic gtfs-rt proxy per fetched FeedMessage). Two HSL-specific fixes: (1) synthesize atrip_idfor VehiclePosition / TripUpdate entities when HSL omits one — HSL uses the compositeroute_id + start_date + start_time + direction_idinstead (per HSL GTFS-RT docs). Format:hsl:<route_id>:<start_date>:<start_time>:<direction_id>. (2) split the HSL<operator>/<vehicle>vehicle id, setvehicle.labelto the bare vehicle number. PlussplitHslVehicleId/syntheticHslTripIdhelpers, exported for tests.Files
adapters/helsinki/package.json— package manifestadapters/helsinki/src/ingest/index.ts—ingestBuild()adapters/helsinki/src/rt/helsinki.ts+index.ts— quirk + barreladapters/helsinki/src/static/{extension,index,route-types}.ts— static pipelineadapters/helsinki/tests/{helsinki,ingest,extension,route-types}.test.ts— 44 testsadapters/helsinki/{LICENSE,README.md,tsconfig.*,vitest.config.js}44 unit tests pass,
pnpm checkclean,pnpm buildemitsdist/(gitignored).Out of scope (separate PRs)
n3ary/gtfs/feeds/helsinki/config.json— wires the adapter into the static pipelinen3ary/gtfs-publisher/feeds/helsinki/config.json— wires the adapter into the RT publisher's feeds.jsonThis PR is the adapter only. Once it lands, the feed configs in the other two repos wire it in.