gendesign/tradein-mvp/backend/app/tasks/domrf_kapremont_load.py
bot-backend 30195c30ce
All checks were successful
CI / changes (pull_request) Successful in 7s
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
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
CI Trade-In / backend-tests (pull_request) Successful in 51s
feat(tradein): fill houses.year_built/material_walls from ДОМ.РФ КР1.1/1.2 + zhkh_year, propagate to listings (#2013)
houses.year_built is only 38% filled and material_walls 0%. The existing ГИС-ЖКХ
loader already populates houses.zhkh_year (70%) but never copies it into
year_built — that's the core gap. Adds a ДОМ.РФ капремонт open-data loader
(КР1.1 house registry + КР1.2 constructive elements, region 66) into a new
domrf_kapremont staging table, then backfills houses.year_built/material_walls/
total_floors via COALESCE(existing, domrf, zhkh_year) — folding in the
already-loaded ЖКХ year so houses without a ДОМ.РФ match still benefit — and
propagates houses.year_built into listings.year_built for still-NULL rows so
the estimator's year_built cohort filter runs on real years across all sources
(avito 0%→~36-52% once linked houses gain a year). Supersedes the parked
cohort-exclusion PR #2474.
2026-07-12 16:09:59 +03:00

124 lines
5.3 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: ДОМ.РФ капремонт 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()