fix: migrate Places and Routes to new GCP APIs

Switch from legacy Places textsearch and Directions APIs (disabled on
this project) to Places API (New) and Routes API (New). Both places.py
and the assistant's _places_search helper updated. Also fixes uid()
recursive self-call in trips page and adds Places API response logging.
This commit is contained in:
Logan
2026-06-21 14:35:12 -04:00
parent 522748f07a
commit 21268ab477
2 changed files with 66 additions and 36 deletions
+45 -23
View File
@@ -5,8 +5,9 @@ from app.internal.logger import logger
router = APIRouter(prefix="/places", tags=["places"])
PLACES_SEARCH_URL = "https://maps.googleapis.com/maps/api/place/textsearch/json"
DIRECTIONS_URL = "https://maps.googleapis.com/maps/api/directions/json"
_PLACES_SEARCH_URL = "https://places.googleapis.com/v1/places:searchText"
_ROUTES_URL = "https://routes.googleapis.com/directions/v2:computeRoutes"
_PLACES_FIELDS = "places.id,places.displayName,places.formattedAddress,places.rating,places.googleMapsUri,places.location"
@router.get("/search")
@@ -17,9 +18,13 @@ async def search_places(query: str = Query(...), near: str = Query("")):
full_query = f"{query} {near}".strip()
try:
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(
PLACES_SEARCH_URL,
params={"query": full_query, "key": settings.google_maps_api_key},
r = await client.post(
_PLACES_SEARCH_URL,
json={"textQuery": full_query},
headers={
"X-Goog-Api-Key": settings.google_maps_api_key,
"X-Goog-FieldMask": _PLACES_FIELDS,
},
)
r.raise_for_status()
data = r.json()
@@ -29,15 +34,15 @@ async def search_places(query: str = Query(...), near: str = Query("")):
return [
{
"name": p.get("name"),
"address": p.get("formatted_address"),
"place_id": p.get("place_id"),
"lat": p.get("geometry", {}).get("location", {}).get("lat"),
"lng": p.get("geometry", {}).get("location", {}).get("lng"),
"maps_link": f"https://www.google.com/maps/place/?q=place_id:{p.get('place_id')}",
"name": p.get("displayName", {}).get("text"),
"address": p.get("formattedAddress"),
"place_id": p.get("id"),
"lat": p.get("location", {}).get("latitude"),
"lng": p.get("location", {}).get("longitude"),
"maps_link": p.get("googleMapsUri"),
"rating": p.get("rating"),
}
for p in data.get("results", [])[:6]
for p in data.get("places", [])[:6]
]
@@ -51,13 +56,16 @@ async def get_directions(
try:
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(
DIRECTIONS_URL,
params={
"origin": origin,
"destination": destination,
"mode": "driving",
"key": settings.google_maps_api_key,
r = await client.post(
_ROUTES_URL,
json={
"origin": {"address": origin},
"destination": {"address": destination},
"travelMode": "DRIVE",
},
headers={
"X-Goog-Api-Key": settings.google_maps_api_key,
"X-Goog-FieldMask": "routes.duration,routes.distanceMeters",
},
)
r.raise_for_status()
@@ -70,9 +78,23 @@ async def get_directions(
if not routes:
return {"duration_text": None, "duration_seconds": None, "distance_text": None}
leg = routes[0]["legs"][0]
route = routes[0]
duration_seconds = int(route.get("duration", "0s").rstrip("s") or 0)
distance_m = route.get("distanceMeters", 0)
# Format human-readable strings
hours, rem = divmod(duration_seconds, 3600)
mins = rem // 60
if hours:
duration_text = f"{hours} hr {mins} min" if mins else f"{hours} hr"
else:
duration_text = f"{mins} min"
miles = distance_m / 1609.34
distance_text = f"{miles:.1f} mi"
return {
"duration_text": leg["duration"]["text"],
"duration_seconds": leg["duration"]["value"],
"distance_text": leg["distance"]["text"],
"duration_text": duration_text,
"duration_seconds": duration_seconds,
"distance_text": distance_text,
}
+21 -13
View File
@@ -74,30 +74,38 @@ _TOOLS = [
]
_PLACES_SEARCH_URL = "https://places.googleapis.com/v1/places:searchText"
_PLACES_FIELDS = "places.id,places.displayName,places.formattedAddress,places.rating,places.googleMapsUri"
async def _places_search(query: str, near: str) -> list[dict]:
if not settings.google_maps_api_key:
return []
full_query = f"{query} {near}".strip()
try:
async with httpx.AsyncClient(timeout=8) as client:
r = await client.get(
"https://maps.googleapis.com/maps/api/place/textsearch/json",
params={"query": f"{query} {near}".strip(), "key": settings.google_maps_api_key},
r = await client.post(
_PLACES_SEARCH_URL,
json={"textQuery": full_query},
headers={
"X-Goog-Api-Key": settings.google_maps_api_key,
"X-Goog-FieldMask": _PLACES_FIELDS,
},
)
data = r.json()
status = data.get("status")
results = data.get("results", [])
logger.info(f"Places search '{query} {near}': status={status}, count={len(results)}")
if status not in ("OK", "ZERO_RESULTS"):
logger.warning(f"Places API error: {status}{data.get('error_message', '')}")
places = data.get("places", [])
logger.info(f"Places search '{full_query}': count={len(places)}")
if not places and "error" in data:
logger.warning(f"Places API error: {data['error'].get('message', '')}")
return [
{
"name": p.get("name"),
"address": p.get("formatted_address"),
"place_id": p.get("place_id"),
"maps_link": f"https://www.google.com/maps/place/?q=place_id:{p.get('place_id')}",
"name": p.get("displayName", {}).get("text"),
"address": p.get("formattedAddress"),
"place_id": p.get("id"),
"maps_link": p.get("googleMapsUri"),
"rating": p.get("rating"),
}
for p in results[:5]
for p in places[:5]
]
except Exception as e:
logger.error(f"Places search in assistant failed: {e}")