Cache cue sheets so each route is enriched once (#30) #89
No reviewers
Labels
No labels
area:companion
area:docs
area:shared
area:tooling
area:watchapp
blocker
kind:chore
kind:feature
kind:spike
kind:test
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
robert/PedalPebble!89
Loading…
Reference in a new issue
No description provided.
Delete branch "area/cue-sheet-cache"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #30.
Builds on tonight's merged #26 (
TieredCueSheetEnricher/CueSheetOutcome, PR #79) and #25 (RouteLibrary/StoredRoute, PR #88).What this adds
CueSheetCache(companion/core/.../route/enrich/CueSheetCache.kt):getOrEnrich(routeId, rawGpx = null)returns a cachedCueSheetOutcomeon a hit — noTieredCueSheetEnricherinvocation at all, so no network call and no BRouter round-trip — and runs+caches the chain on a miss.forceReEnrich(routeId, rawGpx = null)always re-runs it (the "manual re-enrich, e.g. after installing BRouter" criterion). Both callRouteLibrary.updateEnrichment(), the hook #25 built precisely for this, so "which backend produced it" is recorded and already surfaces through the existingRouteLibraryScreenstatus line — no new display logic needed."Enricher version" = one pipeline-wide
CURRENT_ENRICHER_VERSION: Int, not one per tier. The event that should invalidate a cached result is either a tier's logic changing or a previously-nulltier slot getting wired intoTieredCueSheetEnricher(#27/#28/#29 landing) — a single number covers both without inventing a second "pipeline composition" version. Cache rows are keyed by route id in storage, with the enricher version compared at read time (a mismatch is a miss, not a stored-but-invalid row that accumulates).Persistence: a new Room table (
cue_sheets), not extra columns onroutes.RouteEntityalready carries the summary (enrichmentStatus/enrichmentTier) the list UI needs cheaply; this table holds the actual cue payload, encoded via a new hand-rolledCueSheetCodec(mirrorsRoutePointsCodec's existing precedent — nokotlinx-serializationdependency, one reader/one writer, JVM-tested round-trip).RouteDatabasebumps to schema version 2 with a realMigration(1, 2)(a singleCREATE TABLE) rather thanfallbackToDestructiveMigration(), even though nothing has shipped to a device outside this project's own development yet — the correct habit costs nothing here.The
rawGpxgap is stated honestly, not papered over. Tier 0 (GpxCueEnricher) needs the original GPX text; nothing in the route library persists that today —StoredRoute/RouteEntitycarry only the parsed, simplified geometry, andGpxImporter's raw text is discarded the moment parsing finishes.getOrEnrich/forceReEnrichaccept an optionalrawGpx; when absent it becomes""andGpxCueEnricherfails closed to no cues exactly as it already does for malformed source text (seeGpxCueEnricherTest), falling through to tier 1+. This is not a regression — no caller anywhere in the codebase can supplyrawGpxpost-import yet — and the concrete scenario the issue names ("after installing BRouter") is a tier-1 case that needs norawGpxat all. PersistingrawGpx(a schema change toStoredRoute/RouteEntity) is out of scope here and flagged as a real follow-up if a future issue needs tier 0 retried after import.No automatic "enrich at import" or "ride start" trigger is wired. Neither exists in the app yet (
RouteLibrary.importRoutestill starts every route atPENDING, per #25's own honesty note; there is no ride-start flow at all). This issue is scoped to exposing the on-demand "get cached, enrich only on miss" API a future trigger will call — plus a small UI hookup for the acceptance criterion that explicitly asks for manual re-enrich today: a "Re-enrich" button onRouteDetailScreen(RouteLibraryScreen.kt), wired viaRouteStore.cueSheetCache(context).Tests
JVM,
:companion:core, all passing (96 tests total across the module, 13 new):CueSheetCacheTest— cache hit (enricher never invoked), cache miss, version-bump invalidation,forceReEnrichbypass, unknown-route-id handling (no storage/enricher touch), the optional-rawGpxdefault, and one end-to-end run against the realGpxCueEnricher+ the realbikerouter-style-cues.gpxfixture (not just fakes).CueSheetCodecTest— encode/decode round-trip, including a street name containing both separator characters../gradlew :companion:core:test— green.Verification
Host-verified: everything under
:companion:core(./gradlew :companion:core:test, using a JBR JDK asJAVA_HOMEsince this sandbox's system JRE has no compiler).Hand-reviewed only, not compiled: everything under
:companion:route—CueSheetEntity/CueSheetDao/RoomCueSheetCacheStorage, theRouteDatabasemigration, and theRouteStore/RouteLibraryScreen/RouteLibraryActivitywiring. No Android SDK in this sandbox, the same gap every companion PR this session has carried since #25.No Gradle dependency was added or bumped — Room 2.8.4 was already pinned by #25. The
Migration/SupportSQLiteDatabaseAPI used in the new migration was checked against developer.android.com's current Room migration guide (not assumed from memory).https://claude.ai/code/session_01DAoXbRmJUf2uxNYBfdAXPt