Statistiky: dokoncene hry rozdelene na s ludmi / s botmi

Karta "Dokoncene hry" (%) nahradena dvomi poctami dokoncenych hier
podla toho, ci na niektorom sedadle sedel bot (username "bot:...").
Zaroven oprava serializacie avg_game_duration_minutes -- Postgres
vracia z func.avg Decimal, ktory json.dumps nevie serializovat (500
na /api/admin/stats).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tim
2026-07-10 22:25:32 +02:00
co-authored by Claude Opus 4.8
parent 8e18325660
commit 1636757217
3 changed files with 66 additions and 12 deletions
+26 -5
View File
@@ -8,12 +8,16 @@ import os
import geoip2.database
import geoip2.errors
from sqlalchemy import extract, func, select
from sqlalchemy import extract, func, or_, select
from user_agents import parse as parse_ua
from db.db import async_session
from db.models import Game, Guess, PageView, Player
# Konvencia na rozpoznanie botieho uctu (viz api.bots.BOT_PREFIX) -- drzana tu
# lokalne, aby sa do statistickej cesty netahala rl vrstva (siet/numpy).
_BOT_PREFIX = "bot:"
_geoip_reader: "geoip2.database.Reader | None" = None
_geoip_load_attempted = False
@@ -216,8 +220,24 @@ async def get_daily_stats(logged_in_only: bool = False) -> dict:
)
).all()
total, finished = (
await session.execute(select(func.count(), func.count(Game.ended_at)))
# Dokoncene hry rozdelene podla toho, ci na niektorom zo 4 sedadiel sedel
# bot (ucet s username "bot:..."). has_bot je pravdive, ak aspon jedno
# sedadlo patri botiemu uctu.
bot_ids = select(Player.id).where(Player.username.like(f"{_BOT_PREFIX}%"))
has_bot = or_(
Game.player0_id.in_(bot_ids),
Game.player1_id.in_(bot_ids),
Game.player2_id.in_(bot_ids),
Game.player3_id.in_(bot_ids),
)
finished = Game.ended_at.is_not(None)
finished_bot_games, finished_human_games = (
await session.execute(
select(
func.count().filter(finished & has_bot),
func.count().filter(finished & ~has_bot),
)
)
).one()
avg_duration = (
@@ -328,8 +348,9 @@ async def get_daily_stats(logged_in_only: bool = False) -> dict:
return {
"games_per_day": {str(r.day): r.n for r in game_rows},
"players_per_day": {str(r.day): r.n for r in player_rows},
"completion_rate": finished / total if total else None,
"avg_game_duration_minutes": (avg_duration / 60) if avg_duration else None,
"finished_bot_games": finished_bot_games,
"finished_human_games": finished_human_games,
"avg_game_duration_minutes": (float(avg_duration) / 60) if avg_duration else None,
"total_players": total_players,
"unconfirmed_players": unconfirmed_players,
"peak_hours": {int(r.h): r.n for r in peak_hours},