ГАР дико недосчитывает квартиры в МКД (прод-замер: Крауля 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.
83 lines
3.1 KiB
Python
83 lines
3.1 KiB
Python
"""CLI: загрузка ЖКХ-знаменателя «квартир на дом» → houses.zhkh_* (мигр. 146/149).
|
||
|
||
Тянет точный счёт квартир из публичного API ГИС ЖКХ (dom.gosuslugi.ru) и проставляет
|
||
`houses.zhkh_flat_count` / `zhkh_house_guid` / `zhkh_matched_at` / `zhkh_year` /
|
||
`zhkh_floors_raw`. Матч ТРИВИАЛЬНЫЙ: `houses.gar_house_guid` — это И ЕСТЬ ЖКХ `houseGuid`
|
||
(проверено), поэтому ищем дом по нему, без нормализации/канона адреса.
|
||
|
||
Запуск из контейнера tradein-backend (у прода есть интернет к dom.gosuslugi.ru):
|
||
|
||
python -m app.tasks.zhkh_flats_load # полный прогон
|
||
python -m app.tasks.zhkh_flats_load --limit 300 --dry-run # сэмпл, без записи
|
||
|
||
В режиме --dry-run выборка/фетч происходят, но НИ ОДНОЙ записи в БД не делается.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import logging
|
||
|
||
from app.core.db import SessionLocal
|
||
from app.services.zhkh_flats_loader import match_houses_to_zhkh
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def build_parser() -> argparse.ArgumentParser:
|
||
"""Парсер CLI (вынесен для тестируемости флагов без запуска main)."""
|
||
parser = argparse.ArgumentParser(
|
||
description="ГИС ЖКХ loader: квартир на дом → houses.zhkh_* по gar_house_guid"
|
||
)
|
||
parser.add_argument(
|
||
"--region", default="66", help="код региона (информационно, по умолчанию 66)"
|
||
)
|
||
parser.add_argument(
|
||
"--limit", type=int, default=None, help="ограничить число домов (сэмпл); по умолчанию все"
|
||
)
|
||
parser.add_argument(
|
||
"--dry-run", action="store_true", help="без записи в БД — только фетч и подсчёт"
|
||
)
|
||
parser.add_argument(
|
||
"--sleep", type=float, default=0.4, help="пауза между батчами, сек (по умолчанию 0.4)"
|
||
)
|
||
return parser
|
||
|
||
|
||
def main() -> None:
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||
)
|
||
parser = build_parser()
|
||
args = parser.parse_args()
|
||
|
||
db = SessionLocal()
|
||
try:
|
||
counts = match_houses_to_zhkh(
|
||
db,
|
||
region_code=args.region,
|
||
limit=args.limit,
|
||
dry_run=args.dry_run,
|
||
sleep_sec=args.sleep,
|
||
)
|
||
if not args.dry_run:
|
||
db.commit()
|
||
finally:
|
||
db.close()
|
||
|
||
logger.info(
|
||
"zhkh_flats_load DONE: region=%s limit=%s dry_run=%s seen=%d resolved=%d "
|
||
"with_flatcount=%d updated=%d",
|
||
args.region,
|
||
args.limit,
|
||
args.dry_run,
|
||
counts["houses_seen"],
|
||
counts["zhkh_resolved"],
|
||
counts["zhkh_with_flatcount"],
|
||
counts["houses_updated"],
|
||
)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|