215 lines
8.0 KiB
Python
215 lines
8.0 KiB
Python
|
|
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()
|