Cut Google Maps spend: reverse-geocode cache, Details mask trim, confirm skip, closure persistence - #450
Merged
Merged
Conversation
… redundant confirm Details, persist closures Four API-cost fixes plus one freshness fix that costs nothing, from the 2026-08-01 geo cost/freshness audit. No wire-contract changes. - Reverse-geocode results are now cached in Redis per ~8 km search cell (30-day expiry, matching Geocoding caching terms). This was the ONLY Google call a warm nearby scan made — processLocation buys it on every request — so a warm scan's Google spend drops to zero. The RedisClient.ReverseGeocode stub is now real; processLocation and the reverse-geocoding endpoint route through the cached path. - detailed_search_fields drops name and user_ratings_total: no Details consumer reads either (both arrive free with every Nearby/Text Search result), and user_ratings_total alone pulls every Details call into the Atmosphere billing tier. The AddUserRatingsTotal migration passes its own field list and is unaffected. Pinned by TestDetailedSearchFieldsMask. - place-search confirm skips its Place Details call when the cached record's details are current (same placeDetailsAreCurrent rule the nearby path trusts) — re-confirming a place we already hold was a full-price max-tier call for data restoreCachedDetails restores anyway. - Stale-photo recovery requests only the photos field instead of the full Details mask. - Cold searches now PERSIST permanently-closed places instead of filtering them out before the cache write. The old order discarded the closure signal entirely: the stale record kept OPERATIONAL status and cache membership forever. The response-side filter is unchanged (closures never reach callers); the read-side Operational filter now retires them from cache serves at zero extra API cost. Not included (deliberately): the hoursKnown wire flag (API change), text-search caching and timeout salvage (larger changes), and the MapsLastSearchTime dead-field purge (Redis ops, not code). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DjdGnZM6cUBeg6jWwoQfU3
…es flag Now that closures are persisted in the cache, the old contract was one forgotten field away from breaking planners: filtering only happened when a caller remembered BusinessStatus: POI.Operational, so a zero-value PlaceSearchRequest would serve permanently-closed places from both cache reads and cold-search responses. The zero value is now safe: non-Operational places are always filtered unless the caller explicitly opts in with IncludeClosedPlaces. All three production call sites (matcher, both planner handlers) already requested Operational filtering, so their behavior is unchanged — the field they set is simply gone. The RemovePlaces migration test opts in: it verifies raw bucket contents and its fixtures carry no Status. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DjdGnZM6cUBeg6jWwoQfU3
Owner
Author
|
Added a fail-safe flag on top of the closure-persistence change: 🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The no-wire-change batch from the 2026-08-01 geo cost/freshness audit. Four cost cuts plus one zero-cost freshness fix:
1. Reverse-geocode cache (the big one)
Every nearby scan buys a reverse-geocode via
processLocation— and on a warm place cache it was the only Google call the scan made, so it was ~100% of warm-scan spend. Results are now cached per ~8 km search cell (geocode:reverse:<cell>, 30-day expiry per Geocoding caching terms). TheRedisClient.ReverseGeocodestub is now a real implementation;processLocation,PoiSearcher.ReverseGeocode, and the/v1/reverse-geocodinghandler all route through it.2. Details field-mask trim
detailed_search_fieldsdropsnameanduser_ratings_total: no Details consumer reads either (both arrive free with every Nearby/Text Search result), anduser_ratings_totalalone pulls every Details call into the Atmosphere billing tier. TheAddUserRatingsTotalmigration passes its own single-field list and is unaffected.editorial_summarystays (trip planner reads it) — note that means the Atmosphere tier still applies until that's revisited. Pinned byTestDetailedSearchFieldsMask.3. Confirm skips redundant Details
/v1/place-search/confirmbought a full max-tier Place Details call even when the cached record's details were current — datarestoreCachedDetailsrestores anyway. It now applies the sameplaceDetailsAreCurrentrule the nearby path already trusts. This also makes Offerbee's wallet-change re-confirm of manual rows free in the common case.4. Photos-only mask in stale-photo recovery
photos_client.gorequested the entire Details mask to recover one photo reference; now["photos"].5. Closures are persisted (freshness fix, zero cost)
Cold searches filtered non-
OPERATIONALresults before the cache write, discarding the closure signal entirely — a permanently closed place kept itsOPERATIONALrecord and cache membership forever. The write now includes closures (newpersistAndFilterSearchResultsowns write + response filter); the response-side behavior is unchanged, and the existing read-side Operational filter retires closed places from cache serves.Testing
reverse_geocode_cache_test.go(round-trip, cell scoping, expiry, cache-first searcher + processLocation),add_searched_place_currency_test.go(skip-when-current, buy-when-stale, buy-when-uncached),closure_persistence_test.go(persist + response exclusion + read-path retirement),config_yaml_test.go(mask pin).go vet ./...clean; fullgo test ./...green (7 packages).Deliberately not in this PR:
hoursKnownwire flag (API change), text-search caching + timeout salvage (larger),MapsLastSearchTimedead-field purge (Redis ops).🤖 Generated with Claude Code
https://claude.ai/code/session_01DjdGnZM6cUBeg6jWwoQfU3