hockey-blast-common-lib 0.1.61__py3-none-any.whl → 0.1.62__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hockey_blast_common_lib/aggregate_goalie_stats.py +33 -7
- hockey_blast_common_lib/aggregate_referee_stats.py +47 -9
- hockey_blast_common_lib/aggregate_scorekeeper_stats.py +27 -3
- hockey_blast_common_lib/aggregate_skater_stats.py +38 -12
- hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz +0 -0
- hockey_blast_common_lib/progress_utils.py +23 -9
- hockey_blast_common_lib/stats_models.py +20 -4
- {hockey_blast_common_lib-0.1.61.dist-info → hockey_blast_common_lib-0.1.62.dist-info}/METADATA +1 -1
- {hockey_blast_common_lib-0.1.61.dist-info → hockey_blast_common_lib-0.1.62.dist-info}/RECORD +11 -11
- {hockey_blast_common_lib-0.1.61.dist-info → hockey_blast_common_lib-0.1.62.dist-info}/WHEEL +0 -0
- {hockey_blast_common_lib-0.1.61.dist-info → hockey_blast_common_lib-0.1.62.dist-info}/top_level.txt +0 -0
@@ -9,13 +9,19 @@ import sqlalchemy
|
|
9
9
|
from hockey_blast_common_lib.models import Game, Organization, Division, Human, GoalieSaves
|
10
10
|
from hockey_blast_common_lib.stats_models import OrgStatsGoalie, DivisionStatsGoalie, OrgStatsWeeklyGoalie, OrgStatsDailyGoalie, DivisionStatsWeeklyGoalie, DivisionStatsDailyGoalie, LevelStatsGoalie
|
11
11
|
from hockey_blast_common_lib.db_connection import create_session
|
12
|
-
from sqlalchemy.sql import func
|
12
|
+
from sqlalchemy.sql import func, case
|
13
13
|
from hockey_blast_common_lib.options import MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
|
14
14
|
from hockey_blast_common_lib.utils import get_non_human_ids, get_all_division_ids_for_org, get_start_datetime
|
15
15
|
from hockey_blast_common_lib.utils import assign_ranks
|
16
16
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
17
17
|
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
18
18
|
|
19
|
+
# Import status constants for game filtering
|
20
|
+
FINAL_STATUS = "Final"
|
21
|
+
FINAL_SO_STATUS = "Final(SO)"
|
22
|
+
FORFEIT_STATUS = "FORFEIT"
|
23
|
+
NOEVENTS_STATUS = "NOEVENTS"
|
24
|
+
|
19
25
|
def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_human_id=None, aggregation_window=None):
|
20
26
|
human_ids_to_filter = get_non_human_ids(session)
|
21
27
|
|
@@ -72,9 +78,19 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
72
78
|
|
73
79
|
|
74
80
|
# Aggregate games played, goals allowed, and shots faced for each goalie using GoalieSaves table
|
81
|
+
# games_participated: Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
82
|
+
# games_with_stats: Count only FINAL, FINAL_SO (for per-game averages)
|
75
83
|
query = session.query(
|
76
84
|
GoalieSaves.goalie_id.label('human_id'),
|
77
|
-
func.count(GoalieSaves.game_id).label('games_played'),
|
85
|
+
func.count(GoalieSaves.game_id).label('games_played'), # DEPRECATED - will be replaced by games_participated
|
86
|
+
func.sum(case(
|
87
|
+
(Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS]), 1),
|
88
|
+
else_=0
|
89
|
+
)).label('games_participated'),
|
90
|
+
func.sum(case(
|
91
|
+
(Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS]), 1),
|
92
|
+
else_=0
|
93
|
+
)).label('games_with_stats'),
|
78
94
|
func.sum(GoalieSaves.goals_allowed).label('goals_allowed'),
|
79
95
|
func.sum(GoalieSaves.shots_against).label('shots_faced'),
|
80
96
|
func.array_agg(GoalieSaves.game_id).label('game_ids')
|
@@ -94,7 +110,9 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
94
110
|
key = (aggregation_id, stat.human_id)
|
95
111
|
if key not in stats_dict:
|
96
112
|
stats_dict[key] = {
|
97
|
-
'games_played': 0,
|
113
|
+
'games_played': 0, # DEPRECATED - for backward compatibility
|
114
|
+
'games_participated': 0, # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
115
|
+
'games_with_stats': 0, # Games with full stats: FINAL, FINAL_SO only
|
98
116
|
'goals_allowed': 0,
|
99
117
|
'shots_faced': 0,
|
100
118
|
'goals_allowed_per_game': 0.0,
|
@@ -104,6 +122,8 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
104
122
|
'last_game_id': None
|
105
123
|
}
|
106
124
|
stats_dict[key]['games_played'] += stat.games_played
|
125
|
+
stats_dict[key]['games_participated'] += stat.games_participated
|
126
|
+
stats_dict[key]['games_with_stats'] += stat.games_with_stats
|
107
127
|
stats_dict[key]['goals_allowed'] += stat.goals_allowed if stat.goals_allowed is not None else 0
|
108
128
|
stats_dict[key]['shots_faced'] += stat.shots_faced if stat.shots_faced is not None else 0
|
109
129
|
stats_dict[key]['game_ids'].extend(stat.game_ids)
|
@@ -111,10 +131,10 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
111
131
|
# Filter out entries with games_played less than min_games
|
112
132
|
stats_dict = {key: value for key, value in stats_dict.items() if value['games_played'] >= min_games}
|
113
133
|
|
114
|
-
# Calculate per game stats
|
134
|
+
# Calculate per game stats (using games_with_stats as denominator for accuracy)
|
115
135
|
for key, stat in stats_dict.items():
|
116
|
-
if stat['
|
117
|
-
stat['goals_allowed_per_game'] = stat['goals_allowed'] / stat['
|
136
|
+
if stat['games_with_stats'] > 0:
|
137
|
+
stat['goals_allowed_per_game'] = stat['goals_allowed'] / stat['games_with_stats']
|
118
138
|
stat['save_percentage'] = (stat['shots_faced'] - stat['goals_allowed']) / stat['shots_faced'] if stat['shots_faced'] > 0 else 0.0
|
119
139
|
|
120
140
|
# Ensure all keys have valid human_id values
|
@@ -134,6 +154,8 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
134
154
|
|
135
155
|
# Assign ranks within each level
|
136
156
|
assign_ranks(stats_dict, 'games_played')
|
157
|
+
assign_ranks(stats_dict, 'games_participated') # Rank by total participation
|
158
|
+
assign_ranks(stats_dict, 'games_with_stats') # Rank by games with full stats
|
137
159
|
assign_ranks(stats_dict, 'goals_allowed', reverse_rank=True)
|
138
160
|
assign_ranks(stats_dict, 'shots_faced')
|
139
161
|
assign_ranks(stats_dict, 'goals_allowed_per_game', reverse_rank=True)
|
@@ -159,7 +181,11 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
159
181
|
goalie_stat = StatsModel(
|
160
182
|
aggregation_id=aggregation_id,
|
161
183
|
human_id=human_id,
|
162
|
-
games_played=stat['games_played'],
|
184
|
+
games_played=stat['games_played'], # DEPRECATED - for backward compatibility
|
185
|
+
games_participated=stat['games_participated'], # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
186
|
+
games_participated_rank=stat['games_participated_rank'],
|
187
|
+
games_with_stats=stat['games_with_stats'], # Games with full stats: FINAL, FINAL_SO only
|
188
|
+
games_with_stats_rank=stat['games_with_stats_rank'],
|
163
189
|
goals_allowed=stat['goals_allowed'],
|
164
190
|
shots_faced=stat['shots_faced'],
|
165
191
|
goals_allowed_per_game=goals_allowed_per_game,
|
@@ -17,6 +17,12 @@ from hockey_blast_common_lib.utils import get_start_datetime
|
|
17
17
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
18
18
|
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
19
19
|
|
20
|
+
# Import status constants for game filtering
|
21
|
+
FINAL_STATUS = "Final"
|
22
|
+
FINAL_SO_STATUS = "Final(SO)"
|
23
|
+
FORFEIT_STATUS = "FORFEIT"
|
24
|
+
NOEVENTS_STATUS = "NOEVENTS"
|
25
|
+
|
20
26
|
def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregation_window=None):
|
21
27
|
human_ids_to_filter = get_non_human_ids(session)
|
22
28
|
|
@@ -72,15 +78,33 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
72
78
|
|
73
79
|
filter_condition = filter_condition & (Division.id == Game.division_id)
|
74
80
|
# Aggregate games reffed for each referee
|
81
|
+
# games_participated: Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
82
|
+
# games_with_stats: Count only FINAL, FINAL_SO (for per-game averages)
|
75
83
|
games_reffed_stats = session.query(
|
76
84
|
Game.referee_1_id.label('human_id'),
|
77
|
-
func.count(Game.id).label('games_reffed'),
|
85
|
+
func.count(Game.id).label('games_reffed'), # DEPRECATED - will be replaced by games_participated
|
86
|
+
func.sum(case(
|
87
|
+
(Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS]), 1),
|
88
|
+
else_=0
|
89
|
+
)).label('games_participated'),
|
90
|
+
func.sum(case(
|
91
|
+
(Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS]), 1),
|
92
|
+
else_=0
|
93
|
+
)).label('games_with_stats'),
|
78
94
|
func.array_agg(Game.id).label('game_ids')
|
79
95
|
).filter(filter_condition).group_by(Game.referee_1_id).all()
|
80
96
|
|
81
97
|
games_reffed_stats_2 = session.query(
|
82
98
|
Game.referee_2_id.label('human_id'),
|
83
|
-
func.count(Game.id).label('games_reffed'),
|
99
|
+
func.count(Game.id).label('games_reffed'), # DEPRECATED - will be replaced by games_participated
|
100
|
+
func.sum(case(
|
101
|
+
(Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS]), 1),
|
102
|
+
else_=0
|
103
|
+
)).label('games_participated'),
|
104
|
+
func.sum(case(
|
105
|
+
(Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS]), 1),
|
106
|
+
else_=0
|
107
|
+
)).label('games_with_stats'),
|
84
108
|
func.array_agg(Game.id).label('game_ids')
|
85
109
|
).filter(filter_condition).group_by(Game.referee_2_id).all()
|
86
110
|
|
@@ -101,7 +125,9 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
101
125
|
key = (aggregation_id, stat.human_id)
|
102
126
|
if key not in stats_dict:
|
103
127
|
stats_dict[key] = {
|
104
|
-
'games_reffed': 0,
|
128
|
+
'games_reffed': 0, # DEPRECATED - for backward compatibility
|
129
|
+
'games_participated': 0, # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
130
|
+
'games_with_stats': 0, # Games with full stats: FINAL, FINAL_SO only
|
105
131
|
'penalties_given': 0,
|
106
132
|
'gm_given': 0,
|
107
133
|
'penalties_per_game': 0.0,
|
@@ -111,6 +137,8 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
111
137
|
'last_game_id': None
|
112
138
|
}
|
113
139
|
stats_dict[key]['games_reffed'] += stat.games_reffed
|
140
|
+
stats_dict[key]['games_participated'] += stat.games_participated
|
141
|
+
stats_dict[key]['games_with_stats'] += stat.games_with_stats
|
114
142
|
stats_dict[key]['game_ids'].extend(stat.game_ids)
|
115
143
|
|
116
144
|
for stat in games_reffed_stats_2:
|
@@ -119,7 +147,9 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
119
147
|
key = (aggregation_id, stat.human_id)
|
120
148
|
if key not in stats_dict:
|
121
149
|
stats_dict[key] = {
|
122
|
-
'games_reffed': 0,
|
150
|
+
'games_reffed': 0, # DEPRECATED - for backward compatibility
|
151
|
+
'games_participated': 0, # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
152
|
+
'games_with_stats': 0, # Games with full stats: FINAL, FINAL_SO only
|
123
153
|
'penalties_given': 0,
|
124
154
|
'gm_given': 0,
|
125
155
|
'penalties_per_game': 0.0,
|
@@ -129,6 +159,8 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
129
159
|
'last_game_id': None
|
130
160
|
}
|
131
161
|
stats_dict[key]['games_reffed'] += stat.games_reffed
|
162
|
+
stats_dict[key]['games_participated'] += stat.games_participated
|
163
|
+
stats_dict[key]['games_with_stats'] += stat.games_with_stats
|
132
164
|
stats_dict[key]['game_ids'].extend(stat.game_ids)
|
133
165
|
|
134
166
|
# Filter out entries with games_reffed less than min_games
|
@@ -149,11 +181,11 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
149
181
|
stats_dict[key]['gm_given'] += stat.gm_given / 2
|
150
182
|
stats_dict[key]['game_ids'].append(stat.game_id)
|
151
183
|
|
152
|
-
# Calculate per game stats
|
184
|
+
# Calculate per game stats (using games_with_stats as denominator for accuracy)
|
153
185
|
for key, stat in stats_dict.items():
|
154
|
-
if stat['
|
155
|
-
stat['penalties_per_game'] = stat['penalties_given'] / stat['
|
156
|
-
stat['gm_per_game'] = stat['gm_given'] / stat['
|
186
|
+
if stat['games_with_stats'] > 0:
|
187
|
+
stat['penalties_per_game'] = stat['penalties_given'] / stat['games_with_stats']
|
188
|
+
stat['gm_per_game'] = stat['gm_given'] / stat['games_with_stats']
|
157
189
|
|
158
190
|
# Ensure all keys have valid human_id values
|
159
191
|
stats_dict = {key: value for key, value in stats_dict.items() if key[1] is not None}
|
@@ -172,6 +204,8 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
172
204
|
|
173
205
|
# Assign ranks
|
174
206
|
assign_ranks(stats_dict, 'games_reffed')
|
207
|
+
assign_ranks(stats_dict, 'games_participated') # Rank by total participation
|
208
|
+
assign_ranks(stats_dict, 'games_with_stats') # Rank by games with full stats
|
175
209
|
assign_ranks(stats_dict, 'penalties_given')
|
176
210
|
assign_ranks(stats_dict, 'penalties_per_game')
|
177
211
|
assign_ranks(stats_dict, 'gm_given')
|
@@ -185,7 +219,11 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
185
219
|
referee_stat = StatsModel(
|
186
220
|
aggregation_id=aggregation_id,
|
187
221
|
human_id=human_id,
|
188
|
-
games_reffed=stat['games_reffed'],
|
222
|
+
games_reffed=stat['games_reffed'], # DEPRECATED - for backward compatibility
|
223
|
+
games_participated=stat['games_participated'], # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
224
|
+
games_participated_rank=stat['games_participated_rank'],
|
225
|
+
games_with_stats=stat['games_with_stats'], # Games with full stats: FINAL, FINAL_SO only
|
226
|
+
games_with_stats_rank=stat['games_with_stats_rank'],
|
189
227
|
penalties_given=stat['penalties_given'],
|
190
228
|
penalties_per_game=stat['penalties_per_game'],
|
191
229
|
gm_given=stat['gm_given'],
|
@@ -17,6 +17,12 @@ from hockey_blast_common_lib.utils import get_start_datetime
|
|
17
17
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
18
18
|
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
19
19
|
|
20
|
+
# Import status constants for game filtering
|
21
|
+
FINAL_STATUS = "Final"
|
22
|
+
FINAL_SO_STATUS = "Final(SO)"
|
23
|
+
FORFEIT_STATUS = "FORFEIT"
|
24
|
+
NOEVENTS_STATUS = "NOEVENTS"
|
25
|
+
|
20
26
|
def calculate_quality_score(avg_max_saves_5sec, avg_max_saves_20sec, peak_max_saves_5sec, peak_max_saves_20sec):
|
21
27
|
"""
|
22
28
|
Calculate a quality score based on excessive clicking patterns.
|
@@ -89,9 +95,19 @@ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, aggre
|
|
89
95
|
|
90
96
|
|
91
97
|
# Aggregate scorekeeper quality data for each human
|
98
|
+
# games_participated: Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
99
|
+
# games_with_stats: Count only FINAL, FINAL_SO (for per-game averages)
|
92
100
|
scorekeeper_quality_stats = session.query(
|
93
101
|
ScorekeeperSaveQuality.scorekeeper_id.label('human_id'),
|
94
|
-
func.count(ScorekeeperSaveQuality.game_id).label('games_recorded'),
|
102
|
+
func.count(ScorekeeperSaveQuality.game_id).label('games_recorded'), # DEPRECATED - will be replaced by games_participated
|
103
|
+
func.sum(case(
|
104
|
+
(Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS]), 1),
|
105
|
+
else_=0
|
106
|
+
)).label('games_participated'),
|
107
|
+
func.sum(case(
|
108
|
+
(Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS]), 1),
|
109
|
+
else_=0
|
110
|
+
)).label('games_with_stats'),
|
95
111
|
func.sum(ScorekeeperSaveQuality.total_saves_recorded).label('total_saves_recorded'),
|
96
112
|
func.avg(ScorekeeperSaveQuality.total_saves_recorded).label('avg_saves_per_game'),
|
97
113
|
func.avg(ScorekeeperSaveQuality.max_saves_per_5sec).label('avg_max_saves_per_5sec'),
|
@@ -120,7 +136,9 @@ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, aggre
|
|
120
136
|
)
|
121
137
|
|
122
138
|
stats_dict[key] = {
|
123
|
-
'games_recorded': stat.games_recorded,
|
139
|
+
'games_recorded': stat.games_recorded, # DEPRECATED - for backward compatibility
|
140
|
+
'games_participated': stat.games_participated, # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
141
|
+
'games_with_stats': stat.games_with_stats, # Games with full stats: FINAL, FINAL_SO only
|
124
142
|
'sog_given': stat.total_saves_recorded, # Legacy field name mapping
|
125
143
|
'sog_per_game': stat.avg_saves_per_game or 0.0, # Legacy field name mapping
|
126
144
|
'total_saves_recorded': stat.total_saves_recorded,
|
@@ -152,6 +170,8 @@ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, aggre
|
|
152
170
|
|
153
171
|
# Assign ranks - note: for quality metrics, lower values are better (reverse_rank=True for avg and peak clicking)
|
154
172
|
assign_ranks(stats_dict, 'games_recorded')
|
173
|
+
assign_ranks(stats_dict, 'games_participated') # Rank by total participation
|
174
|
+
assign_ranks(stats_dict, 'games_with_stats') # Rank by games with full stats
|
155
175
|
assign_ranks(stats_dict, 'sog_given') # Legacy field
|
156
176
|
assign_ranks(stats_dict, 'sog_per_game') # Legacy field
|
157
177
|
assign_ranks(stats_dict, 'total_saves_recorded')
|
@@ -169,7 +189,11 @@ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, aggre
|
|
169
189
|
scorekeeper_stat = StatsModel(
|
170
190
|
aggregation_id=aggregation_id,
|
171
191
|
human_id=human_id,
|
172
|
-
games_recorded=stat['games_recorded'],
|
192
|
+
games_recorded=stat['games_recorded'], # DEPRECATED - for backward compatibility
|
193
|
+
games_participated=stat['games_participated'], # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
194
|
+
games_participated_rank=stat['games_participated_rank'],
|
195
|
+
games_with_stats=stat['games_with_stats'], # Games with full stats: FINAL, FINAL_SO only
|
196
|
+
games_with_stats_rank=stat['games_with_stats_rank'],
|
173
197
|
sog_given=stat['sog_given'], # Legacy field mapping
|
174
198
|
sog_per_game=stat['sog_per_game'], # Legacy field mapping
|
175
199
|
total_saves_recorded=stat['total_saves_recorded'],
|
@@ -18,6 +18,12 @@ from collections import defaultdict
|
|
18
18
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
19
19
|
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
20
20
|
|
21
|
+
# Import status constants for game filtering
|
22
|
+
FINAL_STATUS = "Final"
|
23
|
+
FINAL_SO_STATUS = "Final(SO)"
|
24
|
+
FORFEIT_STATUS = "FORFEIT"
|
25
|
+
NOEVENTS_STATUS = "NOEVENTS"
|
26
|
+
|
21
27
|
def calculate_current_point_streak(session, human_id, filter_condition):
|
22
28
|
"""
|
23
29
|
Calculate the current point streak for a player.
|
@@ -138,16 +144,26 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
138
144
|
# human_filter = [GameRoster.human_id == debug_human_id]
|
139
145
|
|
140
146
|
# Aggregate games played for each human in each division, excluding goalies
|
147
|
+
# games_participated: Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
148
|
+
# games_with_stats: Count only FINAL, FINAL_SO (for per-game averages)
|
141
149
|
games_played_query = session.query(
|
142
150
|
GameRoster.human_id,
|
143
|
-
func.count(Game.id).label('games_played'),
|
151
|
+
func.count(Game.id).label('games_played'), # DEPRECATED - will be replaced by games_participated
|
152
|
+
func.sum(case(
|
153
|
+
(Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS]), 1),
|
154
|
+
else_=0
|
155
|
+
)).label('games_participated'),
|
156
|
+
func.sum(case(
|
157
|
+
(Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS]), 1),
|
158
|
+
else_=0
|
159
|
+
)).label('games_with_stats'),
|
144
160
|
func.array_agg(Game.id).label('game_ids')
|
145
161
|
).join(Game, Game.id == GameRoster.game_id)
|
146
|
-
|
162
|
+
|
147
163
|
# Only join Division if not level aggregation (since we filter on Game.division_id directly for levels)
|
148
164
|
if aggregation_type != 'level':
|
149
165
|
games_played_query = games_played_query.join(Division, Game.division_id == Division.id)
|
150
|
-
|
166
|
+
|
151
167
|
games_played_stats = games_played_query.filter(filter_condition, ~GameRoster.role.ilike('g'), *human_filter).group_by(GameRoster.human_id).all()
|
152
168
|
|
153
169
|
# Aggregate goals for each human in each division, excluding goalies
|
@@ -206,7 +222,9 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
206
222
|
key = (aggregation_id, stat.human_id)
|
207
223
|
if key not in stats_dict:
|
208
224
|
stats_dict[key] = {
|
209
|
-
'games_played': 0,
|
225
|
+
'games_played': 0, # DEPRECATED - for backward compatibility
|
226
|
+
'games_participated': 0, # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
227
|
+
'games_with_stats': 0, # Games with full stats: FINAL, FINAL_SO only
|
210
228
|
'goals': 0,
|
211
229
|
'assists': 0,
|
212
230
|
'penalties': 0,
|
@@ -224,6 +242,8 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
224
242
|
'last_game_id': None
|
225
243
|
}
|
226
244
|
stats_dict[key]['games_played'] += stat.games_played
|
245
|
+
stats_dict[key]['games_participated'] += stat.games_participated
|
246
|
+
stats_dict[key]['games_with_stats'] += stat.games_with_stats
|
227
247
|
stats_dict[key]['game_ids'].extend(stat.game_ids)
|
228
248
|
|
229
249
|
# Filter out entries with games_played less than min_games
|
@@ -253,14 +273,14 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
253
273
|
stats_dict[key]['penalties'] += stat.penalties
|
254
274
|
stats_dict[key]['gm_penalties'] += stat.gm_penalties # Update GM penalties
|
255
275
|
|
256
|
-
# Calculate per game stats
|
276
|
+
# Calculate per game stats (using games_with_stats as denominator for accuracy)
|
257
277
|
for key, stat in stats_dict.items():
|
258
|
-
if stat['
|
259
|
-
stat['goals_per_game'] = stat['goals'] / stat['
|
260
|
-
stat['points_per_game'] = stat['points'] / stat['
|
261
|
-
stat['assists_per_game'] = stat['assists'] / stat['
|
262
|
-
stat['penalties_per_game'] = stat['penalties'] / stat['
|
263
|
-
stat['gm_penalties_per_game'] = stat['gm_penalties'] / stat['
|
278
|
+
if stat['games_with_stats'] > 0:
|
279
|
+
stat['goals_per_game'] = stat['goals'] / stat['games_with_stats']
|
280
|
+
stat['points_per_game'] = stat['points'] / stat['games_with_stats']
|
281
|
+
stat['assists_per_game'] = stat['assists'] / stat['games_with_stats']
|
282
|
+
stat['penalties_per_game'] = stat['penalties'] / stat['games_with_stats']
|
283
|
+
stat['gm_penalties_per_game'] = stat['gm_penalties'] / stat['games_with_stats'] # Calculate GM penalties per game
|
264
284
|
|
265
285
|
# Ensure all keys have valid human_id values
|
266
286
|
stats_dict = {key: value for key, value in stats_dict.items() if key[1] is not None}
|
@@ -292,6 +312,8 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
292
312
|
stats_dict[key][f'{field}_rank'] = rank
|
293
313
|
|
294
314
|
assign_ranks(stats_dict, 'games_played')
|
315
|
+
assign_ranks(stats_dict, 'games_participated') # Rank by total participation
|
316
|
+
assign_ranks(stats_dict, 'games_with_stats') # Rank by games with full stats
|
295
317
|
assign_ranks(stats_dict, 'goals')
|
296
318
|
assign_ranks(stats_dict, 'assists')
|
297
319
|
assign_ranks(stats_dict, 'points')
|
@@ -330,7 +352,11 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
330
352
|
skater_stat = StatsModel(
|
331
353
|
aggregation_id=aggregation_id,
|
332
354
|
human_id=human_id,
|
333
|
-
games_played=stat['games_played'],
|
355
|
+
games_played=stat['games_played'], # DEPRECATED - for backward compatibility
|
356
|
+
games_participated=stat['games_participated'], # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
357
|
+
games_participated_rank=stat['games_participated_rank'],
|
358
|
+
games_with_stats=stat['games_with_stats'], # Games with full stats: FINAL, FINAL_SO only
|
359
|
+
games_with_stats_rank=stat['games_with_stats_rank'],
|
334
360
|
goals=stat['goals'],
|
335
361
|
assists=stat['assists'],
|
336
362
|
points=stat['goals'] + stat['assists'],
|
Binary file
|
@@ -4,26 +4,34 @@ from datetime import datetime, timedelta
|
|
4
4
|
class ProgressTracker:
|
5
5
|
"""
|
6
6
|
Reusable progress tracker with ETA calculation for stats aggregation processes.
|
7
|
+
Supports optional custom counters for tracking additional metrics.
|
7
8
|
"""
|
8
|
-
|
9
|
-
def __init__(self, total_items, description="Processing"):
|
9
|
+
|
10
|
+
def __init__(self, total_items, description="Processing", custom_counters=None):
|
10
11
|
self.total_items = total_items
|
11
12
|
self.description = description
|
12
13
|
self.start_time = time.time()
|
13
14
|
self.processed_items = 0
|
14
15
|
self.last_update_time = self.start_time
|
16
|
+
self.custom_counters = custom_counters if custom_counters else {}
|
15
17
|
|
16
|
-
def update(self, processed_count=None):
|
18
|
+
def update(self, processed_count=None, **kwargs):
|
17
19
|
"""
|
18
20
|
Update progress. If processed_count is None, increment by 1.
|
21
|
+
kwargs can be used to update custom counters.
|
19
22
|
"""
|
20
23
|
if processed_count is not None:
|
21
24
|
self.processed_items = processed_count
|
22
25
|
else:
|
23
26
|
self.processed_items += 1
|
24
|
-
|
27
|
+
|
28
|
+
# Update custom counters
|
29
|
+
for key, value in kwargs.items():
|
30
|
+
if key in self.custom_counters:
|
31
|
+
self.custom_counters[key] = value
|
32
|
+
|
25
33
|
current_time = time.time()
|
26
|
-
|
34
|
+
|
27
35
|
# Only update display if at least 0.1 seconds have passed (to avoid spamming)
|
28
36
|
if current_time - self.last_update_time >= 0.1 or self.processed_items == self.total_items:
|
29
37
|
self._display_progress()
|
@@ -52,10 +60,15 @@ class ProgressTracker:
|
|
52
60
|
eta_str = self._format_time(eta_seconds)
|
53
61
|
|
54
62
|
elapsed_str = self._format_time(elapsed_time)
|
55
|
-
|
63
|
+
|
56
64
|
progress_msg = f"\r{self.description}: {self.processed_items}/{self.total_items} ({percentage:.1f}%) | "
|
57
65
|
progress_msg += f"Elapsed: {elapsed_str} | ETA: {eta_str}"
|
58
|
-
|
66
|
+
|
67
|
+
# Add custom counters to the display
|
68
|
+
if self.custom_counters:
|
69
|
+
counter_parts = [f"{key}: {value}" for key, value in self.custom_counters.items()]
|
70
|
+
progress_msg += " | " + ", ".join(counter_parts)
|
71
|
+
|
59
72
|
print(progress_msg, end="", flush=True)
|
60
73
|
|
61
74
|
# Add newline when complete
|
@@ -84,8 +97,9 @@ class ProgressTracker:
|
|
84
97
|
self.processed_items = self.total_items
|
85
98
|
self._display_progress()
|
86
99
|
|
87
|
-
def create_progress_tracker(total_items, description="Processing"):
|
100
|
+
def create_progress_tracker(total_items, description="Processing", custom_counters=None):
|
88
101
|
"""
|
89
102
|
Factory function to create a progress tracker.
|
103
|
+
custom_counters: dict of counter_name: initial_value for additional metrics to track
|
90
104
|
"""
|
91
|
-
return ProgressTracker(total_items, description)
|
105
|
+
return ProgressTracker(total_items, description, custom_counters)
|
@@ -52,8 +52,12 @@ class BaseStatsSkater(db.Model):
|
|
52
52
|
__abstract__ = True
|
53
53
|
id = db.Column(db.Integer, primary_key=True)
|
54
54
|
human_id = db.Column(db.Integer, db.ForeignKey('humans.id'), nullable=False)
|
55
|
-
games_played = db.Column(db.Integer, default=0)
|
55
|
+
games_played = db.Column(db.Integer, default=0) # DEPRECATED - use games_participated instead
|
56
56
|
games_played_rank = db.Column(db.Integer, default=0)
|
57
|
+
games_participated = db.Column(db.Integer, default=0) # Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
58
|
+
games_participated_rank = db.Column(db.Integer, default=0)
|
59
|
+
games_with_stats = db.Column(db.Integer, default=0) # Count only FINAL, FINAL_SO
|
60
|
+
games_with_stats_rank = db.Column(db.Integer, default=0)
|
57
61
|
goals = db.Column(db.Integer, default=0)
|
58
62
|
goals_rank = db.Column(db.Integer, default=0)
|
59
63
|
assists = db.Column(db.Integer, default=0)
|
@@ -104,8 +108,12 @@ class BaseStatsGoalie(db.Model):
|
|
104
108
|
__abstract__ = True
|
105
109
|
id = db.Column(db.Integer, primary_key=True)
|
106
110
|
human_id = db.Column(db.Integer, db.ForeignKey('humans.id'), nullable=False)
|
107
|
-
games_played = db.Column(db.Integer, default=0)
|
111
|
+
games_played = db.Column(db.Integer, default=0) # DEPRECATED - use games_participated instead
|
108
112
|
games_played_rank = db.Column(db.Integer, default=0)
|
113
|
+
games_participated = db.Column(db.Integer, default=0) # Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
114
|
+
games_participated_rank = db.Column(db.Integer, default=0)
|
115
|
+
games_with_stats = db.Column(db.Integer, default=0) # Count only FINAL, FINAL_SO
|
116
|
+
games_with_stats_rank = db.Column(db.Integer, default=0)
|
109
117
|
goals_allowed = db.Column(db.Integer, default=0)
|
110
118
|
goals_allowed_rank = db.Column(db.Integer, default=0)
|
111
119
|
goals_allowed_per_game = db.Column(db.Float, default=0.0)
|
@@ -137,8 +145,12 @@ class BaseStatsReferee(db.Model):
|
|
137
145
|
__abstract__ = True
|
138
146
|
id = db.Column(db.Integer, primary_key=True)
|
139
147
|
human_id = db.Column(db.Integer, db.ForeignKey('humans.id'), nullable=False)
|
140
|
-
games_reffed = db.Column(db.Integer, default=0)
|
148
|
+
games_reffed = db.Column(db.Integer, default=0) # DEPRECATED - use games_participated instead
|
141
149
|
games_reffed_rank = db.Column(db.Integer, default=0)
|
150
|
+
games_participated = db.Column(db.Integer, default=0) # Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
151
|
+
games_participated_rank = db.Column(db.Integer, default=0)
|
152
|
+
games_with_stats = db.Column(db.Integer, default=0) # Count only FINAL, FINAL_SO (for per-game averages)
|
153
|
+
games_with_stats_rank = db.Column(db.Integer, default=0)
|
142
154
|
penalties_given = db.Column(db.Integer, default=0)
|
143
155
|
penalties_given_rank = db.Column(db.Integer, default=0)
|
144
156
|
penalties_per_game = db.Column(db.Float, default=0.0)
|
@@ -170,8 +182,12 @@ class BaseStatsScorekeeper(db.Model):
|
|
170
182
|
__abstract__ = True
|
171
183
|
id = db.Column(db.Integer, primary_key=True)
|
172
184
|
human_id = db.Column(db.Integer, db.ForeignKey('humans.id'), nullable=False)
|
173
|
-
games_recorded = db.Column(db.Integer, default=0)
|
185
|
+
games_recorded = db.Column(db.Integer, default=0) # DEPRECATED - use games_participated instead
|
174
186
|
games_recorded_rank = db.Column(db.Integer, default=0)
|
187
|
+
games_participated = db.Column(db.Integer, default=0) # Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
188
|
+
games_participated_rank = db.Column(db.Integer, default=0)
|
189
|
+
games_with_stats = db.Column(db.Integer, default=0) # Count only FINAL, FINAL_SO (for per-game averages)
|
190
|
+
games_with_stats_rank = db.Column(db.Integer, default=0)
|
175
191
|
sog_given = db.Column(db.Integer, default=0)
|
176
192
|
sog_given_rank = db.Column(db.Integer, default=0)
|
177
193
|
sog_per_game = db.Column(db.Float, default=0.0)
|
{hockey_blast_common_lib-0.1.61.dist-info → hockey_blast_common_lib-0.1.62.dist-info}/RECORD
RENAMED
@@ -1,28 +1,28 @@
|
|
1
1
|
hockey_blast_common_lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
hockey_blast_common_lib/aggregate_all_stats.py,sha256=QhuSvGjuk4jVywNRcgxB-9ooJAoAbZRkaLjLe9Q1hEM,1363
|
3
|
-
hockey_blast_common_lib/aggregate_goalie_stats.py,sha256=
|
3
|
+
hockey_blast_common_lib/aggregate_goalie_stats.py,sha256=5FwmRAW8ydgFd7LGZtH1j-7TCA50piT6igCsuy8bBqo,14883
|
4
4
|
hockey_blast_common_lib/aggregate_h2h_stats.py,sha256=dC5TcJZGkpIQTiq3z40kOX6EjEhFbGv5EL0P1EClBQ0,11117
|
5
5
|
hockey_blast_common_lib/aggregate_human_stats.py,sha256=JZcXJofF1wmFPcLbalM_GiXrq22eUJO28R_xyypw4rI,25456
|
6
|
-
hockey_blast_common_lib/aggregate_referee_stats.py,sha256=
|
6
|
+
hockey_blast_common_lib/aggregate_referee_stats.py,sha256=rVxbuDjxYbI5zNlvOSbSHeG8yeuDpJSLNmjZKn3rBhQ,15764
|
7
7
|
hockey_blast_common_lib/aggregate_s2s_stats.py,sha256=urYN0Q06twwLO-XWGlSMVAVOTVR_D2AWdmoGsxIYHXE,6737
|
8
|
-
hockey_blast_common_lib/aggregate_scorekeeper_stats.py,sha256=
|
9
|
-
hockey_blast_common_lib/aggregate_skater_stats.py,sha256=
|
8
|
+
hockey_blast_common_lib/aggregate_scorekeeper_stats.py,sha256=_0HR-KsuEPM7LFTVvmm8RwH_Y1_H9qrvW-wEMFI7LNU,13488
|
9
|
+
hockey_blast_common_lib/aggregate_skater_stats.py,sha256=Tp7tJ03YdlVsm5yBNJYc7TC243m7GRtcHWO2o4X6kG4,24383
|
10
10
|
hockey_blast_common_lib/assign_skater_skill.py,sha256=Asq6iRMPsCMDnvuNSd-M3s4Gee4kDocP9Eznwju_9kA,2749
|
11
11
|
hockey_blast_common_lib/db_connection.py,sha256=HvPxDvOj7j5H85RfslGvHVNevfg7mKCd0syJ6NX21mU,1890
|
12
12
|
hockey_blast_common_lib/dump_sample_db.sh,sha256=MY3lnzTXBoWd76-ZlZr9nWsKMEVgyRsUn-LZ2d1JWZs,810
|
13
13
|
hockey_blast_common_lib/h2h_models.py,sha256=0st4xoJO0U6ONfx3BV03BQvHjZE31e_PqZfphAJMoSU,7968
|
14
|
-
hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=
|
14
|
+
hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=57W5sPGpAp01YtIe5XrQeCgBCDFTiI-347nuzN3d3IU,4648906
|
15
15
|
hockey_blast_common_lib/models.py,sha256=ccM886RcSFDiJ3yj2l9OqRi_PwR1L6WU8AsqzgV3_t0,19598
|
16
16
|
hockey_blast_common_lib/options.py,sha256=XecGGlizbul7BnMePrvIqvEq5_w49UoG3Yu9iv961gg,1499
|
17
|
-
hockey_blast_common_lib/progress_utils.py,sha256=
|
17
|
+
hockey_blast_common_lib/progress_utils.py,sha256=7cqyUTMmW3xAIh5JKFlhnBiybCJ9WvGDz7ihH59Lc_0,3953
|
18
18
|
hockey_blast_common_lib/restore_sample_db.sh,sha256=7W3lzRZeu9zXIu1Bvtnaw8EHc1ulHmFM4mMh86oUQJo,2205
|
19
19
|
hockey_blast_common_lib/skills_in_divisions.py,sha256=m-UEwMwn1KM7wOYvDstgsOEeH57M9V6yrkBoghzGYKE,7005
|
20
20
|
hockey_blast_common_lib/skills_propagation.py,sha256=nUxntyK8M4xWjHpkfze8f0suaBeunxicgDCduGmNJ-A,18468
|
21
|
-
hockey_blast_common_lib/stats_models.py,sha256=
|
21
|
+
hockey_blast_common_lib/stats_models.py,sha256=yLfsR0RhSc95-8ULdJ8tuvLd6RjIFkgZ74ejubJYVUw,28187
|
22
22
|
hockey_blast_common_lib/stats_utils.py,sha256=DXsPO4jw8XsdRUN46TGF_IiBAfz3GCIVBswCGp5ELDk,284
|
23
23
|
hockey_blast_common_lib/utils.py,sha256=1YJRAj1lhftjIAM2frFi4A4K90kCJaxWlgBQ1-77xZY,6486
|
24
24
|
hockey_blast_common_lib/wsgi.py,sha256=y3NxoJfWjdzX3iP7RGvDEer6zcnPyCanpqSgW1BlXgg,779
|
25
|
-
hockey_blast_common_lib-0.1.
|
26
|
-
hockey_blast_common_lib-0.1.
|
27
|
-
hockey_blast_common_lib-0.1.
|
28
|
-
hockey_blast_common_lib-0.1.
|
25
|
+
hockey_blast_common_lib-0.1.62.dist-info/METADATA,sha256=zCGOcewa8kzP4jNrlDR-xmPFh7SygSOsYtXXG9kVhRc,318
|
26
|
+
hockey_blast_common_lib-0.1.62.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
27
|
+
hockey_blast_common_lib-0.1.62.dist-info/top_level.txt,sha256=wIR4LIkE40npoA2QlOdfCYlgFeGbsHR8Z6r0h46Vtgc,24
|
28
|
+
hockey_blast_common_lib-0.1.62.dist-info/RECORD,,
|
File without changes
|
{hockey_blast_common_lib-0.1.61.dist-info → hockey_blast_common_lib-0.1.62.dist-info}/top_level.txt
RENAMED
File without changes
|