from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, session from web.services.web_service import WebService from web.services.stats_service import StatsService from web.services.feature_service import FeatureService import json bp = Blueprint('teams', __name__, url_prefix='/teams') # --- API Endpoints --- @bp.route('/api/search') def api_search(): query = request.args.get('q', '').strip() # Strip whitespace print(f"DEBUG: Search Query Received: '{query}'") # Debug Log if len(query) < 2: return jsonify([]) # Use L2 database for fuzzy search on username from web.services.stats_service import StatsService # Support sorting by matches for better "Find Player" experience sort_by = request.args.get('sort', 'matches') print(f"DEBUG: Calling StatsService.get_players with search='{query}'") players, total = StatsService.get_players(page=1, per_page=50, search=query, sort_by=sort_by) print(f"DEBUG: Found {len(players)} players (Total: {total})") # Format for frontend results = [] for p in players: # Convert sqlite3.Row to dict to avoid AttributeError p_dict = dict(p) # Fetch feature stats for better preview f = FeatureService.get_player_features(p_dict['steam_id_64']) # Manually attach match count if not present matches_played = p_dict.get('matches_played', 0) results.append({ 'steam_id': p_dict['steam_id_64'], 'name': p_dict['username'], 'avatar': p_dict['avatar_url'] or 'https://avatars.steamstatic.com/fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb_full.jpg', 'rating': (f['basic_avg_rating'] if f else 0.0), 'matches': matches_played }) # Python-side sort if DB sort didn't work for 'matches' (since dim_players doesn't have match_count) if sort_by == 'matches': # We need to fetch match counts to sort! # This is expensive for search results but necessary for "matches sample sort" # Let's batch fetch counts for these 50 players steam_ids = [r['steam_id'] for r in results] if steam_ids: from web.services.web_service import query_db placeholders = ','.join('?' for _ in steam_ids) sql = f"SELECT steam_id_64, COUNT(*) as cnt FROM fact_match_players WHERE steam_id_64 IN ({placeholders}) GROUP BY steam_id_64" counts = query_db('l2', sql, steam_ids) cnt_map = {r['steam_id_64']: r['cnt'] for r in counts} for r in results: r['matches'] = cnt_map.get(r['steam_id'], 0) results.sort(key=lambda x: x['matches'], reverse=True) print(f"DEBUG: Returning {len(results)} results") return jsonify(results) @bp.route('/api/roster', methods=['GET', 'POST']) def api_roster(): # Assume single team mode, always operating on ID=1 or the first lineup lineups = WebService.get_lineups() if not lineups: # Auto-create default team if none exists WebService.save_lineup("My Team", "Default Roster", []) lineups = WebService.get_lineups() target_team = dict(lineups[0]) # Get the latest one if request.method == 'POST': # Admin Check if not session.get('is_admin'): return jsonify({'error': 'Unauthorized'}), 403 data = request.json action = data.get('action') steam_id = data.get('steam_id') current_ids = [] try: current_ids = json.loads(target_team['player_ids_json']) except: pass if action == 'add': if steam_id not in current_ids: current_ids.append(steam_id) elif action == 'remove': if steam_id in current_ids: current_ids.remove(steam_id) # Pass lineup_id=target_team['id'] to update existing lineup WebService.save_lineup(target_team['name'], target_team['description'], current_ids, lineup_id=target_team['id']) return jsonify({'status': 'success', 'roster': current_ids}) # GET: Return detailed player info try: print(f"DEBUG: api_roster GET - Target Team: {target_team.get('id')}") p_ids_json = target_team.get('player_ids_json', '[]') p_ids = json.loads(p_ids_json) print(f"DEBUG: Player IDs: {p_ids}") players = StatsService.get_players_by_ids(p_ids) print(f"DEBUG: Players fetched: {len(players) if players else 0}") # Add extra stats needed for cards enriched = [] if players: for p in players: try: # Convert sqlite3.Row to dict p_dict = dict(p) # print(f"DEBUG: Processing player {p_dict.get('steam_id_64')}") # Get features for Rating/KD display f = FeatureService.get_player_features(p_dict['steam_id_64']) # f might be a Row object, convert it p_dict['stats'] = dict(f) if f else {} # Fetch Metadata (Tags) meta = WebService.get_player_metadata(p_dict['steam_id_64']) p_dict['tags'] = meta.get('tags', []) enriched.append(p_dict) except Exception as inner_e: print(f"ERROR: Processing player failed: {inner_e}") import traceback traceback.print_exc() return jsonify({ 'status': 'success', 'team': dict(target_team), # Ensure target_team is dict too 'roster': enriched }) except Exception as e: print(f"CRITICAL ERROR in api_roster: {e}") import traceback traceback.print_exc() return jsonify({'error': str(e)}), 500 # --- Views --- @bp.route('/') def index(): # Directly render the Clubhouse SPA return render_template('teams/clubhouse.html') # Deprecated routes (kept for compatibility if needed, but hidden) @bp.route('/list') def list_view(): lineups = WebService.get_lineups() # ... existing logic ... return render_template('teams/list.html', lineups=lineups) @bp.route('/') def detail(lineup_id): lineup = WebService.get_lineup(lineup_id) if not lineup: return "Lineup not found", 404 p_ids = json.loads(lineup['player_ids_json']) players = StatsService.get_players_by_ids(p_ids) # Shared Matches shared_matches = StatsService.get_shared_matches(p_ids) # Calculate Aggregate Stats agg_stats = { 'avg_rating': 0, 'avg_kd': 0, 'avg_kast': 0 } radar_data = { 'STA': 0, 'BAT': 0, 'HPS': 0, 'PTL': 0, 'SIDE': 0, 'UTIL': 0 } player_features = [] if players: count = len(players) total_rating = 0 total_kd = 0 total_kast = 0 # Radar totals r_totals = {k: 0 for k in radar_data} for p in players: # Fetch L3 features for each player f = FeatureService.get_player_features(p['steam_id_64']) if f: player_features.append(f) total_rating += f['basic_avg_rating'] or 0 total_kd += f['basic_avg_kd'] or 0 total_kast += f['basic_avg_kast'] or 0 # Radar accumulation r_totals['STA'] += f['basic_avg_rating'] or 0 r_totals['BAT'] += f['bat_avg_duel_win_rate'] or 0 r_totals['HPS'] += f['hps_clutch_win_rate_1v1'] or 0 r_totals['PTL'] += f['ptl_pistol_win_rate'] or 0 r_totals['SIDE'] += f['side_rating_ct'] or 0 r_totals['UTIL'] += f['util_usage_rate'] or 0 else: player_features.append(None) if count > 0: agg_stats['avg_rating'] = total_rating / count agg_stats['avg_kd'] = total_kd / count agg_stats['avg_kast'] = total_kast / count for k in radar_data: radar_data[k] = r_totals[k] / count return render_template('teams/detail.html', lineup=lineup, players=players, agg_stats=agg_stats, shared_matches=shared_matches, radar_data=radar_data)