feat(tradein/zhkh): ЖКХ-приоритет знаменателя «квартир в доме» + loader #2077

Merged
lekss361 merged 1 commit from feat/tradein-zhkh-denominator into main 2026-06-28 18:56:35 +00:00
Owner

Проблема

Знаменатель «квартир в доме» на /trade-in/sale-share берётся из ГАР, который дико недосчитывает квартиры в МКД. Прод-замер (383 вторички с gar_house_guid):

  • ул. Крауля 89А — ГАР=11 vs ЖКХ=429 (39×);
  • 27 из 347 домов — ГАР ≥2× недосчёт (gar=7→zhkh=639, gar=4→zhkh=556, gar=2→zhkh=376).

Такой недосчёт даёт либо абсурдные >100% доли, либо отсев плаузибилити-гейтом → дом пропадает из выдачи.

Решение

ГИС ЖКХ (dom.gosuslugi.ru) — точный публичный реестр МКД. Прод-замер: резолвит 90% вторички с gar_house_guid. Наш houses.gar_house_guid == ЖКХ houseGuid (проверено) → матч тривиальный, без канона адреса.

мигр. 149v_building_sale_share

  • flat_count_effectiveЖКХ ПЕРВЫЙ: COALESCE(zhkh_flat_count, gar_flat_count, NULLIF(total_units,0), NULLIF(flat_count,0)) во всех 7 местах (включая оба плаузибилити-гейта).
  • + houses.zhkh_year (smallint), houses.zhkh_floors_raw (text) — заполняются loader'ом.
  • + transparency-колонки во view: zhkh_flat_count (сырой ЖКХ-счёт), flat_count_source (zhkh|gar|total_units|flat_count).
  • Числители (дедуп count(DISTINCT (rooms,round(area_m2),floor))), guard, WHERE — без изменений vs 148.

Loader app/{services/zhkh_flats_loader,tasks/zhkh_flats_load}.py

  • Фетч ГИС ЖКХ searchByAddress (батч ≤100 gar_house_guid на запрос, pageIndex=1, cookieless, retry на 403/5xx).
  • Идемпотентный UPDATE houses.zhkh_*CAST(:x AS type) (psycopg v3) + IS DISTINCT FROM gate + SAVEPOINT-чанки. --dry-run / --limit N.
  • Запуск (вручную, post-deploy): docker exec tradein-backend python -m app.tasks.zhkh_flats_load.

Безопасность мержа

До ручного прогона loader'а zhkh_flat_count=NULL везде → COALESCE падает на ГАР → поведение идентично до-149. Денормализация активируется только после прогона loader'а. Миграция идемпотентна (ADD COLUMN IF NOT EXISTS + CREATE OR REPLACE VIEW; колонки только добавлены в конец).

Проверки

  • code-reviewer: APPROVE (0 blocking) — psycopg v3 CAST, SQL-injection, идемпотентность, COALESCE 7/7 zhkh-first, guard/числители/WHERE unchanged vs 148.
  • ruff clean; pytest tests/test_zhkh_flats_loader.py — 8 green (чистые хелперы, без сети/БД).
  • API-рецепт ЖКХ verified живьём с прода (90% резолв на 383-домовом сэмпле).

После мержа

  1. Deploy авто-применит мигр.149 (strict).
  2. Прогнать loader: --limit 300 --dry-run → full.
  3. Verify: Крауля 89А flat_count_effective=429, coverage растёт с 76.9%.
## Проблема Знаменатель «квартир в доме» на `/trade-in/sale-share` берётся из ГАР, который **дико недосчитывает** квартиры в МКД. Прод-замер (383 вторички с `gar_house_guid`): - ул. Крауля 89А — ГАР=**11** vs ЖКХ=**429** (39×); - **27 из 347** домов — ГАР ≥2× недосчёт (gar=7→zhkh=639, gar=4→zhkh=556, gar=2→zhkh=376). Такой недосчёт даёт либо абсурдные >100% доли, либо отсев плаузибилити-гейтом → дом пропадает из выдачи. ## Решение ГИС ЖКХ (`dom.gosuslugi.ru`) — точный публичный реестр МКД. Прод-замер: резолвит **90%** вторички с `gar_house_guid`. Наш `houses.gar_house_guid` == ЖКХ `houseGuid` (проверено) → матч тривиальный, без канона адреса. ### `мигр. 149` — `v_building_sale_share` - `flat_count_effective` → **ЖКХ ПЕРВЫЙ**: `COALESCE(zhkh_flat_count, gar_flat_count, NULLIF(total_units,0), NULLIF(flat_count,0))` во всех 7 местах (включая оба плаузибилити-гейта). - `+ houses.zhkh_year` (smallint), `houses.zhkh_floors_raw` (text) — заполняются loader'ом. - `+ transparency`-колонки во view: `zhkh_flat_count` (сырой ЖКХ-счёт), `flat_count_source` (`zhkh|gar|total_units|flat_count`). - Числители (дедуп `count(DISTINCT (rooms,round(area_m2),floor))`), guard, WHERE — **без изменений** vs 148. ### Loader `app/{services/zhkh_flats_loader,tasks/zhkh_flats_load}.py` - Фетч ГИС ЖКХ `searchByAddress` (батч ≤100 `gar_house_guid` на запрос, `pageIndex=1`, cookieless, retry на 403/5xx). - Идемпотентный `UPDATE houses.zhkh_*` — `CAST(:x AS type)` (psycopg v3) + `IS DISTINCT FROM` gate + SAVEPOINT-чанки. `--dry-run` / `--limit N`. - Запуск (вручную, post-deploy): `docker exec tradein-backend python -m app.tasks.zhkh_flats_load`. ## Безопасность мержа До ручного прогона loader'а `zhkh_flat_count=NULL` везде → COALESCE падает на ГАР → **поведение идентично до-149**. Денормализация активируется только после прогона loader'а. Миграция идемпотентна (ADD COLUMN IF NOT EXISTS + CREATE OR REPLACE VIEW; колонки только добавлены в конец). ## Проверки - `code-reviewer`: ✅ APPROVE (0 blocking) — psycopg v3 CAST, SQL-injection, идемпотентность, COALESCE 7/7 zhkh-first, guard/числители/WHERE unchanged vs 148. - `ruff` clean; `pytest tests/test_zhkh_flats_loader.py` — 8 green (чистые хелперы, без сети/БД). - API-рецепт ЖКХ verified живьём с прода (90% резолв на 383-домовом сэмпле). ## После мержа 1. Deploy авто-применит мигр.149 (strict). 2. Прогнать loader: `--limit 300 --dry-run` → full. 3. Verify: Крауля 89А `flat_count_effective`=429, coverage растёт с 76.9%.
lekss361 added 1 commit 2026-06-28 18:55:44 +00:00
feat(tradein/zhkh): ЖКХ-приоритет знаменателя «квартир в доме» + loader
All checks were successful
CI / changes (pull_request) Successful in 7s
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
77c01b6d3b
ГАР дико недосчитывает квартиры в МКД (прод-замер: Крауля 89А ГАР=11 vs
ЖКХ=429; 27 из 347 домов ГАР ≥2× недосчёт). ГИС ЖКХ точнее — резолвит ~90%
вторички с gar_house_guid.

- мигр.149: COALESCE знаменателя → ЖКХ ПЕРВЫЙ
  (zhkh_flat_count, gar_flat_count, total_units, flat_count) во всех 7 местах
  view v_building_sale_share; +houses.zhkh_year/zhkh_floors_raw; +transparency-
  колонки zhkh_flat_count/flat_count_source.
- app/services/zhkh_flats_loader.py + app/tasks/zhkh_flats_load.py: фетч ГИС ЖКХ
  searchByAddress (батч ≤100 по gar_house_guid == ЖКХ houseGuid, pageIndex=1,
  cookieless), идемпотентный UPDATE houses.zhkh_* (CAST(:x AS type) +
  IS DISTINCT FROM gate, SAVEPOINT-чанки, dry-run).

Безопасно к мержу: до ручного прогона loader'а zhkh_flat_count=NULL → COALESCE
падает на ГАР → поведение идентично до-149. ruff clean, pytest 8 green.
lekss361 merged commit ec03a43283 into main 2026-06-28 18:56:35 +00:00
lekss361 deleted branch feat/tradein-zhkh-denominator 2026-06-28 18:56:35 +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#2077
No description provided.