feat(server): stats queries support include_test filter
Leaderboard and rank queries take an optional include_test param (default false). Real users never see soak-harness traffic unless they explicitly opt in via ?include_test=true. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -159,6 +159,7 @@ async def get_leaderboard(
|
|||||||
metric: str = Query("wins", pattern="^(wins|win_rate|avg_score|knockouts|streak|rating)$"),
|
metric: str = Query("wins", pattern="^(wins|win_rate|avg_score|knockouts|streak|rating)$"),
|
||||||
limit: int = Query(50, ge=1, le=100),
|
limit: int = Query(50, ge=1, le=100),
|
||||||
offset: int = Query(0, ge=0),
|
offset: int = Query(0, ge=0),
|
||||||
|
include_test: bool = Query(False, description="Include soak-harness test accounts"),
|
||||||
service: StatsService = Depends(get_stats_service_dep),
|
service: StatsService = Depends(get_stats_service_dep),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -172,8 +173,9 @@ async def get_leaderboard(
|
|||||||
- streak: Best win streak
|
- streak: Best win streak
|
||||||
|
|
||||||
Players must have 5+ games to appear on leaderboards.
|
Players must have 5+ games to appear on leaderboards.
|
||||||
|
By default, soak-harness test accounts are hidden.
|
||||||
"""
|
"""
|
||||||
entries = await service.get_leaderboard(metric, limit, offset)
|
entries = await service.get_leaderboard(metric, limit, offset, include_test)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"metric": metric,
|
"metric": metric,
|
||||||
@@ -228,10 +230,11 @@ async def get_player_stats(
|
|||||||
async def get_player_rank(
|
async def get_player_rank(
|
||||||
user_id: str,
|
user_id: str,
|
||||||
metric: str = Query("wins", pattern="^(wins|win_rate|avg_score|knockouts|streak|rating)$"),
|
metric: str = Query("wins", pattern="^(wins|win_rate|avg_score|knockouts|streak|rating)$"),
|
||||||
|
include_test: bool = Query(False, description="Include soak-harness test accounts"),
|
||||||
service: StatsService = Depends(get_stats_service_dep),
|
service: StatsService = Depends(get_stats_service_dep),
|
||||||
):
|
):
|
||||||
"""Get player's rank on a leaderboard."""
|
"""Get player's rank on a leaderboard."""
|
||||||
rank = await service.get_player_rank(user_id, metric)
|
rank = await service.get_player_rank(user_id, metric, include_test)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
@@ -348,11 +351,12 @@ async def get_my_stats(
|
|||||||
@router.get("/me/rank", response_model=PlayerRankResponse)
|
@router.get("/me/rank", response_model=PlayerRankResponse)
|
||||||
async def get_my_rank(
|
async def get_my_rank(
|
||||||
metric: str = Query("wins", pattern="^(wins|win_rate|avg_score|knockouts|streak|rating)$"),
|
metric: str = Query("wins", pattern="^(wins|win_rate|avg_score|knockouts|streak|rating)$"),
|
||||||
|
include_test: bool = Query(False, description="Include soak-harness test accounts"),
|
||||||
user: User = Depends(require_user),
|
user: User = Depends(require_user),
|
||||||
service: StatsService = Depends(get_stats_service_dep),
|
service: StatsService = Depends(get_stats_service_dep),
|
||||||
):
|
):
|
||||||
"""Get current user's rank on a leaderboard."""
|
"""Get current user's rank on a leaderboard."""
|
||||||
rank = await service.get_player_rank(user.id, metric)
|
rank = await service.get_player_rank(user.id, metric, include_test)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"user_id": user.id,
|
"user_id": user.id,
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ class StatsService:
|
|||||||
metric: str = "wins",
|
metric: str = "wins",
|
||||||
limit: int = 50,
|
limit: int = 50,
|
||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
|
include_test: bool = False,
|
||||||
) -> List[LeaderboardEntry]:
|
) -> List[LeaderboardEntry]:
|
||||||
"""
|
"""
|
||||||
Get leaderboard by metric.
|
Get leaderboard by metric.
|
||||||
@@ -179,6 +180,8 @@ class StatsService:
|
|||||||
metric: Ranking metric - wins, win_rate, avg_score, knockouts, streak.
|
metric: Ranking metric - wins, win_rate, avg_score, knockouts, streak.
|
||||||
limit: Maximum entries to return.
|
limit: Maximum entries to return.
|
||||||
offset: Pagination offset.
|
offset: Pagination offset.
|
||||||
|
include_test: If True, include soak-harness test accounts. Default
|
||||||
|
False so real users never see synthetic load-test traffic.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of LeaderboardEntry sorted by metric.
|
List of LeaderboardEntry sorted by metric.
|
||||||
@@ -212,9 +215,10 @@ class StatsService:
|
|||||||
COALESCE(rating, 1500) as rating,
|
COALESCE(rating, 1500) as rating,
|
||||||
ROW_NUMBER() OVER (ORDER BY {column} {direction}) as rank
|
ROW_NUMBER() OVER (ORDER BY {column} {direction}) as rank
|
||||||
FROM leaderboard_overall
|
FROM leaderboard_overall
|
||||||
|
WHERE ($3 OR NOT is_test_account)
|
||||||
ORDER BY {column} {direction}
|
ORDER BY {column} {direction}
|
||||||
LIMIT $1 OFFSET $2
|
LIMIT $1 OFFSET $2
|
||||||
""", limit, offset)
|
""", limit, offset, include_test)
|
||||||
else:
|
else:
|
||||||
# Fall back to direct query
|
# Fall back to direct query
|
||||||
rows = await conn.fetch(f"""
|
rows = await conn.fetch(f"""
|
||||||
@@ -230,9 +234,10 @@ class StatsService:
|
|||||||
WHERE s.games_played >= 5
|
WHERE s.games_played >= 5
|
||||||
AND u.deleted_at IS NULL
|
AND u.deleted_at IS NULL
|
||||||
AND (u.is_banned = false OR u.is_banned IS NULL)
|
AND (u.is_banned = false OR u.is_banned IS NULL)
|
||||||
|
AND ($3 OR NOT COALESCE(u.is_test_account, FALSE))
|
||||||
ORDER BY {column} {direction}
|
ORDER BY {column} {direction}
|
||||||
LIMIT $1 OFFSET $2
|
LIMIT $1 OFFSET $2
|
||||||
""", limit, offset)
|
""", limit, offset, include_test)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
LeaderboardEntry(
|
LeaderboardEntry(
|
||||||
@@ -246,16 +251,26 @@ class StatsService:
|
|||||||
for row in rows
|
for row in rows
|
||||||
]
|
]
|
||||||
|
|
||||||
async def get_player_rank(self, user_id: str, metric: str = "wins") -> Optional[int]:
|
async def get_player_rank(
|
||||||
|
self,
|
||||||
|
user_id: str,
|
||||||
|
metric: str = "wins",
|
||||||
|
include_test: bool = False,
|
||||||
|
) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Get a player's rank on a leaderboard.
|
Get a player's rank on a leaderboard.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user_id: User UUID.
|
user_id: User UUID.
|
||||||
metric: Ranking metric.
|
metric: Ranking metric.
|
||||||
|
include_test: If True, rank within a leaderboard that includes
|
||||||
|
soak-harness test accounts. Default False matches the public
|
||||||
|
leaderboard view.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Rank number or None if not ranked (< 5 games or not found).
|
Rank number or None if not ranked (< 5 games or not found, or
|
||||||
|
filtered out because they are a test account and include_test is
|
||||||
|
False).
|
||||||
"""
|
"""
|
||||||
order_map = {
|
order_map = {
|
||||||
"wins": ("games_won", "DESC"),
|
"wins": ("games_won", "DESC"),
|
||||||
@@ -288,9 +303,10 @@ class StatsService:
|
|||||||
SELECT rank FROM (
|
SELECT rank FROM (
|
||||||
SELECT user_id, ROW_NUMBER() OVER (ORDER BY {column} {direction}) as rank
|
SELECT user_id, ROW_NUMBER() OVER (ORDER BY {column} {direction}) as rank
|
||||||
FROM leaderboard_overall
|
FROM leaderboard_overall
|
||||||
|
WHERE ($2 OR NOT is_test_account)
|
||||||
) ranked
|
) ranked
|
||||||
WHERE user_id = $1
|
WHERE user_id = $1
|
||||||
""", user_id)
|
""", user_id, include_test)
|
||||||
else:
|
else:
|
||||||
row = await conn.fetchrow(f"""
|
row = await conn.fetchrow(f"""
|
||||||
SELECT rank FROM (
|
SELECT rank FROM (
|
||||||
@@ -300,9 +316,10 @@ class StatsService:
|
|||||||
WHERE s.games_played >= 5
|
WHERE s.games_played >= 5
|
||||||
AND u.deleted_at IS NULL
|
AND u.deleted_at IS NULL
|
||||||
AND (u.is_banned = false OR u.is_banned IS NULL)
|
AND (u.is_banned = false OR u.is_banned IS NULL)
|
||||||
|
AND ($2 OR NOT COALESCE(u.is_test_account, FALSE))
|
||||||
) ranked
|
) ranked
|
||||||
WHERE user_id = $1
|
WHERE user_id = $1
|
||||||
""", user_id)
|
""", user_id, include_test)
|
||||||
|
|
||||||
return row["rank"] if row else None
|
return row["rank"] if row else None
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user