Files
吉鑫升andClaude Opus 4.8 d61aa853f1 feat(admin): 用户排行榜每日快照——手动刷新当天 + 凌晨自动定稿前一天
- 新增 POST /api/admin/users/leaderboard/refresh-today:同步强制重建当天快照
  并返回合并区间;前端 useRefreshTodayLeaderboard + AdminUserLeaderboardPage
  「刷新当天」按钮
- Gateway 调度循环在每日 ADMIN_LEADERBOARD_FINALIZE_HOUR(默认凌晨1点)对前一天
  强制重算一次,避免临近午夜落库的 runs/tool-calls 漏统计;判定逻辑下沉为纯函数
  deerflow.persistence.admin_stats.finalize.should_finalize_previous_day
- 新增 scripts/sql/column_additions_mysql.sql:既有 MySQL 库手动补列的幂等脚本
  (Alembic 自动迁移已禁用,create_all 不改既有表)
- CLAUDE.md 补充排行榜快照、Schema 迁移约定,并补齐技能/知识库选模型的文档

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:24:21 +08:00

48 lines
1.7 KiB
Python

"""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