gendesign/tradein-mvp/backend/app/tasks/zhkh_flats_load.py
bot-backend 078dadc440
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
fix(tradein): cadastre ЖКХ-fallback + COALESCE merge-key + median-floors guard
Серия sale-share data-quality (по находкам юзера):
- house_dedup_merge: cluster_key → tradein_canon_addr(COALESCE(short_address,
  full_address, address)) — ловит дубли с район-noise в address (Вайнера 66).
- zhkh_flats_loader: cadastre-fallback (searchByAddress по cadastreNumber,
  guid-независимо) — резолвит дома где gar_house_guid ≠ ЖКХ houseGuid
  (Переходный 6: guid→0, cadastre→165). CLI --cadastre.
- мигр.153: плаузибилити-гард по медиане этажности листингов — denom >=
  GREATEST(total_floors, median(listings.total_floors), 8); отсекает gar-недосчёт
  башен без ЖКХ (gar=9 vs листинги 19-26 эт → NULL).

ruff clean, pytest 36 green.
2026-06-29 01:32:48 +03:00

113 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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 # полный прогон (по gar_house_guid)
python -m app.tasks.zhkh_flats_load --limit 300 --dry-run # сэмпл, без записи
python -m app.tasks.zhkh_flats_load --cadastre # fallback по кадастру листингов
В режиме --dry-run выборка/фетч происходят, но НИ ОДНОЙ записи в БД не делается.
В режиме --cadastre резолвим дома, чей gar_house_guid ≠ ЖКХ houseGuid, по
listings.building_cadastral_number и UPDATE'им строго по houses.id.
"""
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,
match_houses_to_zhkh_by_cadastre,
)
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)"
)
parser.add_argument(
"--cadastre",
action="store_true",
help="cadastre-fallback: резолв ЖКХ по listings.building_cadastral_number для домов, "
"чей gar_house_guid ≠ ЖКХ houseGuid; UPDATE по houses.id",
)
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:
if args.cadastre:
cad_counts = match_houses_to_zhkh_by_cadastre(
db,
limit=args.limit,
dry_run=args.dry_run,
sleep_sec=args.sleep,
)
if not args.dry_run:
db.commit()
logger.info(
"zhkh_flats_load (cadastre) DONE: limit=%s dry_run=%s seen=%d resolved=%d "
"updated=%d",
args.limit,
args.dry_run,
cad_counts["houses_seen"],
cad_counts["resolved"],
cad_counts["updated"],
)
else:
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()
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"],
)
finally:
db.close()
if __name__ == "__main__":
main()