3.0.0 : Reconstructed Database System.

This commit is contained in:
2026-01-29 02:21:44 +08:00
parent 1642adb00e
commit 04ee957af6
69 changed files with 10258 additions and 6546 deletions

View File

@@ -98,50 +98,8 @@ def detail(steam_id):
return "Player not found", 404
features = FeatureService.get_player_features(steam_id)
# --- New: Fetch Detailed Stats from L2 (Clutch, Multi-Kill, Multi-Assist) ---
sql_l2 = """
SELECT
SUM(p.clutch_1v1) as c1, SUM(p.clutch_1v2) as c2, SUM(p.clutch_1v3) as c3, SUM(p.clutch_1v4) as c4, SUM(p.clutch_1v5) as c5,
SUM(a.attempt_1v1) as att1, SUM(a.attempt_1v2) as att2, SUM(a.attempt_1v3) as att3, SUM(a.attempt_1v4) as att4, SUM(a.attempt_1v5) as att5,
SUM(p.kill_2) as k2, SUM(p.kill_3) as k3, SUM(p.kill_4) as k4, SUM(p.kill_5) as k5,
SUM(p.many_assists_cnt2) as a2, SUM(p.many_assists_cnt3) as a3, SUM(p.many_assists_cnt4) as a4, SUM(p.many_assists_cnt5) as a5,
COUNT(*) as matches,
SUM(p.round_total) as total_rounds
FROM fact_match_players p
LEFT JOIN fact_match_clutch_attempts a ON p.match_id = a.match_id AND p.steam_id_64 = a.steam_id_64
WHERE p.steam_id_64 = ?
"""
l2_stats = query_db('l2', sql_l2, [steam_id], one=True)
l2_stats = dict(l2_stats) if l2_stats else {}
# Fetch T/CT splits for comparison
# Note: We use SUM(clutch...) as Total Clutch Wins. We don't have attempts, so 'Win Rate' is effectively Wins/Rounds or just Wins count.
# User asked for 'Win Rate', but without attempts data, we'll provide Rate per Round or just Count.
# Let's provide Rate per Round for Multi-Kill/Assist, and maybe just Count for Clutch?
# User said: "总残局胜率...分t和ct在下方加入对比".
# Since we found clutch == end in DB, we treat it as Wins. We can't calc Win %.
# We will display "Clutch Wins / Round" or just "Clutch Wins".
sql_side = """
SELECT
'T' as side,
SUM(clutch_1v1+clutch_1v2+clutch_1v3+clutch_1v4+clutch_1v5) as total_clutch,
SUM(kill_2+kill_3+kill_4+kill_5) as total_multikill,
SUM(many_assists_cnt2+many_assists_cnt3+many_assists_cnt4+many_assists_cnt5) as total_multiassist,
SUM(round_total) as rounds
FROM fact_match_players_t WHERE steam_id_64 = ?
UNION ALL
SELECT
'CT' as side,
SUM(clutch_1v1+clutch_1v2+clutch_1v3+clutch_1v4+clutch_1v5) as total_clutch,
SUM(kill_2+kill_3+kill_4+kill_5) as total_multikill,
SUM(many_assists_cnt2+many_assists_cnt3+many_assists_cnt4+many_assists_cnt5) as total_multiassist,
SUM(round_total) as rounds
FROM fact_match_players_ct WHERE steam_id_64 = ?
"""
side_rows = query_db('l2', sql_side, [steam_id, steam_id])
side_stats = {row['side']: dict(row) for row in side_rows} if side_rows else {}
l2_stats = {}
side_stats = {}
# Ensure basic stats fallback if features missing or incomplete
basic = StatsService.get_player_basic_stats(steam_id)
@@ -167,6 +125,47 @@ def detail(steam_id):
if 'basic_avg_adr' not in features or features['basic_avg_adr'] is None:
features['basic_avg_adr'] = basic.get('adr', 0) if basic else 0
try:
matches = int(features.get("matches_played") or 0)
except Exception:
matches = 0
try:
total_rounds = int(features.get("total_rounds") or 0)
except Exception:
total_rounds = 0
def _f(key, default=0.0):
v = features.get(key)
if v is None:
return default
try:
return float(v)
except Exception:
return default
l2_stats = {
"matches": matches,
"total_rounds": total_rounds,
"c1": int(_f("tac_clutch_1v1_wins", 0)),
"att1": int(_f("tac_clutch_1v1_attempts", 0)),
"c2": int(_f("tac_clutch_1v2_wins", 0)),
"att2": int(_f("tac_clutch_1v2_attempts", 0)),
"c3": int(_f("tac_clutch_1v3_plus_wins", 0)),
"att3": int(_f("tac_clutch_1v3_plus_attempts", 0)),
"c4": 0,
"att4": 0,
"c5": 0,
"att5": 0,
"k2": int(round(_f("tac_avg_2k", 0) * max(matches, 0))),
"k3": int(round(_f("tac_avg_3k", 0) * max(matches, 0))),
"k4": int(round(_f("tac_avg_4k", 0) * max(matches, 0))),
"k5": int(round(_f("tac_avg_5k", 0) * max(matches, 0))),
"a2": 0,
"a3": 0,
"a4": 0,
"a5": 0,
}
comments = WebService.get_comments('player', steam_id)
metadata = WebService.get_player_metadata(steam_id)
@@ -203,7 +202,7 @@ def detail(steam_id):
map_stats_list.sort(key=lambda x: x['matches'], reverse=True)
# --- New: Recent Performance Stats ---
recent_stats = StatsService.get_recent_performance_stats(steam_id)
# recent_stats = StatsService.get_recent_performance_stats(steam_id)
return render_template('players/profile.html',
player=player,
@@ -214,8 +213,7 @@ def detail(steam_id):
distribution=distribution,
map_stats=map_stats_list,
l2_stats=l2_stats,
side_stats=side_stats,
recent_stats=recent_stats)
side_stats=side_stats)
@bp.route('/comment/<int:comment_id>/like', methods=['POST'])
def like_comment(comment_id):
@@ -234,7 +232,7 @@ def charts_data(steam_id):
radar_dist = FeatureService.get_roster_features_distribution(steam_id)
if features:
# Dimensions: STA, BAT, HPS, PTL, T/CT, UTIL
# Dimensions: AIM, DEFENSE, UTILITY, CLUTCH, ECONOMY, PACE (6 Dimensions)
# Use calculated scores (0-100 scale)
# Helper to get score safely
@@ -243,14 +241,14 @@ def charts_data(steam_id):
return float(val) if val else 0
radar_data = {
'STA': get_score('score_sta'),
'BAT': get_score('score_bat'),
'HPS': get_score('score_hps'),
'PTL': get_score('score_ptl'),
'SIDE': get_score('score_tct'),
'UTIL': get_score('score_util'),
'ECO': get_score('score_eco'),
'PACE': get_score('score_pace')
'AIM': get_score('score_aim'),
'DEFENSE': get_score('score_defense'),
'UTILITY': get_score('score_utility'),
'CLUTCH': get_score('score_clutch'),
'ECONOMY': get_score('score_economy'),
'PACE': get_score('score_pace'),
'PISTOL': get_score('score_pistol'),
'STABILITY': get_score('score_stability')
}
trend_labels = []

View File

@@ -40,7 +40,7 @@ def api_search():
'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),
'rating': (f['core_avg_rating'] if f else 0.0),
'matches': matches_played
})
@@ -163,63 +163,72 @@ def list_view():
@bp.route('/<int:lineup_id>')
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
try:
lineup = WebService.get_lineup(lineup_id)
if not lineup:
return "Lineup not found", 404
for k in radar_data:
radar_data[k] = r_totals[k] / count
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:
# Attach stats to player object for template
p['rating'] = f.get('core_avg_rating') or 0
p['stats'] = f
player_features.append(f)
total_rating += f.get('core_avg_rating') or 0
total_kd += f.get('core_avg_kd') or 0
total_kast += f.get('core_avg_kast') or 0
# Radar accumulation (L3 Mapping)
r_totals['STA'] += f.get('core_avg_rating') or 0 # Rating (Scale ~1.0)
r_totals['BAT'] += (f.get('tac_opening_duel_winrate') or 0) * 2 # WinRate (0.5 -> 1.0) Scale to match Rating?
r_totals['HPS'] += (f.get('tac_clutch_1v1_rate') or 0) * 2 # WinRate (0.5 -> 1.0)
r_totals['PTL'] += ((f.get('score_pistol') or 0) / 50.0) # Score (0-100 -> 0-2.0)
r_totals['SIDE'] += f.get('meta_side_ct_rating') or 0 # Rating (Scale ~1.0)
r_totals['UTIL'] += f.get('tac_util_usage_rate') or 0 # Usage Rate (Count? or Rate?)
else:
player_features.append(None)
p['rating'] = 0
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)
return render_template('teams/detail.html', lineup=lineup, players=players, agg_stats=agg_stats, shared_matches=shared_matches, radar_data=radar_data)
except Exception as e:
import traceback
return f"<pre>{traceback.format_exc()}</pre>", 500