From 16367572172879b0cf27070ebe4f2d97118399f8 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 10 Jul 2026 22:25:32 +0200 Subject: [PATCH] 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 --- api/stats.py | 31 ++++++++++++++++---- frontend/src/pages/admin/AdminStats.tsx | 9 +++--- tests/test_stats.py | 38 +++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/api/stats.py b/api/stats.py index f35c2e7..202ff36 100644 --- a/api/stats.py +++ b/api/stats.py @@ -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}, diff --git a/frontend/src/pages/admin/AdminStats.tsx b/frontend/src/pages/admin/AdminStats.tsx index 06cb546..900f704 100644 --- a/frontend/src/pages/admin/AdminStats.tsx +++ b/frontend/src/pages/admin/AdminStats.tsx @@ -17,7 +17,8 @@ import PageviewsChart from './PageviewsChart'; interface DailyStats { games_per_day: Record; players_per_day: Record; - completion_rate: number | null; + finished_bot_games: number; + finished_human_games: number; avg_game_duration_minutes: number | null; total_players: number; unconfirmed_players: number; @@ -157,10 +158,8 @@ export default function AdminStats() {
- + + pripocita sa k "s ludmi", nie "s botmi" + self.assertEqual(data["finished_human_games"], base_human_games + 1) + self.assertEqual(data["finished_bot_games"], base_bot_games) + + def test_game_with_a_bot_seat_counts_as_bot_game(self): + before = run(stats.get_daily_stats()) + base_human_games = before["finished_human_games"] + base_bot_games = before["finished_bot_games"] + + # 3 ludia + 1 bot na poslednom sedadle + ids = self._make_players(n=3) + [self._make_bot_player()] + gid = str(uuid.uuid4()) + run(history.record_game_started(gid, "SBotom", ids)) + run(history.record_completed_rounds(gid, make_core())) + + data = run(stats.get_daily_stats()) + self.assertEqual(data["finished_bot_games"], base_bot_games + 1) + self.assertEqual(data["finished_human_games"], base_human_games) self.assertGreaterEqual(sum(data["games_per_day"].values()), 1) self.assertGreaterEqual(sum(data["rounds_per_day"].values()), 4)