2026-01-26 02:13:06 +08:00
|
|
|
from flask import Blueprint, render_template, request, jsonify
|
|
|
|
|
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('tactics', __name__, url_prefix='/tactics')
|
|
|
|
|
|
|
|
|
|
@bp.route('/')
|
|
|
|
|
def index():
|
|
|
|
|
return render_template('tactics/index.html')
|
|
|
|
|
|
|
|
|
|
# API: Analyze Lineup
|
|
|
|
|
@bp.route('/api/analyze', methods=['POST'])
|
|
|
|
|
def api_analyze():
|
|
|
|
|
data = request.json
|
|
|
|
|
steam_ids = data.get('steam_ids', [])
|
|
|
|
|
|
|
|
|
|
if not steam_ids:
|
|
|
|
|
return jsonify({'error': 'No players selected'}), 400
|
|
|
|
|
|
|
|
|
|
# 1. Get Basic Info & Stats
|
|
|
|
|
players = StatsService.get_players_by_ids(steam_ids)
|
|
|
|
|
player_data = []
|
|
|
|
|
|
|
|
|
|
total_rating = 0
|
|
|
|
|
total_kd = 0
|
|
|
|
|
total_adr = 0
|
|
|
|
|
count = 0
|
2026-01-29 03:17:24 +08:00
|
|
|
radar_vectors = []
|
2026-01-26 02:13:06 +08:00
|
|
|
|
|
|
|
|
for p in players:
|
|
|
|
|
p_dict = dict(p)
|
|
|
|
|
# Fetch L3 features
|
|
|
|
|
f = FeatureService.get_player_features(p_dict['steam_id_64'])
|
|
|
|
|
stats = dict(f) if f else {}
|
|
|
|
|
p_dict['stats'] = stats
|
|
|
|
|
player_data.append(p_dict)
|
|
|
|
|
|
|
|
|
|
if stats:
|
2026-01-29 03:17:24 +08:00
|
|
|
rating_val = stats.get('core_avg_rating2')
|
|
|
|
|
if rating_val is None:
|
|
|
|
|
rating_val = stats.get('core_avg_rating')
|
|
|
|
|
if rating_val is None:
|
|
|
|
|
rating_val = stats.get('basic_avg_rating')
|
|
|
|
|
total_rating += rating_val or 0
|
|
|
|
|
total_kd += stats.get('core_avg_kd', stats.get('basic_avg_kd', 0)) or 0
|
|
|
|
|
total_adr += stats.get('core_avg_adr', stats.get('basic_avg_adr', 0)) or 0
|
2026-01-26 02:13:06 +08:00
|
|
|
count += 1
|
2026-01-29 03:17:24 +08:00
|
|
|
radar_vectors.append([
|
|
|
|
|
float(stats.get('score_aim') or 0),
|
|
|
|
|
float(stats.get('score_defense') or 0),
|
|
|
|
|
float(stats.get('score_utility') or 0),
|
|
|
|
|
float(stats.get('score_clutch') or 0),
|
|
|
|
|
float(stats.get('score_economy') or 0),
|
|
|
|
|
float(stats.get('score_pace') or 0),
|
|
|
|
|
float(stats.get('score_pistol') or 0),
|
|
|
|
|
float(stats.get('score_stability') or 0)
|
|
|
|
|
])
|
2026-01-26 02:13:06 +08:00
|
|
|
|
|
|
|
|
# 2. Shared Matches
|
|
|
|
|
shared_matches = StatsService.get_shared_matches(steam_ids)
|
2026-01-26 02:22:09 +08:00
|
|
|
# They are already dicts now with 'result_str' and 'is_win'
|
2026-01-26 02:13:06 +08:00
|
|
|
|
|
|
|
|
# 3. Aggregates
|
|
|
|
|
avg_stats = {
|
|
|
|
|
'rating': total_rating / count if count else 0,
|
|
|
|
|
'kd': total_kd / count if count else 0,
|
|
|
|
|
'adr': total_adr / count if count else 0
|
|
|
|
|
}
|
2026-01-26 17:26:43 +08:00
|
|
|
|
2026-01-29 03:17:24 +08:00
|
|
|
chemistry = 0
|
|
|
|
|
if len(radar_vectors) >= 2:
|
|
|
|
|
def cosine_sim(a, b):
|
|
|
|
|
dot = sum(x * y for x, y in zip(a, b))
|
|
|
|
|
na = sum(x * x for x in a) ** 0.5
|
|
|
|
|
nb = sum(y * y for y in b) ** 0.5
|
|
|
|
|
if na == 0 or nb == 0:
|
|
|
|
|
return 0
|
|
|
|
|
return dot / (na * nb)
|
|
|
|
|
|
|
|
|
|
sims = []
|
|
|
|
|
for i in range(len(radar_vectors)):
|
|
|
|
|
for j in range(i + 1, len(radar_vectors)):
|
|
|
|
|
sims.append(cosine_sim(radar_vectors[i], radar_vectors[j]))
|
|
|
|
|
if sims:
|
|
|
|
|
chemistry = sum(sims) / len(sims) * 100
|
|
|
|
|
|
2026-01-26 17:26:43 +08:00
|
|
|
# 4. Map Stats Calculation
|
|
|
|
|
map_stats = {} # {map_name: {'count': 0, 'wins': 0}}
|
|
|
|
|
total_shared_matches = len(shared_matches)
|
|
|
|
|
|
|
|
|
|
for m in shared_matches:
|
|
|
|
|
map_name = m['map_name']
|
|
|
|
|
if map_name not in map_stats:
|
|
|
|
|
map_stats[map_name] = {'count': 0, 'wins': 0}
|
|
|
|
|
|
|
|
|
|
map_stats[map_name]['count'] += 1
|
|
|
|
|
if m['is_win']:
|
|
|
|
|
map_stats[map_name]['wins'] += 1
|
|
|
|
|
|
|
|
|
|
# Convert to list for frontend
|
|
|
|
|
map_stats_list = []
|
|
|
|
|
for k, v in map_stats.items():
|
|
|
|
|
win_rate = (v['wins'] / v['count'] * 100) if v['count'] > 0 else 0
|
|
|
|
|
map_stats_list.append({
|
|
|
|
|
'map_name': k,
|
|
|
|
|
'count': v['count'],
|
|
|
|
|
'wins': v['wins'],
|
|
|
|
|
'win_rate': win_rate
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# Sort by count desc
|
|
|
|
|
map_stats_list.sort(key=lambda x: x['count'], reverse=True)
|
2026-01-26 02:13:06 +08:00
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
'players': player_data,
|
|
|
|
|
'shared_matches': [dict(m) for m in shared_matches],
|
2026-01-26 17:26:43 +08:00
|
|
|
'avg_stats': avg_stats,
|
|
|
|
|
'map_stats': map_stats_list,
|
2026-01-29 03:17:24 +08:00
|
|
|
'total_shared_matches': total_shared_matches,
|
|
|
|
|
'chemistry': chemistry
|
2026-01-26 02:13:06 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# API: Save Board
|
|
|
|
|
@bp.route('/save_board', methods=['POST'])
|
|
|
|
|
def save_board():
|
|
|
|
|
data = request.json
|
|
|
|
|
title = data.get('title', 'Untitled Strategy')
|
|
|
|
|
map_name = data.get('map_name', 'de_mirage')
|
|
|
|
|
markers = data.get('markers')
|
|
|
|
|
|
|
|
|
|
if not markers:
|
|
|
|
|
return jsonify({'success': False, 'message': 'No markers to save'})
|
|
|
|
|
|
|
|
|
|
WebService.save_strategy_board(title, map_name, json.dumps(markers), 'Anonymous')
|
|
|
|
|
return jsonify({'success': True, 'message': 'Board saved successfully'})
|