[P1] Обновлять scraped_at в upsert (base.py) — вернуть ~55% инвентаря эстиматору #2206

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

Эпик: #2200

Находка (Data/Estimator, verified high): estimator.py:80 LISTINGS_FRESH_DAYS=14, фильтр scraped_at > NOW()-14d (:3662, :4060). Но upsert base.py:430-483 ON CONFLICT DO UPDATE ставит last_seen_at=NOW(), а scraped_at НЕ трогает никогда (grep scraped_at = по listings = 0). Итог: эстиматору видно 45.6% активного инвентаря; CIAN 934/15759 = 5.9%, avito 6353/8474, yandex 4561/8878. ~6434 подтверждённо-живых листинга исключены из оценок → смещение к «только свежим», завышенные медианы, сжатый n_analogs.

Acceptance:

  • Upsert обновляет scraped_at=NOW() при ре-подтверждении листинга живым (или вводит отдельную семантику last_scraped с фильтром на неё).
  • Проверка на проде: доля estimator-visible активных растёт к реальной свежести (CIAN сильно выше 5.9%).
  • Ретро-бэкфилл scraped_at для сейчас-активных, недавно виденных (одноразовая миграция, идемпотентно).
  • Regression-guard: тест, что повторный upsert двигает scraped_at.

⚠️ Побочно чинит «часы распада» domklik (перестанет выпадать ~12.07). Scope: tradein-mvp/backend/app/services/scrapers/base.py.

Эпик: #2200 **Находка (Data/Estimator, verified high):** `estimator.py:80` `LISTINGS_FRESH_DAYS=14`, фильтр `scraped_at > NOW()-14d` (`:3662`, `:4060`). Но upsert `base.py:430-483 ON CONFLICT DO UPDATE` ставит `last_seen_at=NOW()`, а `scraped_at` НЕ трогает никогда (grep `scraped_at =` по listings = 0). Итог: эстиматору видно 45.6% активного инвентаря; **CIAN 934/15759 = 5.9%**, avito 6353/8474, yandex 4561/8878. ~6434 подтверждённо-живых листинга исключены из оценок → смещение к «только свежим», завышенные медианы, сжатый n_analogs. **Acceptance:** - [ ] Upsert обновляет `scraped_at=NOW()` при ре-подтверждении листинга живым (или вводит отдельную семантику last_scraped с фильтром на неё). - [ ] Проверка на проде: доля estimator-visible активных растёт к реальной свежести (CIAN сильно выше 5.9%). - [ ] Ретро-бэкфилл `scraped_at` для сейчас-активных, недавно виденных (одноразовая миграция, идемпотентно). - [ ] Regression-guard: тест, что повторный upsert двигает scraped_at. ⚠️ Побочно чинит «часы распада» domklik (перестанет выпадать ~12.07). Scope: `tradein-mvp/backend/app/services/scrapers/base.py`.
lekss361 added the
data
priority/p1
scope/backend
scrapers
tradein
labels 2026-07-02 19:20:18 +00:00
Author
Owner

Working on this (night shift), ветка fix/tradein-scraped-at-upsert. Оба зеркальных upsert-пути (app base.py + scraper-kit base.py — боевой) + drift-reconcile UPDATE получают scraped_at = NOW(). Бэкфилл миграцией 161 только для avito/cian/yandex — domklik исключён намеренно (его last_seen_at испорчен нетрекаемым bulk-touch, см. #2204). Живой baseline-бэктест (engine=full, sample=300, resolve-house-id) снят ДО изменения — сравнение до/после будет в PR.

Working on this (night shift), ветка `fix/tradein-scraped-at-upsert`. Оба зеркальных upsert-пути (app base.py + scraper-kit base.py — боевой) + drift-reconcile UPDATE получают `scraped_at = NOW()`. Бэкфилл миграцией 161 только для avito/cian/yandex — domklik исключён намеренно (его last_seen_at испорчен нетрекаемым bulk-touch, см. #2204). Живой baseline-бэктест (engine=full, sample=300, resolve-house-id) снят ДО изменения — сравнение до/после будет в PR.
Author
Owner

Сделано в PR #2224 (смержен, deploy success, миграция 161 применена).

Acceptance — проверено на проде (2026-07-02 ~20:40 UTC):

  • Upsert обновляет scraped_at при ре-подтверждении (оба зеркальных base.py, kit-путь боевой; regression-тесты + golden-parity).
  • Доля estimator-visible активных выросла: avito 6353→8474 (100% активных), CIAN 934→5598 (×6), yandex 4561→7528. n1 0 (мёртв), domklik 6296 (scraped_at 28.06, жив до ~12.07 — далее #2204 TTL, либо ре-скрейп удержит).
  • Ретро-бэкфилл идемпотентен (повторный прогон matched 0 строк по построению).
  • Regression-guard: 13 тестов.

Backtest-гейт (ценовое изменение, live prod, engine=full sample=300 --resolve-house-id):

метрика до после
median bias −2.73% −2.29%
MAPE 14.70% 14.78%
range coverage 81.52% 81.45%
median rel. width 0.743 0.743
n_no_prediction 22 22

Метрики не деградировали (bias чуть лучше, остальное — в пределах шума). Малое смещение ожидаемо: бэктест по городской выборке ДКП, где аналогов и так хватало; выигрыш — в n_analogs/confidence на живых оценках и в устойчивости к распаду domklik.

Сделано в PR #2224 (смержен, deploy success, миграция 161 применена). **Acceptance — проверено на проде (2026-07-02 ~20:40 UTC):** - ✅ Upsert обновляет scraped_at при ре-подтверждении (оба зеркальных base.py, kit-путь боевой; regression-тесты + golden-parity). - ✅ Доля estimator-visible активных выросла: avito 6353→**8474** (100% активных), **CIAN 934→5598 (×6)**, yandex 4561→**7528**. n1 0 (мёртв), domklik 6296 (scraped_at 28.06, жив до ~12.07 — далее #2204 TTL, либо ре-скрейп удержит). - ✅ Ретро-бэкфилл идемпотентен (повторный прогон matched 0 строк по построению). - ✅ Regression-guard: 13 тестов. **Backtest-гейт (ценовое изменение, live prod, engine=full sample=300 --resolve-house-id):** | метрика | до | после | |---|---|---| | median bias | −2.73% | **−2.29%** | | MAPE | 14.70% | 14.78% | | range coverage | 81.52% | 81.45% | | median rel. width | 0.743 | 0.743 | | n_no_prediction | 22 | 22 | Метрики не деградировали (bias чуть лучше, остальное — в пределах шума). Малое смещение ожидаемо: бэктест по городской выборке ДКП, где аналогов и так хватало; выигрыш — в n_analogs/confidence на живых оценках и в устойчивости к распаду domklik.
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#2206
No description provided.