Commit graph

9 commits

Author SHA1 Message Date
0abfe020df fix(tradein/geocoder): строгий матч литеры дома + починка DaData region-фильтра (#2622)
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 2m36s
Deploy Trade-In / build-backend (push) Successful in 59s
Deploy Trade-In / deploy (push) Successful in 1m26s
2026-08-02 12:06:44 +00:00
bot-backend
3d075632a9 chore(tradein/geocoder): удалить Яндекс-геокодер (#2593)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
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 2m28s
Убирает ядро Yandex Geocoder (forward/reverse/suggest lookups + region-check
+ bias-хелперы + EKB_BBOX dict) из app/services/geocoder.py — Yandex demo-key
исчерпан, Nominatim/DaData/локальные ЕКБ-тиры (geoportal/cadastral) остаются
единственными живыми провайдерами. Цепочка тиров после удаления: кэш →
геопортал ЕКБ → кадастр (house-match) → кадастр (raw) → Nominatim; в
подсказках дополнительно DaData.

НЕ затронуто (намеренно): Yandex.Недвижимость как источник объявлений
(source='yandex', yandex_city_sweep*, providers/yandex/serp.py:geocoderAddress),
Avito geocoder (providers/avito/imv.py:_geocode), EKB_BBOX_TIGHT/WIDE,
_nominatim_region_ok, scripts/*_yandex_reverse.py и их тесты, tests/fixtures/
yandex_geocode_sample.json (всё ещё используется test_audit_address_mismatch.py).

_SNAP_PRECISIONS оставлен с "exact" (недостижимо без Yandex-tier, но дёшево
хранить — parity с frontend MapPicker.tsx SNAP_PRECISIONS и не ломает
test_snap_precision_useful_exact_and_number).
2026-07-31 21:22:09 +03:00
bot-backend
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.
2026-07-31 19:17:47 +03:00
bot-backend
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) зелёный
одновременно.
2026-07-31 18:46:14 +03:00
bot-backend
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 не тронута (тот же файл, сосед по цели).
2026-07-31 18:36:12 +03:00
bot-backend
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, Сухой Лог).
2026-07-13 00:06:19 +03:00
bot-backend
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 c0cbdc2f). _parse_street_house drops the
city, so any non-EKB oblast street+house that collides with an EKB building
(e.g. "проспект Ленина 1" exists in both Nizhny Tagil and EKB) short-circuited
to EKB coordinates.

Add _names_non_ekb_city() — reuses the existing SVERDLOVSK_OBLAST_CITIES
gazetteer (minus Ekaterinburg) and word-boundary matching from
_has_oblast_marker/_DISTRICT_PREFIXES — to detect when an address explicitly
names a different oblast city. Gate tiers 2a/2c/2d behind
`use_local_ekb = not _names_non_ekb_city(address)`; EKB/bare addresses keep
the exact same code path (byte-identical ordering/logic, only additive
gating). Non-EKB addresses fall straight through to Yandex/Nominatim, which
already handle oblast-wide geocoding correctly.

Verified gendesign_cad_buildings is 99.85% EKB-scoped (47043/47111 rows);
ekb_geoportal_buildings is 100% EKB by construction — gating loses no
meaningful local coverage for non-EKB cities.

Follow-up (not in this PR): geocode_cache has rows poisoned by the old
behavior (non-EKB addresses cached with EKB coordinates) — needs a purge.
2026-07-12 23:45:40 +03:00
64d113d649 feat(tradein): EKB geoportal building ingest + geocoder tier (complete city buildings)
All checks were successful
CI / changes (pull_request) Successful in 6s
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
2026-06-19 17:51:07 +03:00
c2a1dc8aeb fix(tradein): parse street+house for cadastral geocode match (resolve ~60% of failing addresses from local DB)
All checks were successful
CI / changes (pull_request) Successful in 6s
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
2026-06-19 14:04:12 +03:00