8 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
af609c18f3 |
fix(tradein/geocoder): починить маркер села + fail-closed для явного city_hint (#2582)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 2m20s
H1 (deep-review PR #2589): `с\.` в _LOCALITY_MARKER_RE был мертворождён — хвостовой \b не может сработать между "." и пробелом (оба не-словесные), формат «с. <село>» (104/77к прод-адресов) не ловился НИ РАЗУ, зато ложно срабатывал на инициалах «им с.п.королева». Живой инцидент воспроизведён: "с. Новоалексеевское, ул. 8 Марта,37" снапалось в ЕКБ (совпадение до 5-го знака, село в 35 км западнее). Заменил на lookahead-форму для однобуквенных маркеров (с/д/дер/пгт/ст): "\.\s*(?=[а-яё])" — различает "д. 5" (дом-номер, не матчит) от "д. Кашино" (деревня, матчит), что заодно позволило безопасно включить "д."/"дер." (раньше исключённые из-за коллизии с house-маркером). M2: гейт по city_hint оставался list-based (_names_non_ekb_city(city_hint)) — для СТРУКТУРНОГО хинта (не свободного текста) fail-closed форма должна быть обратной: "не Екатеринбург → закрыто", а не "есть в списке 37 → закрыто". city_hint принимает произвольную строку без валидации (API напрямую, минуя 6-городный UI-selector) — city_hint="Реж" держал гейт открытым, geoportal отдавал точный ЕКБ-дом, city_ambiguous=False делало это уверенно-неверным. M3: добавлены изолирующие тесты — предыдущая параметризация "незнакомый топоним" всегда несла ещё и "обл."/"республика"/"край" одновременно с проверяемым сигналом, поэтому мёртвый "с\." прошёл незамеченным. Плюс честно зафиксирована известная дыра (гейт по city_hint) — "реж, ленина 5" без маркера остаётся в ЕКБ-дефолте (архитектурное решение отдельно, не список городов). docstring `_names_unrecognized_locality`/`_ekb_local_tiers_allowed` приведён в соответствие с фактическим поведением regex. |
||
|
|
174680a82e |
Merge forgejo/main into fix/tradein-geocoder-fail-closed
All checks were successful
CI / changes (pull_request) Successful in 12s
CI Trade-In / changes (pull_request) Successful in 13s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 2m35s
Разрешён конфликт в geocoder.py между #2580 (city_hint threading в локальный гейт + dual-query Tier 4 Nominatim) и #2582 (fail-closed структурный маркер топонима вместо списка городов): `_ekb_local_tiers_allowed` теперь принимает `city_hint` и закрывает гейт по OR трёх независимых сигналов — известный не-ЕКБ город в тексте (#11), known-city в city_hint (#2580 C1), незнакомый топоним-маркер в тексте (#2582). Добавлены тесты на все 4 достижимые ветки (hint=известный город / маркер в тексте / ничего / hint=Екатеринбург); полный набор тестов (свои + пришедшие из #2580) зелёный одновременно. |
||
|
|
61b329e724 |
fix(tradein/geocoder): fail-closed выбор ЕКБ-реестров вместо списка городов (#2582)
EKB-only локальные тиры (geoportal/cad_buildings) держались на списке из 37 городов области: "нет в списке -> считаем Екатеринбургом". Молча ломало любой адрес вне списка, включая ДРУГИЕ РЕГИОНЫ РФ целиком -- подтверждено живыми случаями в geocode_cache (Ялта/Крым, Трёхгорный/Челябинская обл., пос. Лёвиха, Димитровград/Ульяновская обл. -- все получили координаты ЕКБ). Новый гейт `_ekb_local_tiers_allowed`: локальные тиры разрешены только если Екатеринбург подтверждён явно, ЛИБО в адресе вообще не назван город/регион (bare "Ленина 1" -- основной трафик формы оценки, дефолт ЕКБ оправдан и сохранён без изменений). Если назван ЛЮБОЙ другой топоним -- известный город списка (`_names_non_ekb_city`, без изменений) или незнакомый (`_names_unrecognized_locality` -- новый структурный маркер "обл./край/ республика/г./пос./с.", БЕЗ "д." -- коллизия с house-маркером "д. 5") -- гейт закрыт, идём во внешние oblast/nation-aware провайдеры. city_hint-логика #2580 не тронута (тот же файл, сосед по цели). |
||
|
|
8fdcd8df62 |
fix(tradein/geocoder): word-boundary EKB short-circuit + gate suggest() + gazetteer (#11)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 56s
Deep-review fast-follows on the non-EKB city gate:
1. Word-boundary the "екатеринбург" short-circuit in _names_non_ekb_city
(new _EKATERINBURG_RE = \bекатеринбург\b, .search() instead of substring
`in`). A bare-substring check matched "Екатеринбургское шоссе" (a real
street in satellite towns like Pervouralsk) as if it named EKB, wrongly
keeping EKB-only local tiers on for a non-EKB address. Still returns False
for the existing homonym false-positive test case (word boundary is
satisfied there — "екатеринбург" appears as a standalone token).
2. Gate suggest()'s Tier 1 (same root cause as geocode()): the EKB-only
cadastral matchers _cadastral_house_match / _cadastral_forward_sync ran
unconditionally, so a non-EKB oblast autocomplete query could surface an
EKB building via street+house collision. Gated behind
`not _names_non_ekb_city(query)`, mirroring geocode()'s use_local_ekb.
External suggest tiers (DaData/Yandex/Nominatim) untouched — still the
path for non-EKB autocomplete.
3. Extended SVERDLOVSK_OBLAST_CITIES with unambiguous, deal-heavy oblast
cities: алапаевск, сухой лог, кушва, красноуральск, карпинск, нижняя
тура, верхний тагил, нижние серги. Deliberately did NOT add "лесной"
(reviewer flagged as reviewer-optional, left to judgment): DB check
(ekb_geoportal_buildings) confirms a real EKB street named exactly
"Лесной" (3 buildings) — an exact whole-word collision indistinguishable
from ЗАТО Лесной by word-boundary matching alone (unlike "Серова"/"Серов",
which are different word forms). Adding it would misclassify a bare
EKB address ("Лесной, 5", no "Екатеринбург" mention) as non-EKB.
Tests: word-boundary EKB regression test, suggest() gating test, two new
gazetteer entries (Верхняя Пышма multi-word, Сухой Лог).
|
||
|
|
2ef3e42da1 |
fix(tradein/geocoder): explicit Екатеринбург wins over oblast-city homonym (#11)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 51s
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 "екатеринбург". |
||
|
|
720cee2c8e |
fix(tradein/geocoder): gate EKB-local tiers for non-EKB oblast addresses (#11)
All checks were successful
CI / changes (pull_request) Successful in 8s
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 53s
geocode() ran EKB-only local tiers (2a _geoportal_house_match on
ekb_geoportal_buildings, 2c _cadastral_house_match, 2d
_cadastral_forward_sync — all strictly EKB or EKB-dominated) BEFORE the
already oblast-aware external providers (Yandex/Nominatim via OBLAST66_BBOX
+ region cross-check, shipped in
|
||
|
|
c0cbdc2f99 |
fix(tradein/geocoder): раз-хардкод геокодера/DaData на всю Свердловскую обл. (region 66)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI Trade-In / changes (pull_request) Successful in 8s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 50s
Геокодер был жёстко EKB-bound и ВЫБРАСЫВАЛ корректные non-EKB геокоды (bbox-accept-фильтры) → блокировал оценку любого адреса области. Обобщено до region-66. - OBLAST66_BBOX + is_within_oblast66_bbox (superset EKB-tight, EKB не затронут); EKB Yandex Tier1/2 fast-path сохранён. - Все accept-фильтры (Yandex/Nominatim forward+suggest, geocode() accept+typo) → oblast-66. - City-prefix по word-boundary _has_oblast_marker (не substring): EKB-улицы 'Серова 27'/'Ирбитская 5' и др. больше не роняют 'Екатеринбург,'-префикс (был silent wrong-city баг). - Two-pass tie-break в accept-циклах: tight-EKB кандидат приоритетнее → EKB-результат идентичен прежнему. - Region cross-check (Nominatim address.state / Yandex AdministrativeAreaName) отсекает соседние области (Тюмень и др.); bbox-fallback когда region отсутствует. - DaData suggest: region-параметр (hard-filter), убран no-op restrict_value. - +26 тестов: OBLAST66 superset TIGHT, far-town accept, Тюмень reject, marker +/- (street-collision), DaData region body. Foundation для A2 (геокодинг non-EKB сделок) и live-оценки адресов области. |
||
| 04550bc94f |
fix(tradein): drop non-EKB avito detail coords on ingest (audit #1871 P2)
avito_detail.parse_detail_html писал data-map-lat/lon прямо в listings UPDATE COALESCE без bbox-валидации — в отличие от geocoder.py, который фильтрует везде. Итог: 406 active avito-листингов с координатами Питер/Тюмень/Уфа просочились в ЕКБ-датасет (ложные аналоги через Tier A address-match оценщика). Prod-verified 2026-06-23. - geocoder: DRY-хелпер is_within_ekb_bbox(lat, lon, bbox) + именованные EKB_BBOX_TIGHT (geocoder-фильтр, числа не изменены) и EKB_BBOX_WIDE (ingest-guard); 2 inline tight-проверки → хелпер (поведение identical). - avito_detail: после извлечения lat/lon → reset None + logger.warning если вне wide-bbox; листинг падает в geocode-cron путь (COALESCE NULL не затирает прежнее хорошее значение). - tests: bbox-хелпер (ЕКБ True, Питер/Тюмень/Уфа False, inclusive, WIDE⊇TIGHT инвариант) + parse_detail_html guard end-to-end. WIDE строго содержит TIGHT — guard физически не режет ничего, что geocoder сам бы пропустил. NULL lat (14935, geocode backlog) — отдельная проблема, не в scope. One-time cleanup существующих 406 — отдельно (database-expert, prod data mutation). Refs #1871 |