@@ -18,7 +18,7 @@ def index
1818 private
1919
2020 def fetch_roster_players
21- organization_scoped ( Player ) . includes ( :organization ) . where ( status : %w[ active benched trial ] )
21+ organization_scoped ( Player ) . includes ( :organization ) . where . not ( status : 'removed' )
2222 end
2323
2424 def build_matches_query
@@ -60,59 +60,81 @@ def build_comparison_data(players, matches)
6060 end
6161
6262 # Single GROUP BY query replaces one query per player (N+1 → 1)
63+ # Players with no stats in the period appear with zero values
6364 def build_player_comparisons ( players , matches )
6465 player_ids = players . pluck ( :id )
65- match_ids = matches . pluck ( :id )
66- return [ ] if player_ids . empty? || match_ids . empty?
67-
68- agg_rows = PlayerMatchStat
69- . where ( player_id : player_ids , match_id : match_ids )
70- . group ( :player_id )
71- . select (
72- 'player_id' ,
73- 'COUNT(*) AS games_played' ,
74- 'SUM(kills) AS total_kills' ,
75- 'SUM(deaths) AS total_deaths' ,
76- 'SUM(assists) AS total_assists' ,
77- 'AVG(damage_dealt_total) AS avg_damage' ,
78- 'AVG(gold_earned) AS avg_gold' ,
79- 'AVG(cs) AS avg_cs' ,
80- 'AVG(vision_score) AS avg_vision_score' ,
81- 'AVG(performance_score) AS avg_performance_score' ,
82- 'SUM(double_kills) AS double_kills' ,
83- 'SUM(triple_kills) AS triple_kills' ,
84- 'SUM(quadra_kills) AS quadra_kills' ,
85- 'SUM(penta_kills) AS penta_kills'
86- )
87-
88- players_by_id = players . index_by ( &:id )
89-
90- agg_rows . filter_map do |agg |
91- player = players_by_id [ agg . player_id ]
92- next unless player
93-
94- deaths = agg . total_deaths . to_i . zero? ? 1 : agg . total_deaths . to_i
95- kda = ( ( agg . total_kills . to_i + agg . total_assists . to_i ) . to_f / deaths ) . round ( 2 )
96-
97- {
98- player : PlayerSerializer . render_as_hash ( player ) ,
99- games_played : agg . games_played . to_i ,
100- kda : kda ,
101- avg_damage : agg . avg_damage . to_f . round ( 0 ) ,
102- avg_gold : agg . avg_gold . to_f . round ( 0 ) ,
103- avg_cs : agg . avg_cs . to_f . round ( 1 ) ,
104- avg_vision_score : agg . avg_vision_score . to_f . round ( 1 ) ,
105- avg_performance_score : agg . avg_performance_score . to_f . round ( 1 ) ,
106- multikills : {
107- double : agg . double_kills . to_i ,
108- triple : agg . triple_kills . to_i ,
109- quadra : agg . quadra_kills . to_i ,
110- penta : agg . penta_kills . to_i
111- }
112- }
66+ return [ ] if player_ids . empty?
67+
68+ match_ids = matches . pluck ( :id )
69+
70+ agg_by_player_id = if match_ids . empty?
71+ { }
72+ else
73+ PlayerMatchStat
74+ . where ( player_id : player_ids , match_id : match_ids )
75+ . group ( :player_id )
76+ . select (
77+ 'player_id' ,
78+ 'COUNT(*) AS games_played' ,
79+ 'SUM(kills) AS total_kills' ,
80+ 'SUM(deaths) AS total_deaths' ,
81+ 'SUM(assists) AS total_assists' ,
82+ 'AVG(damage_dealt_total) AS avg_damage' ,
83+ 'AVG(gold_earned) AS avg_gold' ,
84+ 'AVG(cs) AS avg_cs' ,
85+ 'AVG(vision_score) AS avg_vision_score' ,
86+ 'AVG(performance_score) AS avg_performance_score' ,
87+ 'SUM(double_kills) AS double_kills' ,
88+ 'SUM(triple_kills) AS triple_kills' ,
89+ 'SUM(quadra_kills) AS quadra_kills' ,
90+ 'SUM(penta_kills) AS penta_kills'
91+ ) . index_by ( &:player_id )
92+ end
93+
94+ players . map do |player |
95+ agg = agg_by_player_id [ player . id ]
96+ build_player_entry ( player , agg )
11397 end . sort_by { |p | -p [ :avg_performance_score ] }
11498 end
11599
100+ def build_player_entry ( player , agg )
101+ return zero_stats_entry ( player ) unless agg
102+
103+ deaths = agg . total_deaths . to_i . zero? ? 1 : agg . total_deaths . to_i
104+ kda = ( ( agg . total_kills . to_i + agg . total_assists . to_i ) . to_f / deaths ) . round ( 2 )
105+
106+ {
107+ player : PlayerSerializer . render_as_hash ( player ) ,
108+ games_played : agg . games_played . to_i ,
109+ kda : kda ,
110+ avg_damage : agg . avg_damage . to_f . round ( 0 ) ,
111+ avg_gold : agg . avg_gold . to_f . round ( 0 ) ,
112+ avg_cs : agg . avg_cs . to_f . round ( 1 ) ,
113+ avg_vision_score : agg . avg_vision_score . to_f . round ( 1 ) ,
114+ avg_performance_score : agg . avg_performance_score . to_f . round ( 1 ) ,
115+ multikills : {
116+ double : agg . double_kills . to_i ,
117+ triple : agg . triple_kills . to_i ,
118+ quadra : agg . quadra_kills . to_i ,
119+ penta : agg . penta_kills . to_i
120+ }
121+ }
122+ end
123+
124+ def zero_stats_entry ( player )
125+ {
126+ player : PlayerSerializer . render_as_hash ( player ) ,
127+ games_played : 0 ,
128+ kda : 0.0 ,
129+ avg_damage : 0 ,
130+ avg_gold : 0 ,
131+ avg_cs : 0.0 ,
132+ avg_vision_score : 0.0 ,
133+ avg_performance_score : 0.0 ,
134+ multikills : { double : 0 , triple : 0 , quadra : 0 , penta : 0 }
135+ }
136+ end
137+
116138 def calculate_average ( stats , column , precision )
117139 stats . average ( column ) &.round ( precision ) || 0
118140 end
@@ -148,35 +170,50 @@ def calculate_team_averages(matches)
148170 end
149171
150172 # Single GROUP BY across all roles — replaces 3N per-player queries
173+ # Players with no stats appear in their role slot with 0 games
151174 def calculate_role_rankings ( players , matches )
152175 player_ids = players . pluck ( :id )
153- match_ids = matches . pluck ( :id )
154-
155- rankings = { 'top' => [ ] , 'jungle' => [ ] , 'mid' => [ ] , 'adc' => [ ] , 'support' => [ ] }
156- return rankings if player_ids . empty? || match_ids . empty?
157-
158- agg_rows = PlayerMatchStat
159- . joins ( :player )
160- . where ( player_id : player_ids , match_id : match_ids )
161- . group ( 'player_id, players.role, players.summoner_name' )
162- . select (
163- 'player_id' ,
164- 'players.role AS role' ,
165- 'players.summoner_name AS summoner_name' ,
166- 'COUNT(*) AS games' ,
167- 'AVG(performance_score) AS avg_performance'
168- )
169-
170- agg_rows . each do |agg |
171- role = agg . role
176+ rankings = { 'top' => [ ] , 'jungle' => [ ] , 'mid' => [ ] , 'adc' => [ ] , 'support' => [ ] }
177+ return rankings if player_ids . empty?
178+
179+ match_ids = matches . pluck ( :id )
180+
181+ agg_by_player_id = if match_ids . empty?
182+ { }
183+ else
184+ PlayerMatchStat
185+ . joins ( :player )
186+ . where ( player_id : player_ids , match_id : match_ids )
187+ . group ( 'player_id, players.role, players.summoner_name' )
188+ . select (
189+ 'player_id' ,
190+ 'players.role AS role' ,
191+ 'players.summoner_name AS summoner_name' ,
192+ 'COUNT(*) AS games' ,
193+ 'AVG(performance_score) AS avg_performance'
194+ ) . index_by ( &:player_id )
195+ end
196+
197+ players . each do |player |
198+ role = player . role
172199 next unless rankings . key? ( role )
173200
174- rankings [ role ] << {
175- player_id : agg . player_id ,
176- summoner_name : agg . summoner_name ,
177- avg_performance : agg . avg_performance . to_f . round ( 1 ) ,
178- games : agg . games . to_i
179- }
201+ agg = agg_by_player_id [ player . id ]
202+ rankings [ role ] << if agg
203+ {
204+ player_id : player . id ,
205+ summoner_name : player . summoner_name ,
206+ avg_performance : agg . avg_performance . to_f . round ( 1 ) ,
207+ games : agg . games . to_i
208+ }
209+ else
210+ {
211+ player_id : player . id ,
212+ summoner_name : player . summoner_name ,
213+ avg_performance : 0.0 ,
214+ games : 0
215+ }
216+ end
180217 end
181218
182219 rankings . transform_values { |list | list . sort_by { |p | -p [ :avg_performance ] } }
0 commit comments