"""CLI: ДОМ.РФ капремонт open data → houses.year_built/material_walls (issue #2013). Тянет КР1.1 (house registry) + КР1.2 (constructive elements) с открытых данных ДОМ.РФ (region 66), UPSERT'ит в staging `domrf_kapremont` (мигр. 176), затем: 1. backfill_houses_from_domrf — COALESCE(houses.year_built, domrf.commission_year, houses.zhkh_year) + material_walls + total_floors (fold-in уже загруженного ГИС-ЖКХ года — houses без domrf-матча тоже выигрывают). 2. propagate_listings_year_from_houses — UPDATE listings.year_built = houses.year_built там, где listing ещё NULL. Именно это открывает estimator'у (year_built BETWEEN когорта-фильтр) реальные годы на источниках вроде avito (0% year_built, 52.5% linked к houses). Запуск из контейнера tradein-backend (у прода есть интернет к домрф.рф): python -m app.tasks.domrf_kapremont_load # полный прогон python -m app.tasks.domrf_kapremont_load --dry-run # без записи, только подсчёт python -m app.tasks.domrf_kapremont_load --load-only # только staging, без backfill python -m app.tasks.domrf_kapremont_load --backfill-only # staging уже загружена — только # backfill+propagate python -m app.tasks.domrf_kapremont_load \\ --kr11-path /tmp/kr11.csv --kr12-path /tmp/kr12.csv # локальные CSV, без скачивания В режиме --dry-run скачивание/парс (если применимо) происходят, но НИ ОДНОЙ записи в БД не делается. """ from __future__ import annotations import argparse import logging from app.core.db import SessionLocal from app.services.domrf_kapremont_loader import ( backfill_houses_from_domrf, load_domrf_kapremont, propagate_listings_year_from_houses, ) logger = logging.getLogger(__name__) def build_parser() -> argparse.ArgumentParser: """Парсер CLI (вынесен для тестируемости флагов без запуска main).""" parser = argparse.ArgumentParser( description=( "ДОМ.РФ капремонт loader: КР1.1+КР1.2 → domrf_kapremont staging → " "houses.year_built/material_walls/total_floors → listings.year_built" ) ) parser.add_argument( "--kr11-path", default=None, help="локальный CSV КР1.1 (пропустить скачивание)" ) parser.add_argument( "--kr12-path", default=None, help="локальный CSV КР1.2 (пропустить скачивание)" ) parser.add_argument( "--work-dir", default=None, help="каталог для скачанных/распакованных CSV (по умолчанию — временный, удаляется после)", ) parser.add_argument( "--dry-run", action="store_true", help="без записи в БД — только скачивание/парс/подсчёт" ) parser.add_argument( "--load-only", action="store_true", help="только скачать+распарсить+UPSERT staging, без backfill houses/propagate listings", ) parser.add_argument( "--backfill-only", action="store_true", help="пропустить скачивание/load staging (считаем что уже загружена этим же " "или предыдущим прогоном) — только backfill houses + propagate listings", ) 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() if args.load_only and args.backfill_only: parser.error("--load-only и --backfill-only взаимоисключающи") db = SessionLocal() try: load_counts: dict[str, int] = {} if not args.backfill_only: load_counts = load_domrf_kapremont( db, kr11_path=args.kr11_path, kr12_path=args.kr12_path, work_dir=args.work_dir, dry_run=args.dry_run, ) if not args.dry_run: db.commit() logger.info("domrf_kapremont_load: load stage done: %s", load_counts) houses_counts: dict[str, int] = {} listings_counts: dict[str, int] = {} if not args.load_only: houses_counts = backfill_houses_from_domrf(db, dry_run=args.dry_run) listings_counts = propagate_listings_year_from_houses(db, dry_run=args.dry_run) if not args.dry_run: db.commit() logger.info( "domrf_kapremont_load DONE: dry_run=%s load=%s houses=%s listings=%s", args.dry_run, load_counts, houses_counts, listings_counts, ) finally: db.close() if __name__ == "__main__": main()