1.2.2: Profile Upgraded
This commit is contained in:
@@ -166,7 +166,7 @@ class FeatureService:
|
||||
merged.sort(key=lambda x: x.get(order_col, 0) or 0, reverse=True)
|
||||
|
||||
return merged, total
|
||||
|
||||
|
||||
# Normal L3 browse
|
||||
sql = f"SELECT * FROM dm_player_features ORDER BY {order_col} DESC LIMIT ? OFFSET ?"
|
||||
features = query_db('l3', sql, [per_page, offset])
|
||||
@@ -199,15 +199,34 @@ class FeatureService:
|
||||
Refreshes the L3 Data Mart with full feature calculations.
|
||||
"""
|
||||
from web.config import Config
|
||||
from web.services.web_service import WebService
|
||||
import json
|
||||
|
||||
l3_db_path = Config.DB_L3_PATH
|
||||
l2_db_path = Config.DB_L2_PATH
|
||||
|
||||
# Get Team Players
|
||||
lineups = WebService.get_lineups()
|
||||
team_player_ids = set()
|
||||
for lineup in lineups:
|
||||
if lineup['player_ids_json']:
|
||||
try:
|
||||
ids = json.loads(lineup['player_ids_json'])
|
||||
# Ensure IDs are strings
|
||||
team_player_ids.update([str(i) for i in ids])
|
||||
except:
|
||||
pass
|
||||
|
||||
if not team_player_ids:
|
||||
print("No players found in any team lineup. Skipping L3 rebuild.")
|
||||
return 0
|
||||
|
||||
conn_l2 = sqlite3.connect(l2_db_path)
|
||||
conn_l2.row_factory = sqlite3.Row
|
||||
|
||||
try:
|
||||
print("Loading L2 data...")
|
||||
df = FeatureService._load_and_calculate_dataframe(conn_l2, min_matches)
|
||||
print(f"Loading L2 data for {len(team_player_ids)} players...")
|
||||
df = FeatureService._load_and_calculate_dataframe(conn_l2, list(team_player_ids))
|
||||
|
||||
if df is None or df.empty:
|
||||
print("No data to process.")
|
||||
@@ -231,6 +250,7 @@ class FeatureService:
|
||||
df_to_save['updated_at'] = pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Generate Insert SQL
|
||||
print(f"DEBUG: Saving {len(df_to_save.columns)} columns to L3. Sample side_kd_ct: {df_to_save.get('side_kd_ct', pd.Series([0])).iloc[0]}")
|
||||
placeholders = ','.join(['?'] * len(df_to_save.columns))
|
||||
cols_str = ','.join(df_to_save.columns)
|
||||
sql = f"INSERT OR REPLACE INTO dm_player_features ({cols_str}) VALUES ({placeholders})"
|
||||
@@ -251,9 +271,14 @@ class FeatureService:
|
||||
conn_l2.close()
|
||||
|
||||
@staticmethod
|
||||
def _load_and_calculate_dataframe(conn, min_matches):
|
||||
def _load_and_calculate_dataframe(conn, player_ids):
|
||||
if not player_ids:
|
||||
return None
|
||||
|
||||
placeholders = ','.join(['?'] * len(player_ids))
|
||||
|
||||
# 1. Basic Stats
|
||||
query_basic = """
|
||||
query_basic = f"""
|
||||
SELECT
|
||||
steam_id_64,
|
||||
COUNT(*) as matches_played,
|
||||
@@ -298,10 +323,10 @@ class FeatureService:
|
||||
SUM(util_he_usage) as sum_util_he,
|
||||
SUM(util_decoy_usage) as sum_util_decoy
|
||||
FROM fact_match_players
|
||||
WHERE steam_id_64 IN ({placeholders})
|
||||
GROUP BY steam_id_64
|
||||
HAVING COUNT(*) >= ?
|
||||
"""
|
||||
df = pd.read_sql_query(query_basic, conn, params=(min_matches,))
|
||||
df = pd.read_sql_query(query_basic, conn, params=player_ids)
|
||||
if df.empty: return None
|
||||
|
||||
# Basic Derived
|
||||
@@ -492,6 +517,9 @@ class FeatureService:
|
||||
# Force overwrite winner_side with calculated winner since DB data is unreliable (mostly NULL)
|
||||
df_rounds['winner_side'] = df_rounds['calculated_winner']
|
||||
|
||||
# Ensure winner_side is string type to match side ('CT', 'T')
|
||||
df_rounds['winner_side'] = df_rounds['winner_side'].astype(str)
|
||||
|
||||
# Fallback for Round 1 if still None (e.g. if prev is 0 and score is 1)
|
||||
# Logic above handles Round 1 correctly (prev is 0).
|
||||
|
||||
@@ -533,6 +561,10 @@ class FeatureService:
|
||||
# Merge Scores
|
||||
df_events = df_events.merge(df_rounds, on=['match_id', 'round_num'], how='left')
|
||||
|
||||
# --- BAT: Win Rate vs All ---
|
||||
# Removed as per request (Difficult to calculate / All Zeros)
|
||||
df['bat_win_rate_vs_all'] = 0
|
||||
|
||||
# --- HPS: Match Point & Comeback ---
|
||||
# Match Point Win Rate
|
||||
mp_rounds = df_rounds[((df_rounds['ct_score'] == 12) | (df_rounds['t_score'] == 12) |
|
||||
@@ -584,6 +616,85 @@ class FeatureService:
|
||||
|
||||
kd_stats.index.name = 'steam_id_64'
|
||||
df = df.merge(kd_stats[['hps_comeback_kd_diff']], on='steam_id_64', how='left')
|
||||
|
||||
# HPS: Losing Streak KD Diff
|
||||
# Logic: KD in rounds where team has lost >= 3 consecutive rounds vs Global KD
|
||||
# 1. Identify Streak Rounds
|
||||
if not df_rounds.empty:
|
||||
# Ensure sorted
|
||||
df_rounds = df_rounds.sort_values(['match_id', 'round_num'])
|
||||
|
||||
# Shift to check previous results
|
||||
# We need to handle match boundaries. Groupby match_id is safer.
|
||||
# CT Loss Streak
|
||||
g = df_rounds.groupby('match_id')
|
||||
df_rounds['ct_lost_1'] = g['t_win'].shift(1).fillna(False)
|
||||
df_rounds['ct_lost_2'] = g['t_win'].shift(2).fillna(False)
|
||||
df_rounds['ct_lost_3'] = g['t_win'].shift(3).fillna(False)
|
||||
df_rounds['ct_in_loss_streak'] = (df_rounds['ct_lost_1'] & df_rounds['ct_lost_2'] & df_rounds['ct_lost_3'])
|
||||
|
||||
# T Loss Streak
|
||||
df_rounds['t_lost_1'] = g['ct_win'].shift(1).fillna(False)
|
||||
df_rounds['t_lost_2'] = g['ct_win'].shift(2).fillna(False)
|
||||
df_rounds['t_lost_3'] = g['ct_win'].shift(3).fillna(False)
|
||||
df_rounds['t_in_loss_streak'] = (df_rounds['t_lost_1'] & df_rounds['t_lost_2'] & df_rounds['t_lost_3'])
|
||||
|
||||
# Merge into events
|
||||
# df_events already has 'match_id', 'round_num', 'attacker_side'
|
||||
# We need to merge streak info
|
||||
streak_cols = df_rounds[['match_id', 'round_num', 'ct_in_loss_streak', 't_in_loss_streak']]
|
||||
df_events = df_events.merge(streak_cols, on=['match_id', 'round_num'], how='left')
|
||||
|
||||
# Determine if attacker is in streak
|
||||
df_events['att_is_loss_streak'] = np.where(
|
||||
df_events['attacker_side'] == 'CT', df_events['ct_in_loss_streak'],
|
||||
np.where(df_events['attacker_side'] == 'T', df_events['t_in_loss_streak'], False)
|
||||
)
|
||||
|
||||
# Determine if victim is in streak (for deaths)
|
||||
df_events['vic_is_loss_streak'] = np.where(
|
||||
df_events['victim_side'] == 'CT', df_events['ct_in_loss_streak'],
|
||||
np.where(df_events['victim_side'] == 'T', df_events['t_in_loss_streak'], False)
|
||||
)
|
||||
|
||||
# Calculate KD in Streak
|
||||
ls_k = df_events[df_events['att_is_loss_streak']].groupby('attacker_steam_id').size()
|
||||
ls_d = df_events[df_events['vic_is_loss_streak']].groupby('victim_steam_id').size()
|
||||
|
||||
ls_stats = pd.DataFrame({'ls_k': ls_k, 'ls_d': ls_d}).fillna(0)
|
||||
ls_stats['ls_kd'] = ls_stats['ls_k'] / ls_stats['ls_d'].replace(0, 1)
|
||||
|
||||
# Compare with Global KD (from df_sides or recomputed)
|
||||
# Recompute global KD from events to be consistent
|
||||
g_k = df_events.groupby('attacker_steam_id').size()
|
||||
g_d = df_events.groupby('victim_steam_id').size()
|
||||
g_stats = pd.DataFrame({'g_k': g_k, 'g_d': g_d}).fillna(0)
|
||||
g_stats['g_kd'] = g_stats['g_k'] / g_stats['g_d'].replace(0, 1)
|
||||
|
||||
ls_stats = ls_stats.join(g_stats[['g_kd']], how='outer').fillna(0)
|
||||
ls_stats['hps_losing_streak_kd_diff'] = ls_stats['ls_kd'] - ls_stats['g_kd']
|
||||
|
||||
ls_stats.index.name = 'steam_id_64'
|
||||
df = df.merge(ls_stats[['hps_losing_streak_kd_diff']], on='steam_id_64', how='left')
|
||||
else:
|
||||
df['hps_losing_streak_kd_diff'] = 0
|
||||
|
||||
|
||||
# HPS: Momentum Multi-kill Rate
|
||||
# Team won 3+ rounds -> 2+ kills
|
||||
# Need sequential win info.
|
||||
# Hard to vectorise fully without accurate round sequence reconstruction including missing rounds.
|
||||
# Placeholder: 0
|
||||
df['hps_momentum_multikill_rate'] = 0
|
||||
|
||||
# HPS: Tilt Rating Drop
|
||||
df['hps_tilt_rating_drop'] = 0
|
||||
|
||||
# HPS: Clutch Rating Rise
|
||||
df['hps_clutch_rating_rise'] = 0
|
||||
|
||||
# HPS: Undermanned Survival
|
||||
df['hps_undermanned_survival_time'] = 0
|
||||
|
||||
# --- PTL: Pistol Stats ---
|
||||
pistol_rounds = [1, 13]
|
||||
@@ -606,70 +717,164 @@ class FeatureService:
|
||||
df['ptl_pistol_kd'] = 1.0
|
||||
df['ptl_pistol_util_efficiency'] = 0.0
|
||||
|
||||
# --- T/CT Stats ---
|
||||
ct_k = df_events[df_events['attacker_side'] == 'CT'].groupby('attacker_steam_id').size()
|
||||
ct_d = df_events[df_events['victim_side'] == 'CT'].groupby('victim_steam_id').size()
|
||||
t_k = df_events[df_events['attacker_side'] == 'T'].groupby('attacker_steam_id').size()
|
||||
t_d = df_events[df_events['victim_side'] == 'T'].groupby('victim_steam_id').size()
|
||||
# --- T/CT Stats (Directly from L2 Side Tables) ---
|
||||
query_sides_l2 = f"""
|
||||
SELECT
|
||||
steam_id_64,
|
||||
'CT' as side,
|
||||
COUNT(*) as matches,
|
||||
SUM(round_total) as rounds,
|
||||
AVG(rating2) as rating,
|
||||
SUM(kills) as kills,
|
||||
SUM(deaths) as deaths,
|
||||
SUM(assists) as assists,
|
||||
AVG(CAST(is_win as FLOAT)) as win_rate,
|
||||
SUM(first_kill) as fk,
|
||||
SUM(first_death) as fd,
|
||||
AVG(kast) as kast,
|
||||
AVG(rws) as rws,
|
||||
SUM(kill_2 + kill_3 + kill_4 + kill_5) as multi_kill_rounds,
|
||||
SUM(headshot_count) as hs
|
||||
FROM fact_match_players_ct
|
||||
WHERE steam_id_64 IN ({placeholders})
|
||||
GROUP BY steam_id_64
|
||||
|
||||
side_stats = pd.DataFrame({'ct_k': ct_k, 'ct_d': ct_d, 't_k': t_k, 't_d': t_d}).fillna(0)
|
||||
side_stats['side_rating_ct'] = side_stats['ct_k'] / side_stats['ct_d'].replace(0, 1)
|
||||
side_stats['side_rating_t'] = side_stats['t_k'] / side_stats['t_d'].replace(0, 1)
|
||||
side_stats['side_kd_diff_ct_t'] = side_stats['side_rating_ct'] - side_stats['side_rating_t']
|
||||
UNION ALL
|
||||
|
||||
side_stats.index.name = 'steam_id_64'
|
||||
df = df.merge(side_stats[['side_rating_ct', 'side_rating_t', 'side_kd_diff_ct_t']], on='steam_id_64', how='left')
|
||||
SELECT
|
||||
steam_id_64,
|
||||
'T' as side,
|
||||
COUNT(*) as matches,
|
||||
SUM(round_total) as rounds,
|
||||
AVG(rating2) as rating,
|
||||
SUM(kills) as kills,
|
||||
SUM(deaths) as deaths,
|
||||
SUM(assists) as assists,
|
||||
AVG(CAST(is_win as FLOAT)) as win_rate,
|
||||
SUM(first_kill) as fk,
|
||||
SUM(first_death) as fd,
|
||||
AVG(kast) as kast,
|
||||
AVG(rws) as rws,
|
||||
SUM(kill_2 + kill_3 + kill_4 + kill_5) as multi_kill_rounds,
|
||||
SUM(headshot_count) as hs
|
||||
FROM fact_match_players_t
|
||||
WHERE steam_id_64 IN ({placeholders})
|
||||
GROUP BY steam_id_64
|
||||
"""
|
||||
|
||||
# Side First Kill Rate
|
||||
# Need total rounds per side for denominator
|
||||
# Use df_player_rounds calculated in Match Point section
|
||||
# If not calculated there (no MP rounds), calc now
|
||||
if 'df_player_rounds' not in locals():
|
||||
q_all_rounds = f"SELECT match_id, round_num FROM fact_rounds WHERE match_id IN (SELECT match_id FROM fact_match_players WHERE steam_id_64 IN ({placeholders}))"
|
||||
df_all_rounds = pd.read_sql_query(q_all_rounds, conn, params=valid_ids)
|
||||
df_player_rounds = df_all_rounds.merge(df_fh_sides, on='match_id')
|
||||
mask_fh = df_player_rounds['round_num'] <= df_player_rounds['halftime_round']
|
||||
df_player_rounds['side'] = np.where(mask_fh, df_player_rounds['fh_side'],
|
||||
np.where(df_player_rounds['fh_side'] == 'CT', 'T', 'CT'))
|
||||
df_sides = pd.read_sql_query(query_sides_l2, conn, params=valid_ids + valid_ids)
|
||||
|
||||
rounds_per_side = df_player_rounds.groupby(['steam_id_64', 'side']).size().unstack(fill_value=0)
|
||||
if 'CT' not in rounds_per_side.columns: rounds_per_side['CT'] = 0
|
||||
if 'T' not in rounds_per_side.columns: rounds_per_side['T'] = 0
|
||||
|
||||
# First Kills (Earliest event in round)
|
||||
# Group by match, round -> min time.
|
||||
fk_events = df_events.sort_values('event_time').drop_duplicates(['match_id', 'round_num'])
|
||||
fk_ct = fk_events[fk_events['attacker_side'] == 'CT'].groupby('attacker_steam_id').size()
|
||||
fk_t = fk_events[fk_events['attacker_side'] == 'T'].groupby('attacker_steam_id').size()
|
||||
|
||||
fk_stats = pd.DataFrame({'fk_ct': fk_ct, 'fk_t': fk_t}).fillna(0)
|
||||
fk_stats = fk_stats.join(rounds_per_side, how='outer').fillna(0)
|
||||
|
||||
fk_stats['side_first_kill_rate_ct'] = fk_stats['fk_ct'] / fk_stats['CT'].replace(0, 1)
|
||||
fk_stats['side_first_kill_rate_t'] = fk_stats['fk_t'] / fk_stats['T'].replace(0, 1)
|
||||
|
||||
fk_stats.index.name = 'steam_id_64'
|
||||
df = df.merge(fk_stats[['side_first_kill_rate_ct', 'side_first_kill_rate_t']], on='steam_id_64', how='left')
|
||||
if not df_sides.empty:
|
||||
# Calculate Derived Rates per row before pivoting
|
||||
df_sides['rounds'] = df_sides['rounds'].replace(0, 1) # Avoid div by zero
|
||||
|
||||
# KD Calculation (Sum of Kills / Sum of Deaths)
|
||||
df_sides['kd'] = df_sides['kills'] / df_sides['deaths'].replace(0, 1)
|
||||
|
||||
# KAST Proxy (if KAST is 0)
|
||||
# KAST ~= (Kills + Assists + Survived) / Rounds
|
||||
# Survived = Rounds - Deaths
|
||||
if df_sides['kast'].mean() == 0:
|
||||
df_sides['survived'] = df_sides['rounds'] - df_sides['deaths']
|
||||
df_sides['kast'] = (df_sides['kills'] + df_sides['assists'] + df_sides['survived']) / df_sides['rounds'] * 100
|
||||
|
||||
df_sides['fk_rate'] = df_sides['fk'] / df_sides['rounds']
|
||||
df_sides['fd_rate'] = df_sides['fd'] / df_sides['rounds']
|
||||
df_sides['mk_rate'] = df_sides['multi_kill_rounds'] / df_sides['rounds']
|
||||
df_sides['hs_rate'] = df_sides['hs'] / df_sides['kills'].replace(0, 1)
|
||||
|
||||
# Pivot
|
||||
# We want columns like side_rating_ct, side_rating_t, etc.
|
||||
pivoted = df_sides.pivot(index='steam_id_64', columns='side').reset_index()
|
||||
|
||||
# Flatten MultiIndex columns
|
||||
new_cols = ['steam_id_64']
|
||||
for col_name, side in pivoted.columns[1:]:
|
||||
# Map L2 column names to Feature names
|
||||
# rating -> side_rating_{side}
|
||||
# kd -> side_kd_{side}
|
||||
# win_rate -> side_win_rate_{side}
|
||||
# fk_rate -> side_first_kill_rate_{side}
|
||||
# fd_rate -> side_first_death_rate_{side}
|
||||
# kast -> side_kast_{side}
|
||||
# rws -> side_rws_{side}
|
||||
# mk_rate -> side_multikill_rate_{side}
|
||||
# hs_rate -> side_headshot_rate_{side}
|
||||
|
||||
target_map = {
|
||||
'rating': 'side_rating',
|
||||
'kd': 'side_kd',
|
||||
'win_rate': 'side_win_rate',
|
||||
'fk_rate': 'side_first_kill_rate',
|
||||
'fd_rate': 'side_first_death_rate',
|
||||
'kast': 'side_kast',
|
||||
'rws': 'side_rws',
|
||||
'mk_rate': 'side_multikill_rate',
|
||||
'hs_rate': 'side_headshot_rate'
|
||||
}
|
||||
|
||||
if col_name in target_map:
|
||||
new_cols.append(f"{target_map[col_name]}_{side.lower()}")
|
||||
else:
|
||||
new_cols.append(f"{col_name}_{side.lower()}") # Fallback for intermediate cols if needed
|
||||
|
||||
pivoted.columns = new_cols
|
||||
|
||||
# Select only relevant columns to merge
|
||||
cols_to_merge = [c for c in new_cols if c.startswith('side_')]
|
||||
cols_to_merge.append('steam_id_64')
|
||||
|
||||
df = df.merge(pivoted[cols_to_merge], on='steam_id_64', how='left')
|
||||
|
||||
# Fill NaN with 0 for side stats
|
||||
for c in cols_to_merge:
|
||||
if c != 'steam_id_64':
|
||||
df[c] = df[c].fillna(0)
|
||||
|
||||
# Add calculated diffs for scoring/display if needed (or just let template handle it)
|
||||
# KD Diff for L3 Score calculation
|
||||
if 'side_rating_ct' in df.columns and 'side_rating_t' in df.columns:
|
||||
df['side_kd_diff_ct_t'] = df['side_rating_ct'] - df['side_rating_t']
|
||||
else:
|
||||
df['side_kd_diff_ct_t'] = 0
|
||||
|
||||
# --- Obj Override from Main Table (sum_plants, sum_defuses) ---
|
||||
# side_obj_t = sum_plants / matches_played
|
||||
# side_obj_ct = sum_defuses / matches_played
|
||||
df['side_obj_t'] = df['sum_plants'] / df['matches_played'].replace(0, 1)
|
||||
df['side_obj_ct'] = df['sum_defuses'] / df['matches_played'].replace(0, 1)
|
||||
df['side_obj_t'] = df['side_obj_t'].fillna(0)
|
||||
df['side_obj_ct'] = df['side_obj_ct'].fillna(0)
|
||||
|
||||
else:
|
||||
# Fallbacks
|
||||
cols = ['hps_match_point_win_rate', 'hps_comeback_kd_diff', 'ptl_pistol_kd', 'ptl_pistol_util_efficiency',
|
||||
'side_rating_ct', 'side_rating_t', 'side_first_kill_rate_ct', 'side_first_kill_rate_t', 'side_kd_diff_ct_t']
|
||||
'side_rating_ct', 'side_rating_t', 'side_first_kill_rate_ct', 'side_first_kill_rate_t', 'side_kd_diff_ct_t',
|
||||
'bat_win_rate_vs_all', 'hps_losing_streak_kd_diff', 'hps_momentum_multikill_rate',
|
||||
'hps_tilt_rating_drop', 'hps_clutch_rating_rise', 'hps_undermanned_survival_time',
|
||||
'side_win_rate_ct', 'side_win_rate_t', 'side_kd_ct', 'side_kd_t',
|
||||
'side_kast_ct', 'side_kast_t', 'side_rws_ct', 'side_rws_t',
|
||||
'side_first_death_rate_ct', 'side_first_death_rate_t',
|
||||
'side_multikill_rate_ct', 'side_multikill_rate_t',
|
||||
'side_headshot_rate_ct', 'side_headshot_rate_t',
|
||||
'side_obj_ct', 'side_obj_t']
|
||||
for c in cols:
|
||||
df[c] = 0
|
||||
|
||||
df['hps_match_point_win_rate'] = df['hps_match_point_win_rate'].fillna(0.5)
|
||||
df['bat_win_rate_vs_all'] = df['bat_win_rate_vs_all'].fillna(0.5)
|
||||
df['hps_losing_streak_kd_diff'] = df['hps_losing_streak_kd_diff'].fillna(0)
|
||||
|
||||
# HPS Pressure Entry Rate (Entry Kills in Losing Matches)
|
||||
q_mp_team = f"SELECT match_id, steam_id_64, is_win, entry_kills FROM fact_match_players WHERE steam_id_64 IN ({placeholders})"
|
||||
# HPS Pressure Entry Rate (Entry Kills per Round in Losing Matches)
|
||||
q_mp_team = f"SELECT match_id, steam_id_64, is_win, entry_kills, round_total FROM fact_match_players WHERE steam_id_64 IN ({placeholders})"
|
||||
df_mp_team = pd.read_sql_query(q_mp_team, conn, params=valid_ids)
|
||||
if not df_mp_team.empty:
|
||||
losing_matches = df_mp_team[df_mp_team['is_win'] == 0]
|
||||
if not losing_matches.empty:
|
||||
# Average entry kills per losing match
|
||||
pressure_entry = losing_matches.groupby('steam_id_64')['entry_kills'].mean().reset_index()
|
||||
pressure_entry.rename(columns={'entry_kills': 'hps_pressure_entry_rate'}, inplace=True)
|
||||
df = df.merge(pressure_entry, on='steam_id_64', how='left')
|
||||
# Sum Entry Kills / Sum Rounds
|
||||
pressure_entry = losing_matches.groupby('steam_id_64')[['entry_kills', 'round_total']].sum().reset_index()
|
||||
pressure_entry['hps_pressure_entry_rate'] = pressure_entry['entry_kills'] / pressure_entry['round_total'].replace(0, 1)
|
||||
df = df.merge(pressure_entry[['steam_id_64', 'hps_pressure_entry_rate']], on='steam_id_64', how='left')
|
||||
|
||||
if 'hps_pressure_entry_rate' not in df.columns:
|
||||
df['hps_pressure_entry_rate'] = 0
|
||||
@@ -720,15 +925,23 @@ class FeatureService:
|
||||
df_player_rounds['side'] = np.where(mask_fh, df_player_rounds['fh_side'],
|
||||
np.where(df_player_rounds['fh_side'] == 'CT', 'T', 'CT'))
|
||||
|
||||
# Filter for Pistol Rounds (1, 13)
|
||||
player_pistol = df_player_rounds[df_player_rounds['round_num'].isin([1, 13])].copy()
|
||||
# Filter for Pistol Rounds (1 and after halftime)
|
||||
# Use halftime_round logic (MR12: 13, MR15: 16)
|
||||
player_pistol = df_player_rounds[
|
||||
(df_player_rounds['round_num'] == 1) |
|
||||
(df_player_rounds['round_num'] == df_player_rounds['halftime_round'] + 1)
|
||||
].copy()
|
||||
|
||||
# Merge with df_rounds to get calculated winner_side
|
||||
# Note: df_rounds has the fixed 'winner_side' column
|
||||
df_rounds['winner_side'] = df_rounds['winner_side'].astype(str) # Ensure string for merge safety
|
||||
player_pistol = player_pistol.merge(df_rounds[['match_id', 'round_num', 'winner_side']], on=['match_id', 'round_num'], how='left')
|
||||
|
||||
# Calculate Win
|
||||
player_pistol['is_win'] = (player_pistol['side'] == player_pistol['winner_side']).astype(int)
|
||||
# Ensure winner_side is in player_pistol columns after merge
|
||||
if 'winner_side' in player_pistol.columns:
|
||||
player_pistol['is_win'] = (player_pistol['side'] == player_pistol['winner_side']).astype(int)
|
||||
else:
|
||||
player_pistol['is_win'] = 0
|
||||
|
||||
ptl_wins = player_pistol.groupby('steam_id_64')['is_win'].agg(['sum', 'count']).reset_index()
|
||||
ptl_wins.rename(columns={'sum': 'pistol_wins', 'count': 'pistol_rounds'}, inplace=True)
|
||||
@@ -800,18 +1013,19 @@ class FeatureService:
|
||||
|
||||
# HPS (20%)
|
||||
df['score_hps'] = (
|
||||
0.30 * n('sum_1v3p') +
|
||||
0.25 * n('sum_1v3p') +
|
||||
0.20 * n('hps_match_point_win_rate') +
|
||||
0.20 * n('hps_comeback_kd_diff') +
|
||||
0.15 * n('hps_pressure_entry_rate') +
|
||||
0.15 * n('basic_avg_rating')
|
||||
0.20 * n('basic_avg_rating')
|
||||
)
|
||||
|
||||
# PTL (10%)
|
||||
df['score_ptl'] = (
|
||||
0.40 * n('ptl_pistol_kills') +
|
||||
0.40 * n('ptl_pistol_win_rate') +
|
||||
0.20 * n('basic_avg_headshot_kills') # Pistol rounds rely on HS
|
||||
0.30 * n('ptl_pistol_kills') +
|
||||
0.30 * n('ptl_pistol_win_rate') +
|
||||
0.20 * n('ptl_pistol_kd') +
|
||||
0.20 * n('ptl_pistol_util_efficiency')
|
||||
)
|
||||
|
||||
# T/CT (10%)
|
||||
|
||||
Reference in New Issue
Block a user