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
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:
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.
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.
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.
location_history_present: treat Apollo as enabled ⇒ the tool is present, even with an empty local table.
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.
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.
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)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Insights get location context from the local
location_historytable, 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)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".2. The
get_location_historytool (chat and reels)It never shows the requested day.
find_locations_in_rangereturns rowsORDER BY timestamp ASC, and the tool renders the first 20 (LOCATION_HISTORY_DISPLAY_LIMIT).found_headersays "first 20 of N", but the model can't know those are from two weeks earlier.3. Coverage
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 withoffset_s(null pastmax_gap), the containing Place, the covering stay, andbracketed.GET /api/location/day?date=&tz=: an ordered timeline of stays, travel, unnamed dwells and gaps for the day, optionally with a pre-renderedtext.Changes here:
ApolloClient: addlocation_at(ts, max_gap)andlocation_day(date, tz). Same disabled-is-a-no-op and 5 s timeout behaviour asplaces_containing.gather_location_context: when Apollo is enabled, use/at.sampleis null, instead of a position from hours away.tool_get_location_history: when Apollo is enabled, return the/daytimeline 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.location_history_present: treat Apollo as enabled ⇒ the tool is present, even with an empty local table.ORDER BY ABS(timestamp - ?)), or sample evenly across the window, so it shows the requested day rather than the window's first 20 rows.location_history,import_location_historyand the parser, or keep them only for deployments without Apollo.Tests
/daytimeline.Depends on Apollo #51.
🤖 Generated with Claude Code