style: polish admin user leaderboard

This commit is contained in:
Administrator committed 2026-06-04 20:22:37 +08:00
1 parent 7657c45aae
commit 5590f7d85a
7 files changed
+508 -261

No files matched your search

@@ -146,8 +146,27 @@ def _iso(value) -> str:
return value.isoformat() if isinstance(value, datetime) else str(value or "")
def _range_start(days: int) -> datetime:
return _now() - timedelta(days=max(1, min(days, 365)))
def _parse_datetime(value: str | None, fallback: datetime) -> datetime:
if not value:
return fallback
text = value.strip()
if not text:
return fallback
if text.endswith("Z"):
text = text[:-1] + "+00:00"
try:
return datetime.fromisoformat(text)
except ValueError as exc:
raise HTTPException(status_code=400, detail=f"Invalid datetime: {value}") from exc
def _resolve_range(days: int, since: str | None, until: str | None) -> tuple[datetime, datetime, int]:
end = _parse_datetime(until, _now())
start = _parse_datetime(since, end - timedelta(days=max(1, min(days, 365))))
if start > end:
raise HTTPException(status_code=400, detail="since must be before until")
span_days = max(1, min(365, (end - start).days + 1))
return start, end, span_days
def _question_count_expr():
@@ -403,6 +422,8 @@ async def _query_overview(session, since: datetime, until: datetime, *, user_id:
async def get_user_leaderboard(
request: Request,
days: int = Query(default=30, ge=1, le=365),
since: str | None = Query(default=None, description="Inclusive range start, ISO datetime"),
until: str | None = Query(default=None, description="Inclusive range end, ISO datetime"),
limit: int = Query(default=20, ge=5, le=100),
) -> AdminLeaderboardResponse:
"""Return admin analytics for user activity, Q&A, skills, questions, and trends."""
@@ -411,21 +432,20 @@ async def get_user_leaderboard(
if session_factory is None:
raise HTTPException(status_code=503, detail="Analytics requires database persistence")
until = _now()
since = _range_start(days)
since_dt, until_dt, range_days = _resolve_range(days, since, until)
async with session_factory() as session:
overview = await _query_overview(session, since, until)
users = await _query_user_leaderboard(session, since, until)
skills = await _query_call_rankings(session, since, until, user_id=None, field="skill", limit=limit)
tools = await _query_call_rankings(session, since, until, user_id=None, field="tool", limit=limit)
recent_questions = await _query_recent_questions(session, since, until, user_id=None, limit=limit)
frequent_questions = await _query_frequent_questions(session, since, until, limit=limit)
trends = await _query_trends(session, since, until)
overview = await _query_overview(session, since_dt, until_dt)
users = await _query_user_leaderboard(session, since_dt, until_dt)
skills = await _query_call_rankings(session, since_dt, until_dt, user_id=None, field="skill", limit=limit)
tools = await _query_call_rankings(session, since_dt, until_dt, user_id=None, field="tool", limit=limit)
recent_questions = await _query_recent_questions(session, since_dt, until_dt, user_id=None, limit=limit)
frequent_questions = await _query_frequent_questions(session, since_dt, until_dt, limit=limit)
trends = await _query_trends(session, since_dt, until_dt)
return AdminLeaderboardResponse(
range_days=days,
since=_iso(since),
until=_iso(until),
range_days=range_days,
since=_iso(since_dt),
until=_iso(until_dt),
overview=overview,
users_by_activity=sorted(users, key=lambda u: (u.run_count + u.tool_call_count, u.total_tokens), reverse=True)[:limit],
users_by_questions=sorted(users, key=lambda u: (u.qa_count, u.run_count), reverse=True)[:limit],
@@ -443,6 +463,8 @@ async def get_user_analytics(
user_id: str,
request: Request,
days: int = Query(default=30, ge=1, le=365),
since: str | None = Query(default=None, description="Inclusive range start, ISO datetime"),
until: str | None = Query(default=None, description="Inclusive range end, ISO datetime"),
limit: int = Query(default=20, ge=5, le=100),
) -> AdminUserAnalyticsResponse:
"""Return one user's admin analytics profile."""
@@ -451,22 +473,21 @@ async def get_user_analytics(
if session_factory is None:
raise HTTPException(status_code=503, detail="Analytics requires database persistence")
until = _now()
since = _range_start(days)
since_dt, until_dt, range_days = _resolve_range(days, since, until)
async with session_factory() as session:
users = await _query_user_leaderboard(session, since, until, user_id=user_id)
users = await _query_user_leaderboard(session, since_dt, until_dt, user_id=user_id)
if not users:
raise HTTPException(status_code=404, detail="User not found")
overview = await _query_overview(session, since, until, user_id=user_id)
skills = await _query_call_rankings(session, since, until, user_id=user_id, field="skill", limit=limit)
tools = await _query_call_rankings(session, since, until, user_id=user_id, field="tool", limit=limit)
recent_questions = await _query_recent_questions(session, since, until, user_id=user_id, limit=limit)
trends = await _query_trends(session, since, until, user_id=user_id)
overview = await _query_overview(session, since_dt, until_dt, user_id=user_id)
skills = await _query_call_rankings(session, since_dt, until_dt, user_id=user_id, field="skill", limit=limit)
tools = await _query_call_rankings(session, since_dt, until_dt, user_id=user_id, field="tool", limit=limit)
recent_questions = await _query_recent_questions(session, since_dt, until_dt, user_id=user_id, limit=limit)
trends = await _query_trends(session, since_dt, until_dt, user_id=user_id)
return AdminUserAnalyticsResponse(
range_days=days,
since=_iso(since),
until=_iso(until),
range_days=range_days,
since=_iso(since_dt),
until=_iso(until_dt),
user=users[0],
overview=overview,
skills=skills,