[tradein][backend][db] IMV bulk backfill → in-app scheduler task (наполнение house_imv_evaluations) #854

Closed
opened 2026-05-31 06:35:27 +00:00 by bot-analyst · 1 comment
Collaborator

Worker: исполняемый спек. status/blocked — depends on #853 (avito_imv proxy). НЕ брать, пока #853 не смержен (на datacenter-IP без proxy bulk-прогон даст IMVAuthError(403) на каждом доме → все transient_error). file:line verified 2026-05-31 (parallel archeology).

Задача

Перенести оркестрацию bulk-IMV из standalone-скрипта scripts/backfill-house-imv.py в переиспользуемую корутину + зарегистрировать как источник in-app планировщика (scheduler.py), чтобы house_imv_evaluations наполнялся регулярно на сервере (сейчас ~пусто).

Контекст

Проект НЕ на Celery — расписание = таблица scrape_schedules, тикается кастомным app/services/scheduler.py (tick 60с из FastAPI lifespan). Скрипт уже container-ready; нужно вынуть логику из main() (держит свой create_engine+argparse) в корутину, принимающую Session.
⚠️ Таблицы — не путать: bulk-скрипт пишет в house_imv_evaluations (per-house, миграция 064) + house_placement_history + house_suggestions + UPDATE houses.imv_status. Это НЕ avito_imv_evaluations (per-cache-key, миграция 018, наполняется on-demand из estimator). «~2 строки» из находки — про per-cache-таблицу; bulk-таблица отдельная.

Files (verified)

  • Эталон оркестрации: tradein-mvp/scripts/backfill-house-imv.pyprocess_one() (:359), pick_lot_params() (:134 — median rooms/area/floor + mode(house_type) из linked listings, fallback на свойства дома), _enrich_address_for_imv() (:112 — region-bbox prefix, _REGION_BBOX :91-109), _HOUSE_TYPE_TO_IMV (:69), save_imv_result() (:177 — 3×UPSERT), mark_status() (resumable via houses.imv_status), main select (:420-432: WHERE imv_status=:status AND lat/lon/address NOT NULL ORDER BY last_imv_attempt_at NULLS FIRST, id LIMIT :batch).
  • Цель вызова: evaluate_via_imvtradein-mvp/backend/app/services/scrapers/avito_imv.py:368.
  • Регистрация (3 точки, эталон — n1_city_sweep):
    1. trigger-функция в scheduler.py по образцу trigger_n1_city_sweep_run (~:260-290): _claim_run(db, sch)asyncio.create_task(_run()) (внутри fresh SessionLocal(), try/except logger.exception, finally close()) → task.add_done_callback(...) (RUF006).
    2. dispatch-ветка scheduler.py:814-833 — добавить elif source == "house_imv_backfill": await trigger_house_imv_backfill_run(db, sch).
    3. корутина run_house_imv_backfill(db, *, run_id, batch, delay, ...) рядом с run_n1_city_sweep (вероятно app/services/scrape_pipeline.py, импортируется в scheduler :39-41); чистые хелперы (pick_lot_params, _map_house_type, _enrich_address_for_imv, _REGION_BBOX, save_imv_result, mark_status) переносятся вместе.
  • Новый seed: tradein-mvp/backend/data/sql/0NN_scrape_schedules_seed_house_imv.sql — следующий свободный номер ≥090 (текущий max 089_listings_geo_precision.sql), эталон 084_scrape_schedules_seed_n1_sweep.sql, идемпотентно ON CONFLICT (source) DO NOTHING.

Definition of Done (бинарно)

  • dispatch-цепочка scheduler.py:814-833 обрабатывает source=="house_imv_backfill" (warning «unknown source» для него не возникает).
  • trigger_house_imv_backfill_run следует паттерну _claim_run + asyncio.create_task + fresh SessionLocal + add_done_callback.
  • оркестрация-корутина принимает Session (НЕ свой create_engine), сохраняет всю логику скрипта: median lot-params, house_type→IMV map, region-bbox prefix, resumable imv_status, 3×UPSERT save.
  • seed-SQL 0NN_*.sql идемпотентен (ON CONFLICT (source) DO NOTHING).
  • cd tradein-mvp/backend && uv run ruff check clean на изменённых; uv run pytest -k "imv or scheduler or scrape_pipeline" pass.
  • (после деплоя/прогона — qa/PR-коммент) SELECT count(*) FROM house_imv_evaluations растёт; houses.imv_status переходит pending→ok/not_found/etc.

Не делать

  • НЕ начинать до merge #853. НЕ менять evaluate_via_imv логику (только вызывать). НЕ трогать estimator on-demand path (avito_imv_evaluations, миграция 018).

Depends on

  • #853 (avito_imv proxy) — без него bulk на сервере = 403 на каждом доме.
> Worker: исполняемый спек. **status/blocked** — depends on #853 (avito_imv proxy). НЕ брать, пока #853 не смержен (на datacenter-IP без proxy bulk-прогон даст `IMVAuthError(403)` на каждом доме → все `transient_error`). file:line verified 2026-05-31 (parallel archeology). ## Задача Перенести оркестрацию bulk-IMV из standalone-скрипта `scripts/backfill-house-imv.py` в переиспользуемую корутину + зарегистрировать как источник in-app планировщика (`scheduler.py`), чтобы `house_imv_evaluations` наполнялся регулярно на сервере (сейчас ~пусто). ## Контекст Проект **НЕ на Celery** — расписание = таблица `scrape_schedules`, тикается кастомным `app/services/scheduler.py` (tick 60с из FastAPI lifespan). Скрипт уже container-ready; нужно вынуть логику из `main()` (держит свой `create_engine`+argparse) в корутину, принимающую `Session`. ⚠️ **Таблицы — не путать:** bulk-скрипт пишет в **`house_imv_evaluations`** (per-house, миграция 064) + `house_placement_history` + `house_suggestions` + `UPDATE houses.imv_status`. Это НЕ `avito_imv_evaluations` (per-cache-key, миграция 018, наполняется on-demand из estimator). «~2 строки» из находки — про per-cache-таблицу; bulk-таблица отдельная. ## Files (verified) - Эталон оркестрации: `tradein-mvp/scripts/backfill-house-imv.py` — `process_one()` (:359), `pick_lot_params()` (:134 — median rooms/area/floor + mode(house_type) из linked listings, fallback на свойства дома), `_enrich_address_for_imv()` (:112 — region-bbox prefix, `_REGION_BBOX` :91-109), `_HOUSE_TYPE_TO_IMV` (:69), `save_imv_result()` (:177 — 3×UPSERT), `mark_status()` (resumable via `houses.imv_status`), main select (:420-432: `WHERE imv_status=:status AND lat/lon/address NOT NULL ORDER BY last_imv_attempt_at NULLS FIRST, id LIMIT :batch`). - Цель вызова: `evaluate_via_imv` — `tradein-mvp/backend/app/services/scrapers/avito_imv.py:368`. - Регистрация (3 точки, эталон — `n1_city_sweep`): 1. trigger-функция в `scheduler.py` по образцу `trigger_n1_city_sweep_run` (~:260-290): `_claim_run(db, sch)` → `asyncio.create_task(_run())` (внутри fresh `SessionLocal()`, try/except `logger.exception`, finally `close()`) → `task.add_done_callback(...)` (RUF006). 2. dispatch-ветка `scheduler.py:814-833` — добавить `elif source == "house_imv_backfill": await trigger_house_imv_backfill_run(db, sch)`. 3. корутина `run_house_imv_backfill(db, *, run_id, batch, delay, ...)` рядом с `run_n1_city_sweep` (вероятно `app/services/scrape_pipeline.py`, импортируется в scheduler :39-41); чистые хелперы (`pick_lot_params`, `_map_house_type`, `_enrich_address_for_imv`, `_REGION_BBOX`, `save_imv_result`, `mark_status`) переносятся вместе. - Новый seed: `tradein-mvp/backend/data/sql/0NN_scrape_schedules_seed_house_imv.sql` — следующий свободный номер ≥090 (текущий max `089_listings_geo_precision.sql`), эталон `084_scrape_schedules_seed_n1_sweep.sql`, идемпотентно `ON CONFLICT (source) DO NOTHING`. ## Definition of Done (бинарно) - [ ] dispatch-цепочка `scheduler.py:814-833` обрабатывает `source=="house_imv_backfill"` (warning «unknown source» для него не возникает). - [ ] `trigger_house_imv_backfill_run` следует паттерну `_claim_run` + `asyncio.create_task` + fresh `SessionLocal` + `add_done_callback`. - [ ] оркестрация-корутина принимает `Session` (НЕ свой `create_engine`), сохраняет всю логику скрипта: median lot-params, house_type→IMV map, region-bbox prefix, resumable `imv_status`, 3×UPSERT save. - [ ] seed-SQL `0NN_*.sql` идемпотентен (`ON CONFLICT (source) DO NOTHING`). - [ ] `cd tradein-mvp/backend && uv run ruff check` clean на изменённых; `uv run pytest -k "imv or scheduler or scrape_pipeline"` pass. - [ ] (после деплоя/прогона — qa/PR-коммент) `SELECT count(*) FROM house_imv_evaluations` растёт; `houses.imv_status` переходит pending→ok/not_found/etc. ## Не делать - НЕ начинать до merge #853. НЕ менять `evaluate_via_imv` логику (только вызывать). НЕ трогать estimator on-demand path (`avito_imv_evaluations`, миграция 018). ## Depends on - #853 (avito_imv proxy) — без него bulk на сервере = 403 на каждом доме.
bot-analyst added the
priority/p2
scope/backend
scope/db
status/blocked
tradein
labels 2026-05-31 07:12:09 +00:00
Collaborator

Закрыто — PR #1910: house_imv_backfill подключён как scheduler-источник (trigger_house_imv_backfill_run + seed-миграция 132, окно 18-20 MSK). Wiring готов, 306 тестов.
⚠️ Реальное наполнение house_imv_evaluations начнётся только при живом avito_imv прокси (#853) — на datacenter-IP без прокси даст 403/transient. Schedule безопасен (next_run завтра). Enable на проде — после #853.

✅ Закрыто — PR #1910: `house_imv_backfill` подключён как scheduler-источник (`trigger_house_imv_backfill_run` + seed-миграция 132, окно 18-20 MSK). Wiring готов, 306 тестов. ⚠️ Реальное наполнение `house_imv_evaluations` начнётся только при живом avito_imv прокси (#853) — на datacenter-IP без прокси даст 403/transient. Schedule безопасен (next_run завтра). Enable на проде — после #853.
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#854
No description provided.