-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathperformance_controller.rb
More file actions
192 lines (169 loc) · 7.48 KB
/
performance_controller.rb
File metadata and controls
192 lines (169 loc) · 7.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# frozen_string_literal: true
module Api
module V1
module Analytics
# Performance Analytics Controller
#
# Provides endpoints for viewing team and player performance metrics.
# Delegates complex calculations to PerformanceAnalyticsService.
#
# This controller handles:
# - Team overview statistics (wins, losses, KDA, etc.)
# - Win rate trends over time
# - Performance breakdown by role
# - Top performer identification
# - Individual player statistics
#
# Supports filtering by date range, time period, and individual player stats.
# All calculations are scoped to the current organization.
#
# @example Get team performance for last 30 days
# GET /api/v1/analytics/performance
#
# @example Get performance with player stats
# GET /api/v1/analytics/performance?player_id=123
#
# @example Get performance for a specific date range
# GET /api/v1/analytics/performance?start_date=2025-01-01&end_date=2025-01-31
#
# @example Get performance for a time period
# GET /api/v1/analytics/performance?time_period=week
class PerformanceController < Api::V1::BaseController
include Analytics::Concerns::AnalyticsCalculations
# Returns performance analytics for the organization
#
# Supports filtering by date range and includes individual player stats if requested.
#
# GET /api/v1/analytics/performance
#
# @param start_date [Date] Start date for filtering (optional)
# @param end_date [Date] End date for filtering (optional)
# @param time_period [String] Predefined period: week, month, or season (optional)
# @param player_id [Integer] Player ID for individual stats (optional)
# @return [JSON] Performance analytics data
def index
matches = apply_date_filters(organization_scoped(Match))
players = organization_scoped(Player).active
service = Analytics::Services::PerformanceAnalyticsService.new(matches, players)
performance_data = service.calculate_performance_data(player_id: params[:player_id])
render_success(performance_data)
end
private
# Applies date range filters to matches based on params
#
# @param matches [ActiveRecord::Relation] Matches relation to filter
# @return [ActiveRecord::Relation] Filtered matches
def apply_date_filters(matches)
if params[:start_date].present? && params[:end_date].present?
matches.in_date_range(params[:start_date], params[:end_date])
elsif params[:time_period].present?
days = time_period_to_days(params[:time_period])
matches.where('game_start >= ?', days.days.ago)
else
matches.recent(30) # Default to last 30 days
end
end
# Converts time period string to number of days
#
# @param period [String] Time period (week, month, season)
# @return [Integer] Number of days
def time_period_to_days(period)
return 7 if period == 'week'
return 90 if period == 'season'
30
end
# Legacy method - kept for backwards compatibility
# TODO: Remove after migrating all callers to PerformanceAnalyticsService
def calculate_team_overview(matches)
stats = PlayerMatchStat.where(match: matches)
{
total_matches: matches.count,
wins: matches.victories.count,
losses: matches.defeats.count,
win_rate: calculate_win_rate(matches),
avg_game_duration: matches.average(:game_duration)&.round(0),
avg_kda: calculate_avg_kda(stats),
avg_kills_per_game: stats.average(:kills)&.round(1),
avg_deaths_per_game: stats.average(:deaths)&.round(1),
avg_assists_per_game: stats.average(:assists)&.round(1),
avg_gold_per_game: stats.average(:gold_earned)&.round(0),
avg_damage_per_game: stats.average(:damage_dealt_total)&.round(0),
avg_vision_score: stats.average(:vision_score)&.round(1)
}
end
# Legacy methods - moved to PerformanceAnalyticsService and AnalyticsCalculations
# These methods now delegate to the concern
# TODO: Remove after confirming no external dependencies
def identify_best_performers(players, matches)
players.map do |player|
stats = PlayerMatchStat.where(player: player, match: matches)
next if stats.empty?
{
player: PlayerSerializer.render_as_hash(player),
games: stats.count,
avg_kda: calculate_avg_kda(stats),
avg_performance_score: stats.average(:performance_score)&.round(1) || 0,
mvp_count: stats.joins(:match).where(matches: { victory: true }).count
}
end.compact.sort_by { |p| -p[:avg_performance_score] }.take(5)
end
def calculate_match_type_breakdown(matches)
matches.group(:match_type).select(
'match_type',
'COUNT(*) as total',
'SUM(CASE WHEN victory THEN 1 ELSE 0 END) as wins'
).map do |stat|
win_rate = stat.total.zero? ? 0 : ((stat.wins.to_f / stat.total) * 100).round(1)
{
match_type: stat.match_type,
total: stat.total,
wins: stat.wins,
losses: stat.total - stat.wins,
win_rate: win_rate
}
end
end
# Methods moved to Analytics::Concerns::AnalyticsCalculations:
# - calculate_win_rate
# - calculate_avg_kda
def calculate_player_stats(player, matches)
stats = PlayerMatchStat.where(player: player, match: matches)
return nil if stats.empty?
total_kills = stats.sum(:kills)
total_deaths = stats.sum(:deaths)
total_assists = stats.sum(:assists)
games_played = stats.count
# Calculate win rate as decimal (0-1) for frontend
wins = stats.joins(:match).where(matches: { victory: true }).count
win_rate = games_played.zero? ? 0.0 : (wins.to_f / games_played)
# Calculate KDA
deaths = total_deaths.zero? ? 1 : total_deaths
kda = ((total_kills + total_assists).to_f / deaths).round(2)
# Calculate CS per min
total_cs = stats.sum(:cs)
total_duration = matches.where(id: stats.pluck(:match_id)).sum(:game_duration)
cs_per_min = calculate_cs_per_min(total_cs, total_duration)
# Calculate gold per min
total_gold = stats.sum(:gold_earned)
gold_per_min = calculate_gold_per_min(total_gold, total_duration)
# Calculate vision score
vision_score = stats.average(:vision_score)&.round(1) || 0.0
{
player_id: player.id,
summoner_name: player.summoner_name,
games_played: games_played,
win_rate: win_rate,
kda: kda,
cs_per_min: cs_per_min,
gold_per_min: gold_per_min,
vision_score: vision_score,
damage_share: 0.0, # Would need total team damage to calculate
avg_kills: (total_kills.to_f / games_played).round(1),
avg_deaths: (total_deaths.to_f / games_played).round(1),
avg_assists: (total_assists.to_f / games_played).round(1)
}
end
end
end
end
end