From a7a3831ce7b49e1758dc93ac693735b6978d4aa6 Mon Sep 17 00:00:00 2001 From: Light1YT Date: Fri, 22 May 2026 13:32:39 +0500 Subject: [PATCH] =?UTF-8?q?fix(tradein):=20map-picker=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=20=D1=83=D0=BB?= =?UTF-8?q?=D0=B8=D1=87=D0=BD=D1=8B=D0=B9=20=D0=B0=D0=B4=D1=80=D0=B5=D1=81?= =?UTF-8?q?,=20=D0=B0=20=D0=BD=D0=B5=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20POI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Обратный геокодинг (Nominatim /reverse) возвращал display_name, который ведёт с названия ближайшей организации/POI — клик по ЖК Малевич давал «NataliOlympic, 2к1, Трамвайный переулок…» вместо адреса дома. Такой адрес не геокодировался обратно, и оценка находила 0 аналогов. Теперь reverse_geocode собирает адрес из Nominatim address-объекта (улица + дом + город), display_name остаётся фолбэком. Follow-up к #415. --- tradein-mvp/backend/app/services/geocoder.py | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tradein-mvp/backend/app/services/geocoder.py b/tradein-mvp/backend/app/services/geocoder.py index 8cd5d357..d5f0e146 100644 --- a/tradein-mvp/backend/app/services/geocoder.py +++ b/tradein-mvp/backend/app/services/geocoder.py @@ -536,11 +536,46 @@ async def geocode(address: str, db: Session) -> GeocodeResult | None: # ── Reverse: координаты → адрес (для map-picker'а) ────────────────────────── +def _format_reverse_address(addr: dict) -> str | None: + """Собирает чистый уличный адрес из Nominatim address-объекта. + + Nominatim `display_name` ведёт с названия ближайшего POI/организации + («NataliOlympic, 2к1, Трамвайный переулок, …»). Для map-picker'а это + ломает кейс: такой адрес нельзя forward-геокодировать обратно и оценка + не находит аналогов. Берём именно улицу + дом + город. + + Возвращает None если в объекте нет улицы — тогда вызывающий код + откатывается на display_name. + """ + road = ( + addr.get("road") + or addr.get("pedestrian") + or addr.get("footway") + ) + if not road: + return None + house = addr.get("house_number") + city = ( + addr.get("city") + or addr.get("town") + or addr.get("village") + or addr.get("municipality") + ) + parts: list[str] = [str(road)] + if house: + parts.append(str(house)) + if city: + parts.append(str(city)) + return ", ".join(parts) + + @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=8)) async def reverse_geocode(lat: float, lon: float) -> str | None: """Обратный геокодинг: координаты → адрес (Nominatim /reverse). Клик по дому на карте ЕКБ → адрес подставляется в форму оценки. + Возвращаем уличный адрес (улица, дом, город), а не `display_name` — + тот начинается с названия POI/организации и не геокодируется обратно. """ headers = { "User-Agent": f"TradeInMVP/0.1 (contact: {settings.contact_email})", @@ -562,4 +597,9 @@ async def reverse_geocode(lat: float, lon: float) -> str | None: data = response.json() if not isinstance(data, dict) or "error" in data: return None + addr = data.get("address") + if isinstance(addr, dict): + street = _format_reverse_address(addr) + if street: + return street return data.get("display_name")