[HIGH] tradein/houses: 20% домов без координат — геокодер чинит объявление, но в дом координаты не возвращаются #2771

Closed
opened 2026-08-07 08:35:17 +00:00 by bot-backend · 1 comment
Collaborator

Найдено при проверке срока по house_dedup_merge (#2704). Замерено на проде 2026-08-07 08:40–08:55 UTC.

Симптом

1945 домов из 9625 (20.2%) не имеют координат вообще — ни geom, ни lat/lon. На них висит 6393 объявления.

Диагноз: оборванная проводка, а не мёртвый код

Дом получает координаты один раз, в момент создания, из того объявления, которое его породило — matching/houses.py:295. geom дальше строит триггер houses_set_geom_trg из lat/lon. Всё это работает.

Обрыв в том, что происходит потом. Объявление, создавшее дом без координат, позже геокодируется — задача geoportal_coords_backfill живая и бежит ежедневно (последний прогон 08-07 05:47). Но она пишет в listings, а ON CONFLICT у вставки дома обновляет только address. Координаты в дом не возвращаются никогда.

Ни одна задача из 52 в расписании не пишет houses.lat/houses.lon. Единственные писатели — два ручных скрипта, scripts/backfill_house_coords.py и scripts/backfill_houses_dadata.py, и второй зависит от услуги DaData CLEAN, выключенной 69 суток (#2704 п.3).

Проверка диагноза

Если диагноз верен, у домов без координат должны быть объявления с координатами. Предсказание записано до запроса. Результат:

домов
без координат всего 1945
— без объявлений вовсе 114
— объявления есть, но тоже без координат 342
объявления с координатами есть 1489

77% домов без координат могли бы их получить прямо сейчас, из данных, которые уже лежат в соседней таблице.

Насколько безопасно переносить

Перенос имеет смысл только если объявления дома согласны между собой. Разброс (диаметр множества точек):

разброс домов объявлений
одно объявление 1026 1026
до 100 м 332 1647
100–250 м 23 161
250 м – 1 км 48 1107
1–10 км 44 1274
больше 10 км 17 264

Безопасная часть — 1358 домов (одно объявление + разброс до 100 м). С порогом 250 м — 1381.

Дома с координатами станут 7680 → ~9038 из 9625, то есть 79.8% → 93.9%.

Побочно: 109 домов — дефект сопоставителя

Дома, чьи объявления разбросаны дальше 250 м, — это не проблема переноса, а свидетельство неверного сопоставления: в одну запись дома сшиты объявления из разных населённых пунктов.

id адрес объявлений разброс
380628 ул. Кирова,4 5 384.1 км
378018 ул. Попова,13 2 313.6 км
380445 Зелёная ул.,5 2 307.7 км
380305 ул. Строителей,1 2 232.9 км
380284 ул. Гагарина,42 2 232.3 км

Это дословно тот класс коллизии, который описан в #2690 (Кедровка/Екатеринбург, Советская 17, 16 км) — только здесь он виден изнутри одной записи, а не между двумя. Переносить координаты таким домам нельзя ни в каком виде: неизвестно, какое из объявлений относится к настоящему зданию.

Что это меняет для схлопывания дублей

Сейчас в кластерах-дублях по канон-адресу 1024 проигравших отбраковываются гео-ограждением из-за отсутствия координат — ограждение ведёт себя правильно, без координат проверить тождество нечем.

Осторожно с формулировкой: перенос координат не «разблокирует слияния». Он даёт ограждению возможность их оценить — часть после этого сольётся, часть будет отвергнута корректно, как дома в разных городах. Обещать рост числа слияний нельзя, и не надо.

Что сделать

  1. Перенести координаты из объявлений в дом там, где объявления согласны (порог обосновать данными выше, не взять наугад).
  2. Сделать это повторяющейся задачей, а не однократным скриптом, — иначе через месяц дыра нарастёт заново, ровно как сейчас.
  3. Дома с разбросом выше порога — не трогать и вынести отдельным замером как дефекты сопоставления.
  4. Не молчать о пропущенных: в счётчиках прогона должно быть видно, сколько домов пропущено и почему.

Часть эпика #2674.

Найдено при проверке срока по `house_dedup_merge` (#2704). Замерено на проде 2026-08-07 08:40–08:55 UTC. ## Симптом **1945 домов из 9625 (20.2%) не имеют координат вообще** — ни `geom`, ни `lat`/`lon`. На них висит **6393 объявления**. ## Диагноз: оборванная проводка, а не мёртвый код Дом получает координаты **один раз, в момент создания**, из того объявления, которое его породило — `matching/houses.py:295`. `geom` дальше строит триггер `houses_set_geom_trg` из `lat`/`lon`. Всё это работает. Обрыв в том, что происходит **потом**. Объявление, создавшее дом без координат, позже геокодируется — задача `geoportal_coords_backfill` живая и бежит ежедневно (последний прогон 08-07 05:47). Но она пишет **в `listings`**, а `ON CONFLICT` у вставки дома обновляет только `address`. Координаты в дом не возвращаются никогда. Ни одна задача из 52 в расписании не пишет `houses.lat`/`houses.lon`. Единственные писатели — два **ручных скрипта**, `scripts/backfill_house_coords.py` и `scripts/backfill_houses_dadata.py`, и второй зависит от услуги DaData CLEAN, выключенной 69 суток (#2704 п.3). ## Проверка диагноза Если диагноз верен, у домов без координат должны быть объявления **с** координатами. Предсказание записано до запроса. Результат: | | домов | |---|---| | без координат всего | 1945 | | — без объявлений вовсе | 114 | | — объявления есть, но тоже без координат | 342 | | — **объявления с координатами есть** | **1489** | 77% домов без координат могли бы их получить прямо сейчас, из данных, которые уже лежат в соседней таблице. ## Насколько безопасно переносить Перенос имеет смысл только если объявления дома согласны между собой. Разброс (диаметр множества точек): | разброс | домов | объявлений | |---|---|---| | одно объявление | 1026 | 1026 | | до 100 м | 332 | 1647 | | 100–250 м | 23 | 161 | | 250 м – 1 км | 48 | 1107 | | 1–10 км | 44 | 1274 | | **больше 10 км** | **17** | 264 | **Безопасная часть — 1358 домов** (одно объявление + разброс до 100 м). С порогом 250 м — 1381. Дома с координатами станут 7680 → ~9038 из 9625, то есть **79.8% → 93.9%**. ## Побочно: 109 домов — дефект сопоставителя Дома, чьи объявления разбросаны дальше 250 м, — это не проблема переноса, а **свидетельство неверного сопоставления**: в одну запись дома сшиты объявления из разных населённых пунктов. | id | адрес | объявлений | разброс | |---|---|---|---| | 380628 | ул. Кирова,4 | 5 | 384.1 км | | 378018 | ул. Попова,13 | 2 | 313.6 км | | 380445 | Зелёная ул.,5 | 2 | 307.7 км | | 380305 | ул. Строителей,1 | 2 | 232.9 км | | 380284 | ул. Гагарина,42 | 2 | 232.3 км | Это дословно тот класс коллизии, который описан в #2690 (Кедровка/Екатеринбург, Советская 17, 16 км) — только здесь он виден **изнутри одной записи**, а не между двумя. Переносить координаты таким домам нельзя ни в каком виде: неизвестно, какое из объявлений относится к настоящему зданию. ## Что это меняет для схлопывания дублей Сейчас в кластерах-дублях по канон-адресу **1024 проигравших отбраковываются гео-ограждением из-за отсутствия координат** — ограждение ведёт себя правильно, без координат проверить тождество нечем. Осторожно с формулировкой: перенос координат **не «разблокирует слияния»**. Он даёт ограждению возможность их **оценить** — часть после этого сольётся, часть будет отвергнута корректно, как дома в разных городах. Обещать рост числа слияний нельзя, и не надо. ## Что сделать 1. Перенести координаты из объявлений в дом там, где объявления согласны (порог обосновать данными выше, не взять наугад). 2. Сделать это **повторяющейся** задачей, а не однократным скриптом, — иначе через месяц дыра нарастёт заново, ровно как сейчас. 3. Дома с разбросом выше порога — **не трогать** и вынести отдельным замером как дефекты сопоставления. 4. Не молчать о пропущенных: в счётчиках прогона должно быть видно, сколько домов пропущено и почему. Часть эпика #2674.
Author
Collaborator

Поправка к диагнозу: один писатель координат всё-таки есть

В шапке я написал: «из 52 расписаний houses.lat/lon не пишет ни одно; единственные писатели — два ручных скрипта». Это неверно.

На актуальном origin/main координаты дома пишет packages/scraper-kit/.../providers/avito/houses.py — канонический путь сохранения дома-avito, в том же UPDATE, что и остальное обогащение:

address    = COALESCE(houses.address, CAST(:address AS text)),
lat        = COALESCE(houses.lat,     CAST(:lat AS double precision)),
lon        = COALESCE(houses.lon,     CAST(:lon AS double precision)),
year_built = COALESCE(houses.year_built, CAST(:year_built AS int)),

Порядок аргументов важен: существующее значение выигрывает, новое подставляется только если своего нет. То есть это именно путь дозаполнения, а не перезаписи.

Почему я ошибся

Искал grep-ом по рабочему дереву, а оно отстало от origin/main на 351 коммит. Все три файла, на которые я ссылался, на актуальной ветке другие; номер строки вставки дома тоже другой (380, а не 295 — содержимое совпадает). Прод при этом крутит актуальный код, поэтому числа из шапки не затронуты — они мерены на живой БД, а не выведены из кода. Затронуты только утверждения о коде.

Это ровно тот промах, о котором сказано в самой задаче: проверять надо тем же путём, каким идёт рабочий код.

Что диагноз меняет, а что нет

Не меняет:

  • координаты дома по-прежнему не пополняются из его собственных объявлений — путь avito приносит координаты, которые отдала сама площадка по карточке дома, и только для домов, покрытых её каталогом;
  • 1489 домов, у которых объявления с координатами есть, а у дома нет, — измерены на проде и остаются.

Меняет формулировку: это не «писателя нет вовсе», а «писатель есть, но узкий и из другого источника». Диагноз «оборванная проводка» остаётся, обрыв — между геокодером объявлений и домом.

Побочно: главный риск задачи снят

Проверил на проде триггер, который строит точку из широты и долготы:

houses_set_geom_trg | INSERT UPDATE | BEFORE | включён

Он покрывает и UPDATE, а не только вставку. Значит UPDATE houses SET lat=..., lon=... построит geom сам, и отдельно его трогать не нужно. Это снимает риск «правка окажется беззвучной», который я записал как первый пункт проверки.

Сходится и с данными: домов, у которых есть широта с долготой, но нет точки, на проде ноль.

## Поправка к диагнозу: один писатель координат всё-таки есть В шапке я написал: «из 52 расписаний `houses.lat/lon` не пишет ни одно; единственные писатели — два ручных скрипта». **Это неверно.** На актуальном `origin/main` координаты дома пишет `packages/scraper-kit/.../providers/avito/houses.py` — канонический путь сохранения дома-avito, в том же `UPDATE`, что и остальное обогащение: ```sql address = COALESCE(houses.address, CAST(:address AS text)), lat = COALESCE(houses.lat, CAST(:lat AS double precision)), lon = COALESCE(houses.lon, CAST(:lon AS double precision)), year_built = COALESCE(houses.year_built, CAST(:year_built AS int)), ``` Порядок аргументов важен: существующее значение выигрывает, новое подставляется **только если своего нет**. То есть это именно путь **дозаполнения**, а не перезаписи. ### Почему я ошибся Искал `grep`-ом по рабочему дереву, а оно отстало от `origin/main` на **351 коммит**. Все три файла, на которые я ссылался, на актуальной ветке другие; номер строки вставки дома тоже другой (380, а не 295 — содержимое совпадает). Прод при этом крутит актуальный код, поэтому **числа из шапки не затронуты** — они мерены на живой БД, а не выведены из кода. Затронуты только утверждения о коде. Это ровно тот промах, о котором сказано в самой задаче: проверять надо тем же путём, каким идёт рабочий код. ### Что диагноз меняет, а что нет Не меняет: * координаты дома по-прежнему **не пополняются из его собственных объявлений** — путь avito приносит координаты, которые отдала сама площадка по карточке дома, и только для домов, покрытых её каталогом; * 1489 домов, у которых объявления с координатами есть, а у дома нет, — измерены на проде и остаются. Меняет формулировку: это **не «писателя нет вовсе»**, а «писатель есть, но узкий и из другого источника». Диагноз «оборванная проводка» остаётся, обрыв — между геокодером объявлений и домом. ### Побочно: главный риск задачи снят Проверил на проде триггер, который строит точку из широты и долготы: ``` houses_set_geom_trg | INSERT UPDATE | BEFORE | включён ``` Он покрывает **и UPDATE**, а не только вставку. Значит `UPDATE houses SET lat=..., lon=...` построит `geom` сам, и отдельно его трогать не нужно. Это снимает риск «правка окажется беззвучной», который я записал как первый пункт проверки. Сходится и с данными: домов, у которых есть широта с долготой, но нет точки, на проде **ноль**.
Sign in to join this conversation.
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#2771
No description provided.