fix(tradein): писатели наконец пишут то, что обещает схема — фото подсказок, статус «снято», события объявлений (#2674) #2682

Merged
bot-backend merged 3 commits from fix/2674-writers-honor-schema into main 2026-08-05 22:12:30 +00:00

3 commits

Author SHA1 Message Date
77336d351c chore(tradein): перенумеровать миграцию 212 -> 213 (коллизия с #2681)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
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 2m55s
PR #2681 смержен, пока ветка была в работе, и принёс
212_sber_index_pull_weekly.sql. Номер 212 занят — беру 213 (свободен,
проверено git ls-tree по origin/main после fetch).

Почему локальный гейт молчал: test_new_files_do_not_reuse_prefix сравнивает
префиксы файлов В ОДНОМ ДЕРЕВЕ, а смерженный 212_sber в этой ветке
отсутствует. Проверено симуляцией (копия data/sql + stub 212_sber):
с моим 212 тест КРАСНЫЙ («212 уже у нового 212_sber»), с 213 — зелёный.

Кросс-ветковым реестром занятых номеров служит _manifest_applied.txt, но он
отстал на 27 имён (171, 187-188, 189-211, 213), поэтому префикс 212 нигде не
числился занятым. Про долг — отдельно, в этом PR манифест не трогаю.

Apply after в шапке обновлён на 212_sber_index_pull_weekly.sql.
2026-08-06 03:07:09 +05:00
ab01f7cc48 fix(tradein): убрать невыводимые события, развести «снято» и «протухло» (#2674)
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 3m1s
Ревью PR #2682 нашло контрольную группу в наших же данных. Перепроверено
собственными запросами к проду — сходится, местами хуже заявленного.

1. delisted/relisted УБРАНЫ из писателя событий.
   Покрытие обхода за 14-18.07: domklik 99.9-100%, yandex 34-43%, cian 21-27%,
   avito 1.6-3.4%. Переходы за те же дни: domklik — снятий 1/2/0/2/4 в сутки и
   возвратов РОВНО 0 все пять суток; yandex — снятий 343-433 в сутки. Тот же
   обход, тот же день, разница только в покрытии: событие рождается тем, что
   скрейпер снова дошёл, а не тем, что объявление вернулось. Подтверждения:
   avito 13.07 (день остановки обхода) — 3023 «снятия» за сутки против
   контрольной ставки 1-4 (точность ≈4%); 4705 возвратов из 5493 за 12 дней
   (85.7%) — это 2-3.08, два дня после возобновления обхода.
   Сужение окна свежести сделало бы хуже (больше флапаний). Журнал из догадок
   хуже пустого журнала — не пишем. is_active убран из запроса целиком.
   Гейт-тест ослаблен до трёх типов + новый гейт «невыводимые НЕ пишутся».

2. TTL-путь пишет 'stale', а не 'closed'.
   Прогон по домклику 02.08 деактивировал 6131 объявление за раз (TTL 14 суток
   против 12 суток простоя обхода) — под общим статусом это 6131 фальшивая
   «дата продажи» одной датой. 'closed' остаётся только за 404: там ответила
   площадка. CHECK на колонке нет, миграция 212 обновляет только COMMENT.

3. change_time усечён до суток (date_trunc). С now() UNIQUE(source, change_time,
   type) работал только внутри прогона: второй прогон в те же сутки (2 августа
   их было два) давал дубли. Теперь заявленная идемпотентность действительно
   работает.

4. Комнатность в разборе заголовка стала необязательной: 1991 заголовок из
   25 055 (7.9%) — «Квартира-студия, 34,2 м², 9/10 эт.», обязательная группа
   роняла match и обнуляла все четыре поля. Чинит обоих писателей сразу
   (house_suggestions + house_placement_history, там 8.8% без площади).
   Студия → rooms=0 по конвенции kit'а, а не None.

Фальсификация: вернуть delisted — 1 красный; 'closed' на TTL-пути — 6;
обязательная комнатность — 2; now() вместо date_trunc — 1.
2026-08-06 03:01:05 +05:00
43aaf91b97 fix(tradein): писатели наконец пишут то, что обещает схема — фото подсказок, статус «снято», события объявлений (#2674)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 7s
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 2m56s
Три находки одного класса из эпика: колонка есть, писатель есть, тест на писателя
зелёный — а данные не появляются. Тестами это не ловится по построению, только
сверкой с продом.

1. house_suggestions: парсер выбрасывал imageLink, а INSERT не перечислял
   image_link + area_m2/rooms/floor/total_floors. 25 055 строк с NULL во всех
   пяти колонках, ~74 дня с миграции 064. Метрики парсятся из title тем же
   _parse_title, что и у placementHistory.

2. listings_snapshots.status: 'active' у всех 394 299 строк при 55 448 реально
   неактивных объявлений. Оба места вызова с литералом 'active' честны — там
   объявление действительно видели; не писал никто ветку «снято». Теперь оба
   места деактивации пишут снимок 'closed' в ТОЙ ЖЕ транзакции: TTL-задача
   (data-modifying CTE, все 4 источника через один deactivate_stale_listings)
   и 404 из avito_detail_backfill. Дата снятия перестаёт быть догадкой.

3. listing_source_events: схема знает 5 типов, писался 1 (price_change, 8288
   строк). Дописаны ветки delisted/relisted/edited/first_seen в тот же
   set-based statement — данные для них уже лежат в снимке. JOIN → LEFT JOIN
   LATERAL, иначе first_seen недостижим по построению; план #2607 (per-row
   index point-lookup по idx_lss_source_date) сохранён, проверено EXPLAIN на
   проде. Счётчики прогона теперь по типам, все пять всегда присутствуют —
   ровно они показали бы четыре нуля из пяти.

Миграция не нужна: все колонки и CHECK уже существуют.

Тесты: tests/test_2674_writers_honor_schema.py. Гейты сверяют писателя со
СХЕМОЙ (колонки INSERT против CREATE TABLE 064, типы событий против CHECK 079),
поэтому ловят и следующую забытую колонку. Фальсификация патч-методом: без
фикса 1 — 6 красных, без фикса 2 — 6, без фикса 3 — 4.
2026-08-06 02:29:58 +05:00