"""Celery task: refresh mv_layout_velocity (питает «лучшие планировки» best_layouts). Scheduled via hardcoded beat entry in workers/beat_schedule.py: 'refresh-layout-velocity' — weekly on Sunday at 03:00 MSK. Стоит в «ночном» окне воскресенья, отдельно от monday-кластера тяжёлых site_finder-рефрешей (supply-layers / ird / gknspecial / cbr и т.д.), чтобы не конкурировать с ними за БД/CPU. Issue: #1666 (рефреш не был подключён в beat → данные best_layouts молча устаревали в проде). MV-источник: #113 (PR B, mv_layout_velocity → best_layouts). """ from __future__ import annotations import logging from typing import Any from app.core.db import SessionLocal from app.services.site_finder.layout_velocity_refresh import refresh_layout_velocity 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.resume_kn_run). Где их сознательно нет — пишется max_retries=0 с # пояснением (nspd_geo, objective_etl.import_anton_objective). @celery_app.task( name="tasks.refresh_layout_velocity.refresh_layout_velocity", ) def refresh_layout_velocity_task() -> dict[str, Any]: """REFRESH MATERIALIZED VIEW mv_layout_velocity (best_layouts, #113 / #1666). MV рефрешится CONCURRENTLY (non-blocking, требует unique-индекс mv_layout_velocity_pk); сервис сам падает в non-concurrent при unpopulated MV. Returns result dict for Celery task result store / logging. """ db = SessionLocal() try: count = refresh_layout_velocity(db, concurrently=True) logger.info("refresh_layout_velocity: completed, mv rows=%d", count) return {"status": "ok", "mv_layout_velocity_rows": count} except Exception as e: logger.exception("refresh_layout_velocity failed: %s", e) raise finally: db.close()