tradein: выпилить Яндекс-геокодер и остатки источника N1 #2593

Closed
opened 2026-07-31 17:47:51 +00:00 by lekss361 · 2 comments
Owner

Решение владельца 2026-07-31. Ключ Яндекс-геокодера недействителен (#2585, 403 Invalid api key), перевыпускать не будем. Источник N1 не собирается с 16 июня и отсутствует в расписании.

Перед составлением плана проведена разведка четырьмя независимыми агентами + проверка скептиком (все находки сверены с forgejo/main и прод-БД).


⚠️ ЧЕГО НЕ ТРОГАТЬ — читать первым

Обычный поиск по «yandex» или «geocod» находит это, но удаление сломает работающий сбор:

Место Что это на самом деле
packages/scraper-kit/.../providers/yandex/serp.py:273, :397 location.geocoderAddressполе их JSON, откуда берётся адрес объявления. Источник с 12 387 активными объявлениями
packages/scraper-kit/.../providers/avito/imv.py:577 _geocode() + вызов :523 геокодер Авито для оценки, к Яндексу отношения не имеет
tradein-mvp/scripts/backfill-yandex-addresses-ekb.py бэкфилл адресов Недвижимости; попадёт под маску scripts/*yandex* вместе с настоящей целью
EKB_BBOX_TIGHT / EKB_BBOX_WIDE (geocoder.py:59-60) живые, не путать с удаляемым EKB_BBOX (:757)
_nominatim_region_ok (:515) не путать с удаляемым _yandex_region_ok (:628)
geocode_missing.py:160 geo.provider == "cache" про геопортальный тир, не про Яндекс
таблица address_mismatch_audit читается живым house_dedup_merge + его тестами — не дропать

Вся подсистема Яндекс.Недвижимость (source='yandex', yandex_city_sweep*, yandex_detail_backfill, yandex_address_backfill, yandex_newbuilding_sweep, deactivate_stale_yandex) остаётся полностью.


Что удаляем — Яндекс-геокодер

Код (backend/app/services/geocoder.py): _yandex_bias 230-244, _yandex_region_ok 628-649, _yandex_lookup 653-750, EKB_BBOX 757-762, _parse_yandex_members 777-806, _yandex_geocode_request 850-878, _yandex_suggest 881-942, _yandex_reverse 1689-1757. Тир-блоки: suggest() 1435-1444, geocode() 1578-1611 (включая typo-петлю), reverse_geocode() 1884-1891. Типы :39 и :1662 — убрать "yandex" из перечисления.

Конфиг: config.py:74-78 (ключ + мёртвый yandex_suggest_key), описания в api/v1/geocode.py:126,131, комментарии в fdw.py:40, estimator.py:3204-3209, geocode_missing.py:9.

Скрипты целиком: backend/scripts/_yandex_reverse.py, backfill_house_coords.py и audit_address_mismatch.py (оба без ключа мертвы — SystemExit), их тесты.

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

Что удаляем — N1

Уже удалён на 90% миграцией 165. Осталось: frontend/.../v2/SourcesMap.tsx:92, backend/app/api/v1/admin.py:274, докстринг tests/test_estimator_source_quota.py:4.

Данные оставляем (382 объявления, is_active=false): все поверхности провайдер-агностичны с фолбэками, ничего не ломается. Удаление каскадом снесло бы 15 913 снимков источников и 174 снимка объявлений — исторические данные без пользы от их потери.


Ошибки разведки, которые НЕ надо повторять

  1. _cache_get не подставляет row.provider — там жёстко provider="cache" (geocoder.py:480). 6044 яндекс-строки кэша физически не могут вернуться как "yandex". Сужение типа безопасно, предложенный UPDATE geocode_cache — вредная бессмысленная запись в прод.
  2. Провал ночного геокодирования начался 20 июля — за 9 дней до смерти ключа. Реальная причина видна в очереди: топ адресов это Новосибирск, Тюмень, Казань, Челябинск — их режет региональный фильтр. Это норма, а не поломка; удаление Яндекса метрику не поднимет. Экономия времени прогона останется (три пустых попытки на промах), но обосновывать задачу «джоб починится» нельзя.
  3. offer_price_history по объявлениям — каскад CASCADE, не SET NULL (счёт по N1 всё равно 0, но правило сформулировано неверно).
  4. В каскаде N1 пропущена listings_snapshots — 174 строки.

Порядок

Три самостоятельных PR: остатки N1 (мелочь) → ядро геокодера с тестами → скрипты и документация. Каждый обратим по отдельности.

Последствия

Единственным внешним геокодером остаётся Nominatim (локальные екатеринбургские реестры и DaData в подсказках сохраняются). Ключ выставлен в трёх контейнерах — tradein-backend, tradein-scraper и tradein-tgbot; чистка окружения на сервере — ручной шаг, но старт не сломает: настройки читаются с extra="ignore".

Решение владельца 2026-07-31. Ключ Яндекс-геокодера недействителен (#2585, `403 Invalid api key`), перевыпускать не будем. Источник N1 не собирается с 16 июня и отсутствует в расписании. Перед составлением плана проведена разведка четырьмя независимыми агентами + проверка скептиком (все находки сверены с `forgejo/main` и прод-БД). --- ## ⚠️ ЧЕГО НЕ ТРОГАТЬ — читать первым Обычный поиск по «yandex» или «geocod» находит это, но **удаление сломает работающий сбор**: | Место | Что это на самом деле | |---|---| | `packages/scraper-kit/.../providers/yandex/serp.py:273, :397` | `location.geocoderAddress` — **поле их JSON**, откуда берётся адрес объявления. Источник с **12 387 активными** объявлениями | | `packages/scraper-kit/.../providers/avito/imv.py:577` `_geocode()` + вызов `:523` | **геокодер Авито** для оценки, к Яндексу отношения не имеет | | `tradein-mvp/scripts/backfill-yandex-addresses-ekb.py` | бэкфилл адресов **Недвижимости**; попадёт под маску `scripts/*yandex*` вместе с настоящей целью | | `EKB_BBOX_TIGHT` / `EKB_BBOX_WIDE` (`geocoder.py:59-60`) | живые, не путать с удаляемым `EKB_BBOX` (`:757`) | | `_nominatim_region_ok` (`:515`) | не путать с удаляемым `_yandex_region_ok` (`:628`) | | `geocode_missing.py:160` `geo.provider == "cache"` | про геопортальный тир, не про Яндекс | | таблица `address_mismatch_audit` | читается живым `house_dedup_merge` + его тестами — **не дропать** | Вся подсистема **Яндекс.Недвижимость** (`source='yandex'`, `yandex_city_sweep*`, `yandex_detail_backfill`, `yandex_address_backfill`, `yandex_newbuilding_sweep`, `deactivate_stale_yandex`) остаётся полностью. --- ## Что удаляем — Яндекс-геокодер **Код** (`backend/app/services/geocoder.py`): `_yandex_bias` 230-244, `_yandex_region_ok` 628-649, `_yandex_lookup` 653-750, `EKB_BBOX` 757-762, `_parse_yandex_members` 777-806, `_yandex_geocode_request` 850-878, `_yandex_suggest` 881-942, `_yandex_reverse` 1689-1757. Тир-блоки: `suggest()` 1435-1444, `geocode()` 1578-1611 (включая typo-петлю), `reverse_geocode()` 1884-1891. Типы `:39` и `:1662` — убрать `"yandex"` из перечисления. **Конфиг**: `config.py:74-78` (ключ + мёртвый `yandex_suggest_key`), описания в `api/v1/geocode.py:126,131`, комментарии в `fdw.py:40`, `estimator.py:3204-3209`, `geocode_missing.py:9`. **Скрипты целиком**: `backend/scripts/_yandex_reverse.py`, `backfill_house_coords.py` и `audit_address_mismatch.py` (оба без ключа мертвы — `SystemExit`), их тесты. **Тесты** — 6 файлов, точные счётчики в плане. Правило: снимая заглушку, **обязательно ставить заглушку нового тира**, иначе тест пойдёт в реальную сеть. ## Что удаляем — N1 Уже удалён на 90% миграцией 165. Осталось: `frontend/.../v2/SourcesMap.tsx:92`, `backend/app/api/v1/admin.py:274`, докстринг `tests/test_estimator_source_quota.py:4`. **Данные оставляем** (382 объявления, `is_active=false`): все поверхности провайдер-агностичны с фолбэками, ничего не ломается. Удаление каскадом снесло бы 15 913 снимков источников и 174 снимка объявлений — исторические данные без пользы от их потери. --- ## Ошибки разведки, которые НЕ надо повторять 1. **`_cache_get` не подставляет `row.provider`** — там жёстко `provider="cache"` (`geocoder.py:480`). 6044 яндекс-строки кэша физически не могут вернуться как `"yandex"`. Сужение типа безопасно, предложенный `UPDATE geocode_cache` — вредная бессмысленная запись в прод. 2. **Провал ночного геокодирования начался 20 июля — за 9 дней до смерти ключа.** Реальная причина видна в очереди: топ адресов это Новосибирск, Тюмень, Казань, Челябинск — их режет региональный фильтр. Это норма, а не поломка; удаление Яндекса метрику не поднимет. Экономия времени прогона останется (три пустых попытки на промах), но обосновывать задачу «джоб починится» нельзя. 3. `offer_price_history` по объявлениям — каскад `CASCADE`, не `SET NULL` (счёт по N1 всё равно 0, но правило сформулировано неверно). 4. В каскаде N1 пропущена `listings_snapshots` — 174 строки. ## Порядок Три самостоятельных PR: остатки N1 (мелочь) → ядро геокодера с тестами → скрипты и документация. Каждый обратим по отдельности. ## Последствия Единственным внешним геокодером остаётся Nominatim (локальные екатеринбургские реестры и DaData в подсказках сохраняются). Ключ выставлен в **трёх** контейнерах — `tradein-backend`, `tradein-scraper` и `tradein-tgbot`; чистка окружения на сервере — ручной шаг, но старт не сломает: настройки читаются с `extra="ignore"`.
Author
Owner

Части 1 и 2 смержены

  • #2595 — остатки N1. Данные (382 объявления) оставлены как исторические.
  • #2596 — ядро Яндекс-геокодера, −845 строк. APPROVE от deep-review.

Откат геокодера, если решение изменится: git revert 3d075632 — один коммит, чистое удаление, возвращает и код, и тесты (они в том же коммите). Ни миграций, ни изменения контракта API, ни формата данных. Прод-переменную можно не трогать: настройки читаются с extra="ignore". Единственный риск — конфликты в блоках тиров geocoder.py, они растут по мере правок этого файла. Реальная цена возврата не в коде, а в перевыпуске платной квоты.


Дополнения в часть 3 (сверх исходного списка)

Найдено ревью, обязательно к включению:

  1. backend/tests/fixtures/yandex_geocode_sample.json — удалить вместе со скриптами audit_address_mismatch.py/_yandex_reverse.py и их тестами. В исходном списке файла нет; если забыть — останется сирота. Сейчас он ещё используется test_audit_address_mismatch.py:152,174, поэтому в части 2 намеренно оставлен.

  2. Env- и doc-трейл ключа: tradein-mvp/.env.example:13, DEPLOY.md:60,69,83,203, docker-compose.prod.yml:136,149 (только комментарии), docs/Secrets_Rotation_Policy.md:79,149,156, backend/scripts/README.md:19,49,59,88. В части 2 отложено правильно — скрипты до части 3 читают переменную напрямую, и удаление из .env.example сломало бы инструкцию.

  3. 🟡 Пробел покрытия (важнее остального). Удалённые яндексовые тесты были единственными provider-level проверками инварианта #2576 — «молча не подставляем Екатеринбург». Nominatim теперь единственный внешний провайдер, а #2576 это баг с уверенно неверной ценой жителю другого города. Нужен тест, что _nominatim_lookup реально кладёт город в запрос: перехватить исходящий запрос и проверить q == "Нижний Тагил, Ленина, 1" и отсутствие «Екатеринбург».

  4. 🟢 Мелочи: висячий patch(...settings) + mock_settings.yandex_geocoder_api_key = None в tests/services/test_cadastral_reverse.py:207,215 (+ докстринг :203 врёт), мёртвый _REAL_ASYNC_CLIENT в tests/test_geocoder_bbox.py:418.

Оставить как есть (удешевляют откат, стоимость нулевая): OBLAST66_VIEWBOX["ll"]/["spn"] и значение "exact" в _SNAP_PRECISIONS. Последнее — контракт с фронтом (та же константа в MapPicker.tsx:21), причём соседнее "cadastral" было недостижимым и до этого PR.


Vault (отдельно, для родительской сессии)

Устаревают по факту мержа: decisions/2026-05-27_dadata_q_series_shipped.md:19 (цепочка тиров всё ещё описана с Яндексом), fixes/Fix_Geocoder_City_Leak_Non_Ekb_Jul12.md:27,32,48, запись ключа в meta/00_credentials.md:380,619 — пометить как выведенный из эксплуатации.

## Части 1 и 2 смержены - **#2595** — остатки N1. Данные (382 объявления) оставлены как исторические. - **#2596** — ядро Яндекс-геокодера, −845 строк. ✅ APPROVE от deep-review. **Откат геокодера, если решение изменится:** `git revert 3d075632` — один коммит, чистое удаление, возвращает и код, и тесты (они в том же коммите). Ни миграций, ни изменения контракта API, ни формата данных. Прод-переменную можно не трогать: настройки читаются с `extra="ignore"`. Единственный риск — конфликты в блоках тиров `geocoder.py`, они растут по мере правок этого файла. Реальная цена возврата не в коде, а в перевыпуске платной квоты. --- ## Дополнения в часть 3 (сверх исходного списка) Найдено ревью, обязательно к включению: 1. **`backend/tests/fixtures/yandex_geocode_sample.json`** — удалить **вместе** со скриптами `audit_address_mismatch.py`/`_yandex_reverse.py` и их тестами. В исходном списке файла нет; если забыть — останется сирота. Сейчас он ещё используется `test_audit_address_mismatch.py:152,174`, поэтому в части 2 намеренно оставлен. 2. **Env- и doc-трейл ключа**: `tradein-mvp/.env.example:13`, `DEPLOY.md:60,69,83,203`, `docker-compose.prod.yml:136,149` (только комментарии), `docs/Secrets_Rotation_Policy.md:79,149,156`, `backend/scripts/README.md:19,49,59,88`. В части 2 отложено правильно — скрипты до части 3 читают переменную напрямую, и удаление из `.env.example` сломало бы инструкцию. 3. 🟡 **Пробел покрытия (важнее остального).** Удалённые яндексовые тесты были **единственными** provider-level проверками инварианта #2576 — «молча не подставляем Екатеринбург». Nominatim теперь единственный внешний провайдер, а #2576 это баг с уверенно неверной ценой жителю другого города. Нужен тест, что `_nominatim_lookup` реально кладёт город в запрос: перехватить исходящий запрос и проверить `q == "Нижний Тагил, Ленина, 1"` и отсутствие «Екатеринбург». 4. 🟢 Мелочи: висячий `patch(...settings)` + `mock_settings.yandex_geocoder_api_key = None` в `tests/services/test_cadastral_reverse.py:207,215` (+ докстринг `:203` врёт), мёртвый `_REAL_ASYNC_CLIENT` в `tests/test_geocoder_bbox.py:418`. **Оставить как есть** (удешевляют откат, стоимость нулевая): `OBLAST66_VIEWBOX["ll"]/["spn"]` и значение `"exact"` в `_SNAP_PRECISIONS`. Последнее — контракт с фронтом (та же константа в `MapPicker.tsx:21`), причём соседнее `"cadastral"` было недостижимым и до этого PR. --- ## Vault (отдельно, для родительской сессии) Устаревают по факту мержа: `decisions/2026-05-27_dadata_q_series_shipped.md:19` (цепочка тиров всё ещё описана с Яндексом), `fixes/Fix_Geocoder_City_Leak_Non_Ekb_Jul12.md:27,32,48`, запись ключа в `meta/00_credentials.md:380,619` — пометить как выведенный из эксплуатации.
Collaborator

Все три части смержены 31.07 (#2595 N1, #2596 ядро геокодера, #2599 скрипты/доки) — issue оставался открытым. Сегодня (05.08) проведена независимая пост-верификация состояния main:

  • Ноль живых ссылок на удалённые символы (_yandex_lookup/_yandex_suggest/_yandex_reverse/_yandex_bias/_yandex_region_ok/_parse_yandex_members, ключи в config) — только исторические комменты.
  • Вся карта «⚠️ ЧЕГО НЕ ТРОГАТЬ» цела: location.geocoderAddress (serp.py), _geocode Авито, backfill-yandex-addresses-ekb.py, EKB_BBOX_TIGHT/WIDE, _nominatim_region_ok, address_mismatch_audit.
  • Мёртвые скрипты удалены; полный сьют 3269 passed (единственный fail — известный pre-existing test_search_cache_hit 401).

Работы не осталось — закрываю.

Все три части смержены 31.07 (#2595 N1, #2596 ядро геокодера, #2599 скрипты/доки) — issue оставался открытым. Сегодня (05.08) проведена независимая пост-верификация состояния main: - Ноль живых ссылок на удалённые символы (`_yandex_lookup/_yandex_suggest/_yandex_reverse/_yandex_bias/_yandex_region_ok/_parse_yandex_members`, ключи в config) — только исторические комменты. - Вся карта «⚠️ ЧЕГО НЕ ТРОГАТЬ» цела: `location.geocoderAddress` (serp.py), `_geocode` Авито, `backfill-yandex-addresses-ekb.py`, `EKB_BBOX_TIGHT/WIDE`, `_nominatim_region_ok`, `address_mismatch_audit`. - Мёртвые скрипты удалены; полный сьют 3269 passed (единственный fail — известный pre-existing `test_search_cache_hit` 401). Работы не осталось — закрываю.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#2593
No description provided.