1+ # Performance Analytics Controller
2+ #
3+ # Provides endpoints for viewing team and player performance metrics.
4+ # Delegates complex calculations to PerformanceAnalyticsService.
5+ #
6+ # Features:
7+ # - Team overview statistics (wins, losses, KDA, etc.)
8+ # - Win rate trends over time
9+ # - Performance breakdown by role
10+ # - Top performer identification
11+ # - Individual player statistics
12+ #
13+ # @example Get team performance for last 30 days
14+ # GET /api/v1/analytics/performance
15+ #
16+ # @example Get performance with player stats
17+ # GET /api/v1/analytics/performance?player_id=123
18+ #
119class Api ::V1 ::Analytics ::PerformanceController < Api ::V1 ::BaseController
20+ include Analytics ::Concerns ::AnalyticsCalculations
21+
22+ # Returns performance analytics for the organization
23+ #
24+ # Supports filtering by date range and includes individual player stats if requested.
25+ #
26+ # GET /api/v1/analytics/performance
27+ #
28+ # @param start_date [Date] Start date for filtering (optional)
29+ # @param end_date [Date] End date for filtering (optional)
30+ # @param time_period [String] Predefined period: week, month, or season (optional)
31+ # @param player_id [Integer] Player ID for individual stats (optional)
32+ # @return [JSON] Performance analytics data
233 def index
3- # Team performance analytics
4- matches = organization_scoped ( Match )
34+ matches = apply_date_filters ( organization_scoped ( Match ) )
535 players = organization_scoped ( Player ) . active
636
7- # Date range filter
37+ service = Analytics ::Services ::PerformanceAnalyticsService . new ( matches , players )
38+ performance_data = service . calculate_performance_data ( player_id : params [ :player_id ] )
39+
40+ render_success ( performance_data )
41+ end
42+
43+ private
44+
45+ # Applies date range filters to matches based on params
46+ #
47+ # @param matches [ActiveRecord::Relation] Matches relation to filter
48+ # @return [ActiveRecord::Relation] Filtered matches
49+ def apply_date_filters ( matches )
850 if params [ :start_date ] . present? && params [ :end_date ] . present?
9- matches = matches . in_date_range ( params [ :start_date ] , params [ :end_date ] )
51+ matches . in_date_range ( params [ :start_date ] , params [ :end_date ] )
1052 elsif params [ :time_period ] . present?
11- # Handle time_period parameter from frontend
12- days = case params [ :time_period ]
13- when 'week' then 7
14- when 'month' then 30
15- when 'season' then 90
16- else 30
17- end
18- matches = matches . where ( 'game_start >= ?' , days . days . ago )
53+ days = time_period_to_days ( params [ :time_period ] )
54+ matches . where ( 'game_start >= ?' , days . days . ago )
1955 else
20- matches = matches . recent ( 30 ) # Default to last 30 days
56+ matches . recent ( 30 ) # Default to last 30 days
2157 end
58+ end
2259
23- performance_data = {
24- overview : calculate_team_overview ( matches ) ,
25- win_rate_trend : calculate_win_rate_trend ( matches ) ,
26- performance_by_role : calculate_performance_by_role ( matches ) ,
27- best_performers : identify_best_performers ( players , matches ) ,
28- match_type_breakdown : calculate_match_type_breakdown ( matches )
29- }
30-
31- # Add individual player stats if player_id is provided
32- if params [ :player_id ] . present?
33- player = organization_scoped ( Player ) . find_by ( id : params [ :player_id ] )
34- if player
35- performance_data [ :player_stats ] = calculate_player_stats ( player , matches )
36- end
60+ # Converts time period string to number of days
61+ #
62+ # @param period [String] Time period (week, month, season)
63+ # @return [Integer] Number of days
64+ def time_period_to_days ( period )
65+ case period
66+ when 'week' then 7
67+ when 'month' then 30
68+ when 'season' then 90
69+ else 30
3770 end
38-
39- render_success ( performance_data )
4071 end
4172
42- private
43-
73+ # Legacy method - kept for backwards compatibility
74+ # TODO: Remove after migrating all callers to PerformanceAnalyticsService
4475 def calculate_team_overview ( matches )
4576 stats = PlayerMatchStat . where ( match : matches )
4677
@@ -60,21 +91,12 @@ def calculate_team_overview(matches)
6091 }
6192 end
6293
63- def calculate_win_rate_trend ( matches )
64- # Calculate win rate for each week
65- matches . group_by { |m | m . game_start . beginning_of_week } . map do |week , week_matches |
66- wins = week_matches . count ( &:victory? )
67- total = week_matches . size
68- win_rate = total . zero? ? 0 : ( ( wins . to_f / total ) * 100 ) . round ( 1 )
94+ # Legacy methods - moved to PerformanceAnalyticsService
95+ # Kept for backwards compatibility
96+ # TODO: Remove after confirming no external dependencies
6997
70- {
71- week : week . strftime ( '%Y-%m-%d' ) ,
72- matches : total ,
73- wins : wins ,
74- losses : total - wins ,
75- win_rate : win_rate
76- }
77- end . sort_by { |d | d [ :week ] }
98+ def calculate_win_rate_trend ( matches )
99+ super ( matches , group_by : :week )
78100 end
79101
80102 def calculate_performance_by_role ( matches )
@@ -137,21 +159,9 @@ def calculate_match_type_breakdown(matches)
137159 end
138160 end
139161
140- def calculate_win_rate ( matches )
141- return 0 if matches . empty?
142- ( ( matches . victories . count . to_f / matches . count ) * 100 ) . round ( 1 )
143- end
144-
145- def calculate_avg_kda ( stats )
146- return 0 if stats . empty?
147-
148- total_kills = stats . sum ( :kills )
149- total_deaths = stats . sum ( :deaths )
150- total_assists = stats . sum ( :assists )
151-
152- deaths = total_deaths . zero? ? 1 : total_deaths
153- ( ( total_kills + total_assists ) . to_f / deaths ) . round ( 2 )
154- end
162+ # Methods moved to Analytics::Concerns::AnalyticsCalculations:
163+ # - calculate_win_rate
164+ # - calculate_avg_kda
155165
156166 def calculate_player_stats ( player , matches )
157167 stats = PlayerMatchStat . where ( player : player , match : matches )
@@ -174,11 +184,11 @@ def calculate_player_stats(player, matches)
174184 # Calculate CS per min
175185 total_cs = stats . sum ( :cs )
176186 total_duration = matches . where ( id : stats . pluck ( :match_id ) ) . sum ( :game_duration )
177- cs_per_min = total_duration . zero? ? 0.0 : ( total_cs . to_f / ( total_duration / 60.0 ) ) . round ( 1 )
187+ cs_per_min = calculate_cs_per_min ( total_cs , total_duration )
178188
179189 # Calculate gold per min
180190 total_gold = stats . sum ( :gold_earned )
181- gold_per_min = total_duration . zero? ? 0.0 : ( total_gold . to_f / ( total_duration / 60.0 ) ) . round ( 0 )
191+ gold_per_min = calculate_gold_per_min ( total_gold , total_duration )
182192
183193 # Calculate vision score
184194 vision_score = stats . average ( :vision_score ) &.round ( 1 ) || 0.0
0 commit comments