Skip to content

Commit 82e1779

Browse files
committed
fix: code patterns and coverage
1 parent 733ac0e commit 82e1779

22 files changed

Lines changed: 1938 additions & 162 deletions

app/controllers/api/v1/analytics/performance_controller.rb

Lines changed: 72 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,77 @@
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+
#
119
class 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

Comments
 (0)