hockey-blast-common-lib 0.1.60__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.
@@ -9,15 +9,21 @@ 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
13
- from hockey_blast_common_lib.options import not_human_names, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
14
- from hockey_blast_common_lib.utils import get_human_ids_by_names, get_all_division_ids_for_org, get_start_datetime
12
+ from sqlalchemy.sql import func, case
13
+ from hockey_blast_common_lib.options import MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
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
- def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_filter_out, debug_human_id=None, aggregation_window=None):
20
- human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
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
+
25
+ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_human_id=None, aggregation_window=None):
26
+ human_ids_to_filter = get_non_human_ids(session)
21
27
 
22
28
  # Get the name of the aggregation, for debug purposes
23
29
  if aggregation_type == 'org':
@@ -72,9 +78,19 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
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, names_to_f
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, names_to_f
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, names_to_f
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['games_played'] > 0:
117
- stat['goals_allowed_per_game'] = stat['goals_allowed'] / stat['games_played']
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, names_to_f
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, names_to_f
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,
@@ -195,30 +221,30 @@ def run_aggregate_goalie_stats():
195
221
  # Process divisions with progress tracking
196
222
  progress = create_progress_tracker(len(division_ids), f"Processing {len(division_ids)} divisions for {org_name}")
197
223
  for i, division_id in enumerate(division_ids):
198
- aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
199
- aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
200
- aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Daily')
224
+ aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug)
225
+ aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
226
+ aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug, aggregation_window='Daily')
201
227
  progress.update(i + 1)
202
228
  else:
203
229
  # Debug mode or no divisions - process without progress tracking
204
230
  for division_id in division_ids:
205
- aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
206
- aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
207
- aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Daily')
231
+ aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug)
232
+ aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
233
+ aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug, aggregation_window='Daily')
208
234
 
209
235
  # Process org-level stats with progress tracking
210
236
  if human_id_to_debug is None:
211
237
  org_progress = create_progress_tracker(3, f"Processing org-level stats for {org_name}")
212
- aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
238
+ aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug)
213
239
  org_progress.update(1)
214
- aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
240
+ aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
215
241
  org_progress.update(2)
216
- aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Daily')
242
+ aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug, aggregation_window='Daily')
217
243
  org_progress.update(3)
218
244
  else:
219
- aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
220
- aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
221
- aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Daily')
245
+ aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug)
246
+ aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
247
+ aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug, aggregation_window='Daily')
222
248
 
223
249
  # Aggregate by level
224
250
  level_ids = session.query(Division.level_id).distinct().all()
@@ -228,12 +254,12 @@ def run_aggregate_goalie_stats():
228
254
  # Process levels with progress tracking
229
255
  level_progress = create_progress_tracker(len(level_ids), f"Processing {len(level_ids)} skill levels")
230
256
  for i, level_id in enumerate(level_ids):
231
- aggregate_goalie_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
257
+ aggregate_goalie_stats(session, aggregation_type='level', aggregation_id=level_id, debug_human_id=human_id_to_debug)
232
258
  level_progress.update(i + 1)
233
259
  else:
234
260
  # Debug mode or no levels - process without progress tracking
235
261
  for level_id in level_ids:
236
- aggregate_goalie_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
262
+ aggregate_goalie_stats(session, aggregation_type='level', aggregation_id=level_id, debug_human_id=human_id_to_debug)
237
263
 
238
264
  if __name__ == "__main__":
239
265
  run_aggregate_goalie_stats()
@@ -10,15 +10,15 @@ from hockey_blast_common_lib.models import Game, GameRoster, Organization, Divis
10
10
  from hockey_blast_common_lib.stats_models import OrgStatsHuman, DivisionStatsHuman, OrgStatsDailyHuman, OrgStatsWeeklyHuman, DivisionStatsDailyHuman, DivisionStatsWeeklyHuman, LevelStatsHuman
11
11
  from hockey_blast_common_lib.db_connection import create_session
12
12
  from sqlalchemy.sql import func, case
13
- from hockey_blast_common_lib.options import parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS, not_human_names
14
- from hockey_blast_common_lib.utils import get_fake_human_for_stats, get_org_id_from_alias, get_human_ids_by_names, get_division_ids_for_last_season_in_all_leagues, get_all_division_ids_for_org
13
+ from hockey_blast_common_lib.options import parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
14
+ from hockey_blast_common_lib.utils import get_fake_human_for_stats, get_org_id_from_alias, get_non_human_ids, get_division_ids_for_last_season_in_all_leagues, get_all_division_ids_for_org
15
15
  from hockey_blast_common_lib.utils import assign_ranks
16
16
  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
- def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_filter_out, human_id_filter=None, aggregation_window=None):
21
- human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
20
+ def aggregate_human_stats(session, aggregation_type, aggregation_id, human_id_filter=None, aggregation_window=None):
21
+ human_ids_to_filter = get_non_human_ids(session)
22
22
 
23
23
  if aggregation_type == 'org':
24
24
  if aggregation_id == ALL_ORGS_ID:
@@ -434,30 +434,30 @@ def run_aggregate_human_stats():
434
434
  # Process divisions with progress tracking
435
435
  progress = create_progress_tracker(len(division_ids), f"Processing {len(division_ids)} divisions for {org_name}")
436
436
  for i, division_id in enumerate(division_ids):
437
- aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
438
- aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
439
- aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Daily')
437
+ aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, human_id_filter=human_id_to_debug)
438
+ aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
439
+ aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, human_id_filter=human_id_to_debug, aggregation_window='Daily')
440
440
  progress.update(i + 1)
441
441
  else:
442
442
  # Debug mode or no divisions - process without progress tracking
443
443
  for division_id in division_ids:
444
- aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
445
- aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
446
- aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Daily')
444
+ aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, human_id_filter=human_id_to_debug)
445
+ aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
446
+ aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, human_id_filter=human_id_to_debug, aggregation_window='Daily')
447
447
 
448
448
  # Process org-level stats with progress tracking
449
449
  if human_id_to_debug is None:
450
450
  org_progress = create_progress_tracker(3, f"Processing org-level stats for {org_name}")
451
- aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
451
+ aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, human_id_filter=human_id_to_debug)
452
452
  org_progress.update(1)
453
- aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
453
+ aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
454
454
  org_progress.update(2)
455
- aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Daily')
455
+ aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, human_id_filter=human_id_to_debug, aggregation_window='Daily')
456
456
  org_progress.update(3)
457
457
  else:
458
- aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
459
- aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
460
- aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Daily')
458
+ aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, human_id_filter=human_id_to_debug)
459
+ aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
460
+ aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, human_id_filter=human_id_to_debug, aggregation_window='Daily')
461
461
 
462
462
  # Aggregate by level
463
463
  level_ids = session.query(Division.level_id).distinct().all()
@@ -467,12 +467,12 @@ def run_aggregate_human_stats():
467
467
  # Process levels with progress tracking
468
468
  level_progress = create_progress_tracker(len(level_ids), f"Processing {len(level_ids)} skill levels")
469
469
  for i, level_id in enumerate(level_ids):
470
- aggregate_human_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
470
+ aggregate_human_stats(session, aggregation_type='level', aggregation_id=level_id, human_id_filter=human_id_to_debug)
471
471
  level_progress.update(i + 1)
472
472
  else:
473
473
  # Debug mode or no levels - process without progress tracking
474
474
  for level_id in level_ids:
475
- aggregate_human_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
475
+ aggregate_human_stats(session, aggregation_type='level', aggregation_id=level_id, human_id_filter=human_id_to_debug)
476
476
 
477
477
  if __name__ == "__main__":
478
478
  run_aggregate_human_stats()
@@ -10,15 +10,21 @@ from hockey_blast_common_lib.models import Game, Penalty, Organization, Division
10
10
  from hockey_blast_common_lib.stats_models import OrgStatsReferee, DivisionStatsReferee, OrgStatsWeeklyReferee, OrgStatsDailyReferee, DivisionStatsWeeklyReferee, DivisionStatsDailyReferee, LevelStatsReferee
11
11
  from hockey_blast_common_lib.db_connection import create_session
12
12
  from sqlalchemy.sql import func, case
13
- from hockey_blast_common_lib.options import parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS, not_human_names
14
- from hockey_blast_common_lib.utils import get_org_id_from_alias, get_human_ids_by_names, get_division_ids_for_last_season_in_all_leagues, get_all_division_ids_for_org
13
+ from hockey_blast_common_lib.options import parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
14
+ from hockey_blast_common_lib.utils import get_org_id_from_alias, get_non_human_ids, get_division_ids_for_last_season_in_all_leagues, get_all_division_ids_for_org
15
15
  from hockey_blast_common_lib.utils import assign_ranks
16
16
  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
- def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_filter_out, aggregation_window=None):
21
- human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
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
+
26
+ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregation_window=None):
27
+ human_ids_to_filter = get_non_human_ids(session)
22
28
 
23
29
  if aggregation_type == 'org':
24
30
  if aggregation_id == ALL_ORGS_ID:
@@ -72,15 +78,33 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_
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, names_to_
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, names_to_
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, names_to_
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, names_to_
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, names_to_
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['games_reffed'] > 0:
155
- stat['penalties_per_game'] = stat['penalties_given'] / stat['games_reffed']
156
- stat['gm_per_game'] = stat['gm_given'] / stat['games_reffed']
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, names_to_
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, names_to_
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'],
@@ -221,30 +259,30 @@ def run_aggregate_referee_stats():
221
259
  # Process divisions with progress tracking
222
260
  progress = create_progress_tracker(len(division_ids), f"Processing {len(division_ids)} divisions for {org_name}")
223
261
  for i, division_id in enumerate(division_ids):
224
- aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names)
225
- aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
226
- aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
262
+ aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id)
263
+ aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, aggregation_window='Weekly')
264
+ aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, aggregation_window='Daily')
227
265
  progress.update(i + 1)
228
266
  else:
229
267
  # Debug mode or no divisions - process without progress tracking
230
268
  for division_id in division_ids:
231
- aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names)
232
- aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
233
- aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
269
+ aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id)
270
+ aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, aggregation_window='Weekly')
271
+ aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, aggregation_window='Daily')
234
272
 
235
273
  # Process org-level stats with progress tracking
236
274
  if human_id_to_debug is None:
237
275
  org_progress = create_progress_tracker(3, f"Processing org-level stats for {org_name}")
238
- aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names)
276
+ aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id)
239
277
  org_progress.update(1)
240
- aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
278
+ aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, aggregation_window='Weekly')
241
279
  org_progress.update(2)
242
- aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
280
+ aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, aggregation_window='Daily')
243
281
  org_progress.update(3)
244
282
  else:
245
- aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names)
246
- aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
247
- aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
283
+ aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id)
284
+ aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, aggregation_window='Weekly')
285
+ aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, aggregation_window='Daily')
248
286
 
249
287
  # Aggregate by level
250
288
  level_ids = session.query(Division.level_id).distinct().all()
@@ -254,12 +292,12 @@ def run_aggregate_referee_stats():
254
292
  # Process levels with progress tracking
255
293
  level_progress = create_progress_tracker(len(level_ids), f"Processing {len(level_ids)} skill levels")
256
294
  for i, level_id in enumerate(level_ids):
257
- aggregate_referee_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names)
295
+ aggregate_referee_stats(session, aggregation_type='level', aggregation_id=level_id)
258
296
  level_progress.update(i + 1)
259
297
  else:
260
298
  # Debug mode or no levels - process without progress tracking
261
299
  for level_id in level_ids:
262
- aggregate_referee_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names)
300
+ aggregate_referee_stats(session, aggregation_type='level', aggregation_id=level_id)
263
301
 
264
302
  if __name__ == "__main__":
265
303
  run_aggregate_referee_stats()
@@ -10,13 +10,19 @@ from hockey_blast_common_lib.models import Game, ScorekeeperSaveQuality
10
10
  from hockey_blast_common_lib.stats_models import OrgStatsScorekeeper, OrgStatsWeeklyScorekeeper, OrgStatsDailyScorekeeper
11
11
  from hockey_blast_common_lib.db_connection import create_session
12
12
  from sqlalchemy.sql import func, case
13
- from hockey_blast_common_lib.options import parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, not_human_names
14
- from hockey_blast_common_lib.utils import get_org_id_from_alias, get_human_ids_by_names
13
+ from hockey_blast_common_lib.options import parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS
14
+ from hockey_blast_common_lib.utils import get_org_id_from_alias, get_non_human_ids
15
15
  from hockey_blast_common_lib.utils import assign_ranks
16
16
  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.
@@ -51,13 +57,13 @@ def calculate_quality_score(avg_max_saves_5sec, avg_max_saves_20sec, peak_max_sa
51
57
 
52
58
  return round(score, 2)
53
59
 
54
- def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, names_to_filter_out, aggregation_window=None):
60
+ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, aggregation_window=None):
55
61
  # Only process scorekeeper stats for ALL_ORGS_ID - skip individual organizations
56
62
  # This prevents redundant processing when upstream logic calls with all organization IDs
57
63
  if aggregation_type == 'org' and aggregation_id != ALL_ORGS_ID:
58
64
  return # Do nothing for individual organization IDs
59
65
 
60
- human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
66
+ human_ids_to_filter = get_non_human_ids(session)
61
67
 
62
68
  if aggregation_type == 'org':
63
69
  aggregation_name = "All Orgs"
@@ -89,9 +95,19 @@ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, names
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, names
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, names
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, names
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'],
@@ -216,16 +240,16 @@ def run_aggregate_scorekeeper_stats():
216
240
  if human_id_to_debug is None:
217
241
  org_name = "All Organizations" if org_id == ALL_ORGS_ID else session.query(Organization.organization_name).filter(Organization.id == org_id).scalar() or f"org_id {org_id}"
218
242
  org_progress = create_progress_tracker(3, f"Processing scorekeeper stats for {org_name}")
219
- aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names)
243
+ aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id)
220
244
  org_progress.update(1)
221
- aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
245
+ aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id, aggregation_window='Weekly')
222
246
  org_progress.update(2)
223
- aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
247
+ aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id, aggregation_window='Daily')
224
248
  org_progress.update(3)
225
249
  else:
226
- aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names)
227
- aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
228
- aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
250
+ aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id)
251
+ aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id, aggregation_window='Weekly')
252
+ aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=org_id, aggregation_window='Daily')
229
253
 
230
254
  if __name__ == "__main__":
231
255
  run_aggregate_scorekeeper_stats()
@@ -10,14 +10,20 @@ from hockey_blast_common_lib.models import Game, Goal, Penalty, GameRoster, Orga
10
10
  from hockey_blast_common_lib.stats_models import OrgStatsSkater, DivisionStatsSkater, OrgStatsWeeklySkater, OrgStatsDailySkater, DivisionStatsWeeklySkater, DivisionStatsDailySkater, LevelStatsSkater
11
11
  from hockey_blast_common_lib.db_connection import create_session
12
12
  from sqlalchemy.sql import func, case
13
- from hockey_blast_common_lib.options import not_human_names, parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
14
- from hockey_blast_common_lib.utils import get_org_id_from_alias, get_human_ids_by_names, get_division_ids_for_last_season_in_all_leagues, get_all_division_ids_for_org
13
+ from hockey_blast_common_lib.options import parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
14
+ from hockey_blast_common_lib.utils import get_org_id_from_alias, get_non_human_ids, get_division_ids_for_last_season_in_all_leagues, get_all_division_ids_for_org
15
15
  from hockey_blast_common_lib.utils import get_start_datetime
16
16
  from sqlalchemy import func, case, and_
17
17
  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.
@@ -64,8 +70,8 @@ def calculate_current_point_streak(session, human_id, filter_condition):
64
70
 
65
71
  return current_streak, avg_points_during_streak
66
72
 
67
- def aggregate_skater_stats(session, aggregation_type, aggregation_id, names_to_filter_out, debug_human_id=None, aggregation_window=None):
68
- human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
73
+ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_human_id=None, aggregation_window=None):
74
+ human_ids_to_filter = get_non_human_ids(session)
69
75
 
70
76
  # Get the name of the aggregation, for debug purposes
71
77
  if aggregation_type == 'org':
@@ -138,16 +144,26 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, names_to_f
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, names_to_f
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, names_to_f
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, names_to_f
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['games_played'] > 0:
259
- stat['goals_per_game'] = stat['goals'] / stat['games_played']
260
- stat['points_per_game'] = stat['points'] / stat['games_played']
261
- stat['assists_per_game'] = stat['assists'] / stat['games_played']
262
- stat['penalties_per_game'] = stat['penalties'] / stat['games_played']
263
- stat['gm_penalties_per_game'] = stat['gm_penalties'] / stat['games_played'] # Calculate GM penalties per game
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, names_to_f
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, names_to_f
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'],
@@ -382,30 +408,30 @@ def run_aggregate_skater_stats():
382
408
  # Process divisions with progress tracking
383
409
  progress = create_progress_tracker(len(division_ids), f"Processing {len(division_ids)} divisions for {org_name}")
384
410
  for i, division_id in enumerate(division_ids):
385
- aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
386
- aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
387
- aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Daily')
411
+ aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug)
412
+ aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
413
+ aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug, aggregation_window='Daily')
388
414
  progress.update(i + 1)
389
415
  else:
390
416
  # Debug mode or no divisions - process without progress tracking
391
417
  for division_id in division_ids:
392
- aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
393
- aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
394
- aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Daily')
418
+ aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug)
419
+ aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
420
+ aggregate_skater_stats(session, aggregation_type='division', aggregation_id=division_id, debug_human_id=human_id_to_debug, aggregation_window='Daily')
395
421
 
396
422
  # Process org-level stats with progress tracking
397
423
  if human_id_to_debug is None:
398
424
  org_progress = create_progress_tracker(3, f"Processing org-level stats for {org_name}")
399
- aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
425
+ aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug)
400
426
  org_progress.update(1)
401
- aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
427
+ aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
402
428
  org_progress.update(2)
403
- aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Daily')
429
+ aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug, aggregation_window='Daily')
404
430
  org_progress.update(3)
405
431
  else:
406
- aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
407
- aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
408
- aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Daily')
432
+ aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug)
433
+ aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
434
+ aggregate_skater_stats(session, aggregation_type='org', aggregation_id=org_id, debug_human_id=human_id_to_debug, aggregation_window='Daily')
409
435
 
410
436
  # Aggregate by level
411
437
  level_ids = session.query(Division.level_id).distinct().all()
@@ -415,12 +441,12 @@ def run_aggregate_skater_stats():
415
441
  # Process levels with progress tracking
416
442
  level_progress = create_progress_tracker(len(level_ids), f"Processing {len(level_ids)} skill levels")
417
443
  for i, level_id in enumerate(level_ids):
418
- aggregate_skater_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
444
+ aggregate_skater_stats(session, aggregation_type='level', aggregation_id=level_id, debug_human_id=human_id_to_debug)
419
445
  level_progress.update(i + 1)
420
446
  else:
421
447
  # Debug mode or no levels - process without progress tracking
422
448
  for level_id in level_ids:
423
- aggregate_skater_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
449
+ aggregate_skater_stats(session, aggregation_type='level', aggregation_id=level_id, debug_human_id=human_id_to_debug)
424
450
 
425
451
  if __name__ == "__main__":
426
452
  run_aggregate_skater_stats()
@@ -9,17 +9,6 @@ MIN_GAMES_FOR_LEVEL_STATS = 1
9
9
 
10
10
  orgs = {'caha', 'sharksice', 'tvice'}
11
11
 
12
- not_human_names = [
13
- ("Home", None, None),
14
- ("Away", None, None),
15
- (None, "Unknown", None),
16
- ("Not", None , None),
17
- (None , None, "Goalie"),
18
- ("Unassigned",None , None),
19
- # Critical addition - primary cause of 93.4% of roster insertion errors
20
- ("Not", "Signed", "In")
21
- ]
22
-
23
12
  def parse_args():
24
13
  parser = argparse.ArgumentParser(description="Process data for a specific organization.")
25
14
  parser.add_argument("org", choices=orgs, help="The organization to process (e.g., 'caha', 'sharksice', 'tvice').")
@@ -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)
@@ -52,6 +52,24 @@ def get_human_ids_by_names(session, names):
52
52
  human_ids.update([result.id for result in results])
53
53
  return human_ids
54
54
 
55
+ def get_non_human_ids(session):
56
+ """Get IDs of non-human entities (placeholder names, test accounts, etc.)
57
+
58
+ Returns set of human_ids that should be filtered out from statistics.
59
+ Filters out placeholder names like "Home", "Away", "Unknown", etc.
60
+ """
61
+ not_human_names = [
62
+ ("Home", None, None),
63
+ ("Away", None, None),
64
+ (None, "Unknown", None),
65
+ ("Not", None, None),
66
+ (None, None, "Goalie"),
67
+ ("Unassigned", None, None),
68
+ ("Not", "Signed", "In"),
69
+ ("Incognito", None, None)
70
+ ]
71
+ return get_human_ids_by_names(session, not_human_names)
72
+
55
73
  def get_division_ids_for_last_season_in_all_leagues(session, org_id):
56
74
  # # TODO = remove tmp hack
57
75
  # return get_all_division_ids_for_org(session, org_id)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hockey-blast-common-lib
3
- Version: 0.1.60
3
+ Version: 0.1.62
4
4
  Summary: Common library for shared functionality and DB models
5
5
  Author: Pavel Kletskov
6
6
  Author-email: kletskov@gmail.com
@@ -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=lJE5TMBtJLqLeG9jxHV5xUcudusZ4t8yHQN6TvPK_0k,13804
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
- hockey_blast_common_lib/aggregate_human_stats.py,sha256=ku42TAjUIj49822noM8fEeB8GS4vFeCCNrLupTWmqzg,26043
6
- hockey_blast_common_lib/aggregate_referee_stats.py,sha256=mUcTVQH9K4kwmIfgfGsnh_3AqX6Ia3RjfukkYuQas3I,13938
5
+ hockey_blast_common_lib/aggregate_human_stats.py,sha256=JZcXJofF1wmFPcLbalM_GiXrq22eUJO28R_xyypw4rI,25456
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=Y0SSCBoujXNMs1_f-zn6odbLIrdTTkzOCpUyH40WBVs,12288
9
- hockey_blast_common_lib/aggregate_skater_stats.py,sha256=pA_2pDOGcJyEywISg2ySG8gFCuoLWwqw6a3Gm2wHLyo,23302
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=e5qPt-WPwRu0-2l5neKpslO_oQtPUU6yiM9rr_In8Q0,4648905
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
- hockey_blast_common_lib/options.py,sha256=rQaLRYYcaxXrpZoXfUpmvsahC23oVGPEOzEpqtthbIQ,1794
17
- hockey_blast_common_lib/progress_utils.py,sha256=H_zRFOsb2qQQpGw56wJghZ1nUe_m6zqGeR9hZ33Y1Uo,3229
16
+ hockey_blast_common_lib/options.py,sha256=XecGGlizbul7BnMePrvIqvEq5_w49UoG3Yu9iv961gg,1499
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=64sUq_iWhNXi_b_V_1INuQ1RusKaTASjurkRo5gQOs4,26703
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
- hockey_blast_common_lib/utils.py,sha256=PduHp6HoI4sfr5HvJfQAaz7170dy5kTFVdIfWvBR-Jg,5874
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.60.dist-info/METADATA,sha256=6GOkTYr8oNmJWRRPQiAraI4waI5ppLySU5DKBIXP4bw,318
26
- hockey_blast_common_lib-0.1.60.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
27
- hockey_blast_common_lib-0.1.60.dist-info/top_level.txt,sha256=wIR4LIkE40npoA2QlOdfCYlgFeGbsHR8Z6r0h46Vtgc,24
28
- hockey_blast_common_lib-0.1.60.dist-info/RECORD,,
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,,