Insights: read location context from Apollo instead of the stale local location_history #177

Closed
opened 2026-10-17 03:16:32 +00:00 by cameron · 0 comments
Owner

Insights get location context from the local location_history table, a one-time Google Takeout import. That has three problems, measured against Apollo's copy of the same data:

1. Automatic context on every photo insight (gather_location_context)

  • What it does: find_nearest_location(ts, 10800) takes the single nearest sample within ±3 h, and the prompt gets "Location history: You were near 41.2606, -81.9064".
  • Stale positions: nothing checks how far away in time the sample is. Of 1,660 photo timestamps with a sample within ±3 h, 289 (17%) get a sample more than 2 km from where the photo was taken.
  • Raw coordinates: they go to the model unnamed. This path does no reverse geocoding and no Apollo Place lookup, unlike the EXIF GPS path.
  • Bad devices: 32 of those 1,660 (about 2%) get a ghost-device or cell-tower sample that Apollo has since excluded (Apollo #45, #48).

2. The get_location_history tool (chat and reels)

It never shows the requested day.

  • How it works: it defaults to ±14 days around the date. find_locations_in_range returns rows ORDER BY timestamp ASC, and the tool renders the first 20 (LOCATION_HISTORY_DISPLAY_LIMIT).
  • The result: a ±14-day window holds a median of ~19,400 samples, so the 20 rows shown always come from the first hours of the window, about 14 days before the date asked about. found_header says "first 20 of N", but the model can't know those are from two weeks earlier.

3. Coverage

  • Takeout only: the table holds only the Takeout JSON, which ends 2019-11-08.
  • Apollo has more: about 390k samples since then (HA siphon, InfluxDB backfill, Garmin), with ghost and cell-tower cleanup, user Places and stays.
  • Photos from 2020 onward get no location history at all.

Proposal: read location from Apollo, keep the local table as a fallback

Apollo #51 adds:

  • GET /api/location/at?ts=&max_gap=: nearest kept sample with offset_s (null past max_gap), the containing Place, the covering stay, and bracketed.
  • GET /api/location/day?date=&tz=: an ordered timeline of stays, travel, unnamed dwells and gaps for the day, optionally with a pre-rendered text.

Changes here:

  1. ApolloClient: add location_at(ts, max_gap) and location_day(date, tz). Same disabled-is-a-no-op and 5 s timeout behaviour as places_containing.
  2. gather_location_context: when Apollo is enabled, use /at.
    • Render the Place and stay ("at Home, 14:02–18:30"), falling back to Nominatim on the sample when there's no Place.
    • Say nothing when sample is null, instead of a position from hours away.
    • Keep the existing "skip if it matches EXIF and adds nothing" check.
  3. tool_get_location_history: when Apollo is enabled, return the /day timeline for the date, or for each day of a small range. Rework the tool description and arguments to match: default to the single day, cap the range at a few days, and drop the 14-day default.
  4. location_history_present: treat Apollo as enabled ⇒ the tool is present, even with an empty local table.
  5. Fallback: when Apollo is unset, keep the local table. At minimum, fix the tool's ordering: centre on the date (ORDER BY ABS(timestamp - ?)), or sample evenly across the window, so it shows the requested day rather than the window's first 20 rows.
  6. Later, once proven: retire location_history, import_location_history and the parser, or keep them only for deployments without Apollo.

Tests

  • Enrichment with Apollo stubbed: Place and stay rendering; null sample gives no location line.
  • The tool renders the /day timeline.
  • With Apollo disabled, the fallback path shows samples from the requested day, not from 14 days earlier.

Depends on Apollo #51.

🤖 Generated with Claude Code

Insights get location context from the local `location_history` table, a one-time Google Takeout import. That has three problems, measured against Apollo's copy of the same data: ## 1. Automatic context on every photo insight (`gather_location_context`) - **What it does:** `find_nearest_location(ts, 10800)` takes the single nearest sample within **±3 h**, and the prompt gets "Location history: You were near 41.2606, -81.9064". - **Stale positions:** nothing checks how far away in time the sample is. Of 1,660 photo timestamps with a sample within ±3 h, **289 (17%) get a sample more than 2 km from where the photo was taken**. - **Raw coordinates:** they go to the model unnamed. This path does no reverse geocoding and no Apollo Place lookup, unlike the EXIF GPS path. - **Bad devices:** 32 of those 1,660 (about 2%) get a ghost-device or cell-tower sample that Apollo has since excluded (Apollo #45, #48). ## 2. The `get_location_history` tool (chat and reels) **It never shows the requested day.** - **How it works:** it defaults to ±14 days around the date. `find_locations_in_range` returns rows `ORDER BY timestamp ASC`, and the tool renders the first 20 (`LOCATION_HISTORY_DISPLAY_LIMIT`). - **The result:** a ±14-day window holds a median of **~19,400 samples**, so the 20 rows shown always come from the first hours of the window, **about 14 days before the date asked about**. `found_header` says "first 20 of N", but the model can't know those are from two weeks earlier. ## 3. Coverage - **Takeout only:** the table holds only the Takeout JSON, which ends **2019-11-08**. - **Apollo has more:** about 390k samples since then (HA siphon, InfluxDB backfill, Garmin), with ghost and cell-tower cleanup, user Places and stays. - **Photos from 2020 onward get no location history at all.** ## Proposal: read location from Apollo, keep the local table as a fallback Apollo #51 adds: - `GET /api/location/at?ts=&max_gap=`: nearest kept sample with `offset_s` (null past `max_gap`), the containing Place, the covering stay, and `bracketed`. - `GET /api/location/day?date=&tz=`: an ordered timeline of stays, travel, unnamed dwells and gaps for the day, optionally with a pre-rendered `text`. Changes here: 1. **`ApolloClient`:** add `location_at(ts, max_gap)` and `location_day(date, tz)`. Same disabled-is-a-no-op and 5 s timeout behaviour as `places_containing`. 2. **`gather_location_context`:** when Apollo is enabled, use `/at`. - Render the Place and stay ("at Home, 14:02–18:30"), falling back to Nominatim on the sample when there's no Place. - Say nothing when `sample` is null, instead of a position from hours away. - Keep the existing "skip if it matches EXIF and adds nothing" check. 3. **`tool_get_location_history`:** when Apollo is enabled, return the `/day` timeline for the date, or for each day of a small range. Rework the tool description and arguments to match: default to the single day, cap the range at a few days, and drop the 14-day default. 4. **`location_history_present`:** treat Apollo as enabled ⇒ the tool is present, even with an empty local table. 5. **Fallback:** when Apollo is unset, keep the local table. At minimum, fix the tool's ordering: centre on the date (`ORDER BY ABS(timestamp - ?)`), or sample evenly across the window, so it shows the requested day rather than the window's first 20 rows. 6. **Later, once proven:** retire `location_history`, `import_location_history` and the parser, or keep them only for deployments without Apollo. ## Tests - Enrichment with Apollo stubbed: Place and stay rendering; null sample gives no location line. - The tool renders the `/day` timeline. - With Apollo disabled, the fallback path shows samples from the requested day, not from 14 days earlier. Depends on Apollo #51. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.