Skip to content

Disambiguate stops-for-route directions sharing a headsign - #1251

Merged
aaronbrethorst merged 3 commits into
mainfrom
fix/stops-for-route-headsign-disambiguation
Jul 30, 2026
Merged

Disambiguate stops-for-route directions sharing a headsign#1251
aaronbrethorst merged 3 commits into
mainfrom
fix/stops-for-route-headsign-disambiguation

Conversation

@Ahmedhossamdev

@Ahmedhossamdev Ahmedhossamdev commented Jul 25, 2026

Copy link
Copy Markdown
Member

Fixes: #1250

When two direction groups resolved to the same most-common headsign,
both were emitted with identical name.names, and the name-based group
sort left their order nondeterministic. The reference implementation
appends a direction disambiguator whenever directions collide.

Append " - " to every group's name on a collision, before
the sort so ordering is deterministic. Update the RABA end-to-end test
(both directions are "Shasta Lake") and add a unit test for the
collision and no-collision paths.

Summary by CodeRabbit

  • Bug Fixes
    • Improved stop-group (destination) naming when a route has multiple directions with the same headsign.
    • Added deterministic disambiguation to prevent duplicate stop-group names and ensure consistent ordering.
    • Updated end-to-end behavior so outbound/inbound stop-group names and their display name lists reflect collision-free, direction-suffixed values.
  • Tests
    • Added unit tests covering normal cases, duplicate collisions, and order-independent results for stop-group name disambiguation.

When two direction groups resolved to the same most-common headsign,
both were emitted with identical name.names, and the name-based group
sort left their order nondeterministic. The reference implementation
appends a direction disambiguator whenever directions collide.

Append " - <direction id>" to every group's name on a collision, before
the sort so ordering is deterministic. Update the RABA end-to-end test
(both directions are "Shasta Lake") and add a unit test for the
collision and no-collision paths.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7696a4a8-87af-4ca5-96ac-30036c8d6940

📥 Commits

Reviewing files that changed from the base of the PR and between 32df812 and 0c496b9.

📒 Files selected for processing (2)
  • internal/restapi/stops_for_route_handler.go
  • internal/restapi/stops_for_route_handler_test.go

📝 Walkthrough

Walkthrough

Route stop-group assembly now detects shared destination names, appends each group ID to colliding name fields, and sorts the disambiguated groups. End-to-end and unit tests cover distinct names, collisions, and order-independent collision resolution.

Changes

Stop group name disambiguation

Layer / File(s) Summary
Resolve duplicate destination names
internal/restapi/stops_for_route_handler.go, internal/restapi/stops_for_route_handler_test.go
processTripGroups disambiguates groups before sorting, appending group IDs to colliding Name.Name and Name.Names values. Tests cover route expectations, distinct names, suffix collisions, and input-order independence.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: burma-shave

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: disambiguating shared stops-for-route headsigns.
Linked Issues check ✅ Passed The changes append disambiguators for colliding direction names and add tests, matching issue #1250.
Out of Scope Changes check ✅ Passed The code and tests stay focused on headsign disambiguation and deterministic ordering, with no obvious unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@github-actions

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 1.7 ms
Error rate 0.00%
Total requests 325
Req/sec 10.7

Smoke test config: 5 VUs x 30s. Thresholds: p(95) < 300ms, error rate < 1%.

Full results uploaded as workflow artifact: k6-smoke-summary.

@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: 1

🤖 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/stops_for_route_handler.go`:
- Around line 364-367: Update the group-name disambiguation logic around the
groups iteration in internal/restapi/stops_for_route_handler.go: retain
per-original-name occurrence counts and suffix only groups whose name occurs
more than once, leaving unique names unchanged. Update the expectation in
internal/restapi/stops_for_route_handler_test.go at lines 370-372 so the unique
Express group remains Express.
🪄 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: 16d253b3-c587-4aa9-abcf-3484185baeaa

📥 Commits

Reviewing files that changed from the base of the PR and between 77e50aa and f67708d.

📒 Files selected for processing (2)
  • internal/restapi/stops_for_route_handler.go
  • internal/restapi/stops_for_route_handler_test.go

Comment thread internal/restapi/stops_for_route_handler.go Outdated
@github-actions

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 2.3 ms
Error rate 0.00%
Total requests 327
Req/sec 10.7

Smoke test config: 5 VUs x 30s. Thresholds: p(95) < 300ms, error rate < 1%.

Full results uploaded as workflow artifact: k6-smoke-summary.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/restapi/stops_for_route_handler.go (1)

350-370: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Make generated names collision-safe.

A unique headsign can already equal a generated suffix: A/A/A - 0 becomes A - 0/A - 1/A - 0. The final names are still duplicated, and sorting by Name.Name remains input-order dependent. Ensure final names are unique and add a regression test for this case; an ID tie-breaker alone would not fix the duplicate wire names.

🤖 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/stops_for_route_handler.go` around lines 350 - 370, Update
disambiguateGroupNames so generated names are checked against all final names,
including originally unique names, and collisions are resolved deterministically
with an additional suffix or otherwise unique naming scheme. Preserve unchanged
names when they remain unique, ensure both Name.Name and Name.Names match the
final value, and add a regression test covering A/A/A - 0 to verify unique names
and stable sorting independent of input order.
🤖 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.

Outside diff comments:
In `@internal/restapi/stops_for_route_handler.go`:
- Around line 350-370: Update disambiguateGroupNames so generated names are
checked against all final names, including originally unique names, and
collisions are resolved deterministically with an additional suffix or otherwise
unique naming scheme. Preserve unchanged names when they remain unique, ensure
both Name.Name and Name.Names match the final value, and add a regression test
covering A/A/A - 0 to verify unique names and stable sorting independent of
input order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0612fda2-b285-42b5-9d65-ef684ec6cddd

📥 Commits

Reviewing files that changed from the base of the PR and between f67708d and 32df812.

📒 Files selected for processing (2)
  • internal/restapi/stops_for_route_handler.go
  • internal/restapi/stops_for_route_handler_test.go

…lliding group names and add tests for edge cases
@github-actions

Copy link
Copy Markdown

Performance Smoke Test Results

Status: PASSED

Metric Value
p(95) latency 2.1 ms
Error rate 0.00%
Total requests 330
Req/sec 10.9

Smoke test config: 5 VUs x 30s. Thresholds: p(95) < 300ms, error rate < 1%.

Full results uploaded as workflow artifact: k6-smoke-summary.

@sonarqubecloud

Copy link
Copy Markdown

@aaronbrethorst

Copy link
Copy Markdown
Member

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

@aaronbrethorst aaronbrethorst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a tidy fix for a genuinely annoying bug — two groups both named "Shasta Lake" is useless to a rider, and it also left the name-based sort with a tie, so group order wasn't even stable. Nice that fixing the first problem fixes the second for free.

What I checked:

  • The suffix format matches the reference implementation exactly: name + " - " + direction. (The Java side also capitalizes the first character, but that's a no-op for numeric direction ids, so the output is identical.)
  • name.type stays "destination" and name.names is kept in sync with name.name — easy to forget one of those.
  • The function only rewrites name fields; it never adds, removes, or reorders groups, so nothing downstream can see a duplicate or empty group appear.
  • Mutating through groups[i] rather than a range copy is correct, and the result is a function of the group set alone — the TestDisambiguateGroupNames order-independence case is the right property to pin down, since it's what makes the subsequent sort deterministic.
  • Re-checking against the resulting names rather than the original ones is a nice touch, and the bound is safe: direction ids are unique, so any two colliding groups separate on the next pass.

The one deliberate divergence — suffixing only the colliding groups instead of every group whenever any collision exists — is documented in the doc comment, which I appreciate. It's unreachable today regardless: groupTripsByDirection keys on direction_id, which GTFS restricts to 0 and 1 (NULL coerces to 0), so you can't get three groups without the similarity-based grouping we haven't implemented. If we ever add that, this is worth revisiting, and the comment will point whoever does it in the right direction.

Updating the RABA end-to-end expectations rather than working around them was the right call too.

Merging.

@aaronbrethorst
aaronbrethorst merged commit dded35b into main Jul 30, 2026
9 checks passed
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-route: direction groups sharing a headsign get identical names

2 participants