Skip to content

feat: Implement arrivals-and-departures-for-location endpoint#787

Open
ARCoder181105 wants to merge 31 commits into
OneBusAway:mainfrom
ARCoder181105:feature/arrivals-departures-for-location
Open

feat: Implement arrivals-and-departures-for-location endpoint#787
ARCoder181105 wants to merge 31 commits into
OneBusAway:mainfrom
ARCoder181105:feature/arrivals-departures-for-location

Conversation

@ARCoder181105
Copy link
Copy Markdown
Collaborator

@ARCoder181105 ARCoder181105 commented Mar 25, 2026

Overview
This PR ports the arrivals-and-departures-for-location endpoint from the legacy Java codebase into the Go maglev architecture. This endpoint allows clients to retrieve active arrivals and departures for multiple stops within a specified geographic bounding box or radius.

Key Features

  • Geospatial Querying: Utilizes the in-memory R-tree index to efficiently locate stops within the requested coordinate boundaries.
  • Overnight Trip Support: Queries active stop times across a 3-day window (yesterday, today, tomorrow) to ensure accurate scheduling for late-night routes.
  • Separated References: Aggregates Agencies, Routes, Stops, Trips, and Situations into a deduplicated references block to strictly adhere to the legacy JSON schema and optimize payload size.
  • Nearby Stops: Calculates haversine distance for nearbyStopIds to identify and include stops within the radius that may not currently have active arrivals.

Bug Fixes

  • Trip Status Fix: Resolved an existing issue in arrivals_and_departure_for_stop.go where the vehicle parameter was incorrectly passed as nil to BuildTripStatus.

Testing

  • Added table-driven tests for comprehensive parameter validation (radius, spans, minute offsets, timestamps).
  • Implemented end-to-end integration tests verifying payload structure and maximum record counts.
  • Validated context cancellation and timeout handling under load.

fix #799

Summary by CodeRabbit

  • New Features

    • Added arrivals-and-departures-for-location endpoint enabling location-based transit queries with optional nearby stops results.
  • Bug Fixes

    • Fixed vehicle stop sequence inference using GPS coordinates.
    • Corrected overnight layover calculations.
    • Ensured lastUpdateTime field is consistently included in responses.
  • Tests

    • Added comprehensive test coverage for the new location endpoint including parameter validation and end-to-end scenarios.

Review Change Stack

@ARCoder181105 ARCoder181105 changed the title Feature/arrivals departures for location feat: implement arrivals-and-departures-for-location endpoint Mar 25, 2026
@ARCoder181105 ARCoder181105 changed the title feat: implement arrivals-and-departures-for-location endpoint feat: Implement arrivals-and-departures-for-location endpoint Mar 27, 2026
@ARCoder181105 ARCoder181105 marked this pull request as ready for review March 27, 2026 15:54
@ARCoder181105 ARCoder181105 marked this pull request as draft March 28, 2026 03:30
@ARCoder181105 ARCoder181105 marked this pull request as ready for review March 28, 2026 04:40
ARCoder181105 added a commit to ARCoder181105/maglev that referenced this pull request Mar 28, 2026
Port Java arrivals-and-departures-for-location to Go maglev architecture.

- Geospatial stop lookup via in-memory R-tree index
- 3-day window queries (yesterday/today/tomorrow) for overnight trip support
- Batch-fetch routes/trips/stop-times to avoid N+1 queries
- Deduplicated references block (Agencies, Routes, Stops, Trips, Situations)
- nearbyStopIds via haversine distance, excluding stops already in stopIds
- arrivalStatus derived from schedule deviation (LATE/EARLY/ON_TIME/default)
- limitExceeded flag honoring maxCount (mirrors Java MaxCountSupport)
- Add StopWithDistance model and NewArrivalsAndDeparturesForLocationResponse
- Add ORDER BY to GetAgenciesForStops for deterministic results
- Fix nil vehicle passed to BuildTripStatus in arrivals_and_departure_for_stop.go
- Register GET /api/where/arrivals-and-departures-for-location.json
- Add unit, E2E, and context cancellation tests

Closes OneBusAway#787
Fixes OneBusAway#799
@ARCoder181105 ARCoder181105 force-pushed the feature/arrivals-departures-for-location branch from e3c9b4d to b056442 Compare March 28, 2026 11:01
@3rabiii
Copy link
Copy Markdown
Contributor

3rabiii commented Mar 28, 2026

Hey @ARCoder181105 Great work on this endpoint!
While reviewing the legacy Java documentation and code (ArrivalsAndDeparturesForLocationAction) for API parity, I noticed that this endpoint should also accept a few more optional parameters:
1.routeType: an optional comma-delimited list of GTFS routeTypes to filter by.
2.emptyReturnsNotFound: a boolean flag to indicate if an empty result should return a 404 instead of a 200.
I see we are parsing latSpan, lonSpan, time, etc., but it seems these might have been missed in the ArrivalsAndDeparturesForLocationParams struct.

@ARCoder181105
Copy link
Copy Markdown
Collaborator Author

Hi @3rabiii, thanks for the review!

I have implemented the missing parameters you mentioned (routeType and emptyReturnsNotFound which now properly returns a 404).

I also addressed a few other minor parity gaps (added frequencyMinutesBefore and fixed the nearbyStopIds logic to match the Java setIncludeInputIdsInNearby(true) override).

Everything has been tested and verified against the live PugetSound schema. Ready for another look whenever you have time!

@ARCoder181105
Copy link
Copy Markdown
Collaborator Author

Hi @aaronbrethorst , @Ahmedhossamdev

The PR is ready for review ...

@Ahmedhossamdev
Copy link
Copy Markdown
Member

Hi @ARCoder181105
Can you update the openapi now? I have added the new endpoint to our openapi.yml file

@3rabiii
Copy link
Copy Markdown
Contributor

3rabiii commented Mar 29, 2026

@Ahmedhossamdev I already updated it here #807

@ARCoder181105
Copy link
Copy Markdown
Collaborator Author

ARCoder181105 commented Mar 29, 2026

Already done by @3rabiii , PR #807

Port Java arrivals-and-departures-for-location to Go maglev architecture.

- Geospatial stop lookup via in-memory R-tree index
- 3-day window queries (yesterday/today/tomorrow) for overnight trip support
- Batch-fetch routes/trips/stop-times to avoid N+1 queries
- Deduplicated references block (Agencies, Routes, Stops, Trips, Situations)
- nearbyStopIds via haversine distance, excluding stops already in stopIds
- arrivalStatus derived from schedule deviation (LATE/EARLY/ON_TIME/default)
- limitExceeded flag honoring maxCount (mirrors Java MaxCountSupport)
- Add StopWithDistance model and NewArrivalsAndDeparturesForLocationResponse
- Add ORDER BY to GetAgenciesForStops for deterministic results
- Fix nil vehicle passed to BuildTripStatus in arrivals_and_departure_for_stop.go
- Register GET /api/where/arrivals-and-departures-for-location.json
- Add unit, E2E, and context cancellation tests

Closes OneBusAway#787
Fixes OneBusAway#799
…dpoint

- Add parsing and handling for emptyReturnsNotFound parameter
- Add parsing and GTFS filtering for routeType parameter
- Add frequencyMinutesBefore and frequencyMinutesAfter parsing
- Fix nearbyStopIds logic to match Java's includeInputIdsInNearby=true override
…stopSequence offset in arrivals-and-departures-for-location
@ARCoder181105 ARCoder181105 force-pushed the feature/arrivals-departures-for-location branch from e4feabe to 4c74707 Compare April 26, 2026 10:17
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

Warning

Review limit reached

@ARCoder181105, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 1 review/hour. Refill in 38 minutes and 15 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 594515d8-60a0-4bb3-84d0-9ecf11024fb6

📥 Commits

Reviewing files that changed from the base of the PR and between 92c7da8 and 5da7ad8.

📒 Files selected for processing (18)
  • gtfsdb/helpers.go
  • gtfsdb/query.sql
  • gtfsdb/query.sql.go
  • internal/models/arrival_and_departure.go
  • internal/models/response.go
  • internal/models/situation.go
  • internal/models/stops.go
  • internal/restapi/arrival_and_departure_for_stop_handler.go
  • internal/restapi/arrivals_and_departures_for_location_handler.go
  • internal/restapi/arrivals_and_departures_for_location_handler_test.go
  • internal/restapi/arrivals_and_departures_for_stop_handler.go
  • internal/restapi/context_cancellation_test.go
  • internal/restapi/coverage_test.go
  • internal/restapi/input_validation_integration_test.go
  • internal/restapi/reference_utils.go
  • internal/restapi/routes.go
  • internal/restapi/trips_helper.go
  • internal/restapi/trips_helper_test.go

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements the arrivals-and-departures-for-location endpoint, enabling clients to query scheduled and predicted arrivals at a geographic location with spatial and time-window filtering. It includes new data models, trip status refinement, vehicle position inference for existing handlers, comprehensive handler logic with batch queries, full test coverage, and supporting infrastructure.

Changes

Arrivals-and-Departures-for-Location Endpoint

Layer / File(s) Summary
Data Models for Location Queries
internal/models/stops.go, internal/models/situation.go, internal/models/arrival_and_departure.go, internal/models/response.go
StopWithDistance struct models nearby stops; Consequence, ConditionDetails, DiversionPath define situation consequence structure; ArrivalAndDeparture.LastUpdateTime JSON tag always emits; new response constructor wraps location data with references and nearby stops.
Trip Status and Alert Identifier Refinement
internal/restapi/trips_helper.go, internal/restapi/trips_helper_test.go, internal/restapi/arrivals_and_departures_for_stop_handler.go, internal/restapi/reference_utils.go
Trip status no longer sets Scheduled on CANCELED; situation IDs use raw alert.ID instead of combined identifiers; affected entities use scoped FormCombinedID composition; vehicle is passed to BuildTripStatus for context; tests updated to assert Predicted/ScheduleDeviation semantics.
Stop Handler Vehicle Position Inference
internal/restapi/arrival_and_departure_for_stop_handler.go
getNumberOfStopsAway fallback infers vehicle stop sequence from GPS coordinates when direct sequence unavailable, using batch distance-based lookup to find closest stop.
Location Handler Parameters, Validation, and Core Logic
internal/restapi/arrivals_and_departures_for_location_handler.go
Parses spatial (lat/lon/radius/spans) and temporal parameters with defaults and capping; discovers stops; accumulates arrivals with batch route/trip/vehicle fetching; applies trip status, predictions, alerts; builds deduped references; computes nearby stops with distances; sorting by predicted/scheduled arrival time.
Location Handler Tests and Integration
internal/restapi/arrivals_and_departures_for_location_handler_test.go, internal/restapi/context_cancellation_test.go, internal/restapi/input_validation_integration_test.go
Unit tests for parameter parsing (defaults, validation, capping); HTTP integration tests for auth, bad requests, empty areas; end-to-end validation for response shape, reference consistency, sorting, limit enforcement; context cancellation and input validation coverage for the endpoint.
Database Ordering and Route Registration
gtfsdb/query.sql, gtfsdb/query.sql.go, internal/restapi/routes.go, internal/restapi/coverage_test.go, gtfsdb/helpers.go
GetAgenciesForStops adds deterministic ORDER BY; new route registered with cache control and API key validation; block layover bounds corrected; test fixture initialized with clock; clock dependency integrated into reference building.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The PR introduces a substantial new endpoint (1120 lines of core logic) with diverse functionality: parameter validation, spatial/temporal queries, batch database operations, trip status and alert aggregation, reference deduplication, and vehicle position inference. While changes are well-structured across coherent checkpoints, the density of algorithmic logic, cross-file behavioral updates, and test coverage span requires careful review of each integration point.

Possibly related issues

  • OneBusAway/maglev#799: This PR directly implements the arrivals-and-departures-for-location endpoint, completing the feature request with parameter parsing, spatial filtering, arrival aggregation, and references deduplication.

Possibly related PRs

  • OneBusAway/maglev#893: Both PRs modify ArrivalAndDeparture.LastUpdateTime field; #893 migrates the underlying type to ModelTime while this PR changes the JSON serialization tag behavior.

Suggested reviewers

  • aaronbrethorst
  • fletcherw
  • Ahmedhossamdev
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive Most changes are scoped to the new endpoint; however, modifications to trips_helper.go and arrival_and_departure_for_stop_handler.go update shared trip status logic beyond the new endpoint's direct scope. Clarify whether changes to shared trip status and situation ID logic in trips_helper.go and arrival_and_departure_for_stop_handler.go (removing combined agency/alert IDs, vehicle parameter handling) are required scope or intended follow-up improvements.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: implementing the arrivals-and-departures-for-location endpoint.
Linked Issues check ✅ Passed The PR comprehensively implements the arrivals-and-departures-for-location endpoint per #799, including parameter parsing, spatial validation, efficient GTFS processing, JSON schema parity, nearby stops support, and tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
internal/restapi/context_cancellation_test.go (1)

84-95: 429 as an allowed outcome may dilute cancellation-signal quality.

Because all sub-tests reuse the same API/rate limiter, a rate-limit hit can pass this assertion without validating context-cancel handling. Consider isolating limiter state per sub-test (fresh API/mux) for this suite.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/restapi/context_cancellation_test.go` around lines 84 - 95, The test
currently allows HTTP 429 as an acceptable response which can mask failures in
context-cancellation handling because all sub-tests share the same rate limiter;
update the test to initialize a fresh API/mux (and its rate limiter) for each
sub-test so limiter state is isolated, and remove http.StatusTooManyRequests
from the accepted status list in the assertion; specifically, change the
sub-test setup to create a new server/handler (new API/mux/rate limiter
instance) per t.Run and assert only 200, 401, 400, 404, 408, 500, or 504 (no
429).
internal/models/response.go (1)

80-90: Minor: Consider using map[string]any for consistency.

The rest of this file uses map[string]any (lines 22, 31, 41, 55), but this function uses map[string]interface{}. While functionally equivalent since Go 1.18, using any would maintain consistency.

♻️ Optional consistency fix
-	entryData := map[string]interface{}{
+	entryData := map[string]any{
 		"arrivalsAndDepartures": arrivalsAndDepartures,
 		"limitExceeded":         limitExceeded,
 		"nearbyStopIds":         nearbyStopIds,
 		"situationIds":          situationIds,
 		"stopIds":               stopIds,
 	}
-	data := map[string]interface{}{
+	data := map[string]any{
 		"entry":      entryData,
 		"references": references,
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/models/response.go` around lines 80 - 90, The maps entryData and
data are declared with map[string]interface{} but the rest of the file uses
map[string]any; update the declarations for both entryData and data to use
map[string]any (keeping the same keys/values) to maintain type-consistency with
other maps in this file (look for symbols entryData and data in response.go and
replace interface{} with any).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/restapi/arrivals_and_departures_for_location.go`:
- Around line 686-694: Replace direct accesses to nullable fields
trip.TripHeadsign.String and trip.DirectionID.Int64 with explicit .Valid checks:
compute a headsign variable (e.g., headsign := "" ; if trip.TripHeadsign.Valid {
headsign = trip.TripHeadsign.String }) and a direction string (e.g., dir := "" ;
if trip.DirectionID.Valid { dir = strconv.FormatInt(trip.DirectionID.Int64, 10)
}) and pass those into utils.FormCombinedID/strconv.FormatInt call sites instead
of accessing .String/.Int64 directly; apply the same pattern for any other
sql.NullString/sql.NullInt64 usages across handlers in internal/restapi (look
for trip.TripHeadsign and trip.DirectionID to locate occurrences).

---

Nitpick comments:
In `@internal/models/response.go`:
- Around line 80-90: The maps entryData and data are declared with
map[string]interface{} but the rest of the file uses map[string]any; update the
declarations for both entryData and data to use map[string]any (keeping the same
keys/values) to maintain type-consistency with other maps in this file (look for
symbols entryData and data in response.go and replace interface{} with any).

In `@internal/restapi/context_cancellation_test.go`:
- Around line 84-95: The test currently allows HTTP 429 as an acceptable
response which can mask failures in context-cancellation handling because all
sub-tests share the same rate limiter; update the test to initialize a fresh
API/mux (and its rate limiter) for each sub-test so limiter state is isolated,
and remove http.StatusTooManyRequests from the accepted status list in the
assertion; specifically, change the sub-test setup to create a new
server/handler (new API/mux/rate limiter instance) per t.Run and assert only
200, 401, 400, 404, 408, 500, or 504 (no 429).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bc9fb6dd-17be-430c-ba39-c3c40fb2fe67

📥 Commits

Reviewing files that changed from the base of the PR and between 9f32dc9 and 4c74707.

📒 Files selected for processing (12)
  • gtfsdb/query.sql
  • gtfsdb/query.sql.go
  • internal/models/arrival_and_departure.go
  • internal/models/response.go
  • internal/models/stops.go
  • internal/restapi/arrivals_and_departures_for_location.go
  • internal/restapi/arrivals_and_departures_for_location_test.go
  • internal/restapi/arrivals_and_departures_for_stop_handler.go
  • internal/restapi/context_cancellation_test.go
  • internal/restapi/input_validation_integration_test.go
  • internal/restapi/routes.go
  • internal/restapi/trips_helper.go

Comment thread internal/restapi/arrivals_and_departures_for_location_handler.go
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/models/response.go`:
- Around line 62-92: In NewArrivalsAndDeparturesForLocationResponse ensure the
arrivalsAndDepartures parameter is normalized to an empty slice when nil so it
serializes as [] instead of null; update the function near the existing nil
checks for nearbyStopIds/situationIds/stopIds to add a check for
arrivalsAndDepartures and set it to an empty []ArrivalAndDeparture when nil
before building entryData so the response schema stays stable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 36c1862c-4a85-4e03-9758-55bb99397822

📥 Commits

Reviewing files that changed from the base of the PR and between 4c74707 and 3c21702.

📒 Files selected for processing (1)
  • internal/models/response.go

Comment thread internal/models/response.go
@ARCoder181105
Copy link
Copy Markdown
Collaborator Author

Hi @aaronbrethorst , @Ahmedhossamdev

I resolved merge conflicts and failing test

The PR is ready for review....

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
gtfsdb/query.sql (1)

677-677: 💤 Low value

Consider making sort direction explicit.

SQL defaults to ascending order, but explicitly specifying ASC improves readability and makes intent clearer.

✨ Proposed change for clarity
-ORDER BY a.id, stop_times.stop_id;
+ORDER BY a.id ASC, stop_times.stop_id ASC;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gtfsdb/query.sql` at line 677, The ORDER BY clause at the end of the query
("ORDER BY a.id, stop_times.stop_id") relies on implicit ascending order; update
it to be explicit by appending ASC to each sort key (e.g., ORDER BY a.id ASC,
stop_times.stop_id ASC) to improve readability and make intent clear when
editing or reviewing the query.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@gtfsdb/query.sql`:
- Line 677: The ORDER BY clause at the end of the query ("ORDER BY a.id,
stop_times.stop_id") relies on implicit ascending order; update it to be
explicit by appending ASC to each sort key (e.g., ORDER BY a.id ASC,
stop_times.stop_id ASC) to improve readability and make intent clear when
editing or reviewing the query.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b49acfa-42da-4c3b-bcaf-5b07cf917b5a

📥 Commits

Reviewing files that changed from the base of the PR and between 3b9bffc and 5b09806.

📒 Files selected for processing (6)
  • gtfsdb/query.sql
  • gtfsdb/query.sql.go
  • internal/models/arrival_and_departure.go
  • internal/restapi/arrivals_and_departures_for_stop_handler.go
  • internal/restapi/routes.go
  • internal/restapi/trips_helper.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/models/arrival_and_departure.go
  • internal/restapi/routes.go
  • internal/restapi/arrivals_and_departures_for_stop_handler.go
  • internal/restapi/trips_helper.go
  • gtfsdb/query.sql.go

@ARCoder181105 ARCoder181105 marked this pull request as ready for review May 14, 2026 14:27
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
internal/restapi/arrivals_and_departures_for_location.go (2)

586-589: ⚡ Quick win

Don't swallow batch-fetch errors; propagate at least context cancellation.

When batchFetchLocationRoutesAndTrips fails (e.g., ctx cancellation, DB outage), this returns (false, nil), so the outer per-stop loop in arrivalsAndDeparturesForLocationHandler continues iterating over the remaining stops despite the client having gone away or the DB being unhealthy. The internal warning log in the batch helper doesn't help the request loop short-circuit. The inner loop already calls clientCanceledResponse for ctx.Err() at line 599–602; do the same here so cancellation/timeout failures actually abort the handler.

♻️ Proposed change
 	routesLookup, tripsLookup, tripStopCountMap, bErr := api.batchFetchLocationRoutesAndTrips(ctx, stopCode, allActiveStopTimes)
 	if bErr != nil {
-		return false, nil
+		if ctx.Err() != nil {
+			api.clientCanceledResponse(w, r, ctx.Err())
+			return false, ctx.Err()
+		}
+		return false, nil
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/restapi/arrivals_and_departures_for_location.go` around lines 586 -
589, The batchFetchLocationRoutesAndTrips error is being swallowed (returning
false, nil) which prevents request-level cancellation/DB errors from aborting
arrivalsAndDeparturesForLocationHandler; change the handling after calling
batchFetchLocationRoutesAndTrips so that if bErr != nil you check ctx.Err(): if
ctx.Err() != nil call clientCanceledResponse(ctx) (as done in the inner loop)
and return its result, otherwise propagate/return the bErr (or a wrapped error)
so the handler short-circuits on DB failures instead of continuing to the next
stop. Ensure you update the branch that currently returns (false, nil) to return
the appropriate cancellation response or the error from
batchFetchLocationRoutesAndTrips.

297-309: ⚡ Quick win

Several DB/spatial errors are silently dropped.

A few call sites in this handler discard returned errors with _:

  • Line 297: stops, _ := api.GtfsManager.GetStopsForLocation(...) — a failure in the bbox query yields the same "no stops" path as a legitimately empty result, including triggering emptyReturnsNotFound 404s. The caller can't distinguish "out of area" from "lookup failed".
  • Lines 898–899: GetStopsByIDs and GetRoutesForStops errors are dropped during references assembly, which can yield a silently truncated references block (missing stops/routes) without any log line.

At minimum, log these errors (e.g., via api.Logger.Warn) so failures are observable; the line-297 error in particular is worth surfacing as a 5xx since it gates the entire response shape. This matches the warn-and-degrade pattern you already use elsewhere in this file (e.g., fetchStopTimesForDayOffset).

Also applies to: 898-899

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/restapi/arrivals_and_departures_for_location.go` around lines 297 -
309, The handler currently ignores errors from
api.GtfsManager.GetStopsForLocation and later calls
GetStopsByIDs/GetRoutesForStops, causing silent failures; update the call sites
(GetStopsForLocation in the arrivals_and_departures_for_location handler and the
GetStopsByIDs/GetRoutesForStops usages during references assembly) to capture
the returned error, log it with api.Logger.Warn (including context like the
location params or stop IDs), and for the initial GetStopsForLocation error
return an appropriate 5xx response instead of treating it as an empty result
(preserve the existing emptyReturnsNotFound flow only for legitimately empty
slices); follow the existing warn-and-degrade pattern used by
fetchStopTimesForDayOffset for non-fatal downstream errors so references are
logged when truncated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/restapi/arrivals_and_departures_for_location.go`:
- Around line 586-589: The batchFetchLocationRoutesAndTrips error is being
swallowed (returning false, nil) which prevents request-level cancellation/DB
errors from aborting arrivalsAndDeparturesForLocationHandler; change the
handling after calling batchFetchLocationRoutesAndTrips so that if bErr != nil
you check ctx.Err(): if ctx.Err() != nil call clientCanceledResponse(ctx) (as
done in the inner loop) and return its result, otherwise propagate/return the
bErr (or a wrapped error) so the handler short-circuits on DB failures instead
of continuing to the next stop. Ensure you update the branch that currently
returns (false, nil) to return the appropriate cancellation response or the
error from batchFetchLocationRoutesAndTrips.
- Around line 297-309: The handler currently ignores errors from
api.GtfsManager.GetStopsForLocation and later calls
GetStopsByIDs/GetRoutesForStops, causing silent failures; update the call sites
(GetStopsForLocation in the arrivals_and_departures_for_location handler and the
GetStopsByIDs/GetRoutesForStops usages during references assembly) to capture
the returned error, log it with api.Logger.Warn (including context like the
location params or stop IDs), and for the initial GetStopsForLocation error
return an appropriate 5xx response instead of treating it as an empty result
(preserve the existing emptyReturnsNotFound flow only for legitimately empty
slices); follow the existing warn-and-degrade pattern used by
fetchStopTimesForDayOffset for non-fatal downstream errors so references are
logged when truncated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0456dcd8-41d9-4b3b-8e18-40b34e00e525

📥 Commits

Reviewing files that changed from the base of the PR and between 5b09806 and 9d3989b.

📒 Files selected for processing (3)
  • gtfsdb/helpers.go
  • gtfsdb/query.sql
  • internal/restapi/arrivals_and_departures_for_location.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • gtfsdb/query.sql

@ARCoder181105 ARCoder181105 force-pushed the feature/arrivals-departures-for-location branch from 0301916 to ccf440f Compare May 22, 2026 05:42
@ARCoder181105
Copy link
Copy Markdown
Collaborator Author

ARCoder181105 commented May 22, 2026

Hi @aaronbrethorst , @Ahmedhossamdev ,

This PR is ready for review! I was able to successfully replicate the behavior of the legacy Java OBA output for this endpoint.

I verified the data parity using this PROD link:
https://api.pugetsound.onebusaway.org/api/where/arrivals-and-departures-for-location.json?key=TEST&lat=47.653&lon=-122.307&latSpan=0.008&lonSpan=0.008

Please take a look whenever you have time.

@ARCoder181105 ARCoder181105 marked this pull request as draft May 22, 2026 06:58
@ARCoder181105 ARCoder181105 marked this pull request as ready for review May 22, 2026 06:58
@ARCoder181105
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
internal/restapi/context_cancellation_test.go (1)

84-94: ⚡ Quick win

Avoid masking cancellation behavior with rate-limit outcomes.

Allowing 429 here makes the test pass even when cancellation logic regresses. Prefer isolating this test from rate limiting (e.g., dedicated high-limit test API / key) and keep expected statuses focused on cancellation and handler outcomes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/restapi/context_cancellation_test.go` around lines 84 - 94, The test
currently allows http.StatusTooManyRequests which masks cancellation
regressions; in the assertion in context_cancellation_test.go (the block
checking statusCode) remove StatusTooManyRequests from the allowed statuses and
instead ensure the test uses an isolated high-rate test key or test server
(e.g., configure the request setup that exercises the cancellation path in the
same test harness) so that rate limiting cannot produce 429s—update the
assertion to only accept 200, 401, 400, 404, 500, 408, or 504 and adjust the
test setup (API key/limit or dedicated endpoint) to prevent 429 responses during
this test.
internal/restapi/arrivals_and_departures_for_location_handler_test.go (1)

476-493: ⚡ Quick win

Assert entry.limitExceeded explicitly in the limit test.

The test currently only checks returned item count. Please also assert entry["limitExceeded"] == true when truncation happens so the API contract is fully covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/restapi/arrivals_and_departures_for_location_handler_test.go` around
lines 476 - 493, The test TestArrivalsAndDeparturesForLocationLimitExceeded only
asserts the number of arrivals but not the API contract flag; update the test
after extracting entry (in TestArrivalsAndDeparturesForLocationLimitExceeded) to
explicitly assert that entry["limitExceeded"] is present and true (e.g.,
require.True/assert.Equal on entry["limitExceeded"] == true) so the case where
results are truncated is validated alongside the length check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/restapi/arrivals_and_departures_for_location_handler.go`:
- Around line 483-494: The code computes maxBefore/maxAfter but still uses
params.MinutesBefore and params.MinutesAfter when building the time window;
update the stopWindowStart and stopWindowEnd calculations inside the handler so
they use maxBefore and maxAfter instead of
params.MinutesBefore/params.MinutesAfter. Locate the block where stopWindowStart
and stopWindowEnd are set (references: maxBefore, maxAfter, stopWindowStart,
stopWindowEnd, spc.QueryTime) and replace the direct params values with the
computed maxima that include params.FrequencyMinutesBefore and
params.FrequencyMinutesAfter.

---

Nitpick comments:
In `@internal/restapi/arrivals_and_departures_for_location_handler_test.go`:
- Around line 476-493: The test
TestArrivalsAndDeparturesForLocationLimitExceeded only asserts the number of
arrivals but not the API contract flag; update the test after extracting entry
(in TestArrivalsAndDeparturesForLocationLimitExceeded) to explicitly assert that
entry["limitExceeded"] is present and true (e.g., require.True/assert.Equal on
entry["limitExceeded"] == true) so the case where results are truncated is
validated alongside the length check.

In `@internal/restapi/context_cancellation_test.go`:
- Around line 84-94: The test currently allows http.StatusTooManyRequests which
masks cancellation regressions; in the assertion in context_cancellation_test.go
(the block checking statusCode) remove StatusTooManyRequests from the allowed
statuses and instead ensure the test uses an isolated high-rate test key or test
server (e.g., configure the request setup that exercises the cancellation path
in the same test harness) so that rate limiting cannot produce 429s—update the
assertion to only accept 200, 401, 400, 404, 500, 408, or 504 and adjust the
test setup (API key/limit or dedicated endpoint) to prevent 429 responses during
this test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d368b31-47f2-4495-97ca-cf22a78b85ce

📥 Commits

Reviewing files that changed from the base of the PR and between 92c7da8 and 6ccc6c4.

📒 Files selected for processing (18)
  • gtfsdb/helpers.go
  • gtfsdb/query.sql
  • gtfsdb/query.sql.go
  • internal/models/arrival_and_departure.go
  • internal/models/response.go
  • internal/models/situation.go
  • internal/models/stops.go
  • internal/restapi/arrival_and_departure_for_stop_handler.go
  • internal/restapi/arrivals_and_departures_for_location_handler.go
  • internal/restapi/arrivals_and_departures_for_location_handler_test.go
  • internal/restapi/arrivals_and_departures_for_stop_handler.go
  • internal/restapi/context_cancellation_test.go
  • internal/restapi/coverage_test.go
  • internal/restapi/input_validation_integration_test.go
  • internal/restapi/reference_utils.go
  • internal/restapi/routes.go
  • internal/restapi/trips_helper.go
  • internal/restapi/trips_helper_test.go
💤 Files with no reviewable changes (1)
  • internal/restapi/trips_helper_test.go

Comment thread internal/restapi/arrivals_and_departures_for_location_handler.go Outdated
@sonarqubecloud
Copy link
Copy Markdown

@ARCoder181105
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

✅ Actions performed

Full review triggered.

@ARCoder181105
Copy link
Copy Markdown
Collaborator Author

Reference: Arrivals and Departures for Location Endpoint

1. Overview

This document serves as a technical reference for the arrivals-and-departures-for-location endpoint implementation in Maglev. It outlines the architectural flow, key heuristics, and known serialization deviations compared to the legacy Java OneBusAway (OBA) implementation.

Purpose: To retrieve all active scheduled and real-time arrivals/departures for transit stops within a specified geographic bounding box (latSpan/lonSpan) or radius.

2. Architectural Flow & Performance

Arrivals and Departures Workflow

To maintain high performance across a wide geographic radius, the data fetching is heavily batched to avoid N+1 query bottlenecks.

  1. Spatial Resolution: Parses location parameters and queries the spatial index for all stops within the bounds (GetStopsForLocation).
  2. Agency & Timezone Resolution: Batches agency lookups for all returned stops (GetAgenciesForStops) to determine the correct local timezone for the query window.
  3. Active Stop Time Filtering: For each stop, fetches scheduled stop times within the requested time window (minutesBefore / minutesAfter), correctly handling midnight-wraparound logic for trips spanning multiple service dates.
  4. Batch Entity Hydration: Collects all unique route_id and trip_id values from the active stop times and executes bulk queries (GetRoutesByIDs, GetTripsByIDs) to hydrate the static data.
  5. Real-Time Integration: Passes static trip data into BuildTripStatus to merge GTFS-RT updates (calculating scheduleDeviation, distanceFromStop, and predicted times).
  6. References Construction: Deduplicates all referenced agencies, routes, trips, stops, and situations into the final references root block.

3. Key Heuristics & Edge Cases

3.1 Stop Sequence Inference (Nearest-Stop Fallback)

Not all transit agencies provide current_stop_sequence in their GTFS-RT vehicle position feeds (e.g., Sound Transit Link Light Rail). If a vehicle's stop sequence is missing, the endpoint falls back to a new inferStopSequenceFromPosition function. It projects the vehicle's real-time latitude/longitude against the trip's static stop coordinates to determine the geometrically closest stop, restoring the numberOfStopsAway calculation.

3.2 Arrival Status Derivation

Arrival statuses (ON_TIME, LATE, EARLY) are derived directly from the real-time scheduleDeviation to match Java OBA logic:

  • > 300s (5+ min late) → LATE
  • < -180s (3+ min early) → EARLY
  • Otherwise → ON_TIME

4. Known Parity Deviations

The core math (deviations, distances, sequences) is strictly verified against Java OBA. However, there are a few intentional formatting and serialization differences to align with Go's standard library and OpenAPI conformance:

Feature Legacy Java OBA Maglev (Go) Implementation Reason
stopIds Array Contains duplicate stop IDs Strictly deduplicated Maglev utilizes a hash map for collection, returning only unique IDs in the response root.
frequency Field Emits "frequency": null Omitted from TripStatus OpenAPI spec declares this field non-nullable; Go omits it via omitempty rather than violating the spec.(also following the comment made at this field)
Float Serialization Forces .0 (e.g., 270.0) Strips trailing .0 (e.g., 270) Standard Go encoding/json behavior for whole numbers.
Legacy scheduled flag Absent / Varies Included as "scheduled": false Present in openAPI specs.

5. Testing & Verification

Parity was verified using the following production payload:
https://api.pugetsound.onebusaway.org/api/where/arrivals-and-departures-for-location.json?key=TEST&lat=47.653&lon=-122.307&latSpan=0.008&lonSpan=0.008

All distanceFromStop, scheduleDeviation, and numberOfStopsAway values accurately match the legacy output when provided with identical GTFS and GTFS-RT feed inputs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement arrivals-and-departures-for-location endpoint

3 participants