From 81739392da77f99aad467e586ff15724c0687561 Mon Sep 17 00:00:00 2001 From: Jacky Yang Date: Mon, 26 Jan 2026 02:22:09 +0800 Subject: [PATCH] 1.0.1-fix: Fixed 'winner-team' regarded as win. --- l3.db | 0 web/routes/tactics.py | 1 + web/services/stats_service.py | 52 ++++++++++++++++++++---- web/templates/tactics/index.html | 32 +++++++++++---- web/templates/teams/clubhouse.html | 63 ++++++++++++++++++++++++++++-- 5 files changed, 130 insertions(+), 18 deletions(-) delete mode 100644 l3.db diff --git a/l3.db b/l3.db deleted file mode 100644 index e69de29..0000000 diff --git a/web/routes/tactics.py b/web/routes/tactics.py index 6cc91e7..b56235c 100644 --- a/web/routes/tactics.py +++ b/web/routes/tactics.py @@ -44,6 +44,7 @@ def api_analyze(): # 2. Shared Matches shared_matches = StatsService.get_shared_matches(steam_ids) + # They are already dicts now with 'result_str' and 'is_win' # 3. Aggregates avg_stats = { diff --git a/web/services/stats_service.py b/web/services/stats_service.py index fed3f2b..cc42527 100644 --- a/web/services/stats_service.py +++ b/web/services/stats_service.py @@ -178,19 +178,29 @@ class StatsService: @staticmethod def get_shared_matches(steam_ids): - # Find matches where ALL steam_ids were present in the SAME team (or just present?) - # "共同经历" usually means played together. - # Query: Intersect match_ids for each player. - # SQLite doesn't have INTERSECT ALL easily for dynamic list, but we can group by match_id. - - if not steam_ids or len(steam_ids) < 2: + # Find matches where ALL steam_ids were present + if not steam_ids or len(steam_ids) < 1: return [] placeholders = ','.join('?' for _ in steam_ids) count = len(steam_ids) + # We need to know which team the players were on to determine win/loss + # Assuming they were on the SAME team for "shared experience" + # If count=1, it's just match history + + # Query: Get matches where all steam_ids are present + # Also join to get team_id to check if they were on the same team (optional but better) + # For simplicity in v1: Just check presence in the match. + # AND check if the player won. + + # We need to return: match_id, map_name, score, result (Win/Loss) + # "Result" is relative to the lineup. + # If they were on the winning team, it's a Win. + sql = f""" - SELECT m.match_id, m.start_time, m.map_name, m.score_team1, m.score_team2, m.winner_team + SELECT m.match_id, m.start_time, m.map_name, m.score_team1, m.score_team2, m.winner_team, + MAX(mp.team_id) as player_team_id -- Just take one team_id (assuming same) FROM fact_matches m JOIN fact_match_players mp ON m.match_id = mp.match_id WHERE mp.steam_id_64 IN ({placeholders}) @@ -203,7 +213,33 @@ class StatsService: args = list(steam_ids) args.append(count) - return query_db('l2', sql, args) + rows = query_db('l2', sql, args) + + results = [] + for r in rows: + # Determine if Win + # winner_team in DB is 'Team 1' or 'Team 2' usually, or the team name. + # fact_matches.winner_team stores the NAME of the winner? Or 'team1'/'team2'? + # Let's check how L2_Builder stores it. Usually it stores the name. + # But fact_match_players.team_id stores the name too. + + # Logic: If m.winner_team == mp.team_id, then Win. + is_win = (r['winner_team'] == r['player_team_id']) + + # If winner_team is NULL or empty, it's a draw? + if not r['winner_team']: + result_str = 'Draw' + elif is_win: + result_str = 'Win' + else: + result_str = 'Loss' + + res = dict(r) + res['is_win'] = is_win # Boolean for styling + res['result_str'] = result_str # Text for display + results.append(res) + + return results @staticmethod def get_player_trend(steam_id, limit=20): diff --git a/web/templates/tactics/index.html b/web/templates/tactics/index.html index a059493..020ea0d 100644 --- a/web/templates/tactics/index.html +++ b/web/templates/tactics/index.html @@ -34,8 +34,14 @@ draggable="true" @dragstart="dragStart($event, player)"> - + +
@@ -96,9 +102,19 @@
@@ -411,8 +427,10 @@ function tacticsApp() { const displayName = player.username || player.name || player.steam_id_64; const iconHtml = `
- + ${player.avatar_url ? + `` : + `
${(player.username || player.name).substring(0, 2).toUpperCase()}
` + } ${displayName} diff --git a/web/templates/teams/clubhouse.html b/web/templates/teams/clubhouse.html index d154bce..584df20 100644 --- a/web/templates/teams/clubhouse.html +++ b/web/templates/teams/clubhouse.html @@ -21,8 +21,23 @@
- -
+ +
+
+ + + +
+
+ + +

Active Roster

@@ -32,7 +47,15 @@
- + + +

@@ -140,6 +163,7 @@ function clubhouse() { return { team: {}, roster: [], + currentSort: 'rating', // Default sort showScoutModal: false, searchQuery: '', searchResults: [], @@ -154,9 +178,42 @@ function clubhouse() { .then(data => { this.team = data.team; this.roster = data.roster; + this.sortRoster(); // Apply default sort }); }, + sortBy(key) { + this.currentSort = key; + this.sortRoster(); + }, + + sortRoster() { + if (!this.roster || this.roster.length === 0) return; + + this.roster.sort((a, b) => { + let valA = 0, valB = 0; + + if (this.currentSort === 'rating') { + valA = a.stats?.basic_avg_rating || 0; + valB = b.stats?.basic_avg_rating || 0; + } else if (this.currentSort === 'kd') { + valA = a.stats?.basic_avg_kd || 0; + valB = b.stats?.basic_avg_kd || 0; + } else if (this.currentSort === 'matches') { + // matches_played is usually on the player object now? or stats? + // Check API: it's not explicitly in 'stats', but search added it. + // Roster API usually doesn't attach matches_played unless we ask. + // Let's assume stats.total_matches or check object root. + // Looking at roster API: we attach match counts? No, only search. + // But we can use total_matches from stats. + valA = a.stats?.total_matches || 0; + valB = b.stats?.total_matches || 0; + } + + return valB - valA; // Descending + }); + }, + searchPlayers() { if (this.searchQuery.length < 2) { this.searchResults = [];