"""Celery task: refresh the sales-tracker MVs (Issue #61). Scheduled via hardcoded beat entry in workers/beat_schedule.py: 'mv-sales-tracker-refresh-weekly' — weekly on Monday at 04:30 MSK. Refreshes (both CONCURRENTLY, non-blocking): - mv_sales_tracker_velocity_by_district (Site Finder Velocity Score, 4th criterion) - mv_sales_tracker_absorption_curves (recommend_mix + sellout forecast foundation) Both MVs are built from the Объектив sales-tracker ("шахматки") snapshots (objective_lots / objective_lots_history). Source data refreshes via the objective_sync beat job, so a weekly MV refresh keeps the aggregates current. MV-source migration: data/sql/161_mv_sales_tracker_velocity_absorption.sql. """ from __future__ import annotations import logging from typing import Any from app.core.db import SessionLocal from app.services.site_finder.sales_tracker_mv_refresh import refresh_sales_tracker_mvs from app.workers.celery_app import celery_app logger = logging.getLogger(__name__) @celery_app.task( bind=True, name="tasks.mv_sales_tracker_refresh.refresh_sales_tracker_mvs", max_retries=2, ) def refresh_sales_tracker_mvs_task(self: Any) -> dict[str, Any]: """REFRESH both sales-tracker MVs (#61). Both MVs are refreshed CONCURRENTLY (non-blocking, require their UNIQUE indexes created by migration 161); the service falls back to non-concurrent if an MV is found unpopulated (first-run edge case). Returns result dict for the Celery task result store / logging. """ db = SessionLocal() try: counts = refresh_sales_tracker_mvs(db, concurrently=True) logger.info("refresh_sales_tracker_mvs: completed, rows=%s", counts) return {"status": "ok", "rows": counts} except Exception as e: logger.exception("refresh_sales_tracker_mvs failed: %s", e) raise finally: db.close()