gendesign/backend/app/workers/tasks/gisogd_permits_sync.py
bot-backend 1b96ec1baf
Some checks failed
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Failing after 1m56s
CI / backend-tests (pull_request) Successful in 14m37s
feat(scrapers): реестр РНС/РВЭ ГИСОГД-СО из открытого API (#2367 PR-1)
Ранний сигнал future-supply: разрешения на строительство появляются в
ГИСОГД до проектных деклараций ЕИСЖС. Старый minstroy-путь мёртв (WAF),
портал gisogd66.midural.ru отдаёт открытый JSON-API без авторизации.

Миграция 187_gisogd_permits (RS+RV, UNIQUE(doc_group,doc_num), GIST geom
4326, GIN cad_nums) + инкрементальный httpx-лоадер (схемы agate_ekbgo +
agate_sverdregion, сверка по (source_key,date_reg), карточки только для
новых/изменённых, SAVEPOINT per-row, WHERE-гейт предпочтения позднего
date_reg) + Celery weekly (вт 06:30 МСК, limit 3600/3300) + on-demand
admin-триггер + freshness-реестр (data-table, 14/45).

Refs #2367
2026-07-04 07:58:59 +05:00

69 lines
3.1 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.

"""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