fix(tradein/tasks): городской гейт в ночном бэкфилле координат (#2583) #2588

Merged
lekss361 merged 1 commit from fix/tradein-coords-backfill-city-gate into main 2026-07-31 15:43:55 +00:00
Owner

Находка H3 из аудита #2583единственный дефект, который портил данные каждую ночь, а не только выдавал неверный ответ по запросу.

backfill_listings_coords_geoportal.py брала все объявления без координат, парсила адрес (город при этом отбрасывается) и звала екатеринбургский реестр напрямую, минуя geocode() — то есть городской гейт не применялся вообще. Объявление из Тагила получало координаты Екатеринбурга, после чего тянуло медиану чужих оценок, исчезало из выборки своего города и приклеивалось к екатеринбургскому дому при сопоставлении по расстоянию. Задача включена, без лимита, в окне 05:00-06:00 — выигрывала гонку у областного геокодера (06:00-09:00).

Решение: проверка _names_non_ekb_city(address) перед обращением к реестру — та же функция, что уже гейтит эти тиры внутри geocode(). Полноценный geocode() намеренно не используется: он добавил бы вызовы внешних геокодеров на каждый областной адрес из очереди (сотни-тысячи за ночь), а Яндекс сейчас отдаёт 403 (#2585) и живой только Nominatim с паузами. Прямой матчер остаётся локальной операцией по БД.

Порядок окон не менялся: после фикса областные адреса здесь просто не матчатся и остаются ждать корректного геокодера в следующем окне — ровно как раньше вели себя адреса без совпадения в реестре.

geo_precision осознанно остаётся NULL: по конвенции (089_listings_geo_precision.sql, geocode_missing.py и его тест) NULL означает «не coarse» — то же значение, что у точных адресов. Этот тир всегда даёт попадание на уровне дома, поэтому фильтр, исключающий 'city', для него срабатывать и не должен.

Масштаб уже нанесённого ущерба (только SELECT, ничего не менялось): 2040 объявлений имеют координаты внутри границ Екатеринбурга при адресе, называющем другой город региона; 1941 после исключения омонимов-новостроек («ЖК Заречный», «Берёзовский» — реальные названия внутри самого ЕКБ). ⚠️ Но только ~31 из них совпадают по координатам с екатеринбургскими реестрами — значит основная масса пришла не от этой задачи, а из других источников координат (GPS в карточках Авито у приграничных спутников). Перед чисткой нужна точная атрибуция источника — это отдельный шаг, здесь не делается.

Тесты: объявление другого города не матчится, екатеринбургское матчится как раньше. Оба падали на старом коде (проверено откатом только рабочего файла). Полный прогон: 2847 passed, 1 pre-existing. Также поправлен ложный комментарий в миграции 171 («не-ЕКБ адреса не матчатся — корректно»), который и закреплял ошибочную посылку.

Находка H3 из аудита #2583 — **единственный дефект, который портил данные каждую ночь**, а не только выдавал неверный ответ по запросу. `backfill_listings_coords_geoportal.py` брала все объявления без координат, парсила адрес (город при этом отбрасывается) и звала екатеринбургский реестр **напрямую, минуя `geocode()`** — то есть городской гейт не применялся вообще. Объявление из Тагила получало координаты Екатеринбурга, после чего тянуло медиану чужих оценок, исчезало из выборки своего города и приклеивалось к екатеринбургскому дому при сопоставлении по расстоянию. Задача включена, без лимита, в окне 05:00-06:00 — **выигрывала гонку** у областного геокодера (06:00-09:00). **Решение:** проверка `_names_non_ekb_city(address)` перед обращением к реестру — та же функция, что уже гейтит эти тиры внутри `geocode()`. Полноценный `geocode()` намеренно не используется: он добавил бы вызовы внешних геокодеров на каждый областной адрес из очереди (сотни-тысячи за ночь), а Яндекс сейчас отдаёт 403 (#2585) и живой только Nominatim с паузами. Прямой матчер остаётся локальной операцией по БД. Порядок окон не менялся: после фикса областные адреса здесь просто не матчатся и остаются ждать корректного геокодера в следующем окне — ровно как раньше вели себя адреса без совпадения в реестре. `geo_precision` осознанно остаётся `NULL`: по конвенции (`089_listings_geo_precision.sql`, `geocode_missing.py` и его тест) `NULL` означает «не coarse» — то же значение, что у точных адресов. Этот тир всегда даёт попадание на уровне дома, поэтому фильтр, исключающий `'city'`, для него срабатывать и не должен. **Масштаб уже нанесённого ущерба** (только SELECT, ничего не менялось): 2040 объявлений имеют координаты внутри границ Екатеринбурга при адресе, называющем другой город региона; 1941 после исключения омонимов-новостроек («ЖК Заречный», «Берёзовский» — реальные названия внутри самого ЕКБ). ⚠️ Но только **~31** из них совпадают по координатам с екатеринбургскими реестрами — значит основная масса пришла **не от этой задачи**, а из других источников координат (GPS в карточках Авито у приграничных спутников). Перед чисткой нужна точная атрибуция источника — это отдельный шаг, здесь не делается. Тесты: объявление другого города не матчится, екатеринбургское матчится как раньше. Оба падали на старом коде (проверено откатом только рабочего файла). Полный прогон: 2847 passed, 1 pre-existing. Также поправлен ложный комментарий в миграции 171 («не-ЕКБ адреса не матчатся — корректно»), который и закреплял ошибочную посылку.
lekss361 added 1 commit 2026-07-31 15:39:54 +00:00
fix(tradein/tasks): городской гейт в ночном бэкфилле координат (#2583)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI / changes (pull_request) Successful in 14s
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 3m8s
cf35e632db
backfill_coords_from_geoportal брал все listings с lat IS NULL, парсил street+house
и матчил напрямую против EKB-only ekb_geoportal_buildings, минуя geocoder.geocode()
и его городской гейт (_names_non_ekb_city). Улица+дом могут буквально совпасть между
Екатеринбургом и другим городом области ("проспект Ленина 1" есть и в ЕКБ, и в Нижнем
Тагиле) — такие адреса получали екатеринбургские координаты и портили радиусные
выборки аналогов на этой улице в ЕКБ, а сами исчезали из выборки своего города.

Фикс: _names_non_ekb_city(address) перед вызовом _geoportal_house_match — тот же гейт,
что уже используется в geocode(). Прямой вызов geoportal-матчера (а не полноценный
geocode()) сохранён намеренно — pure local-DB операция без внешнего HTTP, полноценный
geocode() добавил бы Nominatim/Yandex вызов на каждый non-EKB адрес backlog'а (лишняя
нагрузка на ограниченный Nominatim, Yandex сейчас 403 — #2585).

geo_precision оставлен NULL для house-level матчей этого тира — по конвенции
089_listings_geo_precision.sql/geocode_missing.py NULL означает "не coarse", то же
значение что geo_precision=None для precise-адресов в geocode_missing_listings;
исключать из radius-аналогов нужно только 'city'-fallback.

Порядок окон (05:00 geoportal → 06:00 geocode_missing_listings) не менялся: гонка была
безвредна для корректно заматченных EKB-адресов, вредна только из-за отсутствия гейта —
теперь non-EKB адреса здесь не матчатся вообще и просто ждут oblast-aware провайдеров
в следующем окне.

Поправлен ложный комментарий в migration 171 ("не-ЕКБ адреса не матчатся — корректно").

Ущерб на проде (SELECT-only, без изменений): 2040 листингов с координатами внутри
EKB-bbox (56.65-56.95, 60.40-60.85) при адресе, называющем другой город области
(1941 после исключения мкр/р-н/жк-омонимов вроде ЖК "Заречный" внутри ЕКБ). Только
~31 из них совпадают по координатам с ekb_geoportal_buildings/gendesign_cad_buildings —
основной массив, вероятно, из других источников координат (не только этот таск).
Чистка — отдельный шаг.
lekss361 merged commit 939c07917b into main 2026-07-31 15:43:55 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2588
No description provided.