fix(tradein/yandex): 3535 source_url ведут на сайт застройщика — переписываем из offerId (#2838) #2840

Merged
bot-backend merged 1 commit from fix/yandex-source-url-backfill into main 2026-08-12 15:33:32 +00:00
Collaborator

Summary

Разовое лечение колонки после #2838. source_url пишется только при вставке (нет ни в ON CONFLICT DO UPDATE, ни в reconcile-UPDATE в scraper_kit/base.py), поэтому канонизация продюсера #2235 вылечила только новые строки, а миграция 164 — только легаси с делённым URL. Строки с уникальной ссылкой на карточку застройщика не попали никуда.

Миграция 257_listings_backfill_yandex_source_url.sql переписывает их source_url формулой продюсера _canonical_source_url (https://realty.yandex.ru/offer/<source_id>/), сохраняя прежние адреса в таблицу отката.

Замер на проде 2026-08-12 (перемерен, не взят из issue)

строк
source='yandex' AND source_url !~ '/offer/[0-9]+' 3535
из них source_id ~ '^[0-9]+$' (адрес восстановим) 3535
нечем адресовать 0
source_url IS NULL 0
is_active 3522

Множество замкнуто: после самой свежей его строки (id 2583989) вставлено 6892 yandex-строк, ни одна в него не попала.

Коллизий: 0 с существующими строками, 0 внутри множества, 0 дублей source_url у yandex после апдейта.

Dry-run на проде (BEGIN … ROLLBACK, тем же телом файла)

CREATE TABLE / COMMENT / INSERT 0 3535 / UPDATE 3535
непарсимых после: 0        дублей source_url у yandex: 0
очередь #2838:  url_from_offer_id 3535 → 0, unenrichable_pending 0
yandex_address_backfill (кандидатов 5545): с непарсимым URL 1777 → 0
откат по таблице: UPDATE 3535 → множество восстановлено ровно (3535)

10 примеров «было → стало» — в шапке миграции.

Обратимость

raw_payload прежний адрес НЕ хранит: ключа url нет ни у одной из 3535 строк (ключи: ceiling_height, kitchen_area_m2, offer_id, page_param, raw_building_type, site_name); house_url/newbuilding_url = NULL у всех. listing_sources.source_url (тоже insert-only) хранит прежний адрес у 3529 из 3535, но самоидентифицирующий предикат по нему берёт 4832 строки (сужение уникальностью URL → 3529 наших + 9 чужих, канонизированных ещё 164). Поэтому прежние значения пишутся поимённо в yandex_source_url_backfill_257; запрос отката — в шапке миграции.

Второй потребитель

yandex_address_backfill: из 5545 кандидатов 1777 ходили на сайты застройщиков (прогон 3586: checked 200, saved 1, errors 21). Миграция лечит их той же правкой колонки — после неё кандидатов с непарсимым URL 0. Кодом сервис не трогаем.

Чего не делает

  • не трогает ON CONFLICT / reconcile — отдельное решение (прецедент #2818);
  • не трогает listing_sources.source_url — читателей у колонки нет, там остаётся история gate-API;
  • не гасит и не удаляет строк: меняется одна колонка.

Test plan

  • scripts/check-migration-lock-timeout.py✓ блокирующий DDL прикрыт lock_timeout (проверено новых миграций: 7)
  • pytest tests/test_migration_257_yandex_source_url_backfill.py tests/test_migrations_manifest.py tests/test_migration_164_yandex_url_canonicalize.py tests/tasks/test_yandex_detail_backfill.py → 30 passed
  • полный файл прогнан на проде в откаченной транзакции (см. выше)
  • после деплоя: source_url !~ '/offer/[0-9]+' у yandex = 0; в следующем прогоне yandex_detail_backfill счётчик url_from_offer_id < 3535 (ожидаемо 0). Если снова ровно 3535 — правка не доехала.

Refs #2838

## Summary Разовое лечение колонки после #2838. `source_url` пишется **только при вставке** (нет ни в `ON CONFLICT DO UPDATE`, ни в reconcile-UPDATE в `scraper_kit/base.py`), поэтому канонизация продюсера #2235 вылечила только новые строки, а миграция 164 — только легаси с **делённым** URL. Строки с уникальной ссылкой на карточку застройщика не попали никуда. Миграция `257_listings_backfill_yandex_source_url.sql` переписывает их `source_url` формулой продюсера `_canonical_source_url` (`https://realty.yandex.ru/offer/<source_id>/`), сохраняя прежние адреса в таблицу отката. ## Замер на проде 2026-08-12 (перемерен, не взят из issue) | | строк | |---|---| | `source='yandex' AND source_url !~ '/offer/[0-9]+'` | 3535 | | из них `source_id ~ '^[0-9]+$'` (адрес восстановим) | 3535 | | нечем адресовать | 0 | | `source_url IS NULL` | 0 | | `is_active` | 3522 | Множество замкнуто: после самой свежей его строки (id 2583989) вставлено 6892 yandex-строк, ни одна в него не попала. Коллизий: 0 с существующими строками, 0 внутри множества, 0 дублей `source_url` у yandex после апдейта. ## Dry-run на проде (BEGIN … ROLLBACK, тем же телом файла) ``` CREATE TABLE / COMMENT / INSERT 0 3535 / UPDATE 3535 непарсимых после: 0 дублей source_url у yandex: 0 очередь #2838: url_from_offer_id 3535 → 0, unenrichable_pending 0 yandex_address_backfill (кандидатов 5545): с непарсимым URL 1777 → 0 откат по таблице: UPDATE 3535 → множество восстановлено ровно (3535) ``` 10 примеров «было → стало» — в шапке миграции. ## Обратимость `raw_payload` прежний адрес **НЕ** хранит: ключа `url` нет ни у одной из 3535 строк (ключи: `ceiling_height, kitchen_area_m2, offer_id, page_param, raw_building_type, site_name`); `house_url`/`newbuilding_url` = NULL у всех. `listing_sources.source_url` (тоже insert-only) хранит прежний адрес у 3529 из 3535, но самоидентифицирующий предикат по нему берёт 4832 строки (сужение уникальностью URL → 3529 наших + 9 чужих, канонизированных ещё 164). Поэтому прежние значения пишутся поимённо в `yandex_source_url_backfill_257`; запрос отката — в шапке миграции. ## Второй потребитель `yandex_address_backfill`: из 5545 кандидатов 1777 ходили на сайты застройщиков (прогон 3586: checked 200, saved 1, errors 21). Миграция лечит их той же правкой колонки — после неё кандидатов с непарсимым URL 0. Кодом сервис не трогаем. ## Чего не делает * не трогает `ON CONFLICT` / reconcile — отдельное решение (прецедент #2818); * не трогает `listing_sources.source_url` — читателей у колонки нет, там остаётся история gate-API; * не гасит и не удаляет строк: меняется одна колонка. ## Test plan - [x] `scripts/check-migration-lock-timeout.py` → `✓ блокирующий DDL прикрыт lock_timeout (проверено новых миграций: 7)` - [x] `pytest tests/test_migration_257_yandex_source_url_backfill.py tests/test_migrations_manifest.py tests/test_migration_164_yandex_url_canonicalize.py tests/tasks/test_yandex_detail_backfill.py` → 30 passed - [x] полный файл прогнан на проде в откаченной транзакции (см. выше) - [ ] после деплоя: `source_url !~ '/offer/[0-9]+'` у yandex = 0; в следующем прогоне `yandex_detail_backfill` счётчик `url_from_offer_id` < 3535 (ожидаемо 0). Если снова ровно 3535 — правка не доехала. Refs #2838
bot-backend added 1 commit 2026-08-12 15:28:53 +00:00
fix(tradein/yandex): 3535 source_url ведут на сайт застройщика — переписываем из offerId (#2838)
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m7s
0a0b126fa3
source_url пишется только при вставке: его нет ни в ON CONFLICT DO UPDATE, ни в
reconcile-UPDATE. Поэтому канонизация продюсера (#2235) вылечила лишь новые
строки, а миграция 164 — лишь те легаси, чей URL делили несколько строк. Строки
с уникальной ссылкой на карточку застройщика не попали никуда: парсер деталей
требует '/offer/<цифры>/' и отказывает им до обращения к HTML, а
yandex_address_backfill ходит по ним на чужие сайты.

Замер на проде 2026-08-12: 3535 строк, у всех числовой offerId в source_id,
0 с NULL-адресом, 3522 активны. Коллизий нового URL — 0 (и с существующими
строками, и внутри множества). Dry-run (BEGIN…ROLLBACK) тем же телом:
UPDATE 3535, непарсимых после 0, дублей source_url у yandex 0;
url_from_offer_id 3535 → 0; кандидаты yandex_address_backfill с непарсимым
URL 1777 → 0.

Форма адреса взята у продюсера (_canonical_source_url) через CANONICAL_URL_SQL,
тест держит сцепление. Прежние адреса сохранены поимённо: raw_payload ключа
'url' не содержит (0 из 3535), а listing_sources покрывает лишь 3529 и не
отделяется предикатом от 9 чужих строк — поэтому откат опирается на таблицу,
а не на догадку.
bot-backend merged commit b5f3366577 into main 2026-08-12 15:33:32 +00:00
bot-backend deleted branch fix/yandex-source-url-backfill 2026-08-12 15:33:32 +00:00
Author
Collaborator

Критерий приёмки выполнен — замером, а не рассуждением

Критерий записан 12.08 до факта: «в счётчиках следующего прогона url_from_offer_id должен быть меньше 3535; если снова ровно 3535 — правка не доехала».

прогон старт url_from_offer_id unenrichable_pending
3771 12.08 08:08 ключа нет 3535
3863 13.08 10:28 0 0

Оба счётчика в нуле, и оба нуля правильные: у первого — «адресовать по идентификатору больше нечего», у второго — «нечем адресовать не осталось».

Состояние очереди на тот же момент:

не адресуемых (source_url не на realty.yandex)   0 из 17 970
обогащено                                        4069  (вчера 3529)
ждут обогащения                                 14038

То есть механизм не просто перестал жаловаться — он начал работать: +540 обогащённых за сутки при полностью вычищенных адресах.

Ловушка, которую этот критерий и снимал

Если бы я посмотрел на счётчик сразу после мержа, увидел бы 3535 и заключил «правка не доехала». На деле те прогоны стартовали за семь часов до деплоя (08:08 против 15:35). Сравнение по номеру прогона и времени старта, а не по факту мержа, — единственное, что отделило верный вывод от неверного.

Ровно поэтому критерий и был записан заранее, с указанием, какое число означает провал.

## Критерий приёмки выполнен — замером, а не рассуждением Критерий записан 12.08 **до** факта: «в счётчиках следующего прогона `url_from_offer_id` должен быть меньше 3535; если снова ровно 3535 — правка не доехала». | прогон | старт | `url_from_offer_id` | `unenrichable_pending` | |---|---|---:|---:| | 3771 | 12.08 08:08 | *ключа нет* | **3535** | | **3863** | **13.08 10:28** | **0** | **0** | Оба счётчика в нуле, и оба нуля правильные: у первого — «адресовать по идентификатору больше нечего», у второго — «нечем адресовать не осталось». Состояние очереди на тот же момент: ``` не адресуемых (source_url не на realty.yandex) 0 из 17 970 обогащено 4069 (вчера 3529) ждут обогащения 14038 ``` То есть механизм не просто перестал жаловаться — он начал работать: +540 обогащённых за сутки при полностью вычищенных адресах. ## Ловушка, которую этот критерий и снимал Если бы я посмотрел на счётчик сразу после мержа, увидел бы `3535` и заключил «правка не доехала». На деле те прогоны стартовали **за семь часов до деплоя** (08:08 против 15:35). Сравнение по номеру прогона и времени старта, а не по факту мержа, — единственное, что отделило верный вывод от неверного. Ровно поэтому критерий и был записан заранее, с указанием, какое число означает провал.
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#2840
No description provided.