"""Celery task: refresh developer_registry materialized view (#1088 «GG-форсайт»). Вызывает ``fn_refresh_developer_registry()`` (миграция 149, ``data/sql/149_developer_attribution.sql``) — REFRESH MATERIALIZED VIEW CONCURRENTLY ``developer_registry`` (реестр застройщиков по норм-ИНН: канон-имя + track-record permits+domrf). CONCURRENTLY не блокирует SELECT-читателей (analyze) во время refresh. Scheduled via hardcoded beat entry в workers/beat_schedule.py: 'developer-registry-refresh-monthly' — ежемесячно 1-го числа в 05:30 МСК, через 30 мин после 'ekburg-permits-monthly' (05:00 МСК), чтобы permits-сторона реестра обновлялась на свежих РНС/РВЭ. domrf-сторона обновляется ежедневно (scrape_kn), но ИНН/track-record застройщиков меняются редко — monthly refresh реестра с запасом достаточно (резолвер всё равно читает domrf-quarter напрямую). Issue: #1088. """ from __future__ import annotations import logging from typing import Any from sqlalchemy import text from app.core.db import SessionLocal from app.workers.celery_app import celery_app logger = logging.getLogger(__name__) # Ретраев здесь нет, и параметров, обещающих их, быть не должно (#2464). Стояло # `bind=True, max_retries=2`, но self не использовался, self.retry() не вызывался и # autoretry_for задан не был — конфигурация не имела эффекта. Соседи, где ретраи # нужны, задают их явно: autoretry_for (nspd_sync, scrape_cadastre) или self.retry() # (scrape_kn). @celery_app.task( name="tasks.developer_registry_refresh.refresh_developer_registry", ) def refresh_developer_registry() -> dict[str, Any]: """REFRESH MATERIALIZED VIEW CONCURRENTLY developer_registry. Лёгкая задача (реестр ~1024 застройщика). CONCURRENTLY — non-blocking для читателей analyze; требует unique-индекс uq_developer_registry_inn (создан миграцией 149). Возвращает row-count реестра после refresh для observability. Returns: dict для Celery result store / логирования. """ db = SessionLocal() try: db.execute(text("SELECT fn_refresh_developer_registry()")) db.commit() row = db.execute(text("SELECT COUNT(*) FROM developer_registry")).first() count = int(row[0]) if row else 0 logger.info("refresh_developer_registry: completed, developers=%d", count) return {"status": "ok", "developer_registry_rows": count} except Exception as e: logger.exception("refresh_developer_registry failed: %s", e) raise finally: db.close()