Skip to content

GTFS-Flex: add adopted safe_duration_factor/offset to trips.txt; keep legacy stop_times duration fields#587

Open
drewda wants to merge 10 commits into
mainfrom
flex-duration-factors
Open

GTFS-Flex: add adopted safe_duration_factor/offset to trips.txt; keep legacy stop_times duration fields#587
drewda wants to merge 10 commits into
mainfrom
flex-duration-factors

Conversation

@drewda

@drewda drewda commented Apr 3, 2026

Copy link
Copy Markdown
Member

closes interline-io/calact-network-analysis-tool#323

Summary

GTFS-Flex's safe_duration_factor and safe_duration_offset were adopted into GTFS on trips.txt (google/transit#598, merged 2026-04-22). This PR adds those two fields to the Trip entity and plumbs them through persistence, the query layer, and the GraphQL API, with validation. The related mean_duration_* fields were never adopted into GTFS (they remain only in the unadopted GTFS-Flex proposal on stop_times.txt), so they are not added to trips; instead the existing stop_times duration fields are reclassified as legacy back-compat.

Adopted safe_duration on trips.txt

  • Add SafeDurationFactor/SafeDurationOffset (tt.Float) to gtfs.Trip.
  • Postgres migration adding safe_duration_factor/safe_duration_offset to gtfs_trips; matching columns in the SQLite schema.
  • Select the new columns in the trip dbfinder query and expose safe_duration_factor/safe_duration_offset on the GraphQL Trip type.
  • Trip.ConditionalErrors validation: the factor and offset must both be present or both absent, and the factor must be positive.

Legacy stop_times duration fields

  • Reclassify mean_duration_* and safe_duration_* on gtfs.StopTime as deprecated/back-compat, with comments documenting that safe_duration_* moved to trips.txt per the adopted spec and mean_duration_* was never adopted. Both are retained only so feeds that still emit them on stop_times continue to parse.

Tests and fixtures

  • Add a real Trillium GTFS-Flex fixture (hopelink-flex) that carries safe_duration_factor/offset on trips.txt and the legacy duration fields on stop_times.txt, wired into the server test DMFR.
  • Unit tests for Trip safe_duration validation (both-present, each-missing, negative factor, zero offset).
  • GraphQL resolver test asserting safe_duration_factor/safe_duration_offset values on a flex trip and null on a non-flex trip.
  • Update server GQL/REST fixture expectations for the added feed.

Test plan

  • Validate and copy a real Trillium GTFS-Flex feed (e.g. paratransitservices-wa-us--flex-v2); confirm trips.txt safe_duration_factor/offset and the legacy stop_times mean_/safe_duration fields round-trip through read → write.
  • Run go test ./gtfs/... and the server suites go test ./server/gql/... ./server/rest/... against a freshly set-up test database (the new hopelink-flex fixture must be imported).
  • Apply migrations against a database already migrated to current main and confirm gtfs_trips.safe_duration_factor/safe_duration_offset are created (the migration must sort after the latest existing migration).

@github-actions

This comment was marked as outdated.

drewda and others added 2 commits June 8, 2026 20:36
Resolve conflicts from concurrent test-fixture additions (this branch's
hopelink-flex feed and main's WMATA feed) and the Trip GraphQL schema:

- Combine both feeds' expected IDs in server/gql and server/rest fixtures
  (assertions use ElementsMatch, so order is irrelevant).
- Keep main's rewritten Trip field descriptions plus this branch's new
  safe_duration_factor / safe_duration_offset fields in schema.graphqls;
  regenerate internal/generated/gqlout/generated.go.
- Bump TestFeedResolver_Cursor feed query limit (10 -> 100) and
  TestOperatorRequest_Pagination expectLength (6 -> 7): each branch had
  independently grown the count to the old limit, so the union exceeded it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- gofmt gtfs/trip.go: comment insertion had broken struct tag alignment.
- Rename trip_safe_duration migration 20260403000001 -> 20260606000001.
  golang-migrate tracks a single high-water version and skips migrations
  older than the current DB version; main has since added migrations up to
  20260605000001, so the original timestamp would never apply on databases
  already migrated past it, leaving safe_duration_* columns missing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@drewda drewda marked this pull request as ready for review June 9, 2026 03:49
Copilot AI review requested due to automatic review settings June 9, 2026 03:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds GTFS-Flex “safe duration” support on trips.txt end-to-end (GTFS model → DB schemas → finder selects → GraphQL schema/output), and updates server fixtures/tests to include an additional GTFS-Flex feed used to exercise the new fields and license filtering behavior.

Changes:

  • Add safe_duration_factor / safe_duration_offset to GTFS Trip, DB schemas (SQLite + Postgres migration), dbfinder trip SELECTs, and GraphQL Trip.
  • Add GraphQL coverage asserting safe-duration fields are returned for a Flex trip and null for a non-Flex trip.
  • Expand test DMFR + adjust REST/GQL expectations (counts/IDs) to account for the newly added hopelink-flex feed and related entities.

Reviewed changes

Copilot reviewed 23 out of 25 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
testdata/server/server-test.dmfr.json Adds hopelink-flex GTFS feed fixture (restrictive license) for tests.
server/rest/trip_request_test.go Updates license-filtered trip count expectations due to added fixture data.
server/rest/stop_request_test.go Updates license-filtered stop count expectations due to added fixture data.
server/rest/route_request_test.go Updates expected route IDs/counts due to added fixture data.
server/rest/operator_request_test.go Updates expected operators and license-filter results due to added fixture data.
server/rest/feed_version_request_test.go Updates expected feed version SHA1 list to include new fixture version.
server/rest/feed_request_test.go Updates expected feed lists and license-filter results to include hopelink-flex.
server/rest/agency_request_test.go Updates expected agencies and license-filter results due to added fixture data.
server/gql/trip_resolver_test.go Updates license-filter expectations and adds a new safe-duration GraphQL test.
server/gql/stop_resolver_test.go Updates license-filter expectations due to added fixture data.
server/gql/route_resolver_test.go Updates license-filter expectations and route list due to added fixture data.
server/gql/operator_resolver_test.go Updates license-filter expectations due to added fixture data.
server/gql/feed_version_resolver_test.go Updates expected feed version lists and license-filter expectations.
server/gql/feed_resolver_test.go Updates expected feed lists and license-filter expectations to include hopelink-flex.
server/gql/booking_rule_resolver_test.go Updates expected booking rule output (internal numeric id shifted by fixture changes).
server/gql/agency_resolver_test.go Updates expected agency lists/location ordering and license-filter expectations.
server/finders/dbfinder/trip.go Adds safe_duration_factor/offset to trip SELECT projection.
schema/sqlite/sqlite.sql Adds safe_duration_factor/offset columns to gtfs_trips (SQLite schema).
schema/postgres/migrations/20260606000001_trip_safe_duration.up.pgsql Adds safe_duration_factor/offset columns to gtfs_trips (Postgres migration).
schema/graphql/schema.graphqls Exposes safe_duration_factor/offset on GraphQL Trip.
internal/generated/gqlout/generated.go Regenerates gqlgen output to include new Trip fields.
gtfs/trip.go Adds safe-duration fields to gtfs.Trip and validates presence/positivity.
gtfs/trip_test.go Adds unit tests for new Trip safe-duration validation rules.
gtfs/stop_time.go Updates GTFS-Flex field comments and marks stop_time safe/mean duration fields as deprecated/back-compat.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread gtfs/trip.go
Comment thread schema/sqlite/sqlite.sql
Comment thread schema/graphql/schema.graphqls
Comment thread gtfs/stop_time.go Outdated
Correct the upstream references after checking the sources:

- safe_duration_factor/offset were adopted into GTFS on trips.txt
  (google/transit#598, merged 2026-04), not stop_times.txt. The GTFS-Flex
  proposal originally placed them on stop_times.txt; some feeds still emit
  them there, so they are kept on StopTime for backward compat.
- mean_duration_factor/offset remain only in the unadopted GTFS-Flex proposal
  (on stop_times.txt) and were not part of google/transit#598. The prior
  comment's "removed from spec per gtfs-flex#73" was inaccurate: #73 is an
  open question about conditional requirement, not a removal, and these
  fields were never in the official spec to begin with.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@drewda drewda changed the title GTFS-Flex: Support final adoption of flex mean_duration_factor and safe_duration_factor columns GTFS-Flex: add adopted safe_duration_factor/offset to trips.txt; keep legacy stop_times duration fields Jun 9, 2026
These two fixtures grew when origin/main (WMATA) and this branch (hopelink-flex)
each added a feed/operator; the merge pushed the totals past values that each
branch had independently matched. Left uncommitted in the merge resolution.

- TestFeedResolver_Cursor: bump the feeds() query limit 10 -> 100 so the
  no-cursor / after-0 cases return all feeds and match FindFeeds (now 11, incl EX).
- TestOperatorRequest_Pagination limit:1000: expectLength 6 -> 7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support final adoption of flex mean_duration_factor and safe_duration_factor columns

3 participants