136 lines
5.2 KiB
Python
136 lines
5.2 KiB
Python
|
|
from flask import Blueprint, render_template, request, Response
|
||
|
|
from web.services.stats_service import StatsService
|
||
|
|
from web.config import Config
|
||
|
|
import json
|
||
|
|
|
||
|
|
bp = Blueprint('matches', __name__, url_prefix='/matches')
|
||
|
|
|
||
|
|
@bp.route('/')
|
||
|
|
def index():
|
||
|
|
page = request.args.get('page', 1, type=int)
|
||
|
|
map_name = request.args.get('map')
|
||
|
|
date_from = request.args.get('date_from')
|
||
|
|
|
||
|
|
# Fetch summary stats (for the dashboard)
|
||
|
|
summary_stats = StatsService.get_team_stats_summary()
|
||
|
|
|
||
|
|
matches, total = StatsService.get_matches(page, Config.ITEMS_PER_PAGE, map_name, date_from)
|
||
|
|
total_pages = (total + Config.ITEMS_PER_PAGE - 1) // Config.ITEMS_PER_PAGE
|
||
|
|
|
||
|
|
return render_template('matches/list.html',
|
||
|
|
matches=matches, total=total, page=page, total_pages=total_pages,
|
||
|
|
summary_stats=summary_stats)
|
||
|
|
|
||
|
|
@bp.route('/<match_id>')
|
||
|
|
def detail(match_id):
|
||
|
|
match = StatsService.get_match_detail(match_id)
|
||
|
|
if not match:
|
||
|
|
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)
|
||
|
|
|
||
|
|
# --- 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)
|
||
|
|
|
||
|
|
# New Data for Enhanced Detail View
|
||
|
|
h2h_stats = StatsService.get_head_to_head_stats(match_id)
|
||
|
|
round_details = StatsService.get_match_round_details(match_id)
|
||
|
|
|
||
|
|
# Convert H2H stats to a more usable format (nested dict)
|
||
|
|
# h2h_matrix[attacker_id][victim_id] = kills
|
||
|
|
h2h_matrix = {}
|
||
|
|
if h2h_stats:
|
||
|
|
for row in h2h_stats:
|
||
|
|
a_id = row['attacker_steam_id']
|
||
|
|
v_id = row['victim_steam_id']
|
||
|
|
kills = row['kills']
|
||
|
|
if a_id not in h2h_matrix: h2h_matrix[a_id] = {}
|
||
|
|
h2h_matrix[a_id][v_id] = kills
|
||
|
|
|
||
|
|
# Create a mapping of SteamID -> Username for the template
|
||
|
|
# We can use the players list we already have
|
||
|
|
player_name_map = {}
|
||
|
|
for p in players:
|
||
|
|
sid = p.get('steam_id_64')
|
||
|
|
name = p.get('username')
|
||
|
|
if sid and name:
|
||
|
|
player_name_map[str(sid)] = name
|
||
|
|
|
||
|
|
return render_template('matches/detail.html', match=match,
|
||
|
|
team1_players=team1_players, team2_players=team2_players,
|
||
|
|
rounds=rounds,
|
||
|
|
h2h_matrix=h2h_matrix,
|
||
|
|
round_details=round_details,
|
||
|
|
player_name_map=player_name_map)
|
||
|
|
|
||
|
|
@bp.route('/<match_id>/raw')
|
||
|
|
def raw_json(match_id):
|
||
|
|
match = StatsService.get_match_detail(match_id)
|
||
|
|
if not match:
|
||
|
|
return "Match not found", 404
|
||
|
|
|
||
|
|
# Construct a raw object from available raw fields
|
||
|
|
data = {
|
||
|
|
'round_list': json.loads(match['round_list_raw']) if match['round_list_raw'] else None,
|
||
|
|
'leetify_data': json.loads(match['leetify_data_raw']) if match['leetify_data_raw'] else None
|
||
|
|
}
|
||
|
|
|
||
|
|
return Response(json.dumps(data, indent=2, ensure_ascii=False), mimetype='application/json')
|