All checks were successful
Deploy Trade-In / changes (push) Successful in 5s
Deploy / changes (push) Successful in 5s
Deploy Trade-In / build-frontend (push) Successful in 25s
Deploy Trade-In / build-backend (push) Successful in 47s
Deploy / build-frontend (push) Successful in 29s
Deploy / build-backend (push) Successful in 1m25s
Deploy Trade-In / deploy (push) Successful in 37s
Deploy / build-worker (push) Successful in 2m29s
Deploy / deploy (push) Successful in 1m2s
101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
"""Cadastral lookup endpoints — reverse geocode via cad_buildings.
|
||
|
||
Used by trade-in backend (and potentially other internal services) to resolve
|
||
координаты → адрес жилого дома без зависимости от внешних геокодеров.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from typing import Annotated
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from pydantic import BaseModel
|
||
from sqlalchemy import text
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.db import get_db
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
class CadastralReverseResult(BaseModel):
|
||
address: str
|
||
lat: float
|
||
lon: float
|
||
distance_m: float
|
||
cad_num: str | None = None
|
||
|
||
|
||
@router.get("/reverse", response_model=CadastralReverseResult)
|
||
async def reverse(
|
||
lat: Annotated[float, Query(ge=-90, le=90)],
|
||
lon: Annotated[float, Query(ge=-180, le=180)],
|
||
radius_m: Annotated[int, Query(ge=10, le=2000)] = 200,
|
||
db: Annotated[Session, Depends(get_db)] = None, # type: ignore[assignment]
|
||
) -> CadastralReverseResult:
|
||
"""Координаты → ближайшее здание из cad_buildings.
|
||
|
||
Filters out гаражи/СНТ/садовод/товарищества — для trade-in нужны жилые дома.
|
||
|
||
Returns 404 если в радиусе нет подходящего здания.
|
||
"""
|
||
row = db.execute(
|
||
text(
|
||
"""
|
||
SELECT
|
||
readable_address,
|
||
cad_num,
|
||
ST_Y(ST_Centroid(geom)) AS lat,
|
||
ST_X(ST_Centroid(geom)) AS lon,
|
||
ST_Distance(
|
||
geom::geography,
|
||
ST_SetSRID(
|
||
ST_MakePoint(
|
||
CAST(:lon AS double precision),
|
||
CAST(:lat AS double precision)
|
||
),
|
||
4326
|
||
)::geography
|
||
) AS distance_m
|
||
FROM cad_buildings
|
||
WHERE ST_DWithin(
|
||
geom::geography,
|
||
ST_SetSRID(
|
||
ST_MakePoint(
|
||
CAST(:lon AS double precision),
|
||
CAST(:lat AS double precision)
|
||
),
|
||
4326
|
||
)::geography,
|
||
CAST(:radius AS double precision)
|
||
)
|
||
AND readable_address !~* '(гараж|снт|садовод|товарищ|уч\\.)'
|
||
ORDER BY ST_Distance(
|
||
geom::geography,
|
||
ST_SetSRID(
|
||
ST_MakePoint(
|
||
CAST(:lon AS double precision),
|
||
CAST(:lat AS double precision)
|
||
),
|
||
4326
|
||
)::geography
|
||
)
|
||
LIMIT 1
|
||
"""
|
||
),
|
||
{"lat": lat, "lon": lon, "radius": float(radius_m)},
|
||
).first()
|
||
|
||
if row is None:
|
||
raise HTTPException(status_code=404, detail="no cadastral building found within radius")
|
||
|
||
return CadastralReverseResult(
|
||
address=row.readable_address,
|
||
lat=float(row.lat),
|
||
lon=float(row.lon),
|
||
distance_m=round(float(row.distance_m), 1),
|
||
cad_num=row.cad_num,
|
||
)
|