feat(insights): read location context from Apollo (#177) #179

Merged
cameron merged 1 commits from feat/apollo-location-context into master 2026-10-17 16:03:27 +00:00
Owner

Closes #177. Depends on Apollo#52 (/api/location/at and /api/location/day). Deploy Apollo first. Until then, every Apollo location call 404s, gets logged at warn, and falls back to the local table, so deploying in the wrong order is safe, just noisy.

What changes

Per-photo location context (gather_location_context)

With APOLLO_API_BASE_URL set, it asks Apollo /api/location/at?ts=<true instant>&max_gap=1800, then:

  • No sample within 30 min: no location line. Before, it took the nearest local sample within ±3 h whatever the distance, and 17% of photo moments got a position more than 2 km from where the photo was taken.
  • Inside a Place stay: Location history: You were at Hyland Software (08:07–16:19), with times in the owner's display zone.
  • Inside a Place, no stay: You were at Home.
  • Unnamed: You were near <Nominatim>. The geocode only runs when the photo has no EXIF GPS (the EXIF line already has it); otherwise it shows coordinates.
  • Extras: an informative activity ((IN_VEHICLE)) and, when the fix is more than 5 min from the photo, [location fix 15 min before the photo].
  • Kept: the existing "skip if within 500 m of EXIF and nothing new" rule.

The rendering is a free function, render_apollo_location, with unit tests.

get_location_history tool (chat and reels)

  • With Apollo: Apollo's day timeline for the date's local day in the display zone, or date±1 at most. For example:
    2017-01-17 (744 location points, 195.2 km):
    00:00–07:32 at Cameron's Home - Grafton Road
    07:34–08:05 moving, 31.0 km, IN_VEHICLE
    08:07–16:19 at Hyland Software
    ...
    
  • Before: the first 20 rows of a ±14-day window sorted ascending, i.e. points from about 14 days before the date, with nothing telling the model so.
  • Description: reworded to match. days_radius now defaults to 0.
  • Offered whenever Apollo is configured, even with an empty local table. Photos from 2020 onward finally get location history.

Fallback

When Apollo is unset or a call fails, the local table is used. The local tool path now:

  • covers the requested local day(s) in the display zone;
  • samples up to 20 records evenly across them (spread_evenly);
  • says so in its header, while keeping Found N … parseable for the trajectory summarizer.

Tests

New:

  • Apollo response parsing (hit, miss, day);
  • the disabled client;
  • render_apollo_location: no sample, stay in the display zone, unnamed with Nominatim / coordinates plus activity plus offset, EXIF-skip;
  • spread_evenly.

cargo test passes: 753 + 846 + the rest. cargo clippy --all-targets is clean on the changed files, and cargo fmt has been run.

Not tested end to end: there's no HTTP mock crate in the repo, so nothing exercises a live Apollo. The wire shapes are pinned by the parsing tests here and by Apollo#52's route tests.

Follow-up (not in this PR)

Once this has run for a while, retire location_history, import_location_history and the parser, or keep them only for Apollo-less deploys.

🤖 Generated with Claude Code

Closes #177. **Depends on [Apollo#52](https://git.crkc.xyz/Apps/Apollo/pulls/52)** (`/api/location/at` and `/api/location/day`). Deploy Apollo first. Until then, every Apollo location call 404s, gets logged at `warn`, and falls back to the local table, so deploying in the wrong order is safe, just noisy. ## What changes ### Per-photo location context (`gather_location_context`) With `APOLLO_API_BASE_URL` set, it asks Apollo `/api/location/at?ts=<true instant>&max_gap=1800`, then: - **No sample within 30 min:** no location line. Before, it took the nearest local sample within ±3 h whatever the distance, and 17% of photo moments got a position more than 2 km from where the photo was taken. - **Inside a Place stay:** `Location history: You were at Hyland Software (08:07–16:19)`, with times in the owner's display zone. - **Inside a Place, no stay:** `You were at Home`. - **Unnamed:** `You were near <Nominatim>`. The geocode only runs when the photo has no EXIF GPS (the EXIF line already has it); otherwise it shows coordinates. - **Extras:** an informative activity (`(IN_VEHICLE)`) and, when the fix is more than 5 min from the photo, `[location fix 15 min before the photo]`. - **Kept:** the existing "skip if within 500 m of EXIF and nothing new" rule. The rendering is a free function, `render_apollo_location`, with unit tests. ### `get_location_history` tool (chat and reels) - **With Apollo:** Apollo's day timeline for the date's local day in the display zone, or date±1 at most. For example: ``` 2017-01-17 (744 location points, 195.2 km): 00:00–07:32 at Cameron's Home - Grafton Road 07:34–08:05 moving, 31.0 km, IN_VEHICLE 08:07–16:19 at Hyland Software ... ``` - **Before:** the first 20 rows of a ±14-day window sorted ascending, i.e. points from about 14 days before the date, with nothing telling the model so. - **Description:** reworded to match. `days_radius` now defaults to 0. - **Offered whenever Apollo is configured,** even with an empty local table. Photos from 2020 onward finally get location history. ### Fallback When Apollo is unset or a call fails, the local table is used. The local tool path now: - covers the requested local day(s) in the display zone; - samples up to 20 records evenly across them (`spread_evenly`); - says so in its header, while keeping `Found N …` parseable for the trajectory summarizer. ## Tests New: - Apollo response parsing (hit, miss, day); - the disabled client; - `render_apollo_location`: no sample, stay in the display zone, unnamed with Nominatim / coordinates plus activity plus offset, EXIF-skip; - `spread_evenly`. `cargo test` passes: 753 + 846 + the rest. `cargo clippy --all-targets` is clean on the changed files, and `cargo fmt` has been run. **Not tested end to end:** there's no HTTP mock crate in the repo, so nothing exercises a live Apollo. The wire shapes are pinned by the parsing tests here and by Apollo#52's route tests. ## Follow-up (not in this PR) Once this has run for a while, retire `location_history`, `import_location_history` and the parser, or keep them only for Apollo-less deploys. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
cameron force-pushed feat/apollo-location-context from d1f0e4c0c0 to 7b6eb0c44b 2026-10-17 03:51:29 +00:00 Compare
cameron force-pushed feat/apollo-location-context from 7b6eb0c44b to e104f452f3 2026-10-17 04:11:39 +00:00 Compare
cameron force-pushed feat/apollo-location-context from e104f452f3 to f62d2ecfb4 2026-10-17 04:21:53 +00:00 Compare
cameron added 1 commit 2026-10-17 16:03:10 +00:00
The local location_history table is a one-time Takeout import: it ends
in 2019, still has the ghost-device and coarse cell-fix samples Apollo
has since excluded, and was read loosely. The per-photo line took the
nearest sample within +/-3 h at any distance (17% of photo moments got a
position >2 km away), and get_location_history listed the first 20 rows
of a +/-14-day window sorted ascending (always ~14 days before the date).

With APOLLO_API_BASE_URL set:
- per-photo context comes from /api/location/at (30 min max gap; no
  sample -> no line). A Place stay renders as "at Home (14:02-18:30)" in
  the display zone; an unnamed sample is reverse-geocoded.
- get_location_history returns Apollo's /api/location/day timeline for
  the date (+/-1 day at most), and is offered even with an empty local
  table.

Both fall back to the local table when Apollo is unset or a call fails;
the local tool path now covers the requested local day(s) and samples
evenly instead of listing a window's first rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cameron force-pushed feat/apollo-location-context from f62d2ecfb4 to dc90407afb 2026-10-17 16:03:10 +00:00 Compare
cameron merged commit 4f35f87ea3 into master 2026-10-17 16:03:27 +00:00
cameron deleted branch feat/apollo-location-context 2026-10-17 16:03:27 +00:00
Sign in to join this conversation.