gendesign/tradein-mvp/backend/app/tasks/gar_flats_load.py
lekss361 d7aaa00326
All checks were successful
Deploy Trade-In / test (push) Successful in 4m0s
Deploy Trade-In / build-backend (push) Successful in 1m12s
Deploy Trade-In / deploy (push) Successful in 1m47s
Deploy Trade-In / deploy-status (push) Successful in 3s
Deploy Trade-In / perimeter-smoke (push) Successful in 1m46s
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Матч ГАР работает по любому региону, а не только по Екатеринбургу (#3523)
2026-09-15 06:59:01 +00:00

160 lines
6.7 KiB
Python
Raw Permalink 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 (мигр. 143/144).
Запуск (каталог — УЖЕ распакованный ГАР региона, напр. папка `66/` из gar_xml.zip):
python -m app.tasks.gar_flats_load --dir /data/gar/66 --region 66 --version 2026-06-01
python -m app.tasks.gar_flats_load --dir /data/gar/77 --region 77 --version 2026-09-11
python -m app.tasks.gar_flats_load --dir /data/gar/50 --region 50 --version 2026-09-11
Делает два шага в одной транзакционной сессии:
1. load_gar_region — стриминговый парс XML → UPSERT gar_house_flats (коммит).
2. match_houses_to_gar — UPDATE houses.gar_flat_count по КАНОНИЧЕСКОМУ адресу (мигр. 144).
Ре-матч без повторного парса многогигабайтного XML (gar_house_flats уже загружена):
python -m app.tasks.gar_flats_load --match-only --region 66
python -m app.tasks.gar_flats_load --match-only --region 77
python -m app.tasks.gar_flats_load --match-only --region 50
В режиме `--match-only` шаг парса/загрузки пропускается целиком; `--dir` не требуется.
Многогигабайтный ДАМП качается/распаковывается отдельно (ops-шаг, см.
docs/gar-flats-runbook.md) — этот лоадер потребляет уже распакованные XML локально.
Город-фильтр матча (--city) по умолчанию НЕ вводится руками на каждый запуск — берётся
ПО РЕГИОНУ (см. app.services.gar_flats_loader.default_city_filter_for_region): region 66
получает byte-for-byte прежний фильтр «Екатеринбург», остальные регионы (77, 50 и любой
новый) — без фильтра. `--city ""` явно отключает фильтр для ЛЮБОГО региона (в т.ч. 66);
`--city "Имя"` — явный override.
"""
from __future__ import annotations
import argparse
import logging
from datetime import date
from app.core.db import SessionLocal
from app.services.gar_flats_loader import (
CITY_FILTER_AUTO,
CityFilterAutoType,
default_city_filter_for_region,
load_gar_region,
match_houses_to_gar,
)
logger = logging.getLogger(__name__)
def run_gar_flats_load(
dir_path: str,
region_code: str,
gar_version: str,
*,
city_filter: str | CityFilterAutoType | None = CITY_FILTER_AUTO,
) -> dict[str, int]:
"""Парс+UPSERT (load_gar_region) затем матч (match_houses_to_gar). Возвращает счётчики."""
db = SessionLocal()
try:
load = load_gar_region(dir_path, region_code, gar_version, db=db)
matched = match_houses_to_gar(db, region_code=region_code, city_filter=city_filter)
db.commit()
finally:
db.close()
counts = load.to_counts()
counts["houses_matched"] = matched
logger.info(
"gar_flats_load DONE: region=%s version=%s houses=%d apartments=%d upserted=%d "
"houses_matched=%d duration=%ds",
region_code,
gar_version,
counts["houses"],
counts["apartments"],
counts["upserted"],
matched,
counts["duration_sec"],
)
return counts
def run_gar_match_only(
region_code: str, *, city_filter: str | CityFilterAutoType | None = CITY_FILTER_AUTO
) -> dict[str, int]:
"""Только ре-матч уже загруженного gar_house_flats → houses (без парса XML).
Идемпотентно (match_houses_to_gar — plain UPDATE с IS DISTINCT FROM gate).
"""
db = SessionLocal()
try:
matched = match_houses_to_gar(db, region_code=region_code, city_filter=city_filter)
db.commit()
finally:
db.close()
logger.info(
"gar_flats_load --match-only DONE: region=%s city=%s houses_matched=%d",
region_code,
city_filter,
matched,
)
return {"houses_matched": matched}
def build_parser() -> argparse.ArgumentParser:
"""Парсер CLI (вынесен для тестируемости флагов без запуска main)."""
parser = argparse.ArgumentParser(
description="ГАР loader: квартир на дом → gar_house_flats + match к houses"
)
# --dir не required: в режиме --match-only парс пропускается (валидируем в main).
parser.add_argument("--dir", help="каталог с распакованными ГАР XML региона")
parser.add_argument("--region", default="66", help="код региона (по умолчанию 66)")
parser.add_argument(
"--version",
default=date.today().isoformat(),
help="версия ГАР-дампа YYYY-MM-DD (по умолчанию сегодня)",
)
parser.add_argument(
"--match-only",
action="store_true",
help="пропустить парс/загрузку ГАР, только пере-матчить gar_house_flats к houses",
)
parser.add_argument(
"--city",
default=None,
help=(
"город-фильтр матча (ILIKE по full_address); по умолчанию берётся ПО РЕГИОНУ "
"(--region) — см. default_city_filter_for_region; пустая строка явно отключает "
"фильтр для ЛЮБОГО региона"
),
)
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()
# --city не передан явно (argparse default=None) → дефолт ПО РЕГИОНУ (66 → «Екатеринбург»
# byte-for-byte как раньше, остальные — без фильтра). Передан явно (в т.ч. "") →
# уважаем волю вызывающего: "" → None (фильтр отключён), непустая строка → override.
if args.city is None:
city_filter: str | None = default_city_filter_for_region(args.region)
else:
city_filter = args.city or None
if args.match_only:
run_gar_match_only(args.region, city_filter=city_filter)
return
if not args.dir:
parser.error("--dir обязателен, кроме режима --match-only")
run_gar_flats_load(args.dir, args.region, args.version, city_filter=city_filter)
if __name__ == "__main__":
main()