"""Celery task: инкрементальная синхронизация реестра РНС/РВЭ ГИСОГД-СО (#2367). Источник: открытый JSON-API gisogd66.midural.ru (раздел 13), БЕЗ авторизации и БЕЗ гео-блока — задача может выполняться и локально, и на проде. Держим weekly (обновляется ежедневно, но лоадер инкрементален и дёшев при отсутствии изменений). Persistent breadcrumbs в nspd_geo_log (как connection_capacity_sync). Import-light: тяжёлый загрузчик импортируется лениво внутри таска. """ import logging from sqlalchemy import text from app.workers.celery_app import celery_app logger = logging.getLogger(__name__) def _log_breadcrumb(stage: str, level: str, message: str) -> None: """Записать строку в nspd_geo_log (job_id=NULL) — диагностика без SSH.""" try: from app.core.db import SessionLocal db = SessionLocal() try: db.execute( text( "INSERT INTO nspd_geo_log (job_id, level, stage, message) " "VALUES (NULL, :lvl, :stage, :msg)" ), {"lvl": level, "stage": stage, "msg": message[:500]}, ) db.commit() finally: db.close() except Exception as e: logger.warning("gisogd_permits breadcrumb failed: %s", e) @celery_app.task( name="tasks.gisogd_permits_sync.sync_gisogd_permits", queue="celery", # Холодный прогон ~6k карточек (2 схемы × RS+RV) при politeness-sleep укладывается # в ~25 мин; щедрый лимит по конвенции full_report/forecast, чтобы beat-ран не убили # mid-upsert. Инкрементальные прогоны — минуты. time_limit=3600, soft_time_limit=3300, ) def sync_gisogd_permits() -> dict: """Инкрементально загрузить РНС/РВЭ ГИСОГД-СО (обе схемы, обе группы) → gisogd_permits. Список группы → сверка с БД по (source_key, date_reg) → карточки только для новых/изменившихся → UPSERT по (doc_group, doc_num) с предпочтением позднего date_reg. Returns: счётчики {listed, fetched_details, inserted, updated, skipped_unchanged, no_cad, no_geom}. """ _log_breadcrumb("gisogd_permits_sync", "info", "task started") try: from app.services.scrapers.gisogd66 import load_gisogd_permits result = load_gisogd_permits() logger.info("sync_gisogd_permits done: %s", result) _log_breadcrumb("gisogd_permits_sync", "info", f"done: {result}") return result except Exception as e: logger.exception("sync_gisogd_permits failed: %s", e) _log_breadcrumb("gisogd_permits_sync", "error", f"{type(e).__name__}: {e}") raise