"""CLI: АИС ППК ФРТ (реестр МКД) → houses.area_land/foundation_type/elevators_total + добор year_built/material_walls/total_floors/entrances/is_emergency/flat_count/ heat_supply_type/gas_supply_type/hot_water (issue #frt-mkd). Тянет CSV реестра МКД (export/{node_id}, по умолчанию 110 = region 66) с открытых данных АИС ППК ФРТ, UPSERT'ит в staging `frt_mkd` (мигр. 290), затем backfill_houses — COALESCE-добор перечисленных houses.* полей (только NULL, никогда не перезаписывает). Запуск из контейнера tradein-scraper (у него есть интернет к источнику): python -m app.tasks.frt_mkd_load # полный прогон (node 110, region 66) python -m app.tasks.frt_mkd_load --dry-run # без записи, только подсчёт python -m app.tasks.frt_mkd_load --load-only # только staging, без backfill python -m app.tasks.frt_mkd_load --backfill-only # staging уже загружена — только backfill python -m app.tasks.frt_mkd_load --src-path /tmp/frt.csv # локальный CSV, без скачивания В режиме --dry-run скачивание/парс (если применимо) происходят, но НИ ОДНОЙ записи в БД не делается. """ from __future__ import annotations import argparse import logging from app.core.db import SessionLocal from app.services.frt_mkd_loader import ( DEFAULT_NODE_ID, DEFAULT_REGION_CODE, backfill_houses, load_frt_mkd, ) logger = logging.getLogger(__name__) def build_parser() -> argparse.ArgumentParser: """Парсер CLI (вынесен для тестируемости флагов без запуска main).""" parser = argparse.ArgumentParser( description=( "АИС ППК ФРТ loader: реестр МКД → frt_mkd staging → " "houses.area_land/foundation_type/elevators_total + добор смежных полей" ) ) parser.add_argument( "--src-path", default=None, help="локальный CSV реестра МКД (пропустить скачивание)" ) parser.add_argument( "--work-dir", default=None, help="каталог для скачанного/распакованного CSV (по умолчанию — временный, " "удаляется после)", ) parser.add_argument( "--node-id", type=int, default=DEFAULT_NODE_ID, help=f"id ноды export АИС ФРТ (по умолчанию {DEFAULT_NODE_ID} = реестр МКД region 66)", ) parser.add_argument( "--region-code", type=int, default=DEFAULT_REGION_CODE, help=f"код региона выгрузки для тега staging-строк (по умолчанию {DEFAULT_REGION_CODE})", ) parser.add_argument( "--dry-run", action="store_true", help="без записи в БД — только скачивание/парс/подсчёт" ) parser.add_argument( "--load-only", action="store_true", help="только скачать+распарсить+UPSERT staging, без backfill houses", ) parser.add_argument( "--backfill-only", action="store_true", help="пропустить скачивание/load staging (считаем что уже загружена этим же " "или предыдущим прогоном) — только backfill houses", ) 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_frt_mkd( db, src_path=args.src_path, work_dir=args.work_dir, node_id=args.node_id, region_code=args.region_code, dry_run=args.dry_run, ) if not args.dry_run: db.commit() logger.info("frt_mkd_load: load stage done: %s", load_counts) houses_counts: dict[str, int] = {} if not args.load_only: houses_counts = backfill_houses(db, dry_run=args.dry_run) if not args.dry_run: db.commit() logger.info( "frt_mkd_load DONE: dry_run=%s load=%s houses=%s", args.dry_run, load_counts, houses_counts, ) finally: db.close() if __name__ == "__main__": main()