From 79dce4292e1ea186a9c12cc55ff6938dba9a21cc Mon Sep 17 00:00:00 2001 From: Anten Skrabec Date: Tue, 28 Jul 2026 21:51:14 -0600 Subject: [PATCH 1/2] fix: add PMC/Scav breakdown to server-wide stats page Adds PMC and Scav raid counts and survival rates to the server-wide stats page at /quma/stats. Previously only per-user profile pages showed this breakdown. Changes: - Add pmc_raids, scav_raids, pmc_survival_rate, scav_survival_rate fields to ServerRaidStats struct - Query PMC/Scav stats in get_server_raid_stats() using player_side='Pmc' and player_side='Savage' filters (headless clients excluded) - Display PMC and Scav stats in equal-weight cards on stats.html Co-Authored-By: Claude Opus 4.6 (1M context) --- TODO.md | 1 - src/db/raids.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++ templates/stats.html | 29 ++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index a62125a5..aa5219cc 100644 --- a/TODO.md +++ b/TODO.md @@ -10,7 +10,6 @@ - infinite use invite codes (no multi-use support, only single-use) ## Bugs -- server-wide stats page has no PMC/Scav raid breakdown (per-user profile already tracks both) - restarting a headless client from the main headless page causes you to end up at that headless' info page - headless client start/stop/restart buttons on `/quma/headless` go past the card length - currency items (USD, EUR) displayed as roubles instead of as currency balances (Stash) diff --git a/src/db/raids.rs b/src/db/raids.rs index 9c571d19..606613b8 100644 --- a/src/db/raids.rs +++ b/src/db/raids.rs @@ -87,6 +87,10 @@ pub struct ServerRaidStats { pub total_raids: i64, pub unique_players: i64, pub overall_survival_rate: f64, + pub pmc_raids: i64, + pub scav_raids: i64, + pub pmc_survival_rate: f64, + pub scav_survival_rate: f64, pub map_counts: Vec<(String, i64)>, pub top_killers: Vec<(String, i64)>, pub recent_raids: Vec<(Raid, String)>, @@ -513,6 +517,44 @@ impl Database { 0.0 }; + // PMC stats + let pmc_raids: i64 = self.conn.query_row( + "SELECT COUNT(*) FROM raids r JOIN users u ON r.user_id = u.id WHERE u.is_headless = 0 AND r.player_side = 'Pmc'", + [], + |row| row.get(0), + )?; + + let pmc_survived: i64 = self.conn.query_row( + "SELECT COUNT(*) FROM raids r JOIN users u ON r.user_id = u.id WHERE u.is_headless = 0 AND r.player_side = 'Pmc' AND r.exit_status = 'Survived'", + [], + |row| row.get(0), + )?; + + let pmc_survival_rate = if pmc_raids > 0 { + (pmc_survived as f64 / pmc_raids as f64) * 100.0 + } else { + 0.0 + }; + + // Scav stats + let scav_raids: i64 = self.conn.query_row( + "SELECT COUNT(*) FROM raids r JOIN users u ON r.user_id = u.id WHERE u.is_headless = 0 AND r.player_side = 'Savage'", + [], + |row| row.get(0), + )?; + + let scav_survived: i64 = self.conn.query_row( + "SELECT COUNT(*) FROM raids r JOIN users u ON r.user_id = u.id WHERE u.is_headless = 0 AND r.player_side = 'Savage' AND r.exit_status = 'Survived'", + [], + |row| row.get(0), + )?; + + let scav_survival_rate = if scav_raids > 0 { + (scav_survived as f64 / scav_raids as f64) * 100.0 + } else { + 0.0 + }; + let mut map_stmt = self.conn.prepare( "SELECT r.map, COUNT(DISTINCT COALESCE(r.server_id, 'solo_' || r.id)) as count FROM raids r JOIN users u ON r.user_id = u.id WHERE u.is_headless = 0 GROUP BY r.map ORDER BY count DESC", )?; @@ -540,6 +582,10 @@ impl Database { total_raids, unique_players, overall_survival_rate, + pmc_raids, + scav_raids, + pmc_survival_rate, + scav_survival_rate, map_counts, top_killers, recent_raids, diff --git a/templates/stats.html b/templates/stats.html index 2dd7d9d5..48618a65 100644 --- a/templates/stats.html +++ b/templates/stats.html @@ -28,6 +28,35 @@

Stats

+
+
+

PMC

+
+
+
Raids
+
{{ raid_stats.pmc_raids }}
+
+
+
Survival Rate
+
{{ raid_stats.pmc_survival_rate|fmt_pct }}
+
+
+
+
+

Scav

+
+
+
Raids
+
{{ raid_stats.scav_raids }}
+
+
+
Survival Rate
+
{{ raid_stats.scav_survival_rate|fmt_pct }}
+
+
+
+
+

Active Raids

From aa2f554c2aa8612ce302dc988dec5490f7471e52 Mon Sep 17 00:00:00 2001 From: Anten Skrabec Date: Tue, 28 Jul 2026 22:27:09 -0600 Subject: [PATCH 2/2] perf: combine 4 queries into 2 for PMC/Scav stats Reduces database round-trips by combining separate count and survived queries into single queries using SUM(CASE WHEN ...), matching the existing overall_survival_rate pattern. Before: 4 queries (PMC count, PMC survived, Scav count, Scav survived) After: 2 queries (PMC combined, Scav combined) Co-Authored-By: Claude Opus 4.6 (1M context) --- src/db/raids.rs | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/db/raids.rs b/src/db/raids.rs index 606613b8..bb675675 100644 --- a/src/db/raids.rs +++ b/src/db/raids.rs @@ -518,16 +518,12 @@ impl Database { }; // PMC stats - let pmc_raids: i64 = self.conn.query_row( - "SELECT COUNT(*) FROM raids r JOIN users u ON r.user_id = u.id WHERE u.is_headless = 0 AND r.player_side = 'Pmc'", - [], - |row| row.get(0), - )?; - - let pmc_survived: i64 = self.conn.query_row( - "SELECT COUNT(*) FROM raids r JOIN users u ON r.user_id = u.id WHERE u.is_headless = 0 AND r.player_side = 'Pmc' AND r.exit_status = 'Survived'", + let (pmc_raids, pmc_survived): (i64, i64) = self.conn.query_row( + "SELECT COUNT(*), COALESCE(SUM(CASE WHEN r.exit_status = 'Survived' THEN 1 ELSE 0 END), 0) + FROM raids r JOIN users u ON r.user_id = u.id + WHERE u.is_headless = 0 AND r.player_side = 'Pmc'", [], - |row| row.get(0), + |row| Ok((row.get(0)?, row.get(1)?)), )?; let pmc_survival_rate = if pmc_raids > 0 { @@ -537,16 +533,12 @@ impl Database { }; // Scav stats - let scav_raids: i64 = self.conn.query_row( - "SELECT COUNT(*) FROM raids r JOIN users u ON r.user_id = u.id WHERE u.is_headless = 0 AND r.player_side = 'Savage'", - [], - |row| row.get(0), - )?; - - let scav_survived: i64 = self.conn.query_row( - "SELECT COUNT(*) FROM raids r JOIN users u ON r.user_id = u.id WHERE u.is_headless = 0 AND r.player_side = 'Savage' AND r.exit_status = 'Survived'", + let (scav_raids, scav_survived): (i64, i64) = self.conn.query_row( + "SELECT COUNT(*), COALESCE(SUM(CASE WHEN r.exit_status = 'Survived' THEN 1 ELSE 0 END), 0) + FROM raids r JOIN users u ON r.user_id = u.id + WHERE u.is_headless = 0 AND r.player_side = 'Savage'", [], - |row| row.get(0), + |row| Ok((row.get(0)?, row.get(1)?)), )?; let scav_survival_rate = if scav_raids > 0 {