From 2ef3e42da15b8e6d0e19153d532ce1355737d5b6 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sun, 12 Jul 2026 23:50:49 +0300 Subject: [PATCH] =?UTF-8?q?fix(tradein/geocoder):=20explicit=20=D0=95?= =?UTF-8?q?=D0=BA=D0=B0=D1=82=D0=B5=D1=80=D0=B8=D0=BD=D0=B1=D1=83=D1=80?= =?UTF-8?q?=D0=B3=20wins=20over=20oblast-city=20homonym=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prod-cache verification (deep-review) found 3 false-positives: EKB addresses that literally contain "екатеринбург" but also happen to contain a homonym of another oblast city inside a neighborhood/posyolok name — ЖК "Заречный" and пос. Сысерть, both administratively inside Ekaterinburg, not the ZATO Zarechny / town Sysert they share a name with. These were gated off the EKB-only local tiers; if Nominatim were momentarily down, a real EKB address would return None — a latent regression not present before this PR. Add an explicit-city short-circuit at the top of _names_non_ekb_city: if the normalized address contains "екатеринбург", return False immediately (EKB tiers stay on). Cannot affect the true-positive (non-EKB) cases, which never contain "екатеринбург". --- tradein-mvp/backend/app/services/geocoder.py | 10 ++++++++++ tradein-mvp/backend/tests/test_geocoder_bbox.py | 3 +++ 2 files changed, 13 insertions(+) diff --git a/tradein-mvp/backend/app/services/geocoder.py b/tradein-mvp/backend/app/services/geocoder.py index e3a8653e..cdc1178d 100644 --- a/tradein-mvp/backend/app/services/geocoder.py +++ b/tradein-mvp/backend/app/services/geocoder.py @@ -193,8 +193,18 @@ def _names_non_ekb_city(address: str) -> bool: <город>»). Используется чтобы НЕ пускать EKB-only локальные тиры (geoportal/cadastral) на не-ЕКБ адрес: улица+дом, совпадающие с ЕКБ-домом (проспект Ленина 1 есть в обоих городах), иначе адрес снапается в ЕКБ (#11). + + Явное "Екатеринбург" в адресе имеет приоритет над любым омонимом другого + города региона — иначе ЖК/посёлки с названием, совпадающим с другим + городом области (ЖК «Заречный», пос. «Сысерть» внутри ЕКБ), ложно + отсекали бы EKB-only тиры для реального ЕКБ-адреса. """ normalized = " ".join(address.lower().split()) + # Явное "Екатеринбург" в адресе → это ЕКБ, даже если в тексте встречается + # омоним соседнего города (ЖК «Заречный», пос. Сысерть в составе МО «город + # Екатеринбург» и т.п. — реальные false-positive из прод-кэша). + if "екатеринбург" in normalized: + return False for m in _NON_EKB_CITY_RE.finditer(normalized): prefix_words = normalized[: m.start()].split() if prefix_words and prefix_words[-1] in _DISTRICT_PREFIXES: diff --git a/tradein-mvp/backend/tests/test_geocoder_bbox.py b/tradein-mvp/backend/tests/test_geocoder_bbox.py index 6c575fc9..56a7e192 100644 --- a/tradein-mvp/backend/tests/test_geocoder_bbox.py +++ b/tradein-mvp/backend/tests/test_geocoder_bbox.py @@ -187,6 +187,9 @@ def test_has_oblast_marker(text: str, expected: bool) -> None: ("Серова 27", False), # word-boundary — НЕ город "Серов" ("мкр Заречный, ул. Ленина 5", False), # район внутри города, не ЗАТО Заречный ("Каменск-Уральский, ул. Ленина 1", True), + # явное "Екатеринбург" перевешивает омоним соседнего города в названии + # ЖК/района внутри города (реальный false-positive из прод-кэша, #11): + ("24, опалихинская улица, заречный, Верх-Исетский район, Екатеринбург", False), ], ) def test_names_non_ekb_city(address: str, expected: bool) -> None: