fix trips-for-route: handle DUPLICATED trips and add time window support#679
fix trips-for-route: handle DUPLICATED trips and add time window support#679Ahmedhossamdev wants to merge 28 commits intomainfrom
Conversation
aaronbrethorst
left a comment
There was a problem hiding this comment.
Hey Ahmed, this is an ambitious PR that tackles several real pain points in the trips-for-route endpoint -- null-block trips, past-midnight handling, DUPLICATED trip support, and the SQLite parameter binding fix are all genuinely important improvements. The investigation work and the detailed PR description are really solid. There are a few things I need you to address before we can merge:
Critical
-
DB errors conflated with "not found" in DUPLICATED trip lookup (
trips_for_route_handler.go:342-347).GetTripreturnssql.ErrNoRowswhen a trip doesn't exist, but returns other errors for real DB failures (connection issues, SQLite busy, etc.). The current code treats any error as "trip not found" and falls back to stripping the numeric suffix. Under DB pressure, this could resolve DUPLICATED trips to the wrong base trip, producing incorrect schedule data. Useerrors.Is(lookupErr, sql.ErrNoRows)to distinguish the two cases. -
Same issue in
BuildTripStatusdbTripID fallback (trips_helper.go:83-88). TheGetTripcheck foractiveTripRawIDtreats any error as "not found" and falls back totripID. A transient DB error would cause stop times, shapes, and distances to be computed against the wrong trip, producing an internally inconsistent TripStatus. Same fix: check forsql.ErrNoRowsspecifically.
Important
-
SQL comment contradicts actual behavior (
query.sql:1047-1049). The comment says "Use time_range_start = now - 10 min and time_range_end = now + 30 min" but the Go code doesnow - 30 min(runningLateNanos) andnow + 10 min(runningEarlyNanos). The Go code is correct (late vehicles are more common, so the backward window should be larger). Please fix the SQL comment to match. -
Previous-day queries silently discard errors throughout. Several previous-day lookups discard errors entirely while their current-day equivalents properly surface them:
- Line 76:
prevServiceIDs, _ := ...GetActiveServiceIDsForDate(...)-- error discarded - Lines 120-138: Previous-day block lookups use
if err == nilguards, hiding DB errors - Lines 151-161: Previous-day null-block trips error discarded with
if err == nil - Line 202:
GetTripsInBlockerror treated same as "no trips"
These don't need to be server errors, but they should all log at Warn level so operators can see when past-midnight data is degraded. The inconsistency between how current-day and previous-day errors are handled is a debugging blind spot.
- Line 76:
-
No tests for the core new logic functions.
selectBestTripInBlockandstripNumericSuffixare both pure functions that are trivially unit-testable, but have zero test coverage.selectBestTripInBlockin particular implements a 4-tier priority system -- a bug there silently returns the wrong active trip for entire blocks. Please add unit tests for both. -
BuildTripStatuswith non-nil vehicle is untested. Every existing test passesnilfor the vehicle parameter. The non-nil path (used by DUPLICATED trips in production) has no direct coverage. Please add at least one test that verifies a pre-resolved vehicle is used correctly. -
PR description says "Still working on it". Is this PR ready for review, or should I wait for more changes? If it's ready, please update the description.
Fit and Finish
-
selectBestTripInBlockfirst pass is dead code (trips_for_route_handler.go:639-644). This function is only called whenGetActiveTripInBlockAtTimereturns an error (no matching trip), but the first pass checks the exact same condition (minArrival <= now <= maxDeparture). It will never match. Either remove it or add a comment explaining why it's retained as a safety net. -
Missing route_id context in null-block trip log (
trips_for_route_handler.go:147). The Warn log "failed to fetch null-block trips" would be much more useful with"route_id", routeIDincluded.
Verdict
Request changes -- please fix the two critical ErrNoRows discrimination issues, add the missing unit tests for selectBestTripInBlock and stripNumericSuffix, and confirm the PR is ready for review. The error logging improvements and the dead code cleanup can be done in the same pass.
|
Hey Aaron Brethorst, thanks for the review!
Thanks |
…tation and timestamps
… with time bounds
…er JSON representation
BuildTripStatus now accepts an optional vehicle parameter. Pass nil so it resolves the vehicle internally via GetVehicleForTrip.
…p reference creation
…route GetStopTimesForTrip already returns stop IDs — no need to fetch them separately.
- Handle routes with NULL block_id (fallback batch query) - Add DUPLICATED trip support (schedule_relationship=6) - Add previous-day service lookup for trips running past midnight - Strip numeric suffix from DUPLICATED trip IDs for DB lookups
… fields - Accept optional *Vehicle param (nil = auto-resolve via GetVehicleForTrip) - Add dbTripID fallback for synthetic DUPLICATED trip IDs not in DB - Early return for CANCELED trips (skip meaningless schedule lookups) - Use value types instead of pointers for TripStatus field assignments - Prefer shape_dist_traveled over geometric projection in stop distances - Fix early-exit threshold to avoid breaking on stops far from shape
…block trips in tripsForRouteHandler
…ipsForRouteHandler
…alculation, adjusting orientation inference, and adding a `SetPredicted` helper.
…ts in route handlers.
…tripsWithStopTimes` slice.
8b9e38e to
942489d
Compare
…ionIds` presence in trip details test.
Fixes: #660 #661, #662, #663, #664
The
trips-for-routeendpoint was returning significantly fewer trips compared to the Java server. In some cases it returned zero trips for routes that should have had 15+.What was wrong
NULL block_id → 0 results. -> The handler only did block-based lookups to find active trips. Routes where every trip has
block_id = NULL(like Sound Transit Link) got nothing back. Added a fallback batch query for null-block trips. (fix: NULL block_id trips returning 0 results - trips-for-route #661)SQLite parameter binding was silently wrong -> The new null-block query used
sqlc.slice()+sqlc.arg()together, but sqlc expands slices into?NNNpositional params at runtime. When thesqlc.arg()came after the slice, its position shifted and SQLite bound the wrong value. Reordered the SQL sosqlc.arg()params come first. (fix: SQLite parameter binding misalignment in null-block query - trips-for-route #662)DUPLICATED trips ignored -> GTFS-RT feeds include
schedule_relationship = DUPLICATED(value 6) for extra real-time runs of a trip. These only exist in real-time data with no static DB entry. AddedGetDuplicatedVehiclesForRoute()and a processing loop that resolves the base trip ID for schedule data. (Include GTFS-RT DUPLICATED trips (schedule_relationship=6) #663)Past-midnight trips missing -> GTFS allows stop times > 24:00:00 for trips that started the previous service day. After midnight, the handler only looked at today's service IDs and missed these. Added previous-day service lookups. (Support previous-day service for trips running past midnight (GTFS times > 24:00:00) #664)
Also in this PR
Cross-route block filter -> Block-based lookups could pull in trips from other routes sharing the same block. Added a post-fetch
route_idfilter.Refactored
BuildTripStatus-> Added an optionalvehicleparameter so callers can pass a pre-resolved vehicle (used by DUPLICATED trips). Whennil, it falls back toGetVehicleForTrip. Also addeddbTripIDfallback so synthetic DUPLICATED trip IDs that don't exist in the DB resolve to the base trip for schedule lookups. Updated all call sites (arrival-and-departure-for-stop,arrivals-and-departures-for-stop,trip-details,trip-for-vehicle,trips-for-location).CANCELED trip early return ->
BuildTripStatusnow returns immediately for CANCELED trips, skipping stop-time and shape calculations that are meaningless for a trip that's not operating.TripStatus fields always present (removed
omitempty) -> Changed pointer fields (*float64,*int,*int64) to value types and removedomitemptyfrom JSON tags so every field is always serialized. This matches Java OBA which never omits fields. Defaults:occupancyCapacity/occupancyCount→-1,vehicleFeatures/situationIds→[],frequency→null.schedule-for-route: removed redundant query ->
GetOrderedStopIDsForTripwas called separately just to collect stop IDs, butGetStopTimesForTripwas called right after and returns the same stop IDs. Removed the extra query and extracted stop IDs from stop times directly.trips-for-location: avoid unnecessary DB round-trip in reference builder ->
buildTripReferenceswas re-fetching each trip from the DB viaGetTrip()just to get route/headsign info that was already present in the in-memoryTripmodel. Removed the extra query and used the existing data directly.Still working on it.