"""Pure scheduling helpers for the admin leaderboard daily snapshots. Kept import-light on purpose (only stdlib ``datetime``) so the decision logic is unit-testable without standing up the FastAPI app, the DB engine, or the ORM. The background scheduler in ``app/gateway/app.py`` wires this to the real snapshot rows. """ from __future__ import annotations from datetime import datetime def should_finalize_previous_day( *, status: str | None, generated_at: datetime | None, now: datetime, finalize_hour: int, ) -> bool: """Decide whether *yesterday's* snapshot should be force-recomputed once. The previous day's snapshot is normally built before midnight, so it can miss runs / tool calls whose rows only land in the database shortly before or after the day boundary. Once per day, on/after ``finalize_hour`` (local Beijing time), we recompute it a single time so the full day is captured. ``now`` and ``generated_at`` are expected to be timezone-aware Beijing datetimes (``generated_at`` may be ``None`` for legacy rows). Returns ``True`` only when all hold: - it is already on/after ``finalize_hour`` today; - a snapshot row exists (``status`` is not ``None`` — a missing day is left to the normal prewarm/queue path); - that snapshot is not already ``queued``/``running`` (a finalize already in flight, or normal processing); - it was generated before today (i.e. not yet finalized for this day). """ if status is None: return False if now.hour < finalize_hour: return False if status in ("queued", "running"): return False if generated_at is not None and generated_at.date() >= now.date(): return False return True