Cue-sheet review on the phone before riding #55

Open
opened 2026-08-31 17:30:07 +02:00 by robert · 2 comments
robert commented 2026-08-31 17:30:07 +02:00 (Migrated from git.butzei.de)

Goal

The only practical defence against a mis-enriched route. If BRouter places a turn 30 m off, or the geometric fallback invents a turn on a curve, the rider should find out at home rather than at the junction.

Acceptance criteria

  • Scrollable list of every cue: direction icon, distance along route, street name
  • Tapping a cue shows it on a map preview
  • Which enricher produced the sheet is displayed, so a geometric sheet is visibly less trustworthy
  • Cues flagged where the geometric check disagrees with the backend's classification
  • Suspiciously dense cue clusters flagged - usually a curve mis-read as a sequence of turns
  • A cue can be deleted or its direction corrected by hand, and edits persist with the cached sheet
  • Total cue count and route distance shown, as a sanity check against the planner

Files

  • companion/.../route/enrich/CueSheetReviewScreen.kt

Update — 2026-09-01: show which tier produced each cue

The enrichment chain now reports its tier per cue (D26), and BRouter enrichment carries a measured
divergence from the source route (D25, #27). Both belong in the review — this screen is where a rider
finds out whether to trust the cue sheet.

  • Each cue shows which tier produced it: from the file, BRouter, Valhalla, or a geometric guess
  • Geometric guesses (NAV_CUE_CONFIDENCE = 0) visibly marked as guesses
  • BRouter's measured divergence from the source GPX shown for the route as a whole
  • A route enriched entirely at tier 3 says so plainly before the ride starts
## Goal The only practical defence against a mis-enriched route. If BRouter places a turn 30 m off, or the geometric fallback invents a turn on a curve, the rider should find out at home rather than at the junction. ## Acceptance criteria - [ ] Scrollable list of every cue: direction icon, distance along route, street name - [ ] Tapping a cue shows it on a map preview - [ ] Which enricher produced the sheet is displayed, so a geometric sheet is visibly less trustworthy - [ ] Cues flagged where the geometric check disagrees with the backend's classification - [ ] Suspiciously dense cue clusters flagged - usually a curve mis-read as a sequence of turns - [ ] A cue can be deleted or its direction corrected by hand, and edits persist with the cached sheet - [ ] Total cue count and route distance shown, as a sanity check against the planner ## Files - `companion/.../route/enrich/CueSheetReviewScreen.kt` ## Update — 2026-09-01: show which tier produced each cue The enrichment chain now reports its tier per cue (D26), and BRouter enrichment carries a measured divergence from the source route (D25, #27). Both belong in the review — this screen is where a rider finds out whether to trust the cue sheet. - [ ] Each cue shows **which tier produced it**: from the file, BRouter, Valhalla, or a geometric guess - [ ] Geometric guesses (`NAV_CUE_CONFIDENCE = 0`) visibly marked as guesses - [ ] BRouter's measured divergence from the source GPX shown for the route as a whole - [ ] A route enriched entirely at tier 3 says so plainly before the ride starts
Owner

Opened PR #118 (area/cue-sheet-review -> main): #118

Covers the original acceptance criteria and the 2026-09-01 tier update, with real dense-cluster and geometric-mismatch detection (documented thresholds, tested against real GpxCueEnricher/tier-0 fixtures) and edits (delete/correct) persisted through a new CueSheetCache.updateCues on the existing cache row.

One line from the 2026-09-01 update is honestly out of scope for now: "BRouter's measured divergence from the source GPX, shown for the route as a whole." That figure is D25's divergence measurement, which is BRouterEnricher (#27)'s own job to compute — nothing in this codebase carries a divergence number today for this screen to display, and building UI for a number nothing produces would mean fabricating one. Added a dependency edge onto #27 for this reason; a real BRouterEnricher PR should extend wherever it lands that figure (CueSheetOutcome or similar) and this screen together once it exists.

Everything else dispatches generically on EnrichmentTier/Direction rather than assuming which tiers exist, so #27/#28/#29 landing later needs no changes here — see the PR description for exactly which parts are tested against real tier-0 output vs. hand-built EnrichmentTier.GEOMETRY fixtures (since #29 doesn't exist yet either).

Opened PR #118 (`area/cue-sheet-review` -> `main`): https://git.butzei.de/robert/PedalPebble/pulls/118 Covers the original acceptance criteria and the 2026-09-01 tier update, with real dense-cluster and geometric-mismatch detection (documented thresholds, tested against real `GpxCueEnricher`/tier-0 fixtures) and edits (delete/correct) persisted through a new `CueSheetCache.updateCues` on the existing cache row. One line from the 2026-09-01 update is honestly out of scope for now: **"BRouter's measured divergence from the source GPX, shown for the route as a whole."** That figure is D25's divergence measurement, which is `BRouterEnricher` (#27)'s own job to compute — nothing in this codebase carries a divergence number today for this screen to display, and building UI for a number nothing produces would mean fabricating one. Added a dependency edge onto #27 for this reason; a real `BRouterEnricher` PR should extend wherever it lands that figure (`CueSheetOutcome` or similar) and this screen together once it exists. Everything else dispatches generically on `EnrichmentTier`/`Direction` rather than assuming which tiers exist, so #27/#28/#29 landing later needs no changes here — see the PR description for exactly which parts are tested against real tier-0 output vs. hand-built `EnrichmentTier.GEOMETRY` fixtures (since #29 doesn't exist yet either).
Owner

Closed by PR #118 (area/cue-sheet-review).

New CueSheetReviewScreen (companion/route), reached from a "Review cues" button on RouteDetailScreen (this project has no navigation library, per #46's earlier finding — same local-state-toggle pattern as everywhere else). Shows the full cue list (icon/street/distance), which tier produced the sheet (with an honest "every cue is a geometric guess, tier 3" banner when the whole sheet is EnrichmentTier.GEOMETRY), tap-to-preview via RoutePreview's new highlightPoint param (reused, not duplicated), hand delete/correct persisted through a new CueSheetCache.updateCues() that writes back through the exact same cached row getOrEnrich/forceReEnrich already use, and the total cue count + route distance sanity line.

Both detection heuristics are real, tested logic, not hand-waved: findDenseCueClusters (3+ cues within a 40m sliding window — deliberately the same span D27 already reasoned about for the geometric fallback's own heading-delta check) and findGeometricMismatches (independent ±20m look-behind/look-ahead bearing computation, 20° straight/turn threshold — intentionally more sensitive than D27's 25°, since a false positive here only costs a glance, not a wrong instruction). Two new RouteGeodesy.kt helpers (bearingDegrees, pointAtDistanceMeters) back these.

Honestly scoped: only tier 0 (GpxCueEnricher, #63) exists in any build today — #27 (BRouter), #28 (Valhalla matching), #29 (geometric fallback) are all still open. The tier-display logic dispatches generically on the EnrichmentTier enum (exhaustive when, no hardcoded special-casing) so it needs no changes once those land, but the "entirely tier 3" banner is only tested against hand-built Cue data tagged GEOMETRY, honestly noted as such. "BRouter's measured divergence from the source GPX" (the issue's own 2026-09-01 update) has no real data anywhere in the codebase to display — rather than fabricate a number, this PR added the dependency edge #55 → #27 and left that acceptance line unbuilt; a real BRouterEnricher PR should extend CueSheetOutcome and this screen together once the figure exists.

A genuine, non-fabricated finding from a real fixture: the dense-cluster/mismatch tests run against bikerouter-style-cues.gpx (the same fixture #30's CueSheetCacheTest already uses) and correctly flag both of its real cues as geometric mismatches — not because the cue text is wrong, but because GpxCueEnricher snaps a cue to the nearest RDP-simplified polyline segment rather than the route's actual turn locus, which this independent check has no way to know and correctly calls out as worth a rider's second look either way.

Real verification: genuine ./gradlew :companion:core:test :companion:route:assembleDebug :companion:assembleDebug --rerun-tasks full rebuild, all green (CueSheetReviewAnalysisTest 14/14, CueSheetCacheTest 11/11, RouteGeodesyTest 20/20, full Compose UI compile clean with the required @OptIn(ExperimentalMaterial3Api::class) present).

42 issues closed.

Closed by PR #118 (`area/cue-sheet-review`). New `CueSheetReviewScreen` (`companion/route`), reached from a "Review cues" button on `RouteDetailScreen` (this project has no navigation library, per #46's earlier finding — same local-state-toggle pattern as everywhere else). Shows the full cue list (icon/street/distance), which tier produced the sheet (with an honest "every cue is a geometric guess, tier 3" banner when the whole sheet is `EnrichmentTier.GEOMETRY`), tap-to-preview via `RoutePreview`'s new `highlightPoint` param (reused, not duplicated), hand delete/correct persisted through a new `CueSheetCache.updateCues()` that writes back through the exact same cached row `getOrEnrich`/`forceReEnrich` already use, and the total cue count + route distance sanity line. Both detection heuristics are real, tested logic, not hand-waved: `findDenseCueClusters` (3+ cues within a 40m sliding window — deliberately the same span D27 already reasoned about for the geometric fallback's own heading-delta check) and `findGeometricMismatches` (independent ±20m look-behind/look-ahead bearing computation, 20° straight/turn threshold — intentionally more sensitive than D27's 25°, since a false positive here only costs a glance, not a wrong instruction). Two new `RouteGeodesy.kt` helpers (`bearingDegrees`, `pointAtDistanceMeters`) back these. **Honestly scoped**: only tier 0 (`GpxCueEnricher`, #63) exists in any build today — #27 (BRouter), #28 (Valhalla matching), #29 (geometric fallback) are all still open. The tier-display logic dispatches generically on the `EnrichmentTier` enum (exhaustive `when`, no hardcoded special-casing) so it needs no changes once those land, but the "entirely tier 3" banner is only tested against hand-built `Cue` data tagged `GEOMETRY`, honestly noted as such. "BRouter's measured divergence from the source GPX" (the issue's own 2026-09-01 update) has no real data anywhere in the codebase to display — rather than fabricate a number, this PR added the dependency edge **#55 → #27** and left that acceptance line unbuilt; a real `BRouterEnricher` PR should extend `CueSheetOutcome` and this screen together once the figure exists. A genuine, non-fabricated finding from a real fixture: the dense-cluster/mismatch tests run against `bikerouter-style-cues.gpx` (the same fixture #30's `CueSheetCacheTest` already uses) and correctly flag both of its real cues as geometric mismatches — not because the cue text is wrong, but because `GpxCueEnricher` snaps a cue to the nearest RDP-simplified polyline segment rather than the route's actual turn locus, which this independent check has no way to know and correctly calls out as worth a rider's second look either way. **Real verification**: genuine `./gradlew :companion:core:test :companion:route:assembleDebug :companion:assembleDebug --rerun-tasks` full rebuild, all green (`CueSheetReviewAnalysisTest` 14/14, `CueSheetCacheTest` 11/11, `RouteGeodesyTest` 20/20, full Compose UI compile clean with the required `@OptIn(ExperimentalMaterial3Api::class)` present). 42 issues closed.
Sign in to join this conversation.
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
robert/PedalPebble#55
No description provided.