2.1 : New
This commit is contained in:
@@ -2,6 +2,7 @@ from web.database import query_db, get_db, execute_db
|
||||
import sqlite3
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from web.services.weapon_service import get_weapon_info
|
||||
|
||||
class FeatureService:
|
||||
@staticmethod
|
||||
@@ -357,6 +358,46 @@ class FeatureService:
|
||||
|
||||
valid_ids = tuple(df['steam_id_64'].tolist())
|
||||
placeholders = ','.join(['?'] * len(valid_ids))
|
||||
|
||||
try:
|
||||
query_weapon_kills = f"""
|
||||
SELECT attacker_steam_id as steam_id_64,
|
||||
SUM(CASE WHEN lower(weapon) LIKE '%knife%' OR lower(weapon) LIKE '%bayonet%' THEN 1 ELSE 0 END) as knife_kills,
|
||||
SUM(CASE WHEN lower(weapon) LIKE '%taser%' OR lower(weapon) LIKE '%zeus%' THEN 1 ELSE 0 END) as zeus_kills
|
||||
FROM fact_round_events
|
||||
WHERE event_type = 'kill'
|
||||
AND attacker_steam_id IN ({placeholders})
|
||||
GROUP BY attacker_steam_id
|
||||
"""
|
||||
df_weapon_kills = pd.read_sql_query(query_weapon_kills, conn, params=valid_ids)
|
||||
if not df_weapon_kills.empty:
|
||||
df = df.merge(df_weapon_kills, on='steam_id_64', how='left')
|
||||
else:
|
||||
df['knife_kills'] = 0
|
||||
df['zeus_kills'] = 0
|
||||
except Exception:
|
||||
df['knife_kills'] = 0
|
||||
df['zeus_kills'] = 0
|
||||
|
||||
df['basic_avg_knife_kill'] = df['knife_kills'].fillna(0) / df['matches_played'].replace(0, 1)
|
||||
df['basic_avg_zeus_kill'] = df['zeus_kills'].fillna(0) / df['matches_played'].replace(0, 1)
|
||||
|
||||
try:
|
||||
query_zeus_pick = f"""
|
||||
SELECT steam_id_64,
|
||||
AVG(CASE WHEN has_zeus = 1 THEN 1.0 ELSE 0.0 END) as basic_zeus_pick_rate
|
||||
FROM fact_round_player_economy
|
||||
WHERE steam_id_64 IN ({placeholders})
|
||||
GROUP BY steam_id_64
|
||||
"""
|
||||
df_zeus_pick = pd.read_sql_query(query_zeus_pick, conn, params=valid_ids)
|
||||
if not df_zeus_pick.empty:
|
||||
df = df.merge(df_zeus_pick, on='steam_id_64', how='left')
|
||||
except Exception:
|
||||
df['basic_zeus_pick_rate'] = 0.0
|
||||
|
||||
df['basic_zeus_pick_rate'] = df.get('basic_zeus_pick_rate', 0.0)
|
||||
df['basic_zeus_pick_rate'] = pd.to_numeric(df['basic_zeus_pick_rate'], errors='coerce').fillna(0.0)
|
||||
|
||||
# 2. STA (Detailed)
|
||||
query_sta = f"""
|
||||
@@ -481,12 +522,18 @@ class FeatureService:
|
||||
break
|
||||
|
||||
df_fh_sides = pd.DataFrame(fh_rows)
|
||||
if not df_fh_sides.empty:
|
||||
if df_fh_sides.empty:
|
||||
df_fh_sides = pd.DataFrame(columns=['match_id', 'steam_id_64', 'fh_side', 'halftime_round'])
|
||||
else:
|
||||
df_fh_sides = df_fh_sides.merge(df_meta[['match_id', 'halftime_round']], on='match_id', how='left')
|
||||
if 'halftime_round' not in df_fh_sides.columns:
|
||||
df_fh_sides['halftime_round'] = 15
|
||||
df_fh_sides['halftime_round'] = df_fh_sides['halftime_round'].fillna(15).astype(int)
|
||||
|
||||
# B. Get Kill Events
|
||||
query_events = f"""
|
||||
SELECT match_id, round_num, attacker_steam_id, victim_steam_id, event_type, is_headshot, event_time
|
||||
SELECT match_id, round_num, attacker_steam_id, victim_steam_id, event_type, is_headshot, event_time,
|
||||
weapon, trade_killer_steam_id, flash_assist_steam_id
|
||||
FROM fact_round_events
|
||||
WHERE event_type='kill'
|
||||
AND (attacker_steam_id IN ({placeholders}) OR victim_steam_id IN ({placeholders}))
|
||||
@@ -495,7 +542,7 @@ class FeatureService:
|
||||
|
||||
# C. Get Round Scores
|
||||
query_rounds = f"""
|
||||
SELECT match_id, round_num, ct_score, t_score, winner_side
|
||||
SELECT match_id, round_num, ct_score, t_score, winner_side, duration
|
||||
FROM fact_rounds
|
||||
WHERE match_id IN (SELECT match_id FROM fact_match_players WHERE steam_id_64 IN ({placeholders}))
|
||||
"""
|
||||
@@ -982,7 +1029,7 @@ class FeatureService:
|
||||
# Fetch Base Data for Calculation
|
||||
q_new_feats = f"""
|
||||
SELECT mp.steam_id_64, mp.match_id, mp.match_team_id, mp.team_id,
|
||||
mp.rating, mp.adr, mp.is_win
|
||||
mp.rating, mp.adr, mp.is_win, mp.map as map_name
|
||||
FROM fact_match_players mp
|
||||
WHERE mp.steam_id_64 IN ({placeholders})
|
||||
"""
|
||||
@@ -1139,10 +1186,448 @@ class FeatureService:
|
||||
if df_pace is not None:
|
||||
df = df.merge(df_pace, on='steam_id_64', how='left')
|
||||
|
||||
if not df_base.empty:
|
||||
player_mean = df_base.groupby('steam_id_64', as_index=False)['rating'].mean().rename(columns={'rating': 'player_mean_rating'})
|
||||
map_mean = df_base.groupby(['steam_id_64', 'map_name'], as_index=False)['rating'].mean().rename(columns={'rating': 'map_mean_rating'})
|
||||
map_dev = map_mean.merge(player_mean, on='steam_id_64', how='left')
|
||||
map_dev['abs_dev'] = (map_dev['map_mean_rating'] - map_dev['player_mean_rating']).abs()
|
||||
map_coef = map_dev.groupby('steam_id_64', as_index=False)['abs_dev'].mean().rename(columns={'abs_dev': 'map_stability_coef'})
|
||||
df = df.merge(map_coef, on='steam_id_64', how='left')
|
||||
|
||||
import json
|
||||
|
||||
df['rd_phase_kill_early_share'] = 0.0
|
||||
df['rd_phase_kill_mid_share'] = 0.0
|
||||
df['rd_phase_kill_late_share'] = 0.0
|
||||
df['rd_phase_death_early_share'] = 0.0
|
||||
df['rd_phase_death_mid_share'] = 0.0
|
||||
df['rd_phase_death_late_share'] = 0.0
|
||||
df['rd_firstdeath_team_first_death_rounds'] = 0
|
||||
df['rd_firstdeath_team_first_death_win_rate'] = 0.0
|
||||
df['rd_invalid_death_rounds'] = 0
|
||||
df['rd_invalid_death_rate'] = 0.0
|
||||
df['rd_pressure_kpr_ratio'] = 0.0
|
||||
df['rd_pressure_perf_ratio'] = 0.0
|
||||
df['rd_pressure_rounds_down3'] = 0
|
||||
df['rd_pressure_rounds_normal'] = 0
|
||||
df['rd_matchpoint_kpr_ratio'] = 0.0
|
||||
df['rd_matchpoint_perf_ratio'] = 0.0
|
||||
df['rd_matchpoint_rounds'] = 0
|
||||
df['rd_comeback_kill_share'] = 0.0
|
||||
df['rd_comeback_rounds'] = 0
|
||||
df['rd_trade_response_10s_rate'] = 0.0
|
||||
df['rd_weapon_top_json'] = "[]"
|
||||
df['rd_roundtype_split_json'] = "{}"
|
||||
|
||||
if not df_events.empty:
|
||||
df_events['event_time'] = pd.to_numeric(df_events['event_time'], errors='coerce').fillna(0).astype(int)
|
||||
|
||||
df_events['phase_bucket'] = pd.cut(
|
||||
df_events['event_time'],
|
||||
bins=[-1, 30, 60, float('inf')],
|
||||
labels=['early', 'mid', 'late']
|
||||
)
|
||||
|
||||
k_cnt = df_events.groupby(['attacker_steam_id', 'phase_bucket']).size().unstack(fill_value=0)
|
||||
k_tot = k_cnt.sum(axis=1).replace(0, 1)
|
||||
k_share = k_cnt.div(k_tot, axis=0)
|
||||
k_share.index.name = 'steam_id_64'
|
||||
k_share = k_share.reset_index().rename(columns={
|
||||
'early': 'rd_phase_kill_early_share',
|
||||
'mid': 'rd_phase_kill_mid_share',
|
||||
'late': 'rd_phase_kill_late_share'
|
||||
})
|
||||
df = df.merge(
|
||||
k_share[['steam_id_64', 'rd_phase_kill_early_share', 'rd_phase_kill_mid_share', 'rd_phase_kill_late_share']],
|
||||
on='steam_id_64',
|
||||
how='left',
|
||||
suffixes=('', '_calc')
|
||||
)
|
||||
for c in ['rd_phase_kill_early_share', 'rd_phase_kill_mid_share', 'rd_phase_kill_late_share']:
|
||||
if f'{c}_calc' in df.columns:
|
||||
df[c] = df[f'{c}_calc'].fillna(df[c])
|
||||
df.drop(columns=[f'{c}_calc'], inplace=True)
|
||||
|
||||
d_cnt = df_events.groupby(['victim_steam_id', 'phase_bucket']).size().unstack(fill_value=0)
|
||||
d_tot = d_cnt.sum(axis=1).replace(0, 1)
|
||||
d_share = d_cnt.div(d_tot, axis=0)
|
||||
d_share.index.name = 'steam_id_64'
|
||||
d_share = d_share.reset_index().rename(columns={
|
||||
'early': 'rd_phase_death_early_share',
|
||||
'mid': 'rd_phase_death_mid_share',
|
||||
'late': 'rd_phase_death_late_share'
|
||||
})
|
||||
df = df.merge(
|
||||
d_share[['steam_id_64', 'rd_phase_death_early_share', 'rd_phase_death_mid_share', 'rd_phase_death_late_share']],
|
||||
on='steam_id_64',
|
||||
how='left',
|
||||
suffixes=('', '_calc')
|
||||
)
|
||||
for c in ['rd_phase_death_early_share', 'rd_phase_death_mid_share', 'rd_phase_death_late_share']:
|
||||
if f'{c}_calc' in df.columns:
|
||||
df[c] = df[f'{c}_calc'].fillna(df[c])
|
||||
df.drop(columns=[f'{c}_calc'], inplace=True)
|
||||
|
||||
if 'victim_side' in df_events.columns and 'winner_side' in df_events.columns:
|
||||
death_rows = df_events[['match_id', 'round_num', 'event_time', 'victim_steam_id', 'victim_side', 'winner_side']].copy()
|
||||
death_rows = death_rows[death_rows['victim_side'].isin(['CT', 'T']) & death_rows['winner_side'].isin(['CT', 'T'])]
|
||||
if not death_rows.empty:
|
||||
min_death = death_rows.groupby(['match_id', 'round_num', 'victim_side'], as_index=False)['event_time'].min().rename(columns={'event_time': 'min_time'})
|
||||
first_deaths = death_rows.merge(min_death, on=['match_id', 'round_num', 'victim_side'], how='inner')
|
||||
first_deaths = first_deaths[first_deaths['event_time'] == first_deaths['min_time']]
|
||||
first_deaths['is_win'] = (first_deaths['victim_side'] == first_deaths['winner_side']).astype(int)
|
||||
fd_agg = first_deaths.groupby('victim_steam_id')['is_win'].agg(['count', 'mean']).reset_index()
|
||||
fd_agg.rename(columns={
|
||||
'victim_steam_id': 'steam_id_64',
|
||||
'count': 'rd_firstdeath_team_first_death_rounds',
|
||||
'mean': 'rd_firstdeath_team_first_death_win_rate'
|
||||
}, inplace=True)
|
||||
df = df.merge(fd_agg, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
for c in ['rd_firstdeath_team_first_death_rounds', 'rd_firstdeath_team_first_death_win_rate']:
|
||||
if f'{c}_calc' in df.columns:
|
||||
df[c] = df[f'{c}_calc'].fillna(df[c])
|
||||
df.drop(columns=[f'{c}_calc'], inplace=True)
|
||||
|
||||
kills_per_round = df_events.groupby(['match_id', 'round_num', 'attacker_steam_id']).size().reset_index(name='kills')
|
||||
flash_round = df_events[df_events['flash_assist_steam_id'].notna() & (df_events['flash_assist_steam_id'] != '')] \
|
||||
.groupby(['match_id', 'round_num', 'flash_assist_steam_id']).size().reset_index(name='flash_assists')
|
||||
death_round = df_events.groupby(['match_id', 'round_num', 'victim_steam_id']).size().reset_index(name='deaths')
|
||||
|
||||
death_eval = death_round.rename(columns={'victim_steam_id': 'steam_id_64'}).merge(
|
||||
kills_per_round.rename(columns={'attacker_steam_id': 'steam_id_64'})[['match_id', 'round_num', 'steam_id_64', 'kills']],
|
||||
on=['match_id', 'round_num', 'steam_id_64'],
|
||||
how='left'
|
||||
).merge(
|
||||
flash_round.rename(columns={'flash_assist_steam_id': 'steam_id_64'})[['match_id', 'round_num', 'steam_id_64', 'flash_assists']],
|
||||
on=['match_id', 'round_num', 'steam_id_64'],
|
||||
how='left'
|
||||
).fillna({'kills': 0, 'flash_assists': 0})
|
||||
death_eval['is_invalid'] = ((death_eval['kills'] <= 0) & (death_eval['flash_assists'] <= 0)).astype(int)
|
||||
invalid_agg = death_eval.groupby('steam_id_64')['is_invalid'].agg(['sum', 'count']).reset_index()
|
||||
invalid_agg.rename(columns={'sum': 'rd_invalid_death_rounds', 'count': 'death_rounds'}, inplace=True)
|
||||
invalid_agg['rd_invalid_death_rate'] = invalid_agg['rd_invalid_death_rounds'] / invalid_agg['death_rounds'].replace(0, 1)
|
||||
df = df.merge(
|
||||
invalid_agg[['steam_id_64', 'rd_invalid_death_rounds', 'rd_invalid_death_rate']],
|
||||
on='steam_id_64',
|
||||
how='left',
|
||||
suffixes=('', '_calc')
|
||||
)
|
||||
for c in ['rd_invalid_death_rounds', 'rd_invalid_death_rate']:
|
||||
if f'{c}_calc' in df.columns:
|
||||
df[c] = df[f'{c}_calc'].fillna(df[c])
|
||||
df.drop(columns=[f'{c}_calc'], inplace=True)
|
||||
|
||||
if 'weapon' in df_events.columns:
|
||||
w = df_events.copy()
|
||||
w['weapon'] = w['weapon'].fillna('').astype(str)
|
||||
w = w[w['weapon'] != '']
|
||||
if not w.empty:
|
||||
w_agg = w.groupby(['attacker_steam_id', 'weapon']).agg(
|
||||
kills=('weapon', 'size'),
|
||||
hs=('is_headshot', 'sum'),
|
||||
).reset_index()
|
||||
top_json = {}
|
||||
for pid, g in w_agg.groupby('attacker_steam_id'):
|
||||
g = g.sort_values('kills', ascending=False)
|
||||
total = float(g['kills'].sum()) if g['kills'].sum() else 1.0
|
||||
top = g.head(5)
|
||||
items = []
|
||||
for _, r in top.iterrows():
|
||||
k = float(r['kills'])
|
||||
hs = float(r['hs'])
|
||||
wi = get_weapon_info(r['weapon'])
|
||||
items.append({
|
||||
'weapon': r['weapon'],
|
||||
'kills': int(k),
|
||||
'share': k / total,
|
||||
'hs_rate': hs / k if k else 0.0,
|
||||
'price': wi.price if wi else None,
|
||||
'side': wi.side if wi else None,
|
||||
'category': wi.category if wi else None,
|
||||
})
|
||||
top_json[str(pid)] = json.dumps(items, ensure_ascii=False)
|
||||
if top_json:
|
||||
df['rd_weapon_top_json'] = df['steam_id_64'].map(top_json).fillna("[]")
|
||||
|
||||
if not df_rounds.empty and not df_fh_sides.empty and not df_events.empty:
|
||||
df_rounds2 = df_rounds.copy()
|
||||
if not df_meta.empty:
|
||||
df_rounds2 = df_rounds2.merge(df_meta[['match_id', 'halftime_round']], on='match_id', how='left')
|
||||
df_rounds2 = df_rounds2.sort_values(['match_id', 'round_num'])
|
||||
df_rounds2['prev_ct'] = df_rounds2.groupby('match_id')['ct_score'].shift(1).fillna(0)
|
||||
df_rounds2['prev_t'] = df_rounds2.groupby('match_id')['t_score'].shift(1).fillna(0)
|
||||
df_rounds2['ct_deficit'] = df_rounds2['prev_t'] - df_rounds2['prev_ct']
|
||||
df_rounds2['t_deficit'] = df_rounds2['prev_ct'] - df_rounds2['prev_t']
|
||||
df_rounds2['mp_score'] = df_rounds2['halftime_round'].fillna(15)
|
||||
df_rounds2['is_match_point_round'] = (df_rounds2['prev_ct'] == df_rounds2['mp_score']) | (df_rounds2['prev_t'] == df_rounds2['mp_score'])
|
||||
df_rounds2['reg_rounds'] = (df_rounds2['halftime_round'].fillna(15) * 2).astype(int)
|
||||
df_rounds2['is_overtime_round'] = df_rounds2['round_num'] > df_rounds2['reg_rounds']
|
||||
|
||||
all_rounds = df_rounds2[['match_id', 'round_num']].drop_duplicates()
|
||||
df_player_rounds = all_rounds.merge(df_fh_sides, on='match_id', how='inner')
|
||||
if 'halftime_round' not in df_player_rounds.columns:
|
||||
df_player_rounds['halftime_round'] = 15
|
||||
df_player_rounds['halftime_round'] = pd.to_numeric(df_player_rounds['halftime_round'], errors='coerce').fillna(15).astype(int)
|
||||
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_player_rounds = df_player_rounds.merge(
|
||||
df_rounds2[['match_id', 'round_num', 'ct_deficit', 't_deficit', 'is_match_point_round', 'is_overtime_round', 'reg_rounds']],
|
||||
on=['match_id', 'round_num'],
|
||||
how='left'
|
||||
)
|
||||
df_player_rounds['deficit'] = np.where(
|
||||
df_player_rounds['side'] == 'CT',
|
||||
df_player_rounds['ct_deficit'],
|
||||
np.where(df_player_rounds['side'] == 'T', df_player_rounds['t_deficit'], 0)
|
||||
)
|
||||
df_player_rounds['is_pressure_round'] = (df_player_rounds['deficit'] >= 3).astype(int)
|
||||
df_player_rounds['is_pistol_round'] = (
|
||||
(df_player_rounds['round_num'] == 1) |
|
||||
(df_player_rounds['round_num'] == df_player_rounds['halftime_round'] + 1)
|
||||
).astype(int)
|
||||
|
||||
kills_per_round = df_events.groupby(['match_id', 'round_num', 'attacker_steam_id']).size().reset_index(name='kills')
|
||||
df_player_rounds = df_player_rounds.merge(
|
||||
kills_per_round.rename(columns={'attacker_steam_id': 'steam_id_64'}),
|
||||
on=['match_id', 'round_num', 'steam_id_64'],
|
||||
how='left'
|
||||
)
|
||||
df_player_rounds['kills'] = df_player_rounds['kills'].fillna(0)
|
||||
|
||||
grp = df_player_rounds.groupby(['steam_id_64', 'is_pressure_round'])['kills'].agg(['mean', 'count']).reset_index()
|
||||
pressure = grp.pivot(index='steam_id_64', columns='is_pressure_round').fillna(0)
|
||||
if ('mean', 1) in pressure.columns and ('mean', 0) in pressure.columns:
|
||||
pressure_kpr_ratio = (pressure[('mean', 1)] / pressure[('mean', 0)].replace(0, 1)).reset_index()
|
||||
pressure_kpr_ratio.columns = ['steam_id_64', 'rd_pressure_kpr_ratio']
|
||||
df = df.merge(pressure_kpr_ratio, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
if 'rd_pressure_kpr_ratio_calc' in df.columns:
|
||||
df['rd_pressure_kpr_ratio'] = df['rd_pressure_kpr_ratio_calc'].fillna(df['rd_pressure_kpr_ratio'])
|
||||
df.drop(columns=['rd_pressure_kpr_ratio_calc'], inplace=True)
|
||||
if ('count', 1) in pressure.columns:
|
||||
pr_cnt = pressure[('count', 1)].reset_index()
|
||||
pr_cnt.columns = ['steam_id_64', 'rd_pressure_rounds_down3']
|
||||
df = df.merge(pr_cnt, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
if 'rd_pressure_rounds_down3_calc' in df.columns:
|
||||
df['rd_pressure_rounds_down3'] = df['rd_pressure_rounds_down3_calc'].fillna(df['rd_pressure_rounds_down3'])
|
||||
df.drop(columns=['rd_pressure_rounds_down3_calc'], inplace=True)
|
||||
if ('count', 0) in pressure.columns:
|
||||
nr_cnt = pressure[('count', 0)].reset_index()
|
||||
nr_cnt.columns = ['steam_id_64', 'rd_pressure_rounds_normal']
|
||||
df = df.merge(nr_cnt, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
if 'rd_pressure_rounds_normal_calc' in df.columns:
|
||||
df['rd_pressure_rounds_normal'] = df['rd_pressure_rounds_normal_calc'].fillna(df['rd_pressure_rounds_normal'])
|
||||
df.drop(columns=['rd_pressure_rounds_normal_calc'], inplace=True)
|
||||
|
||||
mp_grp = df_player_rounds.groupby(['steam_id_64', 'is_match_point_round'])['kills'].agg(['mean', 'count']).reset_index()
|
||||
mp = mp_grp.pivot(index='steam_id_64', columns='is_match_point_round').fillna(0)
|
||||
if ('mean', 1) in mp.columns and ('mean', 0) in mp.columns:
|
||||
mp_ratio = (mp[('mean', 1)] / mp[('mean', 0)].replace(0, 1)).reset_index()
|
||||
mp_ratio.columns = ['steam_id_64', 'rd_matchpoint_kpr_ratio']
|
||||
df = df.merge(mp_ratio, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
if 'rd_matchpoint_kpr_ratio_calc' in df.columns:
|
||||
df['rd_matchpoint_kpr_ratio'] = df['rd_matchpoint_kpr_ratio_calc'].fillna(df['rd_matchpoint_kpr_ratio'])
|
||||
df.drop(columns=['rd_matchpoint_kpr_ratio_calc'], inplace=True)
|
||||
if ('count', 1) in mp.columns:
|
||||
mp_cnt = mp[('count', 1)].reset_index()
|
||||
mp_cnt.columns = ['steam_id_64', 'rd_matchpoint_rounds']
|
||||
df = df.merge(mp_cnt, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
if 'rd_matchpoint_rounds_calc' in df.columns:
|
||||
df['rd_matchpoint_rounds'] = df['rd_matchpoint_rounds_calc'].fillna(df['rd_matchpoint_rounds'])
|
||||
df.drop(columns=['rd_matchpoint_rounds_calc'], inplace=True)
|
||||
|
||||
try:
|
||||
q_player_team = f"SELECT match_id, steam_id_64, team_id FROM fact_match_players WHERE steam_id_64 IN ({placeholders})"
|
||||
df_player_team = pd.read_sql_query(q_player_team, conn, params=valid_ids)
|
||||
except Exception:
|
||||
df_player_team = pd.DataFrame()
|
||||
|
||||
if not df_player_team.empty:
|
||||
try:
|
||||
q_team_roles = f"""
|
||||
SELECT match_id, group_id as team_id, group_fh_role
|
||||
FROM fact_match_teams
|
||||
WHERE match_id IN (SELECT match_id FROM fact_match_players WHERE steam_id_64 IN ({placeholders}))
|
||||
"""
|
||||
df_team_roles = pd.read_sql_query(q_team_roles, conn, params=valid_ids)
|
||||
except Exception:
|
||||
df_team_roles = pd.DataFrame()
|
||||
|
||||
if not df_team_roles.empty:
|
||||
team_round = df_rounds2[['match_id', 'round_num', 'ct_score', 't_score', 'prev_ct', 'prev_t', 'halftime_round']].merge(df_team_roles, on='match_id', how='inner')
|
||||
fh_ct = team_round['group_fh_role'] == 1
|
||||
mask_fh = team_round['round_num'] <= team_round['halftime_round']
|
||||
team_round['team_side'] = np.where(mask_fh, np.where(fh_ct, 'CT', 'T'), np.where(fh_ct, 'T', 'CT'))
|
||||
team_round['team_prev_score'] = np.where(team_round['team_side'] == 'CT', team_round['prev_ct'], team_round['prev_t'])
|
||||
team_round['team_score_after'] = np.where(team_round['team_side'] == 'CT', team_round['ct_score'], team_round['t_score'])
|
||||
team_round['opp_prev_score'] = np.where(team_round['team_side'] == 'CT', team_round['prev_t'], team_round['prev_ct'])
|
||||
team_round['opp_score_after'] = np.where(team_round['team_side'] == 'CT', team_round['t_score'], team_round['ct_score'])
|
||||
team_round['deficit_before'] = team_round['opp_prev_score'] - team_round['team_prev_score']
|
||||
team_round['deficit_after'] = team_round['opp_score_after'] - team_round['team_score_after']
|
||||
team_round['is_comeback_round'] = ((team_round['deficit_before'] > 0) & (team_round['deficit_after'] < team_round['deficit_before'])).astype(int)
|
||||
comeback_keys = team_round[team_round['is_comeback_round'] == 1][['match_id', 'round_num', 'team_id']].drop_duplicates()
|
||||
|
||||
if not comeback_keys.empty:
|
||||
ev_att = df_events[['match_id', 'round_num', 'attacker_steam_id', 'event_time']].merge(
|
||||
df_player_team.rename(columns={'steam_id_64': 'attacker_steam_id', 'team_id': 'att_team_id'}),
|
||||
on=['match_id', 'attacker_steam_id'],
|
||||
how='left'
|
||||
)
|
||||
team_kills = ev_att[ev_att['att_team_id'].notna()].groupby(['match_id', 'round_num', 'att_team_id']).size().reset_index(name='team_kills')
|
||||
player_kills = ev_att.groupby(['match_id', 'round_num', 'attacker_steam_id', 'att_team_id']).size().reset_index(name='player_kills')
|
||||
|
||||
player_kills = player_kills.merge(
|
||||
comeback_keys.rename(columns={'team_id': 'att_team_id'}),
|
||||
on=['match_id', 'round_num', 'att_team_id'],
|
||||
how='inner'
|
||||
)
|
||||
if not player_kills.empty:
|
||||
player_kills = player_kills.merge(team_kills, on=['match_id', 'round_num', 'att_team_id'], how='left').fillna({'team_kills': 0})
|
||||
player_kills['share'] = player_kills['player_kills'] / player_kills['team_kills'].replace(0, 1)
|
||||
cb_share = player_kills.groupby('attacker_steam_id')['share'].mean().reset_index()
|
||||
cb_share.rename(columns={'attacker_steam_id': 'steam_id_64', 'share': 'rd_comeback_kill_share'}, inplace=True)
|
||||
df = df.merge(cb_share, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
if 'rd_comeback_kill_share_calc' in df.columns:
|
||||
df['rd_comeback_kill_share'] = df['rd_comeback_kill_share_calc'].fillna(df['rd_comeback_kill_share'])
|
||||
df.drop(columns=['rd_comeback_kill_share_calc'], inplace=True)
|
||||
|
||||
cb_rounds = comeback_keys.merge(df_player_team, left_on=['match_id', 'team_id'], right_on=['match_id', 'team_id'], how='inner')
|
||||
cb_cnt = cb_rounds.groupby('steam_id_64').size().reset_index(name='rd_comeback_rounds')
|
||||
df = df.merge(cb_cnt, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
if 'rd_comeback_rounds_calc' in df.columns:
|
||||
df['rd_comeback_rounds'] = df['rd_comeback_rounds_calc'].fillna(df['rd_comeback_rounds'])
|
||||
df.drop(columns=['rd_comeback_rounds_calc'], inplace=True)
|
||||
|
||||
death_team = df_events[['match_id', 'round_num', 'event_time', 'victim_steam_id']].merge(
|
||||
df_player_team.rename(columns={'steam_id_64': 'victim_steam_id', 'team_id': 'team_id'}),
|
||||
on=['match_id', 'victim_steam_id'],
|
||||
how='left'
|
||||
)
|
||||
death_team = death_team[death_team['team_id'].notna()]
|
||||
if not death_team.empty:
|
||||
roster = df_player_team.rename(columns={'steam_id_64': 'steam_id_64', 'team_id': 'team_id'})[['match_id', 'team_id', 'steam_id_64']].drop_duplicates()
|
||||
opp = death_team.merge(roster, on=['match_id', 'team_id'], how='inner', suffixes=('', '_teammate'))
|
||||
opp = opp[opp['steam_id_64'] != opp['victim_steam_id']]
|
||||
opp_time = opp.groupby(['match_id', 'round_num', 'steam_id_64'], as_index=False)['event_time'].min().rename(columns={'event_time': 'teammate_death_time'})
|
||||
|
||||
kills_time = df_events[['match_id', 'round_num', 'event_time', 'attacker_steam_id']].rename(columns={'attacker_steam_id': 'steam_id_64', 'event_time': 'kill_time'})
|
||||
m = opp_time.merge(kills_time, on=['match_id', 'round_num', 'steam_id_64'], how='left')
|
||||
m['in_window'] = ((m['kill_time'] >= m['teammate_death_time']) & (m['kill_time'] <= m['teammate_death_time'] + 10)).astype(int)
|
||||
success = m.groupby(['match_id', 'round_num', 'steam_id_64'], as_index=False)['in_window'].max()
|
||||
rate = success.groupby('steam_id_64')['in_window'].mean().reset_index()
|
||||
rate.rename(columns={'in_window': 'rd_trade_response_10s_rate'}, inplace=True)
|
||||
df = df.merge(rate, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
if 'rd_trade_response_10s_rate_calc' in df.columns:
|
||||
df['rd_trade_response_10s_rate'] = df['rd_trade_response_10s_rate_calc'].fillna(df['rd_trade_response_10s_rate'])
|
||||
df.drop(columns=['rd_trade_response_10s_rate_calc'], inplace=True)
|
||||
|
||||
eco_rows = []
|
||||
try:
|
||||
q_econ = f"""
|
||||
SELECT match_id, round_num, steam_id_64, equipment_value, round_performance_score
|
||||
FROM fact_round_player_economy
|
||||
WHERE steam_id_64 IN ({placeholders})
|
||||
"""
|
||||
df_econ = pd.read_sql_query(q_econ, conn, params=valid_ids)
|
||||
except Exception:
|
||||
df_econ = pd.DataFrame()
|
||||
|
||||
if not df_econ.empty:
|
||||
df_econ['equipment_value'] = pd.to_numeric(df_econ['equipment_value'], errors='coerce').fillna(0).astype(int)
|
||||
df_econ['round_performance_score'] = pd.to_numeric(df_econ['round_performance_score'], errors='coerce').fillna(0.0)
|
||||
df_econ = df_econ.merge(df_rounds2[['match_id', 'round_num', 'is_overtime_round', 'is_match_point_round', 'ct_deficit', 't_deficit', 'prev_ct', 'prev_t']], on=['match_id', 'round_num'], how='left')
|
||||
df_econ = df_econ.merge(df_fh_sides[['match_id', 'steam_id_64', 'fh_side', 'halftime_round']], on=['match_id', 'steam_id_64'], how='left')
|
||||
mask_fh = df_econ['round_num'] <= df_econ['halftime_round']
|
||||
df_econ['side'] = np.where(mask_fh, df_econ['fh_side'], np.where(df_econ['fh_side'] == 'CT', 'T', 'CT'))
|
||||
df_econ['deficit'] = np.where(df_econ['side'] == 'CT', df_econ['ct_deficit'], df_econ['t_deficit'])
|
||||
df_econ['is_pressure_round'] = (df_econ['deficit'] >= 3).astype(int)
|
||||
|
||||
perf_grp = df_econ.groupby(['steam_id_64', 'is_pressure_round'])['round_performance_score'].agg(['mean', 'count']).reset_index()
|
||||
perf = perf_grp.pivot(index='steam_id_64', columns='is_pressure_round').fillna(0)
|
||||
if ('mean', 1) in perf.columns and ('mean', 0) in perf.columns:
|
||||
perf_ratio = (perf[('mean', 1)] / perf[('mean', 0)].replace(0, 1)).reset_index()
|
||||
perf_ratio.columns = ['steam_id_64', 'rd_pressure_perf_ratio']
|
||||
df = df.merge(perf_ratio, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
if 'rd_pressure_perf_ratio_calc' in df.columns:
|
||||
df['rd_pressure_perf_ratio'] = df['rd_pressure_perf_ratio_calc'].fillna(df['rd_pressure_perf_ratio'])
|
||||
df.drop(columns=['rd_pressure_perf_ratio_calc'], inplace=True)
|
||||
|
||||
mp_perf_grp = df_econ.groupby(['steam_id_64', 'is_match_point_round'])['round_performance_score'].agg(['mean', 'count']).reset_index()
|
||||
mp_perf = mp_perf_grp.pivot(index='steam_id_64', columns='is_match_point_round').fillna(0)
|
||||
if ('mean', 1) in mp_perf.columns and ('mean', 0) in mp_perf.columns:
|
||||
mp_perf_ratio = (mp_perf[('mean', 1)] / mp_perf[('mean', 0)].replace(0, 1)).reset_index()
|
||||
mp_perf_ratio.columns = ['steam_id_64', 'rd_matchpoint_perf_ratio']
|
||||
df = df.merge(mp_perf_ratio, on='steam_id_64', how='left', suffixes=('', '_calc'))
|
||||
if 'rd_matchpoint_perf_ratio_calc' in df.columns:
|
||||
df['rd_matchpoint_perf_ratio'] = df['rd_matchpoint_perf_ratio_calc'].fillna(df['rd_matchpoint_perf_ratio'])
|
||||
df.drop(columns=['rd_matchpoint_perf_ratio_calc'], inplace=True)
|
||||
|
||||
eco = df_econ.copy()
|
||||
eco['round_type'] = np.select(
|
||||
[
|
||||
eco['is_overtime_round'] == 1,
|
||||
eco['equipment_value'] < 2000,
|
||||
eco['equipment_value'] >= 4000,
|
||||
],
|
||||
[
|
||||
'overtime',
|
||||
'eco',
|
||||
'fullbuy',
|
||||
],
|
||||
default='rifle'
|
||||
)
|
||||
eco_rounds = eco.groupby(['steam_id_64', 'round_type']).size().reset_index(name='rounds')
|
||||
perf_mean = eco.groupby(['steam_id_64', 'round_type'])['round_performance_score'].mean().reset_index(name='perf')
|
||||
eco_rows = eco_rounds.merge(perf_mean, on=['steam_id_64', 'round_type'], how='left')
|
||||
|
||||
if eco_rows is not None and len(eco_rows) > 0:
|
||||
kpr_rounds = df_player_rounds[['match_id', 'round_num', 'steam_id_64', 'kills', 'is_pistol_round', 'is_overtime_round']].copy()
|
||||
kpr_rounds['round_type'] = np.select(
|
||||
[
|
||||
kpr_rounds['is_overtime_round'] == 1,
|
||||
kpr_rounds['is_pistol_round'] == 1,
|
||||
],
|
||||
[
|
||||
'overtime',
|
||||
'pistol',
|
||||
],
|
||||
default='reg'
|
||||
)
|
||||
kpr = kpr_rounds.groupby(['steam_id_64', 'round_type']).agg(kpr=('kills', 'mean'), rounds=('kills', 'size')).reset_index()
|
||||
kpr_dict = {}
|
||||
for pid, g in kpr.groupby('steam_id_64'):
|
||||
d = {}
|
||||
for _, r in g.iterrows():
|
||||
d[r['round_type']] = {'kpr': float(r['kpr']), 'rounds': int(r['rounds'])}
|
||||
kpr_dict[str(pid)] = d
|
||||
|
||||
econ_dict = {}
|
||||
if isinstance(eco_rows, pd.DataFrame) and not eco_rows.empty:
|
||||
for pid, g in eco_rows.groupby('steam_id_64'):
|
||||
d = {}
|
||||
for _, r in g.iterrows():
|
||||
d[r['round_type']] = {'perf': float(r['perf']) if r['perf'] is not None else 0.0, 'rounds': int(r['rounds'])}
|
||||
econ_dict[str(pid)] = d
|
||||
|
||||
out = {}
|
||||
for pid in df['steam_id_64'].astype(str).tolist():
|
||||
merged = {}
|
||||
if pid in kpr_dict:
|
||||
merged.update(kpr_dict[pid])
|
||||
if pid in econ_dict:
|
||||
for k, v in econ_dict[pid].items():
|
||||
merged.setdefault(k, {}).update(v)
|
||||
out[pid] = json.dumps(merged, ensure_ascii=False)
|
||||
df['rd_roundtype_split_json'] = df['steam_id_64'].astype(str).map(out).fillna("{}")
|
||||
|
||||
# Final Mappings
|
||||
df['total_matches'] = df['matches_played']
|
||||
|
||||
return df.fillna(0)
|
||||
for c in df.columns:
|
||||
if df[c].dtype.kind in "biufc":
|
||||
df[c] = df[c].fillna(0)
|
||||
else:
|
||||
df[c] = df[c].fillna("")
|
||||
return df
|
||||
|
||||
@staticmethod
|
||||
def _calculate_economy_features(conn, player_ids):
|
||||
|
||||
Reference in New Issue
Block a user