Сбор Яндекса по Москве уже идёт, а лить его было нечем: в SOURCE_VIEWS стояли
только cian и avito.
Отбор Москвы у Яндекса не требует ни префикса округа (как у Циана), ни
пред-геокода (как у Авито). Адрес приходит полным и нормализованным — «Россия,
Москва, Коробейников переулок, 1», регион читается вторым компонентом. Замер по
21 393 карточкам первого прохода: во втором компоненте ровно ДВА значения,
«Москва» 10 610 и «Московская область» 10 783, третьего не встречается. Новая
Москва отдельным значением не приходит — Троицк и Зеленоград Яндекс кладёт под
«Москва», что совпадает с кодом региона 77.
Координаты, адрес и ссылка заполнены у 100% карточек, поэтому geom появляется
сразу и ждать ночного `geocode_missing` не нужно. `--geocode` для yandex
отклоняется так же, как для cian: квота нужна только Авито.
`filter_by_okrug` заменён словарём CITY_FILTERS — источник либо сам говорит про
город, либо его в словаре нет и без пред-геокода писать его нельзя. Поведение
cian и avito байт в байт прежнее.
`uv run python -m pytest tests/test_msk_raw_import.py` — 42 passed, ruff чист.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ8jqr4SFirX5tFLwdSrXh
Кэш `msk_raw.avito_geocode` создаётся только боевым прогоном (`geocode and not
dry_run`), а читается безусловно. На проде это роняло `--dry-run` первым же
запросом — UndefinedTable msk_raw.avito_geocode, то есть ломалась ровно та
репетиция, ради которой сухой прогон и существует.
Наличие отношения проверяется через `to_regclass`, а не ловится исключением: в
Postgres упавший оператор кладёт транзакцию целиком, и except потребовал бы
rollback посреди чужого батча.
Замер после правки (500 карточек, прод): отобрано 458, область 7, не разрешено
35 (7%), геокод-вызовов 338 на 500 карточек — дедупликация адреса внутри
страницы работает. Счётчики сходятся.
Заодно выяснилось, что дневная квота DaData на подсказки — 200 000, а не 10 000:
`stat/daily` на проде показывает suggestions remaining 200000 при нулевом
расходе. Весь корпус (21 565 различных адресов) проходит за один заход, дробить
на трое суток не нужно.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ8jqr4SFirX5tFLwdSrXh
У карточек Авито нет координат ни у одной из 50 335, а адрес — голая улица с
домом («Варшавское ш.,62к1»). Сбор шёл по `/moskva_i_mo/`, поэтому Москву от
области отделить было нечем: префиксный фильтр, работающий у Циана по округу,
здесь отбросил бы 100% строк. Наивный матч адресов к `houses(77)` даёт ровно 0
совпадений — дома лежат как «ЮАО, р-н Даниловский, проспект Андропова, 18».
Замер показал, что одного геокода мало: с констрейнтом «Москва + Московская»
дом находится у 92% адресов, но верхний кандидат DaData расходится с реальным
городом у 15% и почти всегда в пользу столицы. Недостающий сигнал лежал рядом и
бесплатно — слаг города в `source_url` (`avito.ru/moskva/...`), заполнен у 100%
карточек: 22 120 с `moskva`, остальное — подмосковные слаги.
Поэтому город берётся из слага и сужает констрейнт, а регион — из КЛАДР ответа,
не из слага: Новая Москва (Троицк, Щербинка, Коммунарка, Зеленоград) идёт своими
слагами, но это регион 77. Регион 77 пишется в `listings` сразу с координатами и
`geo_precision='house'`, поэтому строки попадают в radius-подбор аналогов без
ожидания `geocode_missing`. Регион 50 не пишется, а копится строкой в кэше
`msk_raw.avito_geocode` — до появления региона в реестре.
Кэш ключуется слагом и нормализованным адресом, отрицательные ответы тоже
кэшируются, так что повторный прогон внешний сервис не дёргает. `geocode_cache`
приложения не тронут — там другой ключ. `--allow-unfiltered` без `--geocode`
остаётся прежним аварийным режимом.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ8jqr4SFirX5tFLwdSrXh
Три куска, каждый нужен, чтобы поиск и оценка по Москве заработали end-to-end.
1. Импортёр msk_raw -> listings (app/tasks/msk_raw_import.py).
Переиспользует штатный save_listings из кита: писатель уже параметризован
регионом, свой не нужен. payload в msk_raw — сериализованный ScrapedLot
один в один, так что импорт сводится к сборке модели и вызову писателя.
Москва отбирается по префиксу административного округа в адресе, а не по
bbox. Причина: адрес Циан не содержит города, а границы региона 77
захватывают ближний пояс области. Замер по проду: с округом 35 552, все
внутри bbox 77; без округа внутри bbox 17 576 — это область. Отдельно
отсекаются 212 карточек с адресом «Екатеринбург (Cian)», артефакт парсера.
listing_segment ПЕРЕСЧИТЫВАЕТСЯ перед записью, а не копируется из payload.
Кит ставит novostroyki по одному наличию offer.newbuilding.id. Замер по
всем 60 464 карточкам: is_from_developer=true у НУЛЯ, false у 29 000,
отсутствует у 31 464. Застройщик не продаёт ни одной карточки корпуса.
В проде есть гвард (estimator.py): в аналоги идут строки только с
listing_segment IS NULL или 'vtorichka' — копирование метки как есть
выбросило бы 29 000 строк из подбора.
Авито импортируется только с явным --allow-unfiltered: в его адресе нет ни
города, ни округа, координат нет ни у одной из 50 335 карточек, отличить
область от Москвы нечем.
Прогон на проде: прочитано 60 464, записано 35 552, все с геометрией,
35 182 привязаны к дому, создано 10 960 домов. Повторный проход строки не
дублирует — idempotency на dedup_hash, проверено.
2. Подсказки адреса стали региональными (geocoder.suggest, api/v1/geocode).
Раньше suggest вообще не принимал регион: DaData звалась с жёстким
region='Свердловская', Nominatim — с viewbox 66-го и bounded=1. Московский
адрес давал ПУСТОЙ список молча, без ошибки; в коде это уже было описано
как известный баг. Механику по регионам переиспользовали из geocode(),
вторую не писали. Кадастровый тир для не-66 не зовётся: он на ЕКБ-данных.
3. Оценка перестала геокодировать Москву свердловским скоупом (estimator).
geocode() звалась без региона, то есть с дефолтом 66, и московский адрес
возвращал бы пустую оценку с причиной address_not_geocoded даже с рабочими
подсказками. Регион запроса определяется по координатам через реестр, затем
по city_hint, затем дефолт. Fast-path клиентских координат стал
региононезависимым: OBLAST66_BBOX и REGIONS[66].bbox_region совпадают
байт-в-байт, поэтому для 66 поведение прежнее, добавились координаты Москвы.
Регресс-нейтральность по Свердловской области — главный критерий всех трёх
кусков. Тесты: 1160 passed по затронутым областям.
Известные ограничения. Границы 77 захватывают ближний пояс области, Химки
резолвятся в Москву. У региона 77 нет ни одного тира обогащения, оценка
поедет на аналогах и сделках. Ценовая полоса по Москве одна на весь город.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ8jqr4SFirX5tFLwdSrXh