Skip to content

Fix stop-code queries in stops-for-location - #1270

Open
Mister-Raggs wants to merge 3 commits into
OneBusAway:mainfrom
Mister-Raggs:fix/1269-1239-stops-for-location-query-mode
Open

Fix stop-code queries in stops-for-location#1270
Mister-Raggs wants to merge 3 commits into
OneBusAway:mainfrom
Mister-Raggs:fix/1269-1239-stops-for-location-query-mode

Conversation

@Mister-Raggs

@Mister-Raggs Mister-Raggs commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1269 and #1239 (sub-issues of the #1235 spec review).

These two are in one PR because they can't be reviewed independently: the closest-match fallback from #1239 is dead code while query mode still drops stops that have no service on the queried date (#1269). One commit each.

Return all routes for stop-code queries (#1269)

Routes were resolved through the active-service query for every search mode, and stops with nothing scheduled that day were dropped — so a stop-code lookup returned nothing outside the stop's service window. The existing test documents the workaround this forced:

// Stop 2042 only has trips on service c_2713_b_80332_d_56 (Thu/Fri/Sat, May 22 - Sep 6, 2025).
// Use a Friday within that range to ensure active service.

Per the spec (Main Success Scenario step 6, Suspected Defect #2), stop-code queries report every route serving the stop regardless of date. Geospatial searches are unchanged.

Search stop codes feed-wide with a closest-match fallback (#1239)

Candidates were taken only from stops already inside the search bounds, so there was no fallback, and the bounds used the narrow default radius. Candidates now come from the whole feed (GetStopsByCode, capped at 10 as the spec describes), are filtered to the bounds, and fall back to the nearest candidate when none qualify.

Three things worth a reviewer's eye

  1. Out-of-range short-circuit is scope beyond both issues. An unbounded fallback returns the nearest match worldwidelat=0&lon=0&query=2042 returned a Redding stop. Bounds that miss every agency's coverage area now return an empty list before any search runs, which is Extension 3a and what the existing TestStopsForLocationQueryOutOfArea was already asserting.

  2. Truncation is deterministic, not shuffled. The spec says legacy shuffles before truncating in query mode. DC-6 records that shuffle as a defect on routes-for-location, so I didn't reproduce it here — happy to switch if you'd rather match legacy exactly.

  3. GetStopsByCode keeps maglev's exact-match semantics. The spec describes legacy searching a stop-code index; changing match semantics felt like a separate gap rather than part of these two.

Also note the combined-ID sort fixture from #1244 moves inside the RABA coverage area — the out-of-range short-circuit would otherwise empty its result. Same assertions, different coordinates.

Tests

TestStopsForLocationQueryIgnoresActiveService (lookup on a day the stop has no service) and TestStopsForLocationQueryFallsBackToClosestMatch (radius that excludes the match). Both were confirmed to fail against the pre-fix implementation.

Verified: full suite in both sqlite_fts5 and purego modes, go vet both, race detector on restapi/gtfs, gofmt.

Summary by CodeRabbit

  • New Features

    • Added stop-code search support, returning up to 10 matching stops.
    • Stop-code searches now include stops regardless of active service status.
    • Searches can find stops outside the requested radius and select the closest match when needed.
    • Matching stops are associated with available routes and agencies for improved results.
  • Bug Fixes

    • Prevented searches from running when requested bounds fall outside agency coverage areas.
    • Improved handling of stops without scheduled routes and malformed route information.

Routes for a matched stop were resolved through the active-service query
used by geospatial searches, and stops with nothing scheduled that day
were then dropped. A stop-code lookup therefore returned nothing outside
the stop's service window, while legacy resolves the stop regardless of
the queried date.

Split the route lookup by search mode: stop-code queries report every
route serving the stop, geospatial searches keep reporting only routes
active on the queried date, and only geospatial searches drop stops that
have none.

Fixes OneBusAway#1269
Stop-code candidates were drawn only from stops already inside the search
bounds, so a code that fell outside them returned nothing, where legacy
falls back to the nearest matching stop. The bounds also used the narrow
default radius rather than the wider one stop-code queries expect.

Candidates now come from the whole feed, are filtered to the search
bounds, and fall back to the nearest candidate when none qualify.

That fallback needs a limit, so bounds which miss every agency's coverage
area now short-circuit to an empty list before any search runs, rather
than returning the nearest match worldwide. The combined-ID sort fixture
moves inside the coverage area for the same reason.

Fixes OneBusAway#1239
Copilot AI review requested due to automatic review settings July 31, 2026 23:34

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Mister-Raggs, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e08ebcf8-820c-46e0-a9d0-5c412f708ef8

📥 Commits

Reviewing files that changed from the base of the PR and between c35c2b6 and fe73de7.

📒 Files selected for processing (6)
  • gtfsdb/query.sql
  • gtfsdb/query.sql.go
  • gtfsdb/schema.sql
  • internal/gtfs/gtfs_manager.go
  • internal/restapi/stops_for_location_handler.go
  • internal/restapi/stops_for_location_handler_test.go
📝 Walkthrough

Walkthrough

Adds prepared stop-code lookup support, closest-stop fallback, and query-mode route aggregation without active-date filtering. The REST handler now preserves eligible stops without active service and skips searches outside agency coverage.

Changes

Stop-code lookup

Layer / File(s) Summary
Database stop-code lookup
gtfsdb/db.go, gtfsdb/query.sql, gtfsdb/query.sql.go
Adds GetStopsByCode, which returns up to 10 exact code matches and propagates database errors.
Stop-code resolution and fallback
internal/gtfs/gtfs_manager.go
Queries stop codes across the feed, filters by bounds, and returns the closest candidate when no match is in bounds.
Query-mode route aggregation
internal/restapi/stops_for_location_handler.go, internal/restapi/stops_for_location_handler_test.go
Loads all routes for stop-code queries, retains stops without active service, skips out-of-coverage searches, and tests fallback behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: LGTM

Suggested reviewers: ahmedhossamdev, arcoder181105

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation satisfies #1269 by using all stop routes without date filtering and retaining stops with no active service.
Out of Scope Changes check ✅ Passed The changes align with the linked objectives, including query-mode route handling, candidate fallback, coverage checks, and tests.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change to stop-code query behavior in the stops-for-location endpoint.

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.

@Mister-Raggs Mister-Raggs self-assigned this Jul 31, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

🤖 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 `@gtfsdb/query.sql`:
- Around line 315-337: Add an index for the stops.code column through the
appropriate schema change or SQL migration, ensuring the GetStopsByCode lookup
is indexed. Then run make models to regenerate the model artifacts and
regenerate gtfsdb/query.sql.go so the repository reflects the schema update.
- Around line 315-337: The GetStopsByCode query should return the complete
candidate set for stopsMatchingCode instead of limiting results to 10 before
bounds and radius filtering. Confirm duplicate stop codes are possible across
merged feeds and remove the LIMIT 10 accordingly; add an explicit deterministic
ORDER BY using stable stop fields, preferably id as the final tie-breaker.

In `@internal/gtfs/gtfs_manager.go`:
- Around line 391-428: Update stopsMatchingCode to sort the in-bounds within
candidates by distance to loc before applying the maxCount truncation, following
the existing GetStopsForLocation ORDERED_BY_CLOSEST ordering pattern. Preserve
the current fallback and return behavior, but ensure within[:maxCount] contains
the nearest stops.

In `@internal/restapi/stops_for_location_handler_test.go`:
- Around line 360-384: Add a test covering the stopsMatchingCode branch where
more than maxCount matching stops are within the search bounds. Seed multiple
stops with the same code, invoke the stops-for-location query so the in-bounds
matches exceed maxCount, and assert the response contains exactly the truncated
count and reports limitExceeded as true, following existing response-test
conventions.

In `@internal/restapi/stops_for_location_handler.go`:
- Around line 96-104: Reuse the existing outOfRange value from
CheckIfOutOfBounds(loc) at both response-building sites around the handlers’
later branches, replacing the repeated CheckIfOutOfBounds(loc) calls while
preserving the current response behavior.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 63f70173-6860-4214-ad8f-1bf669b38bdf

📥 Commits

Reviewing files that changed from the base of the PR and between c64305e and c35c2b6.

📒 Files selected for processing (6)
  • gtfsdb/db.go
  • gtfsdb/query.sql
  • gtfsdb/query.sql.go
  • internal/gtfs/gtfs_manager.go
  • internal/restapi/stops_for_location_handler.go
  • internal/restapi/stops_for_location_handler_test.go

Comment thread gtfsdb/query.sql
Comment thread internal/gtfs/gtfs_manager.go
Comment thread internal/restapi/stops_for_location_handler_test.go
Comment thread internal/restapi/stops_for_location_handler.go
The candidate query had no ORDER BY, so which rows survived the ten-row
cap depended on storage order, and stops.code had no index to serve the
lookup. Order candidates by id and index the column.

Sort in-bounds matches by distance before truncating to maxCount so the
nearest ones are kept rather than an arbitrary subset, matching how the
route-type path already orders its results. Cover the truncation branch,
which merged feeds can reach by repeating a stop code.
@sonarqubecloud

Copy link
Copy Markdown

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.

stops-for-location: query mode must not time-filter routeIds (blocks #1239)

2 participants