"""Celery task: backfill кэша ПЗЗ-регламента ВСЕХ терзон ЕКБ (эпик #1881). В отличие от ``zone_regulation_refresh.refresh_zone_regulations`` (ленивый прогрев, beat ~ежемесячно) эта задача — явный одноразовый/ad-hoc backfill, чтобы ``financial_estimate`` (#1881) фаерил для большинства участков: ``max_far`` резолвится из ``zone_regulation_cache`` без live-urbanCard в hot-пути. На момент задачи в кэше всего ~33 зоны → задача докэширует остальные. Идемпотентно: ``backfill_ekb_zone_regulations`` пропускает уже закэшированные зоны (cached skip), так что повторный запуск дешёв (только WFS-перечисление). Per-zone try/except в сервисе — сбой одной зоны не валит прогон. ``rate_delay_s`` — вежливость к геопорталу. Mirror conventions (zone_regulation_refresh.py / backend.md): SessionLocal()+try/finally, sync (не async), commit per-upsert внутри сервиса. """ from __future__ import annotations import logging from app.core.db import SessionLocal from app.services.scrapers.ekb_geoportal_client import EKBGeoportalClient from app.services.site_finder.zone_regulation import ( _EKB_CITY_BBOX, backfill_ekb_zone_regulations, ) from app.workers.celery_app import celery_app logger = logging.getLogger(__name__) @celery_app.task(name="tasks.backfill_zone_regulations.backfill_zone_regulations") def backfill_zone_regulations( bbox: tuple[float, float, float, float] | None = None, limit: int | None = None, rate_delay_s: float = 1.0, ) -> dict[str, int]: """Backfill ``zone_regulation_cache`` по ВСЕМ терзонам ЕКБ (эпик #1881). Args: bbox: (minlon, minlat, maxlon, maxlat) EPSG:4326. None → дефолтный bbox ЕКБ. limit: максимум уникальных незакэшированных зон к фетчу (None = все). Для smoke-теста. rate_delay_s: пауза (сек) между live-fetch'ами зон — вежливость к геопорталу. Returns: {'enumerated_features', 'unique_zones', 'cached_before', 'fetched', 'upserted', 'failed'}. """ target_bbox = bbox if bbox is not None else _EKB_CITY_BBOX client = EKBGeoportalClient() db = SessionLocal() try: result = backfill_ekb_zone_regulations( db, client=client, bbox=target_bbox, limit=limit, rate_delay_s=rate_delay_s, ) logger.info("backfill_zone_regulations done: %s", result) return result finally: db.close()