From 1b9cab5628ea14798bcdcbcd09c758214765152b Mon Sep 17 00:00:00 2001 From: Jacky Yang Date: Mon, 26 Jan 2026 22:04:29 +0800 Subject: [PATCH] 1.2.1 : Updated calculation --- database/L3/L3_Features.sqlite | Bin 405504 -> 405504 bytes database/L3/schema.sql | 4 ++++ web/routes/players.py | 17 ++++++++++++----- web/services/feature_service.py | 12 ++++++++++++ web/services/stats_service.py | 3 ++- web/templates/players/profile.html | 14 ++++++++++---- 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/database/L3/L3_Features.sqlite b/database/L3/L3_Features.sqlite index bf53e977f2601673fab3018a9f069e47e6fa1a58..9a525ea903cf442e504a573756328d0dff5f938b 100644 GIT binary patch delta 3177 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 bYC_FfR5A7p?y{tO(mVEZ<@VGv^A`UH`j}_z delta 4375 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 diff --git a/database/L3/schema.sql b/database/L3/schema.sql index 97ab170..eabd6ee 100644 --- a/database/L3/schema.sql +++ b/database/L3/schema.sql @@ -32,6 +32,10 @@ CREATE TABLE IF NOT EXISTS dm_player_features ( basic_avg_revenge_kill REAL, basic_avg_awp_kill REAL, basic_avg_jump_count REAL, + basic_avg_mvps REAL, + basic_avg_plants REAL, + basic_avg_defuses REAL, + basic_avg_flash_assists REAL, -- ========================================== -- 1. STA: Stability & Time Series diff --git a/web/routes/players.py b/web/routes/players.py index 541a2f2..23a8af7 100644 --- a/web/routes/players.py +++ b/web/routes/players.py @@ -101,18 +101,25 @@ def detail(steam_id): # Ensure basic stats fallback if features missing or incomplete basic = StatsService.get_player_basic_stats(steam_id) + from collections import defaultdict + if not features: + # Fallback to defaultdict with basic stats + features = defaultdict(lambda: None) if basic: - features = { + features.update({ 'basic_avg_rating': basic.get('rating', 0), 'basic_avg_kd': basic.get('kd', 0), 'basic_avg_kast': basic.get('kast', 0), - 'basic_avg_adr': basic.get('adr', 0), # Pass ADR - } + 'basic_avg_adr': basic.get('adr', 0), + }) else: + # Convert to defaultdict to handle missing keys gracefully (e.g. newly added columns) + # Use lambda: None so that Jinja can check 'if value is not none' + features = defaultdict(lambda: None, dict(features)) + # If features exist but ADR is missing (not in L3), try to patch it from basic - if 'basic_avg_adr' not in features: - features = dict(features) # Convert to dict if row + if 'basic_avg_adr' not in features or features['basic_avg_adr'] is None: features['basic_avg_adr'] = basic.get('adr', 0) if basic else 0 comments = WebService.get_comments('player', steam_id) diff --git a/web/services/feature_service.py b/web/services/feature_service.py index e61d7a2..8111cbd 100644 --- a/web/services/feature_service.py +++ b/web/services/feature_service.py @@ -280,6 +280,14 @@ class FeatureService: SUM(revenge_kill) as sum_revenge, SUM(awp_kill) as sum_awp, SUM(jump_count) as sum_jump, + SUM(mvp_count) as sum_mvps, + SUM(planted_bomb) as sum_plants, + SUM(defused_bomb) as sum_defuses, + SUM(CASE + WHEN flash_assists > 0 THEN flash_assists + WHEN assists > assisted_kill THEN assists - assisted_kill + ELSE 0 + END) as sum_flash_assists, SUM(throw_harm) as sum_util_dmg, SUM(flash_time) as sum_flash_time, SUM(flash_enemy) as sum_flash_enemy, @@ -312,6 +320,10 @@ class FeatureService: df['basic_avg_revenge_kill'] = df['sum_revenge'] / df['matches_played'] df['basic_avg_awp_kill'] = df['sum_awp'] / df['matches_played'] df['basic_avg_jump_count'] = df['sum_jump'] / df['matches_played'] + df['basic_avg_mvps'] = df['sum_mvps'] / df['matches_played'] + df['basic_avg_plants'] = df['sum_plants'] / df['matches_played'] + df['basic_avg_defuses'] = df['sum_defuses'] / df['matches_played'] + df['basic_avg_flash_assists'] = df['sum_flash_assists'] / df['matches_played'] # UTIL Basic df['util_avg_nade_dmg'] = df['sum_util_dmg'] / df['matches_played'] diff --git a/web/services/stats_service.py b/web/services/stats_service.py index 9cc1546..92832eb 100644 --- a/web/services/stats_service.py +++ b/web/services/stats_service.py @@ -632,12 +632,13 @@ class StatsService: 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_mvps', 'basic_avg_plants', 'basic_avg_defuses', 'basic_avg_flash_assists', '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', + 'bat_kd_diff_high_elo', 'bat_avg_duel_win_rate', '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', diff --git a/web/templates/players/profile.html b/web/templates/players/profile.html index 8be5a52..6fa41d7 100644 --- a/web/templates/players/profile.html +++ b/web/templates/players/profile.html @@ -203,20 +203,27 @@ {{ detail_item('AWP Kills (狙击击杀)', features['basic_avg_awp_kill'], 'basic_avg_awp_kill') }} {{ detail_item('Jumps (场均跳跃)', features['basic_avg_jump_count'], 'basic_avg_jump_count', '{:.1f}') }} - + + {{ detail_item('MVP (最有价值)', features['basic_avg_mvps'], 'basic_avg_mvps') }} + {{ detail_item('Plants (下包)', features['basic_avg_plants'], 'basic_avg_plants') }} + {{ detail_item('Defuses (拆包)', features['basic_avg_defuses'], 'basic_avg_defuses') }} + {{ detail_item('Flash Assist (闪光助攻)', features['basic_avg_flash_assists'], 'basic_avg_flash_assists') }} + + + {{ detail_item('First Kill (场均首杀)', features['basic_avg_first_kill'], 'basic_avg_first_kill') }} {{ detail_item('First Death (场均首死)', features['basic_avg_first_death'], 'basic_avg_first_death') }} {{ detail_item('FK Rate (首杀率)', features['basic_first_kill_rate'], 'basic_first_kill_rate', '{:.1%}') }} {{ detail_item('FD Rate (首死率)', features['basic_first_death_rate'], 'basic_first_death_rate', '{:.1%}') }} - + {{ detail_item('2K Rounds (双杀)', features['basic_avg_kill_2'], 'basic_avg_kill_2') }} {{ detail_item('3K Rounds (三杀)', features['basic_avg_kill_3'], 'basic_avg_kill_3') }} {{ detail_item('4K Rounds (四杀)', features['basic_avg_kill_4'], 'basic_avg_kill_4') }} {{ detail_item('5K Rounds (五杀)', features['basic_avg_kill_5'], 'basic_avg_kill_5') }} - + {{ detail_item('Perfect Kills (无伤杀)', features['basic_avg_perfect_kill'], 'basic_avg_perfect_kill') }} {{ detail_item('Revenge Kills (复仇杀)', features['basic_avg_revenge_kill'], 'basic_avg_revenge_kill') }} @@ -245,7 +252,6 @@ {{ detail_item('High Elo KD Diff (高分抗压)', features['bat_kd_diff_high_elo'], 'bat_kd_diff_high_elo') }} {{ detail_item('Duel Win% (对枪胜率)', features['bat_avg_duel_win_rate'], 'bat_avg_duel_win_rate', '{:.1%}') }} - {{ detail_item('Duel Freq (对枪频率)', features['bat_avg_duel_freq'], 'bat_avg_duel_freq', '{:.1%}') }}