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>
This commit is contained in:
1 parent
96e0d695c0
commit
d61aa853f1
8 files changed
+356
-3
No files matched your search
@@ -474,6 +474,13 @@ _LEADERBOARD_SNAPSHOT_STARTUP_DELAY_SECONDS = _env_float(
|
||||
"ADMIN_LEADERBOARD_BACKGROUND_STARTUP_DELAY_SECONDS",
|
||||
60.0,
|
||||
)
|
||||
# Hour (local Beijing time, 0-23) after which the scheduler force-recomputes the
|
||||
# *previous* day's snapshot once, so records that landed near midnight are fully
|
||||
# captured. Default 1 → "每天凌晨 1 点重新统计前一天".
|
||||
_LEADERBOARD_FINALIZE_HOUR = min(
|
||||
23,
|
||||
_env_int("ADMIN_LEADERBOARD_FINALIZE_HOUR", 1, minimum=0),
|
||||
)
|
||||
|
||||
|
||||
def _leaderboard_scheduler_sql_for_log(stmt) -> str:
|
||||
@@ -653,6 +660,58 @@ async def _claim_leaderboard_snapshot(
|
||||
return True
|
||||
|
||||
|
||||
async def _maybe_finalize_previous_leaderboard_day(session_factory, settings_hash: str) -> bool:
|
||||
"""Once per day after ``_LEADERBOARD_FINALIZE_HOUR``, force-recompute yesterday.
|
||||
|
||||
A ``ready`` past-day snapshot is otherwise treated as fresh forever and never
|
||||
rebuilt. But the previous day's snapshot is usually generated before midnight,
|
||||
so it can miss runs/tool-calls that only land in the DB around the day
|
||||
boundary. This re-queues yesterday (``force=True``) a single time; the rebuild
|
||||
stamps ``generated_at`` to today, which stops it from re-firing for the rest of
|
||||
the day.
|
||||
"""
|
||||
from datetime import timedelta
|
||||
|
||||
from app.gateway.routers.admin_users import (
|
||||
_as_beijing,
|
||||
_queue_daily_leaderboard_snapshots,
|
||||
)
|
||||
from deerflow.persistence.admin_stats.finalize import should_finalize_previous_day
|
||||
from deerflow.persistence.admin_stats.model import AdminLeaderboardDailyStatRow
|
||||
from deerflow.persistence.types import BEIJING_TZ
|
||||
|
||||
now = datetime.now(BEIJING_TZ)
|
||||
yesterday = (now.date() - timedelta(days=1)).isoformat()
|
||||
async with session_factory() as session:
|
||||
row = await session.get(
|
||||
AdminLeaderboardDailyStatRow,
|
||||
{"stat_date": yesterday, "settings_hash": settings_hash},
|
||||
)
|
||||
generated_at = _as_beijing(row.generated_at) if (row is not None and row.generated_at is not None) else None
|
||||
if not should_finalize_previous_day(
|
||||
status=(row.status if row is not None else None),
|
||||
generated_at=generated_at,
|
||||
now=now,
|
||||
finalize_hour=_LEADERBOARD_FINALIZE_HOUR,
|
||||
):
|
||||
return False
|
||||
queued = await _queue_daily_leaderboard_snapshots(
|
||||
session_factory,
|
||||
[yesterday],
|
||||
settings_hash,
|
||||
force=True,
|
||||
message="每日凌晨重新统计前一天完整数据,确保全部记录纳入统计。",
|
||||
)
|
||||
if queued:
|
||||
logger.info(
|
||||
"Leaderboard snapshot finalize: queued previous day %s for full recompute (hash=%s)",
|
||||
yesterday,
|
||||
settings_hash[:10],
|
||||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def _run_leaderboard_snapshot_tick() -> tuple[int, int]:
|
||||
from app.gateway.routers.admin_users import (
|
||||
_daily_job_key,
|
||||
@@ -677,6 +736,12 @@ async def _run_leaderboard_snapshot_tick() -> tuple[int, int]:
|
||||
message="排行榜后台统计任务定期预热/补算队列。",
|
||||
)
|
||||
|
||||
# 每天凌晨(默认 1 点)后强制重算"前一天"的完整快照一次,覆盖跨午夜才落库的记录。
|
||||
try:
|
||||
await _maybe_finalize_previous_leaderboard_day(session_factory, settings_hash)
|
||||
except Exception:
|
||||
logger.exception("Leaderboard snapshot finalize check failed (non-fatal)")
|
||||
|
||||
due_dates = await _leaderboard_snapshot_due_dates(
|
||||
session_factory,
|
||||
settings_hash=settings_hash,
|
||||
|
||||
Reference in new issue
Block a user