guideCity/backend/app/services/geocoding.py
vrubelroman be67902b45 Add structured logging throughout the backend
Request-level logging middleware (method/path/status/duration),
per-module loggers in routes/services (nearby search radius
expansion, 404s, name search), LOG_LEVEL setting wired through
docker-compose, and seed_loader converted from print() to logging.

Verified: pytest passes, and manually confirmed log lines appear for
both successful and 404 requests via `docker compose logs api`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 20:10:10 +00:00

53 lines
1.6 KiB
Python

"""Fallback name/address search over our own `places` dataset.
Used when the user denies location permission and types a place of interest,
or when 2GIS's own geocoding API isn't wired into a given client flow yet.
"""
import logging
from geoalchemy2 import Geometry
from sqlalchemy import and_, cast, func, select
from sqlalchemy.orm import Session
from app.models.city import City
from app.models.enums import ContentLanguage, ContentLength
from app.models.place import Place
from app.models.place_content import PlaceContent
logger = logging.getLogger("guidecity.geocoding")
def search_places(
db: Session,
*,
q: str,
language: ContentLanguage,
city_slug: str | None = None,
limit: int = 20,
):
stmt = (
select(
Place,
PlaceContent,
func.ST_X(cast(Place.location, Geometry)).label("lon"),
func.ST_Y(cast(Place.location, Geometry)).label("lat"),
)
.join(
PlaceContent,
and_(
PlaceContent.place_id == Place.id,
PlaceContent.language == language,
PlaceContent.length == ContentLength.short,
),
)
.where(Place.is_active.is_(True))
.where(PlaceContent.title.ilike(f"%{q}%"))
.limit(limit)
)
if city_slug is not None:
stmt = stmt.join(City, City.id == Place.city_id).where(City.slug == city_slug)
results = db.execute(stmt).all()
logger.info("name search q=%r city_slug=%s lang=%s -> %d result(s)", q, city_slug, language, len(results))
return results