1.2.0: Refined all 6D calcs and UI/UX Experiences.
This commit is contained in:
@@ -589,8 +589,10 @@ class StatsService:
|
||||
def get_roster_stats_distribution(target_steam_id):
|
||||
"""
|
||||
Calculates rank and distribution of the target player within the active roster.
|
||||
Now covers all L3 Basic Features for Detailed Panel.
|
||||
"""
|
||||
from web.services.web_service import WebService
|
||||
from web.services.feature_service import FeatureService
|
||||
import json
|
||||
import numpy as np
|
||||
|
||||
@@ -604,72 +606,64 @@ class StatsService:
|
||||
except:
|
||||
pass
|
||||
|
||||
# Ensure target is in list (if not in roster, compare against roster anyway)
|
||||
# If roster is empty, return None
|
||||
if not active_roster_ids:
|
||||
return None
|
||||
|
||||
# 2. Fetch stats for all roster members
|
||||
# 2. Fetch L3 features for all roster members
|
||||
# We need to use FeatureService to get the full L3 set (including detailed stats)
|
||||
# Assuming L3 data is up to date.
|
||||
|
||||
placeholders = ','.join('?' for _ in active_roster_ids)
|
||||
sql = f"""
|
||||
SELECT
|
||||
CAST(steam_id_64 AS TEXT) as steam_id_64,
|
||||
AVG(rating) as rating,
|
||||
AVG(kd_ratio) as kd,
|
||||
AVG(adr) as adr,
|
||||
AVG(kast) as kast
|
||||
FROM fact_match_players
|
||||
WHERE CAST(steam_id_64 AS TEXT) IN ({placeholders})
|
||||
GROUP BY steam_id_64
|
||||
"""
|
||||
rows = query_db('l2', sql, active_roster_ids)
|
||||
sql = f"SELECT * FROM dm_player_features WHERE steam_id_64 IN ({placeholders})"
|
||||
rows = query_db('l3', sql, active_roster_ids)
|
||||
|
||||
if not rows:
|
||||
return None
|
||||
|
||||
stats_map = {row['steam_id_64']: dict(row) for row in rows}
|
||||
|
||||
# Ensure target_steam_id is string
|
||||
target_steam_id = str(target_steam_id)
|
||||
|
||||
# If target player not in stats_map (e.g. no matches), handle gracefullly
|
||||
# If target not in map (e.g. no L3 data), try to add empty default
|
||||
if target_steam_id not in stats_map:
|
||||
# Try fetch target stats individually if not in roster list
|
||||
target_stats = StatsService.get_player_basic_stats(target_steam_id)
|
||||
if target_stats:
|
||||
stats_map[target_steam_id] = target_stats
|
||||
else:
|
||||
# If still no stats, we can't rank them.
|
||||
# But we can still return the roster stats for others?
|
||||
# The prompt implies "No team data" appears, meaning this function returns valid structure but empty values?
|
||||
# Or returns None.
|
||||
# Let's verify what happens if target has no stats but others do.
|
||||
# We should probably add a dummy entry for target so dashboard renders '0' instead of crashing or 'No data'
|
||||
stats_map[target_steam_id] = {'rating': 0, 'kd': 0, 'adr': 0, 'kast': 0}
|
||||
|
||||
# 3. Calculate Distribution
|
||||
metrics = ['rating', 'kd', 'adr', 'kast']
|
||||
stats_map[target_steam_id] = {}
|
||||
|
||||
# 3. Calculate Distribution for ALL metrics
|
||||
# Define metrics list (must match Detailed Panel keys)
|
||||
metrics = [
|
||||
'basic_avg_rating', 'basic_avg_kd', 'basic_avg_kast', 'basic_avg_rws', 'basic_avg_adr',
|
||||
'basic_avg_headshot_kills', 'basic_headshot_rate', 'basic_avg_assisted_kill', 'basic_avg_awp_kill', 'basic_avg_jump_count',
|
||||
'basic_avg_first_kill', 'basic_avg_first_death', 'basic_first_kill_rate', 'basic_first_death_rate',
|
||||
'basic_avg_kill_2', 'basic_avg_kill_3', 'basic_avg_kill_4', 'basic_avg_kill_5',
|
||||
'basic_avg_perfect_kill', 'basic_avg_revenge_kill',
|
||||
# L3 Advanced Dimensions
|
||||
'sta_last_30_rating', 'sta_win_rating', 'sta_loss_rating', 'sta_rating_volatility', 'sta_time_rating_corr',
|
||||
'bat_kd_diff_high_elo', 'bat_avg_duel_win_rate', 'bat_avg_duel_freq',
|
||||
'hps_clutch_win_rate_1v1', 'hps_clutch_win_rate_1v3_plus', 'hps_match_point_win_rate', 'hps_pressure_entry_rate', 'hps_comeback_kd_diff',
|
||||
'ptl_pistol_kills', 'ptl_pistol_win_rate', 'ptl_pistol_kd',
|
||||
'side_rating_ct', 'side_rating_t', 'side_first_kill_rate_ct', 'side_first_kill_rate_t', 'side_kd_diff_ct_t',
|
||||
'util_avg_nade_dmg', 'util_avg_flash_time', 'util_avg_flash_enemy', 'util_usage_rate'
|
||||
]
|
||||
|
||||
# Mapping for L2 legacy calls (if any) - mainly map 'rating' to 'basic_avg_rating' etc if needed
|
||||
# But here we just use L3 columns directly.
|
||||
|
||||
result = {}
|
||||
|
||||
for m in metrics:
|
||||
# Extract values for this metric from all players
|
||||
values = [p[m] for p in stats_map.values() if p[m] is not None]
|
||||
target_val = stats_map[target_steam_id].get(m)
|
||||
values = [p.get(m, 0) or 0 for p in stats_map.values()]
|
||||
target_val = stats_map[target_steam_id].get(m, 0) or 0
|
||||
|
||||
if target_val is None or not values:
|
||||
if not values:
|
||||
result[m] = None
|
||||
continue
|
||||
|
||||
# Sort descending (higher is better)
|
||||
values.sort(reverse=True)
|
||||
|
||||
# Rank (1-based)
|
||||
# Rank
|
||||
try:
|
||||
rank = values.index(target_val) + 1
|
||||
except ValueError:
|
||||
# Floating point precision issue? Find closest
|
||||
closest = min(values, key=lambda x: abs(x - target_val))
|
||||
rank = values.index(closest) + 1
|
||||
rank = len(values)
|
||||
|
||||
result[m] = {
|
||||
'val': target_val,
|
||||
@@ -680,6 +674,16 @@ class StatsService:
|
||||
'avg': sum(values) / len(values)
|
||||
}
|
||||
|
||||
# Legacy mapping for top cards (rating, kd, adr, kast)
|
||||
legacy_map = {
|
||||
'basic_avg_rating': 'rating',
|
||||
'basic_avg_kd': 'kd',
|
||||
'basic_avg_adr': 'adr',
|
||||
'basic_avg_kast': 'kast'
|
||||
}
|
||||
if m in legacy_map:
|
||||
result[legacy_map[m]] = result[m]
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user