diff --git a/database/Web/Web_App.sqlite b/database/Web/Web_App.sqlite index 0921300..9daf785 100644 Binary files a/database/Web/Web_App.sqlite and b/database/Web/Web_App.sqlite differ diff --git a/web/routes/matches.py b/web/routes/matches.py index e1f2819..3fdafed 100644 --- a/web/routes/matches.py +++ b/web/routes/matches.py @@ -23,11 +23,66 @@ def detail(match_id): return "Match not found", 404 players = StatsService.get_match_players(match_id) + # Convert sqlite3.Row objects to dicts to allow modification + players = [dict(p) for p in players] + rounds = StatsService.get_match_rounds(match_id) - # Organize players by team - team1_players = [p for p in players if p['team_id'] == 1] - team2_players = [p for p in players if p['team_id'] == 2] + # --- Roster Identification --- + # Fetch active roster to identify "Our Team" players + from web.services.web_service import WebService + lineups = WebService.get_lineups() + # Assume we use the first/active lineup + active_roster_ids = [] + if lineups: + try: + active_roster_ids = json.loads(lineups[0]['player_ids_json']) + except: + pass + + # Mark roster players (Ensure strict string comparison) + roster_set = set(str(uid) for uid in active_roster_ids) + for p in players: + p['is_in_roster'] = str(p['steam_id_64']) in roster_set + + # --- Party Size Calculation --- + # Only calculate party size for OUR ROSTER members. + # Group roster members by match_team_id + roster_parties = {} # match_team_id -> count of roster members + + for p in players: + if p['is_in_roster']: + mtid = p.get('match_team_id') + if mtid and mtid > 0: + key = f"tid_{mtid}" + roster_parties[key] = roster_parties.get(key, 0) + 1 + + # Assign party size ONLY to roster members + for p in players: + if p['is_in_roster']: + mtid = p.get('match_team_id') + if mtid and mtid > 0: + p['party_size'] = roster_parties.get(f"tid_{mtid}", 1) + else: + p['party_size'] = 1 # Solo roster player + else: + p['party_size'] = 0 # Hide party info for non-roster players + + # Organize players by Side (team_id) + # team_id 1 = Team 1, team_id 2 = Team 2 + # Note: group_id 1/2 usually corresponds to Team 1/2. + # Fallback to team_id if group_id is missing or 0 (legacy data compatibility) + team1_players = [p for p in players if p.get('group_id') == 1] + team2_players = [p for p in players if p.get('group_id') == 2] + + # If group_id didn't work (empty lists), try team_id grouping (if team_id is 1/2 only) + if not team1_players and not team2_players: + team1_players = [p for p in players if p['team_id'] == 1] + team2_players = [p for p in players if p['team_id'] == 2] + + # Explicitly sort by Rating DESC + team1_players.sort(key=lambda x: x.get('rating', 0) or 0, reverse=True) + team2_players.sort(key=lambda x: x.get('rating', 0) or 0, reverse=True) return render_template('matches/detail.html', match=match, team1_players=team1_players, team2_players=team2_players, diff --git a/web/services/stats_service.py b/web/services/stats_service.py index cc42527..0c70ec3 100644 --- a/web/services/stats_service.py +++ b/web/services/stats_service.py @@ -44,6 +44,109 @@ class StatsService: matches = query_db('l2', sql, args) + # Enrich matches with Avg ELO, Party info, and Our Team Result + if matches: + match_ids = [m['match_id'] for m in matches] + placeholders = ','.join('?' for _ in match_ids) + + # Fetch ELO + elo_sql = f""" + SELECT match_id, AVG(group_origin_elo) as avg_elo + FROM fact_match_teams + WHERE match_id IN ({placeholders}) AND group_origin_elo > 0 + GROUP BY match_id + """ + elo_rows = query_db('l2', elo_sql, match_ids) + elo_map = {row['match_id']: row['avg_elo'] for row in elo_rows} + + # Fetch Max Party Size + party_sql = f""" + SELECT match_id, MAX(cnt) as max_party + FROM ( + SELECT match_id, match_team_id, COUNT(*) as cnt + FROM fact_match_players + WHERE match_id IN ({placeholders}) AND match_team_id > 0 + GROUP BY match_id, match_team_id + ) + GROUP BY match_id + """ + party_rows = query_db('l2', party_sql, match_ids) + party_map = {row['match_id']: row['max_party'] for row in party_rows} + + # --- New: Determine "Our Team" Result --- + # Logic: Check if any player from `active_roster` played in these matches. + # Use WebService to get the active roster + from web.services.web_service import WebService + import json + + lineups = WebService.get_lineups() + active_roster_ids = [] + if lineups: + try: + # Load IDs and ensure they are all strings for DB comparison consistency + raw_ids = json.loads(lineups[0]['player_ids_json']) + active_roster_ids = [str(uid) for uid in raw_ids] + except: + pass + + # If no roster, we can't determine "Our Result" + if not active_roster_ids: + result_map = {} + else: + roster_placeholders = ','.join('?' for _ in active_roster_ids) + + # We cast steam_id_64 to TEXT to ensure match even if stored as int + our_result_sql = f""" + SELECT mp.match_id, mp.team_id, m.winner_team, COUNT(*) as our_count + FROM fact_match_players mp + JOIN fact_matches m ON mp.match_id = m.match_id + WHERE mp.match_id IN ({placeholders}) + AND CAST(mp.steam_id_64 AS TEXT) IN ({roster_placeholders}) + GROUP BY mp.match_id, mp.team_id + """ + + # Combine args: match_ids + roster_ids + combined_args = match_ids + active_roster_ids + our_rows = query_db('l2', our_result_sql, combined_args) + + # Map match_id -> result ('win', 'loss', 'draw', 'mixed') + result_map = {} + + match_sides = {} + match_winners = {} + + for r in our_rows: + mid = r['match_id'] + if mid not in match_sides: match_sides[mid] = {} + match_sides[mid][r['team_id']] = r['our_count'] + match_winners[mid] = r['winner_team'] + + for mid, sides in match_sides.items(): + winner = match_winners.get(mid) + if not winner: + result_map[mid] = 'draw' + continue + + our_on_winner = sides.get(winner, 0) + loser = 2 if winner == 1 else 1 + our_on_loser = sides.get(loser, 0) + + if our_on_winner > 0 and our_on_loser == 0: + result_map[mid] = 'win' + elif our_on_loser > 0 and our_on_winner == 0: + result_map[mid] = 'loss' + elif our_on_winner > 0 and our_on_loser > 0: + result_map[mid] = 'mixed' + else: + result_map[mid] = None + + # Convert to dict to modify + matches = [dict(m) for m in matches] + for m in matches: + m['avg_elo'] = elo_map.get(m['match_id'], 0) + m['max_party'] = party_map.get(m['match_id'], 1) + m['our_result'] = result_map.get(m['match_id']) + # Count total for pagination count_sql = f"SELECT COUNT(*) as cnt FROM fact_matches WHERE {where_str}" total = query_db('l2', count_sql, args[:-2], one=True)['cnt'] @@ -243,20 +346,25 @@ class StatsService: @staticmethod def get_player_trend(steam_id, limit=20): - sql = """ - SELECT m.start_time, mp.rating, mp.kd_ratio, mp.adr, m.match_id, m.map_name - FROM fact_match_players mp - JOIN fact_matches m ON mp.match_id = m.match_id - WHERE mp.steam_id_64 = ? - ORDER BY m.start_time ASC - """ - # We fetch all then slice last 'limit' in python or use subquery. - # DESC LIMIT gets recent, but we want chronological for chart. - # So: SELECT ... ORDER BY time DESC LIMIT ? -> then reverse in code. - + # We need party_size: count of players with same match_team_id in the same match + # Using a correlated subquery for party_size sql = """ SELECT * FROM ( - SELECT m.start_time, mp.rating, mp.kd_ratio, mp.adr, m.match_id, m.map_name, mp.is_win + SELECT + m.start_time, + mp.rating, + mp.kd_ratio, + mp.adr, + m.match_id, + m.map_name, + mp.is_win, + mp.match_team_id, + (SELECT COUNT(*) + FROM fact_match_players p2 + WHERE p2.match_id = mp.match_id + AND p2.match_team_id = mp.match_team_id + AND p2.match_team_id > 0 -- Ensure we don't count 0 (solo default) as a massive party + ) as party_size FROM fact_match_players mp JOIN fact_matches m ON mp.match_id = m.match_id WHERE mp.steam_id_64 = ? diff --git a/web/templates/matches/detail.html b/web/templates/matches/detail.html index 4731782..591e6be 100644 --- a/web/templates/matches/detail.html +++ b/web/templates/matches/detail.html @@ -47,12 +47,35 @@
{{ player.steam_id_64 }}
diff --git a/web/templates/players/profile.html b/web/templates/players/profile.html index b40306b..260a9cf 100644 --- a/web/templates/players/profile.html +++ b/web/templates/players/profile.html @@ -150,6 +150,8 @@