fix(tradein/scraper): проставлять город объявления из контекста развёртки (#2594) #2598

Merged
lekss361 merged 1 commit from fix/tradein-listing-city-from-sweep into main 2026-07-31 20:14:39 +00:00
Owner

Closes #2594 частично (см. «что осталось»). Критический дефект: скрапер знает город в момент сбора, но не записывает его — объявления области рискуют получить координаты Екатеринбурга.

Замер, с которого всё началось: сбор Авито дал 243 объявления, координаты у 12, город в адресе — у 5. Приходит пр-т Вагоностроителей,70, ул. Победы,30 — тагильские улицы без города. В екатеринбургском реестре улица Победы есть в 89 домах, Ленина в 112, Тенистая в 17.

Решение: отдельная колонка, а не дописывание в адрес

Выбрана колонка listings.city (миграция 196), заполняется из контекста развёртки.

Почему не дописывать город в address — исполнитель проверил разбор адреса: для адресов без указателя типа улицы («Дружинина, 33») парсер берёт весь текст до номера дома как название улицы. Префикс «Нижний Тагил, » попал бы в поле улицы и сломал бы сопоставление домов сразу в трёх местах. Существующая функция вырезает из адреса только «Екатеринбург», а расширять её означало бы трогать геокодер, который только что переработан. Отдельная колонка этого риска не несёт: исходный текст адреса не меняется.

Детали

save_listings(..., city=...) пишет колонку, при конфликте — COALESCE(EXCLUDED.city, listings.city), чтобы вызов без города не затирал уже известный. Новая функция resolve_city_name(city_slug) переводит слаг в название и проброшена во все боевые пути записи: городские развёртки Авито, Яндекса, Циана, плюс полные загрузки и новостройки (они подтверждённо екатеринбургские по докстрингам).

Симметрия соблюдена: resolve_city_name(None) даёт «Екатеринбург», так что екатеринбургские объявления тоже получают город — асимметрии «у области есть, у ЕКБ нет» не возникает.

Домклик получает честный None для незнакомого идентификатора города вместо угадывания — его областные развёртки ещё не подключены.

Другие источники — проверены

  • Циан: тот же дефект, причём с жёстко зашитым запасным значением «Екатеринбург (Cian)» при пустом адресе;
  • Яндекс: адрес приходит от их геокодера, обычно с городом — менее подвержен, но городская развёртка теперь тоже проставляет город как страховку;
  • Домклик: как у Авито, без города.

⚠️ Что осталось (важно, отдельный PR)

Колонка заполняется, но петля пока не замкнута: ночное геокодирование и бэкфилл координат по-прежнему решают по тексту адреса и новую колонку не читают. То есть новые объявления получат правильный город в базе, но при геокодировании bare-адрес всё ещё может уйти в Екатеринбург.

Исполнитель осознанно не стал это делать здесь: трогать геокодер было прямо запрещено (он только что переработан в #2582/#2580), а задача сужена до пути записи. Следующий шаг — передавать город из колонки как подсказку в геокодирование, правя только вызывающий код.

Бэкфилл накопленных строк (384 Тагил, 267 Каменск-Уральский, 114 Первоуральск) — третий шаг.

Тесты: +62. Все 19 новых падали на старом коде (KeyError: 'city', функции не существует). Полный прогон: 2961 passed, 1 pre-existing.

Closes #2594 частично (см. «что осталось»). Критический дефект: скрапер знает город в момент сбора, но не записывает его — объявления области рискуют получить координаты Екатеринбурга. Замер, с которого всё началось: сбор Авито дал 243 объявления, координаты у 12, город в адресе — у 5. Приходит `пр-т Вагоностроителей,70`, `ул. Победы,30` — тагильские улицы без города. В екатеринбургском реестре улица Победы есть в 89 домах, Ленина в 112, Тенистая в 17. ## Решение: отдельная колонка, а не дописывание в адрес Выбрана колонка `listings.city` (миграция 196), заполняется из контекста развёртки. **Почему не дописывать город в `address`** — исполнитель проверил разбор адреса: для адресов без указателя типа улицы («Дружинина, 33») парсер берёт весь текст до номера дома как название улицы. Префикс «Нижний Тагил, » попал бы в поле улицы и сломал бы сопоставление домов сразу в трёх местах. Существующая функция вырезает из адреса только «Екатеринбург», а расширять её означало бы трогать геокодер, который только что переработан. Отдельная колонка этого риска не несёт: исходный текст адреса не меняется. ## Детали `save_listings(..., city=...)` пишет колонку, при конфликте — `COALESCE(EXCLUDED.city, listings.city)`, чтобы вызов без города не затирал уже известный. Новая функция `resolve_city_name(city_slug)` переводит слаг в название и проброшена во все боевые пути записи: городские развёртки Авито, Яндекса, Циана, плюс полные загрузки и новостройки (они подтверждённо екатеринбургские по докстрингам). **Симметрия соблюдена**: `resolve_city_name(None)` даёт «Екатеринбург», так что екатеринбургские объявления тоже получают город — асимметрии «у области есть, у ЕКБ нет» не возникает. **Домклик** получает честный `None` для незнакомого идентификатора города вместо угадывания — его областные развёртки ещё не подключены. ## Другие источники — проверены - **Циан**: тот же дефект, причём с жёстко зашитым запасным значением «Екатеринбург (Cian)» при пустом адресе; - **Яндекс**: адрес приходит от их геокодера, обычно с городом — менее подвержен, но городская развёртка теперь тоже проставляет город как страховку; - **Домклик**: как у Авито, без города. ## ⚠️ Что осталось (важно, отдельный PR) Колонка заполняется, но **петля пока не замкнута**: ночное геокодирование и бэкфилл координат по-прежнему решают по тексту адреса и новую колонку не читают. То есть новые объявления получат правильный город в базе, но при геокодировании bare-адрес всё ещё может уйти в Екатеринбург. Исполнитель осознанно не стал это делать здесь: трогать геокодер было прямо запрещено (он только что переработан в #2582/#2580), а задача сужена до пути записи. Следующий шаг — передавать город из колонки как подсказку в геокодирование, правя только вызывающий код. Бэкфилл накопленных строк (384 Тагил, 267 Каменск-Уральский, 114 Первоуральск) — третий шаг. Тесты: +62. Все 19 новых падали на старом коде (`KeyError: 'city'`, функции не существует). Полный прогон: 2961 passed, 1 pre-existing.
lekss361 added 1 commit 2026-07-31 19:18:05 +00:00
fix(tradein/scraper): проставлять город объявления из контекста развёртки (#2594)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / 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 2m28s
0a4e126b30
Скрапер знает город в момент сбора (city_slug из CITY_LOCATIONS/CITY_ANCHORS,
scraper_kit.orchestration.pipeline), но раньше нигде его не записывал. Провайдеры
(avito/cian) часто отдают адрес БЕЗ города в тексте ("ул. Победы, 30" вместо
"Нижний Тагил, ул. Победы, 30" — cian даже явно вырезает location-часть перед
записью, providers/cian/serp.py _format_address skip_types={"location",...}).
Без города такой адрес при геокодинге считался "город не назван" и коллизировал
с одноимённой екатеринбургской улицей (Ленина/Победы/Тенистая — сотни совпадений
в ЕКБ-реестрах) → объявление получало координаты Екатеринбурга.

Fix: отдельная колонка listings.city (196_listings_city.sql), проставляется из
sweep-контекста через save_listings(..., city=...) — НЕ парсингом/дописыванием
в address. Раздельная колонка не портит исходный текст адреса: downstream
text-парсеры (geocoder._parse_street_house/_names_non_ekb_city, estimator
house-matching) продолжают работать на исходном сыром тексте неизменёнными —
дописывание города в address ломало бы bare-form адреса без street-маркера
("Дружинина, 33" без "ул.") в этих же парсерах.

Симметрия: EKB-варианты city-sweep функций (city_slug=None) тоже получают
city="Екатеринбург" — resolve_city_name(None) даёт тот же ЕКБ-дефолт, что и
get_city_location/get_city_anchors. Проставлено во всех продовых write-путях:
run_avito_city_sweep/run_yandex_city_sweep/run_cian_city_sweep (city_slug-aware),
run_avito_newbuilding_sweep/run_cian_full_load/run_yandex_full_load/
run_avito_full_load (подтверждённо EKB-only по докстрингам), run_domclick_city_sweep
(EKB city_id, oblast B2 ещё не wired — честный None для неизвестного city_id).

Scope: только write-path для НОВЫХ листингов. Бэкфилл накопленных строк и
консультация city в geocode_missing_listings/backfill_coords_from_geoportal
(gate там пока text-only, _names_non_ekb_city) — geocoder.py намеренно не
тронут (#2582/#2580) — отдельные follow-up задачи.
lekss361 merged commit da329cda5d into main 2026-07-31 20:14:39 +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#2598
No description provided.