1.2.0: Refined all 6D calcs and UI/UX Experiences.

This commit is contained in:
2026-01-26 21:10:42 +08:00
parent 8cc359b0ec
commit ade29ec1e8
25 changed files with 2498 additions and 482 deletions

214
scripts/analyze_features.py Normal file
View File

@@ -0,0 +1,214 @@
import sqlite3
import pandas as pd
import numpy as np
import os
DB_L2_PATH = r'd:\Documents\trae_projects\yrtv\database\L2\L2_Main.sqlite'
def get_db_connection():
conn = sqlite3.connect(DB_L2_PATH)
conn.row_factory = sqlite3.Row
return conn
def load_data_and_calculate(conn, min_matches=5):
print("Loading Basic Stats...")
# 1. Basic Stats
query_basic = """
SELECT
steam_id_64,
COUNT(*) as matches_played,
AVG(rating) as avg_rating,
AVG(kd_ratio) as avg_kd,
AVG(adr) as avg_adr,
AVG(kast) as avg_kast,
SUM(first_kill) as total_fk,
SUM(first_death) as total_fd,
SUM(clutch_1v1) + SUM(clutch_1v2) + SUM(clutch_1v3) + SUM(clutch_1v4) + SUM(clutch_1v5) as total_clutches,
SUM(throw_harm) as total_util_dmg,
SUM(flash_time) as total_flash_time,
SUM(flash_enemy) as total_flash_enemy
FROM fact_match_players
GROUP BY steam_id_64
HAVING COUNT(*) >= ?
"""
df_basic = pd.read_sql_query(query_basic, conn, params=(min_matches,))
valid_ids = tuple(df_basic['steam_id_64'].tolist())
if not valid_ids:
print("No players found.")
return None
placeholders = ','.join(['?'] * len(valid_ids))
# 2. Side Stats (T/CT) via Economy Table (which has side info)
print("Loading Side Stats via Round Map...")
# Map each round+player to a side
query_side_map = f"""
SELECT match_id, round_num, steam_id_64, side
FROM fact_round_player_economy
WHERE steam_id_64 IN ({placeholders})
"""
try:
df_sides = pd.read_sql_query(query_side_map, conn, params=valid_ids)
# Get all Kills
query_kills = f"""
SELECT match_id, round_num, attacker_steam_id as steam_id_64, COUNT(*) as kills
FROM fact_round_events
WHERE event_type = 'kill'
AND attacker_steam_id IN ({placeholders})
GROUP BY match_id, round_num, attacker_steam_id
"""
df_kills = pd.read_sql_query(query_kills, conn, params=valid_ids)
# Merge to get Kills per Side
df_merged = df_kills.merge(df_sides, on=['match_id', 'round_num', 'steam_id_64'], how='inner')
# Aggregate
side_stats = df_merged.groupby(['steam_id_64', 'side'])['kills'].sum().unstack(fill_value=0)
side_stats.columns = [f'kills_{c.lower()}' for c in side_stats.columns]
# Also need deaths to calc KD (approx)
# Assuming deaths are in events as victim
query_deaths = f"""
SELECT match_id, round_num, victim_steam_id as steam_id_64, COUNT(*) as deaths
FROM fact_round_events
WHERE event_type = 'kill'
AND victim_steam_id IN ({placeholders})
GROUP BY match_id, round_num, victim_steam_id
"""
df_deaths = pd.read_sql_query(query_deaths, conn, params=valid_ids)
df_merged_d = df_deaths.merge(df_sides, on=['match_id', 'round_num', 'steam_id_64'], how='inner')
side_stats_d = df_merged_d.groupby(['steam_id_64', 'side'])['deaths'].sum().unstack(fill_value=0)
side_stats_d.columns = [f'deaths_{c.lower()}' for c in side_stats_d.columns]
# Combine
df_side_final = side_stats.join(side_stats_d).fillna(0)
df_side_final['ct_kd'] = df_side_final.get('kills_ct', 0) / df_side_final.get('deaths_ct', 1).replace(0, 1)
df_side_final['t_kd'] = df_side_final.get('kills_t', 0) / df_side_final.get('deaths_t', 1).replace(0, 1)
except Exception as e:
print(f"Side stats failed: {e}")
df_side_final = pd.DataFrame({'steam_id_64': list(valid_ids)})
# 3. PTL (Pistol) via Rounds 1 and 13
print("Loading Pistol Stats via Rounds...")
query_pistol_kills = f"""
SELECT
ev.attacker_steam_id as steam_id_64,
COUNT(*) as pistol_kills
FROM fact_round_events ev
WHERE ev.attacker_steam_id IN ({placeholders})
AND ev.event_type = 'kill'
AND ev.round_num IN (1, 13)
GROUP BY ev.attacker_steam_id
"""
df_ptl = pd.read_sql_query(query_pistol_kills, conn, params=valid_ids)
# 4. HPS
print("Loading HPS Stats...")
query_close = f"""
SELECT mp.steam_id_64, AVG(mp.rating) as close_match_rating
FROM fact_match_players mp
JOIN fact_matches m ON mp.match_id = m.match_id
WHERE mp.steam_id_64 IN ({placeholders})
AND ABS(m.score_team1 - m.score_team2) <= 3
GROUP BY mp.steam_id_64
"""
df_hps = pd.read_sql_query(query_close, conn, params=valid_ids)
# 5. STA
query_sta = f"""
SELECT mp.steam_id_64, mp.rating, mp.is_win
FROM fact_match_players mp
WHERE mp.steam_id_64 IN ({placeholders})
"""
df_matches = pd.read_sql_query(query_sta, conn, params=valid_ids)
sta_data = []
for pid, group in df_matches.groupby('steam_id_64'):
rating_std = group['rating'].std()
win_rating = group[group['is_win']==1]['rating'].mean()
loss_rating = group[group['is_win']==0]['rating'].mean()
sta_data.append({'steam_id_64': pid, 'rating_std': rating_std, 'win_rating': win_rating, 'loss_rating': loss_rating})
df_sta = pd.DataFrame(sta_data)
# --- Merge All ---
df = df_basic.merge(df_side_final, on='steam_id_64', how='left')
df = df.merge(df_hps, on='steam_id_64', how='left')
df = df.merge(df_ptl, on='steam_id_64', how='left').fillna(0)
df = df.merge(df_sta, on='steam_id_64', how='left')
return df
def normalize_series(series):
min_v = series.min()
max_v = series.max()
if pd.isna(min_v) or pd.isna(max_v) or min_v == max_v:
return pd.Series([50]*len(series), index=series.index)
return (series - min_v) / (max_v - min_v) * 100
def calculate_scores(df):
df = df.copy()
# BAT
df['n_rating'] = normalize_series(df['avg_rating'])
df['n_kd'] = normalize_series(df['avg_kd'])
df['n_adr'] = normalize_series(df['avg_adr'])
df['n_kast'] = normalize_series(df['avg_kast'])
df['score_BAT'] = 0.4*df['n_rating'] + 0.3*df['n_kd'] + 0.2*df['n_adr'] + 0.1*df['n_kast']
# STA
df['n_std'] = normalize_series(df['rating_std'].fillna(0))
df['n_win_r'] = normalize_series(df['win_rating'].fillna(0))
df['n_loss_r'] = normalize_series(df['loss_rating'].fillna(0))
df['score_STA'] = 0.5*(100 - df['n_std']) + 0.25*df['n_win_r'] + 0.25*df['n_loss_r']
# UTIL
df['n_util_dmg'] = normalize_series(df['total_util_dmg'] / df['matches_played'])
df['n_flash'] = normalize_series(df['total_flash_time'] / df['matches_played'])
df['score_UTIL'] = 0.6*df['n_util_dmg'] + 0.4*df['n_flash']
# T/CT (Calculated from Event Logs)
df['n_ct_kd'] = normalize_series(df['ct_kd'].fillna(0))
df['n_t_kd'] = normalize_series(df['t_kd'].fillna(0))
df['score_TCT'] = 0.5*df['n_ct_kd'] + 0.5*df['n_t_kd']
# HPS
df['n_clutch'] = normalize_series(df['total_clutches'] / df['matches_played'])
df['n_close_r'] = normalize_series(df['close_match_rating'].fillna(0))
df['score_HPS'] = 0.5*df['n_clutch'] + 0.5*df['n_close_r']
# PTL
df['n_pistol'] = normalize_series(df['pistol_kills'] / df['matches_played'])
df['score_PTL'] = df['n_pistol']
return df
def main():
conn = get_db_connection()
try:
df = load_data_and_calculate(conn)
if df is None: return
# Debug: Print raw stats for checking T/CT issue
print("\n--- Raw T/CT Stats Sample ---")
if 'ct_kd' in df.columns:
print(df[['steam_id_64', 'ct_kd', 't_kd']].head())
else:
print("CT/KD columns missing")
results = calculate_scores(df)
print("\n--- Final Dimension Scores (Top 5 by BAT) ---")
cols = ['steam_id_64', 'score_BAT', 'score_STA', 'score_UTIL', 'score_TCT', 'score_HPS', 'score_PTL']
print(results[cols].sort_values('score_BAT', ascending=False).head(5))
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
finally:
conn.close()
if __name__ == "__main__":
main()