272 lines
10 KiB
Python
272 lines
10 KiB
Python
"""
|
|
Economy Processor - Handles leetify economic data
|
|
|
|
Responsibilities:
|
|
- Parse bron_equipment (equipment lists)
|
|
- Parse player_bron_crash (starting money)
|
|
- Calculate equipment_value
|
|
- Write to fact_round_player_economy and update fact_rounds
|
|
"""
|
|
|
|
import sqlite3
|
|
import json
|
|
import logging
|
|
import uuid
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class EconomyProcessor:
|
|
@staticmethod
|
|
def process_classic(match_data, conn: sqlite3.Connection) -> bool:
|
|
"""
|
|
Process classic economy data (extracted from round_list equiped)
|
|
"""
|
|
try:
|
|
cursor = conn.cursor()
|
|
|
|
for r in match_data.rounds:
|
|
if not r.economies:
|
|
continue
|
|
|
|
for eco in r.economies:
|
|
if eco.side not in ['CT', 'T']:
|
|
# Skip rounds where side cannot be determined (avoids CHECK constraint failure)
|
|
continue
|
|
|
|
cursor.execute('''
|
|
INSERT OR REPLACE INTO fact_round_player_economy (
|
|
match_id, round_num, steam_id_64, side, start_money,
|
|
equipment_value, main_weapon, has_helmet, has_defuser,
|
|
has_zeus, round_performance_score, data_source_type
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', (
|
|
match_data.match_id, r.round_num, eco.steam_id_64, eco.side, eco.start_money,
|
|
eco.equipment_value, eco.main_weapon, eco.has_helmet, eco.has_defuser,
|
|
eco.has_zeus, eco.round_performance_score, 'classic'
|
|
))
|
|
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Error processing classic economy for match {match_data.match_id}: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
@staticmethod
|
|
def process_leetify(match_data, conn: sqlite3.Connection) -> bool:
|
|
"""
|
|
Process leetify economy and round data
|
|
|
|
Args:
|
|
match_data: MatchData object with leetify_data parsed
|
|
conn: L2 database connection
|
|
|
|
Returns:
|
|
bool: True if successful
|
|
"""
|
|
try:
|
|
if not hasattr(match_data, 'data_leetify') or not match_data.data_leetify:
|
|
return True
|
|
|
|
leetify_data = match_data.data_leetify.get('leetify_data', {})
|
|
round_stats = leetify_data.get('round_stat', [])
|
|
|
|
if not round_stats:
|
|
return True
|
|
|
|
cursor = conn.cursor()
|
|
|
|
for r in round_stats:
|
|
round_num = r.get('round', 0)
|
|
|
|
# Extract round-level data
|
|
ct_money_start = r.get('ct_money_group', 0)
|
|
t_money_start = r.get('t_money_group', 0)
|
|
win_reason = r.get('win_reason', 0)
|
|
|
|
# Get timestamps
|
|
begin_ts = r.get('begin_ts', '')
|
|
end_ts = r.get('end_ts', '')
|
|
|
|
# Get sfui_event for scores
|
|
sfui = r.get('sfui_event', {})
|
|
ct_score = sfui.get('score_ct', 0)
|
|
t_score = sfui.get('score_t', 0)
|
|
|
|
# Determine winner_side based on show_event
|
|
show_events = r.get('show_event', [])
|
|
winner_side = 'None'
|
|
duration = 0.0
|
|
|
|
if show_events:
|
|
last_event = show_events[-1]
|
|
# Check if there's a win_reason in the last event
|
|
if last_event.get('win_reason'):
|
|
win_reason = last_event.get('win_reason', 0)
|
|
# Map win_reason to winner_side
|
|
# Typical mappings: 1=T_Win, 2=CT_Win, etc.
|
|
winner_side = _map_win_reason_to_side(win_reason)
|
|
|
|
# Calculate duration from event timestamps
|
|
if 'ts' in last_event:
|
|
duration = float(last_event.get('ts', 0))
|
|
|
|
# Insert/update fact_rounds
|
|
cursor.execute('''
|
|
INSERT OR REPLACE INTO fact_rounds (
|
|
match_id, round_num, winner_side, win_reason, win_reason_desc,
|
|
duration, ct_score, t_score, ct_money_start, t_money_start,
|
|
begin_ts, end_ts, data_source_type
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', (
|
|
match_data.match_id, round_num, winner_side, win_reason,
|
|
_map_win_reason_desc(win_reason), duration, ct_score, t_score,
|
|
ct_money_start, t_money_start, begin_ts, end_ts, 'leetify'
|
|
))
|
|
|
|
# Process economy data
|
|
bron_equipment = r.get('bron_equipment', {})
|
|
player_t_score = r.get('player_t_score', {})
|
|
player_ct_score = r.get('player_ct_score', {})
|
|
player_bron_crash = r.get('player_bron_crash', {})
|
|
|
|
# Build side mapping
|
|
side_scores = {}
|
|
for sid, val in player_t_score.items():
|
|
side_scores[str(sid)] = ("T", float(val) if val is not None else 0.0)
|
|
for sid, val in player_ct_score.items():
|
|
side_scores[str(sid)] = ("CT", float(val) if val is not None else 0.0)
|
|
|
|
# Process each player's economy
|
|
for sid in set(list(side_scores.keys()) + [str(k) for k in bron_equipment.keys()]):
|
|
if sid not in side_scores:
|
|
continue
|
|
|
|
side, perf_score = side_scores[sid]
|
|
items = bron_equipment.get(sid) or bron_equipment.get(str(sid)) or []
|
|
|
|
start_money = _pick_money(items)
|
|
equipment_value = player_bron_crash.get(sid) or player_bron_crash.get(str(sid))
|
|
equipment_value = int(equipment_value) if equipment_value is not None else 0
|
|
|
|
main_weapon = _pick_main_weapon(items)
|
|
has_helmet = _has_item_type(items, ['weapon_vest', 'item_assaultsuit', 'item_kevlar'])
|
|
has_defuser = _has_item_type(items, ['item_defuser'])
|
|
has_zeus = _has_item_type(items, ['weapon_taser'])
|
|
|
|
cursor.execute('''
|
|
INSERT OR REPLACE INTO fact_round_player_economy (
|
|
match_id, round_num, steam_id_64, side, start_money,
|
|
equipment_value, main_weapon, has_helmet, has_defuser,
|
|
has_zeus, round_performance_score, data_source_type
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', (
|
|
match_data.match_id, round_num, sid, side, start_money,
|
|
equipment_value, main_weapon, has_helmet, has_defuser,
|
|
has_zeus, perf_score, 'leetify'
|
|
))
|
|
|
|
logger.debug(f"Processed {len(round_stats)} leetify rounds for match {match_data.match_id}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing leetify economy for match {match_data.match_id}: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
|
|
def _pick_main_weapon(items):
|
|
"""Extract main weapon from equipment list"""
|
|
if not isinstance(items, list):
|
|
return ""
|
|
|
|
ignore = {
|
|
"weapon_knife", "weapon_knife_t", "weapon_knife_gg", "weapon_knife_ct",
|
|
"weapon_c4", "weapon_flashbang", "weapon_hegrenade", "weapon_smokegrenade",
|
|
"weapon_molotov", "weapon_incgrenade", "weapon_decoy"
|
|
}
|
|
|
|
# First pass: ignore utility
|
|
for it in items:
|
|
if not isinstance(it, dict):
|
|
continue
|
|
name = it.get('WeaponName')
|
|
if name and name not in ignore:
|
|
return name
|
|
|
|
# Second pass: any weapon
|
|
for it in items:
|
|
if not isinstance(it, dict):
|
|
continue
|
|
name = it.get('WeaponName')
|
|
if name:
|
|
return name
|
|
|
|
return ""
|
|
|
|
|
|
def _pick_money(items):
|
|
"""Extract starting money from equipment list"""
|
|
if not isinstance(items, list):
|
|
return 0
|
|
|
|
vals = []
|
|
for it in items:
|
|
if isinstance(it, dict) and it.get('Money') is not None:
|
|
vals.append(it.get('Money'))
|
|
|
|
return int(max(vals)) if vals else 0
|
|
|
|
|
|
def _has_item_type(items, keywords):
|
|
"""Check if equipment list contains item matching keywords"""
|
|
if not isinstance(items, list):
|
|
return False
|
|
|
|
for it in items:
|
|
if not isinstance(it, dict):
|
|
continue
|
|
name = it.get('WeaponName', '')
|
|
if any(kw in name for kw in keywords):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _map_win_reason_to_side(win_reason):
|
|
"""Map win_reason integer to winner_side"""
|
|
# Common mappings from CS:GO/CS2:
|
|
# 1 = Target_Bombed (T wins)
|
|
# 2 = Bomb_Defused (CT wins)
|
|
# 7 = CTs_Win (CT eliminates T)
|
|
# 8 = Terrorists_Win (T eliminates CT)
|
|
# 9 = Target_Saved (CT wins, time runs out)
|
|
# etc.
|
|
t_win_reasons = {1, 8, 12, 17}
|
|
ct_win_reasons = {2, 7, 9, 11}
|
|
|
|
if win_reason in t_win_reasons:
|
|
return 'T'
|
|
elif win_reason in ct_win_reasons:
|
|
return 'CT'
|
|
else:
|
|
return 'None'
|
|
|
|
|
|
def _map_win_reason_desc(win_reason):
|
|
"""Map win_reason integer to description"""
|
|
reason_map = {
|
|
0: 'None',
|
|
1: 'TargetBombed',
|
|
2: 'BombDefused',
|
|
7: 'CTsWin',
|
|
8: 'TerroristsWin',
|
|
9: 'TargetSaved',
|
|
11: 'CTSurrender',
|
|
12: 'TSurrender',
|
|
17: 'TerroristsPlanted'
|
|
}
|
|
return reason_map.get(win_reason, f'Unknown_{win_reason}')
|