"""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__) @celery_app.task( bind=True, name="tasks.developer_registry_refresh.refresh_developer_registry", max_retries=2, ) def refresh_developer_registry(self: Any) -> 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()