feat(tradein): ГАР-loader знаменателя — apartments-per-house → gar_house_flats + match к houses #2059

Merged
lekss361 merged 1 commit from feat/tradein-gar-flats-loader into main 2026-06-28 14:54:44 +00:00
Owner

Summary

Загрузчик знаменателя «всего квартир в доме» для страницы «доля квартир дома в продаже». Парсит региональный ГАР/ФИАС-дамп (новый формат, папка региона напр. 66/), считает помещения-квартиры на дом и матчит к houses по нормализованному адресу → проставляет houses.gar_flat_count (его подхватывает view v_building_sale_share, мигр. 143).

  • services/gar_flats_loader.py — стриминговый lxml.iterparse (clear + prune siblings, huge_tree, XXE-safe; региональные AS_APARTMENTS/AS_MUN_HIERARCHY многогигабайтны). Считает квартиры = число активных помещений, чей PARENTOBJID == OBJECTID дома; собирает адрес по иерархии; batched upsert (ON CONFLICT, SAVEPOINT-per-batch); матчер.
  • tasks/gar_flats_load.py — CLI: python -m app.tasks.gar_flats_load --dir /data/gar/66 --region 66 --version 2026-06-01.
  • docs/gar-flats-runbook.md — откуда брать дамп (fias.nalog.ru → регион 66 / распаковать папку 66 из gar_xml.zip), размер, команда. Лоадер потребляет уже распакованные XML (скачивание десятков ГБ — ops-шаг).
  • tests — 14 passed / 1 skip (синтетические ГАР-фикстуры: count на дом, фильтр ISACTIVE/ISACTUAL, дом с 0 квартир, мульти-сегментный адрес, корпус, ADM-fallback, idempotency, SAVEPOINT).
  • +lxml в pyproject.

Качество / проверки

  • code-reviewer: APPROVE (streaming memory-safe, psycopg-v3 CAST(:x AS type) + executemany/ON CONFLICT, детерминированный tie-break DISTINCT ON (norm_address) … flat_count DESC, идемпотентный матчер через IS DISTINCT FROM, XXE-safe).
  • Матчер прогнан вживую на prod (ROLLBACK): insert gar-строки + match → houses.gar_flat_count=150, tie-break выбрал max-flat-count guid, view посчитал sale_share_pct, повторный прогон UPDATE 0 (idempotent), ROLLBACK — ничего не записано.

⚠️ Известное ограничение (важно для запуска)

Матч — точное равенство нормализованных адресов. tradein_normalize_short_addr слабо канонизирует («ул. Шаумяна,20» остаётся как есть, с точкой/пунктуацией/«р-н …»), а ГАР даёт «ул Шаумяна, д 20» → реальный yield точного матча будет низким. Перед опорой на знаменатель нужен tune матчинга (усиленная канонизация обеих сторон + trgm-fuzzy; GIN-trgm индекс на gar_house_flats.norm_address уже есть, мигр. 143) — измерить matched/total после первого реального прогона. Это отдельный шаг, не блокер кода.

Test plan

  • pytest tests/test_gar_flats_loader.py → 14 passed / 1 skip; ruff clean.
  • Матчер вживую (ROLLBACK) на prod — chain gar_flat_count → view → % работает.
  • Прогон на реальном дампе региона 66 + замер match-yield → tune канонизации/trgm.

Финальный PR серии (loader). Остальное (схема, API, страница) уже в main.

## Summary Загрузчик знаменателя «всего квартир в доме» для страницы «доля квартир дома в продаже». Парсит региональный ГАР/ФИАС-дамп (новый формат, папка региона напр. `66/`), считает помещения-квартиры на дом и матчит к `houses` по нормализованному адресу → проставляет `houses.gar_flat_count` (его подхватывает view `v_building_sale_share`, мигр. 143). - **`services/gar_flats_loader.py`** — стриминговый `lxml.iterparse` (clear + prune siblings, `huge_tree`, XXE-safe; региональные `AS_APARTMENTS`/`AS_MUN_HIERARCHY` многогигабайтны). Считает квартиры = число активных помещений, чей `PARENTOBJID` == `OBJECTID` дома; собирает адрес по иерархии; batched upsert (`ON CONFLICT`, SAVEPOINT-per-batch); матчер. - **`tasks/gar_flats_load.py`** — CLI: `python -m app.tasks.gar_flats_load --dir /data/gar/66 --region 66 --version 2026-06-01`. - **`docs/gar-flats-runbook.md`** — откуда брать дамп (fias.nalog.ru → регион 66 / распаковать папку `66` из gar_xml.zip), размер, команда. Лоадер потребляет уже распакованные XML (скачивание десятков ГБ — ops-шаг). - **tests** — 14 passed / 1 skip (синтетические ГАР-фикстуры: count на дом, фильтр ISACTIVE/ISACTUAL, дом с 0 квартир, мульти-сегментный адрес, корпус, ADM-fallback, idempotency, SAVEPOINT). - +lxml в pyproject. ## Качество / проверки - code-reviewer: ✅ APPROVE (streaming memory-safe, psycopg-v3 `CAST(:x AS type)` + `executemany`/ON CONFLICT, детерминированный tie-break `DISTINCT ON (norm_address) … flat_count DESC`, идемпотентный матчер через `IS DISTINCT FROM`, XXE-safe). - **Матчер прогнан вживую на prod (ROLLBACK):** insert gar-строки + match → `houses.gar_flat_count=150`, tie-break выбрал max-flat-count guid, view посчитал `sale_share_pct`, повторный прогон `UPDATE 0` (idempotent), ROLLBACK — ничего не записано. ## ⚠️ Известное ограничение (важно для запуска) Матч — **точное** равенство нормализованных адресов. `tradein_normalize_short_addr` слабо канонизирует (`«ул. Шаумяна,20»` остаётся как есть, с точкой/пунктуацией/«р-н …»), а ГАР даёт «ул Шаумяна, д 20» → **реальный yield точного матча будет низким**. Перед опорой на знаменатель нужен tune матчинга (усиленная канонизация обеих сторон + trgm-fuzzy; GIN-trgm индекс на `gar_house_flats.norm_address` уже есть, мигр. 143) — измерить `matched/total` после первого реального прогона. Это отдельный шаг, не блокер кода. ## Test plan - [x] `pytest tests/test_gar_flats_loader.py` → 14 passed / 1 skip; ruff clean. - [x] Матчер вживую (ROLLBACK) на prod — chain `gar_flat_count → view → %` работает. - [ ] Прогон на реальном дампе региона 66 + замер match-yield → tune канонизации/trgm. Финальный PR серии (loader). Остальное (схема, API, страница) уже в main.
lekss361 added 1 commit 2026-06-28 14:54:23 +00:00
feat(tradein): ГАР-loader знаменателя — apartments-per-house → gar_house_flats + match к houses
All checks were successful
CI / changes (pull_request) Successful in 9s
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
55819aa821
lekss361 merged commit 123717994b into main 2026-06-28 14:54:44 +00:00
lekss361 deleted branch feat/tradein-gar-flats-loader 2026-06-28 14:54:44 +00:00
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#2059
No description provided.