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