hockey-blast-common-lib 0.1.31__py3-none-any.whl → 0.1.33__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.
@@ -5,16 +5,31 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
5
5
 
6
6
  from datetime import datetime, timedelta
7
7
  import sqlalchemy
8
- from hockey_blast_common_lib.models import Game, GameRoster
9
- from hockey_blast_common_lib.stats_models import OrgStatsGoalie, DivisionStatsGoalie, OrgStatsWeeklyGoalie, OrgStatsDailyGoalie, DivisionStatsWeeklyGoalie, DivisionStatsDailyGoalie
8
+
9
+ from hockey_blast_common_lib.models import Game, Goal, Penalty, GameRoster, Organization, Division, Human, Level
10
+ from hockey_blast_common_lib.stats_models import OrgStatsGoalie, DivisionStatsGoalie, OrgStatsWeeklyGoalie, OrgStatsDailyGoalie, DivisionStatsWeeklyGoalie, DivisionStatsDailyGoalie, LevelStatsGoalie
10
11
  from hockey_blast_common_lib.db_connection import create_session
11
12
  from sqlalchemy.sql import func, case
12
- from hockey_blast_common_lib.options import not_human_names, parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS
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
13
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
15
+ from hockey_blast_common_lib.stats_utils import assign_ranks
16
+ from sqlalchemy import func, case, and_
17
+ from collections import defaultdict
14
18
 
15
- def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_filter_out, aggregation_window=None):
19
+ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_filter_out, debug_human_id=None, aggregation_window=None):
16
20
  human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
17
21
 
22
+ # Get the name of the aggregation, for debug purposes
23
+ if aggregation_type == 'org':
24
+ aggregation_name = session.query(Organization).filter(Organization.id == aggregation_id).first().organization_name
25
+ print(f"Aggregating goalie stats for {aggregation_name} with window {aggregation_window}...")
26
+ elif aggregation_type == 'division':
27
+ aggregation_name = session.query(Division).filter(Division.id == aggregation_id).first().level
28
+ elif aggregation_type == 'level':
29
+ aggregation_name = session.query(Level).filter(Level.id == aggregation_id).first().level_name
30
+ else:
31
+ aggregation_name = "Unknown"
32
+
18
33
  if aggregation_type == 'org':
19
34
  if aggregation_window == 'Daily':
20
35
  StatsModel = OrgStatsDailyGoalie
@@ -33,6 +48,14 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
33
48
  StatsModel = DivisionStatsGoalie
34
49
  min_games = MIN_GAMES_FOR_DIVISION_STATS
35
50
  filter_condition = Game.division_id == aggregation_id
51
+ elif aggregation_type == 'level':
52
+ StatsModel = LevelStatsGoalie
53
+ min_games = MIN_GAMES_FOR_LEVEL_STATS
54
+ filter_condition = Division.level_id == aggregation_id
55
+ # Add filter to only include games for the last 5 years
56
+ five_years_ago = datetime.now() - timedelta(days=5*365)
57
+ level_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP) >= five_years_ago
58
+ filter_condition = filter_condition & level_window_filter
36
59
  else:
37
60
  raise ValueError("Invalid aggregation type")
38
61
 
@@ -55,6 +78,11 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
55
78
  session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
56
79
  session.commit()
57
80
 
81
+ # Filter for specific human_id if provided
82
+ human_filter = []
83
+ # if debug_human_id:
84
+ # human_filter = [GameRoster.human_id == debug_human_id]
85
+
58
86
  # Aggregate games played, goals allowed, and shots faced for each goalie
59
87
  goalie_stats = session.query(
60
88
  GameRoster.human_id,
@@ -62,7 +90,7 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
62
90
  func.sum(case((GameRoster.team_id == Game.home_team_id, Game.visitor_final_score), else_=Game.home_final_score)).label('goals_allowed'),
63
91
  func.sum(case((GameRoster.team_id == Game.home_team_id, Game.visitor_period_1_shots + Game.visitor_period_2_shots + Game.visitor_period_3_shots + Game.visitor_ot_shots + Game.visitor_so_shots), else_=Game.home_period_1_shots + Game.home_period_2_shots + Game.home_period_3_shots + Game.home_ot_shots + Game.home_so_shots)).label('shots_faced'),
64
92
  func.array_agg(Game.id).label('game_ids')
65
- ).join(Game, GameRoster.game_id == Game.id).filter(filter_condition, GameRoster.role == 'G').group_by(GameRoster.human_id).all()
93
+ ).join(Game, GameRoster.game_id == Game.id).join(Division, Game.division_id == Division.id).filter(filter_condition, GameRoster.role.ilike('g')).group_by(GameRoster.human_id).all()
66
94
 
67
95
  # Combine the results
68
96
  stats_dict = {}
@@ -70,6 +98,8 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
70
98
  if stat.human_id in human_ids_to_filter:
71
99
  continue
72
100
  key = (aggregation_id, stat.human_id)
101
+ if stat.games_played < min_games:
102
+ continue
73
103
  stats_dict[key] = {
74
104
  'games_played': stat.games_played,
75
105
  'goals_allowed': stat.goals_allowed if stat.goals_allowed is not None else 0,
@@ -102,37 +132,43 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
102
132
  # Calculate total_in_rank
103
133
  total_in_rank = len(stats_dict)
104
134
 
105
- # Assign ranks
106
- def assign_ranks(stats_dict, field):
107
- sorted_stats = sorted(stats_dict.items(), key=lambda x: x[1][field], reverse=True)
108
- for rank, (key, stat) in enumerate(sorted_stats, start=1):
109
- stats_dict[key][f'{field}_rank'] = rank
110
-
135
+ # Assign ranks within each level
111
136
  assign_ranks(stats_dict, 'games_played')
112
- assign_ranks(stats_dict, 'goals_allowed')
113
- assign_ranks(stats_dict, 'goals_allowed_per_game')
137
+ assign_ranks(stats_dict, 'goals_allowed', reverse_rank=True)
114
138
  assign_ranks(stats_dict, 'shots_faced')
139
+ assign_ranks(stats_dict, 'goals_allowed_per_game', reverse_rank=True)
115
140
  assign_ranks(stats_dict, 'save_percentage')
116
141
 
142
+ # Debug output for specific human
143
+ if debug_human_id:
144
+ if any(key[1] == debug_human_id for key in stats_dict):
145
+ human = session.query(Human).filter(Human.id == debug_human_id).first()
146
+ human_name = f"{human.first_name} {human.last_name}" if human else "Unknown"
147
+ print(f"For Human {debug_human_id} ({human_name}) for {aggregation_type} {aggregation_id} ({aggregation_name}) , total_in_rank {total_in_rank} and window {aggregation_window}:")
148
+ for key, stat in stats_dict.items():
149
+ if key[1] == debug_human_id:
150
+ for k, v in stat.items():
151
+ print(f"{k}: {v}")
152
+
117
153
  # Insert aggregated stats into the appropriate table with progress output
118
154
  total_items = len(stats_dict)
119
155
  batch_size = 1000
120
156
  for i, (key, stat) in enumerate(stats_dict.items(), 1):
121
157
  aggregation_id, human_id = key
122
- if stat['games_played'] < min_games:
123
- continue
158
+ goals_allowed_per_game = stat['goals_allowed'] / stat['games_played'] if stat['games_played'] > 0 else 0.0
159
+ save_percentage = (stat['shots_faced'] - stat['goals_allowed']) / stat['shots_faced'] if stat['shots_faced'] > 0 else 0.0
124
160
  goalie_stat = StatsModel(
125
161
  aggregation_id=aggregation_id,
126
162
  human_id=human_id,
127
163
  games_played=stat['games_played'],
128
164
  goals_allowed=stat['goals_allowed'],
129
- goals_allowed_per_game=stat['goals_allowed_per_game'],
130
165
  shots_faced=stat['shots_faced'],
131
- save_percentage=stat['save_percentage'],
166
+ goals_allowed_per_game=goals_allowed_per_game,
167
+ save_percentage=save_percentage,
132
168
  games_played_rank=stat['games_played_rank'],
133
169
  goals_allowed_rank=stat['goals_allowed_rank'],
134
- goals_allowed_per_game_rank=stat['goals_allowed_per_game_rank'],
135
170
  shots_faced_rank=stat['shots_faced_rank'],
171
+ goals_allowed_per_game_rank=stat['goals_allowed_per_game_rank'],
136
172
  save_percentage_rank=stat['save_percentage_rank'],
137
173
  total_in_rank=total_in_rank,
138
174
  first_game_id=stat['first_game_id'],
@@ -142,23 +178,42 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
142
178
  # Commit in batches
143
179
  if i % batch_size == 0:
144
180
  session.commit()
145
- print(f"\r{i}/{total_items} ({(i/total_items)*100:.2f}%)", end="")
146
181
  session.commit()
147
- print(f"\r{total_items}/{total_items} (100.00%)")
148
- print("\nDone.")
149
182
 
150
- # Example usage
151
183
  if __name__ == "__main__":
152
- args = parse_args()
153
- org_alias = args.org
154
184
  session = create_session("boss")
155
- org_id = get_org_id_from_alias(session, org_alias)
156
- division_ids = get_division_ids_for_last_season_in_all_leagues(session, org_id)
157
- print(f"Aggregating goalie stats for {len(division_ids)} divisions in {org_alias}...")
158
- for division_id in division_ids:
159
- aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names)
160
- aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
161
- aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
162
- aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names)
163
- aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
164
- aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
185
+ human_id_to_debug = None
186
+
187
+ # Get all org_id present in the Organization table
188
+ org_ids = session.query(Organization.id).all()
189
+ org_ids = [org_id[0] for org_id in org_ids]
190
+
191
+ for org_id in org_ids:
192
+ division_ids = get_all_division_ids_for_org(session, org_id)
193
+ print(f"Aggregating goalie stats for {len(division_ids)} divisions in org_id {org_id}...")
194
+ total_divisions = len(division_ids)
195
+ processed_divisions = 0
196
+ for division_id in division_ids:
197
+ 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)
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, aggregation_window='Weekly')
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='Daily')
200
+ processed_divisions += 1
201
+ if human_id_to_debug is None:
202
+ print(f"\rProcessed {processed_divisions}/{total_divisions} divisions ({(processed_divisions/total_divisions)*100:.2f}%)", end="")
203
+
204
+ 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)
205
+ 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')
206
+ 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')
207
+
208
+ # Aggregate by level
209
+ level_ids = session.query(Division.level_id).distinct().all()
210
+ level_ids = [level_id[0] for level_id in level_ids]
211
+ total_levels = len(level_ids)
212
+ processed_levels = 0
213
+ for level_id in level_ids:
214
+ if level_id is None:
215
+ continue
216
+ if human_id_to_debug is None:
217
+ print(f"\rProcessed {processed_levels}/{total_levels} levels ({(processed_levels/total_levels)*100:.2f}%)", end="")
218
+ processed_levels += 1
219
+ 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)
@@ -6,17 +6,20 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
6
6
 
7
7
  from datetime import datetime, timedelta
8
8
  import sqlalchemy
9
- from hockey_blast_common_lib.models import Game, GameRoster
10
- from hockey_blast_common_lib.stats_models import OrgStatsHuman, DivisionStatsHuman, OrgStatsDailyHuman, OrgStatsWeeklyHuman, DivisionStatsDailyHuman, DivisionStatsWeeklyHuman
9
+ from hockey_blast_common_lib.models import Game, GameRoster, Organization, Division
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, 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
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
15
+ from hockey_blast_common_lib.stats_utils import assign_ranks
15
16
 
16
17
  def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_filter_out, human_id_filter=None, aggregation_window=None):
17
18
  human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
18
19
 
19
20
  if aggregation_type == 'org':
21
+ aggregation_name = session.query(Organization).filter(Organization.id == aggregation_id).first().organization_name
22
+ print(f"Aggregating goalie stats for {aggregation_name} with window {aggregation_window}...")
20
23
  if aggregation_window == 'Daily':
21
24
  StatsModel = OrgStatsDailyHuman
22
25
  elif aggregation_window == 'Weekly':
@@ -34,24 +37,20 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
34
37
  StatsModel = DivisionStatsHuman
35
38
  min_games = MIN_GAMES_FOR_DIVISION_STATS
36
39
  filter_condition = Game.division_id == aggregation_id
40
+ elif aggregation_type == 'level':
41
+ StatsModel = LevelStatsHuman
42
+ min_games = MIN_GAMES_FOR_LEVEL_STATS
43
+ filter_condition = Division.level_id == aggregation_id
44
+ # Add filter to only include games for the last 5 years
45
+ five_years_ago = datetime.now() - timedelta(days=5*365)
46
+ level_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP) >= five_years_ago
47
+ filter_condition = filter_condition & level_window_filter
37
48
  else:
38
49
  raise ValueError("Invalid aggregation type")
39
50
 
40
- # Delete existing items from the stats table
41
- session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
42
- session.commit()
43
-
44
- # Filter for specific human_id if provided
45
- human_filter = []
46
- if human_id_filter:
47
- human_filter = [GameRoster.human_id == human_id_filter]
48
-
49
- # Filter games by status
50
- game_status_filter = Game.status.like('Final%')
51
-
52
51
  # Apply aggregation window filter
53
52
  if aggregation_window:
54
- last_game_datetime = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, game_status_filter).scalar()
53
+ last_game_datetime = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
55
54
  if last_game_datetime:
56
55
  last_game_datetime = datetime.strptime(last_game_datetime, '%Y-%m-%d %H:%M:%S')
57
56
  if aggregation_window == 'Daily':
@@ -64,50 +63,106 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
64
63
  game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime)
65
64
  filter_condition = filter_condition & game_window_filter
66
65
 
67
- # Aggregate games played for each human in each role
68
- human_stats = session.query(
66
+ # Delete existing items from the stats table
67
+ session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
68
+ session.commit()
69
+
70
+ # Filter for specific human_id if provided
71
+ human_filter = []
72
+ if human_id_filter:
73
+ human_filter = [GameRoster.human_id == human_id_filter]
74
+
75
+ # Filter games by status
76
+ game_status_filter = Game.status.like('Final%')
77
+
78
+ # Aggregate skater games played
79
+ skater_stats = session.query(
80
+ GameRoster.human_id,
81
+ func.count(func.distinct(Game.id)).label('games_skater'),
82
+ func.array_agg(func.distinct(Game.id)).label('skater_game_ids')
83
+ ).join(Game, GameRoster.game_id == Game.id).join(Division, Game.division_id == Division.id).filter(filter_condition, game_status_filter, ~GameRoster.role.ilike('G'), *human_filter).group_by(GameRoster.human_id).all()
84
+
85
+ # Aggregate goalie games played
86
+ goalie_stats = session.query(
69
87
  GameRoster.human_id,
70
- func.count(func.distinct(case((GameRoster.role != 'G', Game.id), else_=None))).label('games_skater'),
71
- func.count(func.distinct(case((GameRoster.role == 'G', Game.id), else_=None))).label('games_goalie'),
72
- func.array_agg(func.distinct(Game.id)).label('game_ids')
73
- ).join(Game, GameRoster.game_id == Game.id).filter(filter_condition, game_status_filter, *human_filter).group_by(GameRoster.human_id).all()
88
+ func.count(func.distinct(Game.id)).label('games_goalie'),
89
+ func.array_agg(func.distinct(Game.id)).label('goalie_game_ids')
90
+ ).join(Game, GameRoster.game_id == Game.id).join(Division, Game.division_id == Division.id).filter(filter_condition, game_status_filter, GameRoster.role.ilike('G'), *human_filter).group_by(GameRoster.human_id).all()
74
91
 
75
92
  # Aggregate referee and scorekeeper games from Game table
76
93
  referee_stats = session.query(
77
94
  Game.referee_1_id.label('human_id'),
78
95
  func.count(func.distinct(Game.id)).label('games_referee'),
79
96
  func.array_agg(func.distinct(Game.id)).label('referee_game_ids')
80
- ).filter(filter_condition, game_status_filter, *human_filter).group_by(Game.referee_1_id).all()
97
+ ).join(Division, Game.division_id == Division.id).filter(filter_condition, game_status_filter, *human_filter).group_by(Game.referee_1_id).all()
81
98
 
82
99
  referee_stats_2 = session.query(
83
100
  Game.referee_2_id.label('human_id'),
84
101
  func.count(func.distinct(Game.id)).label('games_referee'),
85
102
  func.array_agg(func.distinct(Game.id)).label('referee_game_ids')
86
- ).filter(filter_condition, game_status_filter, *human_filter).group_by(Game.referee_2_id).all()
103
+ ).join(Division, Game.division_id == Division.id).filter(filter_condition, game_status_filter, *human_filter).group_by(Game.referee_2_id).all()
87
104
 
88
105
  scorekeeper_stats = session.query(
89
106
  Game.scorekeeper_id.label('human_id'),
90
107
  func.count(func.distinct(Game.id)).label('games_scorekeeper'),
91
108
  func.array_agg(func.distinct(Game.id)).label('scorekeeper_game_ids')
92
- ).filter(filter_condition, game_status_filter, *human_filter).group_by(Game.scorekeeper_id).all()
109
+ ).join(Division, Game.division_id == Division.id).filter(filter_condition, game_status_filter, *human_filter).group_by(Game.scorekeeper_id).all()
93
110
 
94
111
  # Combine the results
95
112
  stats_dict = {}
96
- for stat in human_stats:
113
+ for stat in skater_stats:
97
114
  if stat.human_id in human_ids_to_filter:
98
115
  continue
99
116
  key = (aggregation_id, stat.human_id)
100
117
  stats_dict[key] = {
101
- 'games_total': stat.games_skater + stat.games_goalie,
118
+ 'games_total': stat.games_skater,
102
119
  'games_skater': stat.games_skater,
103
- 'games_goalie': stat.games_goalie,
120
+ 'games_goalie': 0,
104
121
  'games_referee': 0,
105
122
  'games_scorekeeper': 0,
106
- 'game_ids': stat.game_ids,
123
+ 'skater_game_ids': stat.skater_game_ids,
124
+ 'goalie_game_ids': [],
107
125
  'referee_game_ids': [],
108
- 'scorekeeper_game_ids': []
126
+ 'scorekeeper_game_ids': [],
127
+ 'first_game_id_skater': None,
128
+ 'last_game_id_skater': None,
129
+ 'first_game_id_goalie': None,
130
+ 'last_game_id_goalie': None,
131
+ 'first_game_id_referee': None,
132
+ 'last_game_id_referee': None,
133
+ 'first_game_id_scorekeeper': None,
134
+ 'last_game_id_scorekeeper': None
109
135
  }
110
136
 
137
+ for stat in goalie_stats:
138
+ if stat.human_id in human_ids_to_filter:
139
+ continue
140
+ key = (aggregation_id, stat.human_id)
141
+ if key not in stats_dict:
142
+ stats_dict[key] = {
143
+ 'games_total': stat.games_goalie,
144
+ 'games_skater': 0,
145
+ 'games_goalie': stat.games_goalie,
146
+ 'games_referee': 0,
147
+ 'games_scorekeeper': 0,
148
+ 'skater_game_ids': [],
149
+ 'goalie_game_ids': stat.goalie_game_ids,
150
+ 'referee_game_ids': [],
151
+ 'scorekeeper_game_ids': [],
152
+ 'first_game_id_skater': None,
153
+ 'last_game_id_skater': None,
154
+ 'first_game_id_goalie': None,
155
+ 'last_game_id_goalie': None,
156
+ 'first_game_id_referee': None,
157
+ 'last_game_id_referee': None,
158
+ 'first_game_id_scorekeeper': None,
159
+ 'last_game_id_scorekeeper': None
160
+ }
161
+ else:
162
+ stats_dict[key]['games_goalie'] += stat.games_goalie
163
+ stats_dict[key]['games_total'] += stat.games_goalie
164
+ stats_dict[key]['goalie_game_ids'] += stat.goalie_game_ids
165
+
111
166
  for stat in referee_stats:
112
167
  if stat.human_id in human_ids_to_filter:
113
168
  continue
@@ -119,9 +174,18 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
119
174
  'games_goalie': 0,
120
175
  'games_referee': stat.games_referee,
121
176
  'games_scorekeeper': 0,
122
- 'game_ids': [],
177
+ 'skater_game_ids': [],
178
+ 'goalie_game_ids': [],
123
179
  'referee_game_ids': stat.referee_game_ids,
124
- 'scorekeeper_game_ids': []
180
+ 'scorekeeper_game_ids': [],
181
+ 'first_game_id_skater': None,
182
+ 'last_game_id_skater': None,
183
+ 'first_game_id_goalie': None,
184
+ 'last_game_id_goalie': None,
185
+ 'first_game_id_referee': None,
186
+ 'last_game_id_referee': None,
187
+ 'first_game_id_scorekeeper': None,
188
+ 'last_game_id_scorekeeper': None
125
189
  }
126
190
  else:
127
191
  stats_dict[key]['games_referee'] += stat.games_referee
@@ -139,9 +203,18 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
139
203
  'games_goalie': 0,
140
204
  'games_referee': stat.games_referee,
141
205
  'games_scorekeeper': 0,
142
- 'game_ids': [],
206
+ 'skater_game_ids': [],
207
+ 'goalie_game_ids': [],
143
208
  'referee_game_ids': stat.referee_game_ids,
144
- 'scorekeeper_game_ids': []
209
+ 'scorekeeper_game_ids': [],
210
+ 'first_game_id_skater': None,
211
+ 'last_game_id_skater': None,
212
+ 'first_game_id_goalie': None,
213
+ 'last_game_id_goalie': None,
214
+ 'first_game_id_referee': None,
215
+ 'last_game_id_referee': None,
216
+ 'first_game_id_scorekeeper': None,
217
+ 'last_game_id_scorekeeper': None
145
218
  }
146
219
  else:
147
220
  stats_dict[key]['games_referee'] += stat.games_referee
@@ -159,9 +232,18 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
159
232
  'games_goalie': 0,
160
233
  'games_referee': 0,
161
234
  'games_scorekeeper': stat.games_scorekeeper,
162
- 'game_ids': [],
235
+ 'skater_game_ids': [],
236
+ 'goalie_game_ids': [],
163
237
  'referee_game_ids': [],
164
- 'scorekeeper_game_ids': stat.scorekeeper_game_ids
238
+ 'scorekeeper_game_ids': stat.scorekeeper_game_ids,
239
+ 'first_game_id_skater': None,
240
+ 'last_game_id_skater': None,
241
+ 'first_game_id_goalie': None,
242
+ 'last_game_id_goalie': None,
243
+ 'first_game_id_referee': None,
244
+ 'last_game_id_referee': None,
245
+ 'first_game_id_scorekeeper': None,
246
+ 'last_game_id_scorekeeper': None
165
247
  }
166
248
  else:
167
249
  stats_dict[key]['games_scorekeeper'] += stat.games_scorekeeper
@@ -174,36 +256,61 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
174
256
  # Calculate total_in_rank
175
257
  total_in_rank = len(stats_dict)
176
258
 
177
- # Assign ranks
178
- def assign_ranks(stats_dict, field):
179
- sorted_stats = sorted(stats_dict.items(), key=lambda x: x[1][field], reverse=True)
180
- for rank, (key, stat) in enumerate(sorted_stats, start=1):
181
- stats_dict[key][f'{field}_rank'] = rank
259
+ # Calculate number of items in rank per role
260
+ skaters_in_rank = len([stat for stat in stats_dict.values() if stat['games_skater'] > 0])
261
+ goalies_in_rank = len([stat for stat in stats_dict.values() if stat['games_goalie'] > 0])
262
+ referees_in_rank = len([stat for stat in stats_dict.values() if stat['games_referee'] > 0])
263
+ scorekeepers_in_rank = len([stat for stat in stats_dict.values() if stat['games_scorekeeper'] > 0])
264
+
265
+ # Filter out humans with less than min_games
266
+ stats_dict = {key: value for key, value in stats_dict.items() if value['games_total'] >= min_games}
182
267
 
268
+ # Assign ranks
183
269
  assign_ranks(stats_dict, 'games_total')
184
270
  assign_ranks(stats_dict, 'games_skater')
185
271
  assign_ranks(stats_dict, 'games_goalie')
186
272
  assign_ranks(stats_dict, 'games_referee')
187
273
  assign_ranks(stats_dict, 'games_scorekeeper')
188
274
 
189
- # Populate first_game_id and last_game_id
275
+ # Populate first_game_id and last_game_id for each role
190
276
  for key, stat in stats_dict.items():
191
- all_game_ids = stat['game_ids'] + stat['referee_game_ids'] + stat['scorekeeper_game_ids']
277
+ all_game_ids = stat['skater_game_ids'] + stat['goalie_game_ids'] + stat['referee_game_ids'] + stat['scorekeeper_game_ids']
192
278
  if all_game_ids:
193
279
  first_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date, Game.time).first()
194
280
  last_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date.desc(), Game.time.desc()).first()
195
281
  stat['first_game_id'] = first_game.id if first_game else None
196
282
  stat['last_game_id'] = last_game.id if last_game else None
197
283
 
284
+ if stat['skater_game_ids']:
285
+ first_game_skater = session.query(Game).filter(Game.id.in_(stat['skater_game_ids'])).order_by(Game.date, Game.time).first()
286
+ last_game_skater = session.query(Game).filter(Game.id.in_(stat['skater_game_ids'])).order_by(Game.date.desc(), Game.time.desc()).first()
287
+ stat['first_game_id_skater'] = first_game_skater.id if first_game_skater else None
288
+ stat['last_game_id_skater'] = last_game_skater.id if last_game_skater else None
289
+
290
+ if stat['goalie_game_ids']:
291
+ first_game_goalie = session.query(Game).filter(Game.id.in_(stat['goalie_game_ids'])).order_by(Game.date, Game.time).first()
292
+ last_game_goalie = session.query(Game).filter(Game.id.in_(stat['goalie_game_ids'])).order_by(Game.date.desc(), Game.time.desc()).first()
293
+ stat['first_game_id_goalie'] = first_game_goalie.id if first_game_goalie else None
294
+ stat['last_game_id_goalie'] = last_game_goalie.id if last_game_goalie else None
295
+
296
+ if stat['referee_game_ids']:
297
+ first_game_referee = session.query(Game).filter(Game.id.in_(stat['referee_game_ids'])).order_by(Game.date, Game.time).first()
298
+ last_game_referee = session.query(Game).filter(Game.id.in_(stat['referee_game_ids'])).order_by(Game.date.desc(), Game.time.desc()).first()
299
+ stat['first_game_id_referee'] = first_game_referee.id if first_game_referee else None
300
+ stat['last_game_id_referee'] = last_game_referee.id if last_game_referee else None
301
+
302
+ if stat['scorekeeper_game_ids']:
303
+ first_game_scorekeeper = session.query(Game).filter(Game.id.in_(stat['scorekeeper_game_ids'])).order_by(Game.date, Game.time).first()
304
+ last_game_scorekeeper = session.query(Game).filter(Game.id.in_(stat['scorekeeper_game_ids'])).order_by(Game.date.desc(), Game.time.desc()).first()
305
+ stat['first_game_id_scorekeeper'] = first_game_scorekeeper.id if first_game_scorekeeper else None
306
+ stat['last_game_id_scorekeeper'] = last_game_scorekeeper.id if last_game_scorekeeper else None
307
+
198
308
  # Insert aggregated stats into the appropriate table with progress output
199
- total_items = len(stats_dict)
200
309
  batch_size = 1000
201
310
  for i, (key, stat) in enumerate(stats_dict.items(), 1):
202
311
  aggregation_id, human_id = key
203
312
  if human_id_filter and human_id != human_id_filter:
204
313
  continue
205
- if stat['games_total'] < min_games:
206
- continue
207
314
 
208
315
  human_stat = StatsModel(
209
316
  aggregation_id=aggregation_id,
@@ -219,14 +326,25 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
219
326
  games_scorekeeper=stat['games_scorekeeper'],
220
327
  games_scorekeeper_rank=stat['games_scorekeeper_rank'],
221
328
  total_in_rank=total_in_rank,
329
+ skaters_in_rank=skaters_in_rank,
330
+ goalies_in_rank=goalies_in_rank,
331
+ referees_in_rank=referees_in_rank,
332
+ scorekeepers_in_rank=scorekeepers_in_rank,
222
333
  first_game_id=stat['first_game_id'],
223
- last_game_id=stat['last_game_id']
334
+ last_game_id=stat['last_game_id'],
335
+ first_game_id_skater=stat['first_game_id_skater'],
336
+ last_game_id_skater=stat['last_game_id_skater'],
337
+ first_game_id_goalie=stat['first_game_id_goalie'],
338
+ last_game_id_goalie=stat['last_game_id_goalie'],
339
+ first_game_id_referee=stat['first_game_id_referee'],
340
+ last_game_id_referee=stat['last_game_id_referee'],
341
+ first_game_id_scorekeeper=stat['first_game_id_scorekeeper'],
342
+ last_game_id_scorekeeper=stat['last_game_id_scorekeeper']
224
343
  )
225
344
  session.add(human_stat)
226
345
  # Commit in batches
227
346
  if i % batch_size == 0:
228
347
  session.commit()
229
- print(f"\r{i}/{total_items} ({(i/total_items)*100:.2f}%)", end="")
230
348
  session.commit()
231
349
 
232
350
  # Fetch fake human ID for overall stats
@@ -240,12 +358,24 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
240
358
  'games_referee': sum(stat['games_referee'] for stat in stats_dict.values()),
241
359
  'games_scorekeeper': sum(stat['games_scorekeeper'] for stat in stats_dict.values()),
242
360
  'total_in_rank': total_in_rank,
361
+ 'skaters_in_rank': skaters_in_rank,
362
+ 'goalies_in_rank': goalies_in_rank,
363
+ 'referees_in_rank': referees_in_rank,
364
+ 'scorekeepers_in_rank': scorekeepers_in_rank,
243
365
  'first_game_id': None,
244
- 'last_game_id': None
366
+ 'last_game_id': None,
367
+ 'first_game_id_skater': None,
368
+ 'last_game_id_skater': None,
369
+ 'first_game_id_goalie': None,
370
+ 'last_game_id_goalie': None,
371
+ 'first_game_id_referee': None,
372
+ 'last_game_id_referee': None,
373
+ 'first_game_id_scorekeeper': None,
374
+ 'last_game_id_scorekeeper': None
245
375
  }
246
376
 
247
377
  # Populate first_game_id and last_game_id for overall stats
248
- all_game_ids = [game_id for stat in stats_dict.values() for game_id in stat['game_ids'] + stat['referee_game_ids'] + stat['scorekeeper_game_ids']]
378
+ all_game_ids = [game_id for stat in stats_dict.values() for game_id in stat['skater_game_ids'] + stat['goalie_game_ids'] + stat['referee_game_ids'] + stat['scorekeeper_game_ids']]
249
379
  if all_game_ids:
250
380
  first_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date, Game.time).first()
251
381
  last_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date.desc(), Game.time.desc()).first()
@@ -267,29 +397,58 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
267
397
  games_scorekeeper=overall_stats['games_scorekeeper'],
268
398
  games_scorekeeper_rank=0, # Overall stats do not need a rank
269
399
  total_in_rank=overall_stats['total_in_rank'],
400
+ skaters_in_rank=overall_stats['skaters_in_rank'],
401
+ goalies_in_rank=overall_stats['goalies_in_rank'],
402
+ referees_in_rank=overall_stats['referees_in_rank'],
403
+ scorekeepers_in_rank=overall_stats['scorekeepers_in_rank'],
270
404
  first_game_id=overall_stats['first_game_id'],
271
- last_game_id=overall_stats['last_game_id']
405
+ last_game_id=overall_stats['last_game_id'],
406
+ first_game_id_skater=overall_stats['first_game_id_skater'],
407
+ last_game_id_skater=overall_stats['last_game_id_skater'],
408
+ first_game_id_goalie=overall_stats['first_game_id_goalie'],
409
+ last_game_id_goalie=overall_stats['last_game_id_goalie'],
410
+ first_game_id_referee=overall_stats['first_game_id_referee'],
411
+ last_game_id_referee=overall_stats['last_game_id_referee'],
412
+ first_game_id_scorekeeper=overall_stats['first_game_id_scorekeeper'],
413
+ last_game_id_scorekeeper=overall_stats['last_game_id_scorekeeper']
272
414
  )
273
415
  session.add(overall_human_stat)
274
416
  session.commit()
275
417
 
276
- print(f"\r{total_items}/{total_items} (100.00%)")
277
- print("\nDone.")
278
-
279
- # Example usage
280
418
  if __name__ == "__main__":
281
- args = parse_args()
282
- org_alias=args.org
283
419
  session = create_session("boss")
284
- org_id = get_org_id_from_alias(session, org_alias)
285
-
286
- division_ids = get_division_ids_for_last_season_in_all_leagues(session, org_id)
287
- print(f"Aggregating human stats for {len(division_ids)} divisions in {org_alias}...")
288
- for division_id in division_ids:
289
- aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=None)
290
- aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=None, aggregation_window='Daily')
291
- aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=None, aggregation_window='Weekly')
292
-
293
- aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=None)
294
- aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=None, aggregation_window='Daily')
295
- aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=None, aggregation_window='Weekly')
420
+ human_id_to_debug = None
421
+
422
+ # Get all org_id present in the Organization table
423
+ # org_ids = session.query(Organization.id).all()
424
+ # org_ids = [org_id[0] for org_id in org_ids]
425
+
426
+ # for org_id in org_ids:
427
+ # division_ids = get_all_division_ids_for_org(session, org_id)
428
+ # print(f"Aggregating human stats for {len(division_ids)} divisions in org_id {org_id}...")
429
+ # total_divisions = len(division_ids)
430
+ # processed_divisions = 0
431
+ # for division_id in division_ids:
432
+ # 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)
433
+ # 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')
434
+ # 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')
435
+ # processed_divisions += 1
436
+ # if human_id_to_debug is None:
437
+ # print(f"\rProcessed {processed_divisions}/{total_divisions} divisions ({(processed_divisions/total_divisions)*100:.2f}%)", end="")
438
+ # print("")
439
+ # 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)
440
+ # 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')
441
+ # 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')
442
+
443
+ # Aggregate by level
444
+ level_ids = session.query(Division.level_id).distinct().all()
445
+ level_ids = [level_id[0] for level_id in level_ids]
446
+ total_levels = len(level_ids)
447
+ processed_levels = 0
448
+ for level_id in level_ids:
449
+ if level_id is None:
450
+ continue
451
+ if human_id_to_debug is None:
452
+ print(f"\rProcessed {processed_levels}/{total_levels} levels ({(processed_levels/total_levels)*100:.2f}%)", end="")
453
+ processed_levels += 1
454
+ 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)
@@ -6,17 +6,20 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
6
6
 
7
7
  from datetime import datetime, timedelta
8
8
  import sqlalchemy
9
- from hockey_blast_common_lib.models import Game, Penalty
10
- from hockey_blast_common_lib.stats_models import OrgStatsReferee, DivisionStatsReferee,OrgStatsWeeklyReferee, OrgStatsDailyReferee, DivisionStatsWeeklyReferee, DivisionStatsDailyReferee
9
+ from hockey_blast_common_lib.models import Game, Penalty, Organization, Division
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, 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
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
15
+ from hockey_blast_common_lib.stats_utils import assign_ranks
15
16
 
16
17
  def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_filter_out, aggregation_window=None):
17
18
  human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
18
19
 
19
20
  if aggregation_type == 'org':
21
+ aggregation_name = session.query(Organization).filter(Organization.id == aggregation_id).first().organization_name
22
+ print(f"Aggregating referee stats for {aggregation_name} with window {aggregation_window}...")
20
23
  if aggregation_window == 'Daily':
21
24
  StatsModel = OrgStatsDailyReferee
22
25
  elif aggregation_window == 'Weekly':
@@ -34,6 +37,14 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_
34
37
  StatsModel = DivisionStatsReferee
35
38
  min_games = MIN_GAMES_FOR_DIVISION_STATS
36
39
  filter_condition = Game.division_id == aggregation_id
40
+ elif aggregation_type == 'level':
41
+ StatsModel = LevelStatsReferee
42
+ min_games = MIN_GAMES_FOR_LEVEL_STATS
43
+ filter_condition = Division.level_id == aggregation_id
44
+ # Add filter to only include games for the last 5 years
45
+ five_years_ago = datetime.now() - timedelta(days=5*365)
46
+ level_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP) >= five_years_ago
47
+ filter_condition = filter_condition & level_window_filter
37
48
  else:
38
49
  raise ValueError("Invalid aggregation type")
39
50
 
@@ -117,36 +128,14 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_
117
128
  for stat in penalties_given_stats:
118
129
  if stat.referee_1_id and stat.referee_1_id not in human_ids_to_filter:
119
130
  key = (aggregation_id, stat.referee_1_id)
120
- if key not in stats_dict:
121
- stats_dict[key] = {
122
- 'games_reffed': 0,
123
- 'penalties_given': stat.penalties_given / 2,
124
- 'gm_given': stat.gm_given / 2,
125
- 'penalties_per_game': 0.0,
126
- 'gm_per_game': 0.0,
127
- 'game_ids': [stat.game_id],
128
- 'first_game_id': None,
129
- 'last_game_id': None
130
- }
131
- else:
131
+ if key in stats_dict:
132
132
  stats_dict[key]['penalties_given'] += stat.penalties_given / 2
133
133
  stats_dict[key]['gm_given'] += stat.gm_given / 2
134
134
  stats_dict[key]['game_ids'].append(stat.game_id)
135
135
 
136
136
  if stat.referee_2_id and stat.referee_2_id not in human_ids_to_filter:
137
137
  key = (aggregation_id, stat.referee_2_id)
138
- if key not in stats_dict:
139
- stats_dict[key] = {
140
- 'games_reffed': 0,
141
- 'penalties_given': stat.penalties_given / 2,
142
- 'gm_given': stat.gm_given / 2,
143
- 'penalties_per_game': 0.0,
144
- 'gm_per_game': 0.0,
145
- 'game_ids': [stat.game_id],
146
- 'first_game_id': None,
147
- 'last_game_id': None
148
- }
149
- else:
138
+ if key in stats_dict:
150
139
  stats_dict[key]['penalties_given'] += stat.penalties_given / 2
151
140
  stats_dict[key]['gm_given'] += stat.gm_given / 2
152
141
  stats_dict[key]['game_ids'].append(stat.game_id)
@@ -160,6 +149,9 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_
160
149
  # Ensure all keys have valid human_id values
161
150
  stats_dict = {key: value for key, value in stats_dict.items() if key[1] is not None}
162
151
 
152
+ # Filter out referees with less than min_games
153
+ stats_dict = {key: value for key, value in stats_dict.items() if value['games_reffed'] >= min_games}
154
+
163
155
  # Populate first_game_id and last_game_id
164
156
  for key, stat in stats_dict.items():
165
157
  all_game_ids = stat['game_ids']
@@ -173,11 +165,6 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_
173
165
  total_in_rank = len(stats_dict)
174
166
 
175
167
  # Assign ranks
176
- def assign_ranks(stats_dict, field):
177
- sorted_stats = sorted(stats_dict.items(), key=lambda x: x[1][field], reverse=True)
178
- for rank, (key, stat) in enumerate(sorted_stats, start=1):
179
- stats_dict[key][f'{field}_rank'] = rank
180
-
181
168
  assign_ranks(stats_dict, 'games_reffed')
182
169
  assign_ranks(stats_dict, 'penalties_given')
183
170
  assign_ranks(stats_dict, 'penalties_per_game')
@@ -189,8 +176,6 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_
189
176
  batch_size = 1000
190
177
  for i, (key, stat) in enumerate(stats_dict.items(), 1):
191
178
  aggregation_id, human_id = key
192
- if stat['games_reffed'] < min_games:
193
- continue
194
179
  referee_stat = StatsModel(
195
180
  aggregation_id=aggregation_id,
196
181
  human_id=human_id,
@@ -217,18 +202,40 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_
217
202
  print(f"\r{total_items}/{total_items} (100.00%)")
218
203
  print("\nDone.")
219
204
 
220
- # Example usage
221
205
  if __name__ == "__main__":
222
- args = parse_args()
223
- org_alias = args.org
224
206
  session = create_session("boss")
225
- org_id = get_org_id_from_alias(session, org_alias)
226
- division_ids = get_division_ids_for_last_season_in_all_leagues(session, org_id)
227
- print(f"Aggregating referee stats for {len(division_ids)} divisions in {org_alias}...")
228
- for division_id in division_ids:
229
- aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names)
230
- aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
231
- aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
232
- aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names)
233
- aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
234
- aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
207
+ human_id_to_debug = None
208
+
209
+ # Get all org_id present in the Organization table
210
+ org_ids = session.query(Organization.id).all()
211
+ org_ids = [org_id[0] for org_id in org_ids]
212
+
213
+ for org_id in org_ids:
214
+ division_ids = get_all_division_ids_for_org(session, org_id)
215
+ print(f"Aggregating referee stats for {len(division_ids)} divisions in org_id {org_id}...")
216
+ total_divisions = len(division_ids)
217
+ processed_divisions = 0
218
+ for division_id in division_ids:
219
+ aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names)
220
+ aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
221
+ aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
222
+ processed_divisions += 1
223
+ if human_id_to_debug is None:
224
+ print(f"\rProcessed {processed_divisions}/{total_divisions} divisions ({(processed_divisions/total_divisions)*100:.2f}%)", end="")
225
+
226
+ aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names)
227
+ aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
228
+ aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
229
+
230
+ # Aggregate by level
231
+ level_ids = session.query(Division.level_id).distinct().all()
232
+ level_ids = [level_id[0] for level_id in level_ids]
233
+ total_levels = len(level_ids)
234
+ processed_levels = 0
235
+ for level_id in level_ids:
236
+ if level_id is None:
237
+ continue
238
+ if human_id_to_debug is None:
239
+ print(f"\rProcessed {processed_levels}/{total_levels} levels ({(processed_levels/total_levels)*100:.2f}%)", end="")
240
+ processed_levels += 1
241
+ aggregate_referee_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names)
@@ -3,17 +3,19 @@ import sys, os
3
3
  # Add the package directory to the Python path
4
4
  sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
5
5
 
6
- from hockey_blast_common_lib.models import Human, LevelStatsSkater
6
+ from hockey_blast_common_lib.models import Human, Level
7
+ from hockey_blast_common_lib.stats_models import LevelStatsSkater
7
8
  from hockey_blast_common_lib.db_connection import create_session
8
9
  from sqlalchemy.sql import func
9
10
 
10
- def calculate_skater_skill_value(human_id, level_stats):
11
+ def calculate_skater_skill_value(session, human_id, level_stats):
11
12
  max_skill_value = 0
12
13
 
13
14
  for stat in level_stats:
14
- level_skill_value = stat.level.skill_value
15
- if level_skill_value < 0:
15
+ level = session.query(Level).filter(Level.id == stat.level_id).first()
16
+ if not level or level.skill_value < 0:
16
17
  continue
18
+ level_skill_value = level.skill_value
17
19
  ppg_ratio = stat.points_per_game_rank / stat.total_in_rank
18
20
  games_played_ratio = stat.games_played_rank / stat.total_in_rank
19
21
 
@@ -33,7 +35,7 @@ def assign_skater_skill_values():
33
35
  for human in humans:
34
36
  level_stats = session.query(LevelStatsSkater).filter(LevelStatsSkater.human_id == human.id).all()
35
37
  if level_stats:
36
- skater_skill_value = calculate_skater_skill_value(human.id, level_stats)
38
+ skater_skill_value = calculate_skater_skill_value(session, human.id, level_stats)
37
39
  human.skater_skill_value = skater_skill_value
38
40
  session.commit()
39
41
 
@@ -106,6 +106,7 @@ class Human(db.Model):
106
106
  last_name = db.Column(db.String(100))
107
107
  first_date = db.Column(db.Date)
108
108
  last_date = db.Column(db.Date)
109
+ skater_skill_value = db.Column(db.Float, nullable=True)
109
110
  __table_args__ = (
110
111
  db.UniqueConstraint('first_name', 'middle_name', 'last_name', name='_human_name_uc'),
111
112
  )
@@ -137,9 +137,8 @@ def populate_league_ids():
137
137
  session.commit()
138
138
  print("League IDs have been populated into the Season table.")
139
139
 
140
- if __name__ == "__main__":
140
+ #if __name__ == "__main__":
141
141
  # delete_all_skills()
142
142
  #fill_seed_skills()
143
- reset_skill_values_in_divisions()
144
143
  #populate_season_ids() # Call the function to populate season_ids
145
144
  #populate_league_ids() # Call the new function to populate league_ids
@@ -17,8 +17,20 @@ class BaseStatsHuman(db.Model):
17
17
  games_goalie = db.Column(db.Integer, default=0)
18
18
  games_goalie_rank = db.Column(db.Integer, default=0)
19
19
  total_in_rank = db.Column(db.Integer, default=0)
20
- first_game_id = db.Column(db.Integer, db.ForeignKey('games.id'))
21
- last_game_id = db.Column(db.Integer, db.ForeignKey('games.id'))
20
+ skaters_in_rank = db.Column(db.Integer, default=0)
21
+ goalies_in_rank = db.Column(db.Integer, default=0)
22
+ referees_in_rank = db.Column(db.Integer, default=0)
23
+ scorekeepers_in_rank = db.Column(db.Integer, default=0)
24
+ first_game_id = db.Column(db.Integer, db.ForeignKey('games.id'), nullable=True)
25
+ last_game_id = db.Column(db.Integer, db.ForeignKey('games.id'), nullable=True)
26
+ first_game_id_skater = db.Column(db.Integer, db.ForeignKey('games.id'), nullable=True)
27
+ last_game_id_skater = db.Column(db.Integer, db.ForeignKey('games.id'), nullable=True)
28
+ first_game_id_goalie = db.Column(db.Integer, db.ForeignKey('games.id'), nullable=True)
29
+ last_game_id_goalie = db.Column(db.Integer, db.ForeignKey('games.id'), nullable=True)
30
+ first_game_id_referee = db.Column(db.Integer, db.ForeignKey('games.id'), nullable=True)
31
+ last_game_id_referee = db.Column(db.Integer, db.ForeignKey('games.id'), nullable=True)
32
+ first_game_id_scorekeeper = db.Column(db.Integer, db.ForeignKey('games.id'), nullable=True)
33
+ last_game_id_scorekeeper = db.Column(db.Integer, db.ForeignKey('games.id'), nullable=True)
22
34
 
23
35
  @declared_attr
24
36
  def __table_args__(cls):
@@ -196,6 +208,19 @@ class DivisionStatsHuman(BaseStatsHuman):
196
208
  def get_aggregation_column(cls):
197
209
  return 'division_id'
198
210
 
211
+ class LevelStatsHuman(BaseStatsHuman):
212
+ __tablename__ = 'level_stats_human'
213
+ level_id = db.Column(db.Integer, db.ForeignKey('levels.id'), nullable=False)
214
+ aggregation_id = synonym('level_id')
215
+
216
+ @declared_attr
217
+ def aggregation_type(cls):
218
+ return 'level'
219
+
220
+ @classmethod
221
+ def get_aggregation_column(cls):
222
+ return 'level_id'
223
+
199
224
  class OrgStatsSkater(BaseStatsSkater):
200
225
  __tablename__ = 'org_stats_skater'
201
226
  org_id = db.Column(db.Integer, db.ForeignKey('organizations.id'), nullable=False)
@@ -262,6 +287,19 @@ class DivisionStatsGoalie(BaseStatsGoalie):
262
287
  def get_aggregation_column(cls):
263
288
  return 'division_id'
264
289
 
290
+ class LevelStatsGoalie(BaseStatsGoalie):
291
+ __tablename__ = 'level_stats_goalie'
292
+ level_id = db.Column(db.Integer, db.ForeignKey('levels.id'), nullable=False)
293
+ aggregation_id = synonym('level_id')
294
+
295
+ @declared_attr
296
+ def aggregation_type(cls):
297
+ return 'level'
298
+
299
+ @classmethod
300
+ def get_aggregation_column(cls):
301
+ return 'level_id'
302
+
265
303
 
266
304
  class OrgStatsReferee(BaseStatsReferee):
267
305
  __tablename__ = 'org_stats_referee'
@@ -289,6 +327,19 @@ class DivisionStatsReferee(BaseStatsReferee):
289
327
  def get_aggregation_column(cls):
290
328
  return 'division_id'
291
329
 
330
+ class LevelStatsReferee(BaseStatsReferee):
331
+ __tablename__ = 'level_stats_referee'
332
+ level_id = db.Column(db.Integer, db.ForeignKey('levels.id'), nullable=False)
333
+ aggregation_id = synonym('level_id')
334
+
335
+ @declared_attr
336
+ def aggregation_type(cls):
337
+ return 'level'
338
+
339
+ @classmethod
340
+ def get_aggregation_column(cls):
341
+ return 'level_id'
342
+
292
343
 
293
344
  class OrgStatsScorekeeper(BaseStatsScorekeeper):
294
345
  __tablename__ = 'org_stats_scorekeeper'
@@ -0,0 +1,4 @@
1
+ def assign_ranks(stats_dict, field, reverse_rank=False):
2
+ sorted_stats = sorted(stats_dict.items(), key=lambda x: x[1][field], reverse=not reverse_rank)
3
+ for rank, (key, stat) in enumerate(sorted_stats, start=1):
4
+ stats_dict[key][f'{field}_rank'] = rank
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hockey-blast-common-lib
3
- Version: 0.1.31
3
+ Version: 0.1.33
4
4
  Summary: Common library for shared functionality and DB models
5
5
  Author: Pavel Kletskov
6
6
  Author-email: kletskov@gmail.com
@@ -0,0 +1,22 @@
1
+ hockey_blast_common_lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ hockey_blast_common_lib/aggregate_goalie_stats.py,sha256=GRlKfZcfQWLHfeVZjHSG4uc9RNVttJaOC9_-VncqDP8,12190
3
+ hockey_blast_common_lib/aggregate_human_stats.py,sha256=BlG-TDPEJyUsx3mYxEt50pVgCftCr1r9L0u5q9Zf4BU,23958
4
+ hockey_blast_common_lib/aggregate_referee_stats.py,sha256=BmvEiewtwT5z4GMrmhNKIPWDxPtFHLMuUZ_UNucu0YQ,11910
5
+ hockey_blast_common_lib/aggregate_skater_stats.py,sha256=jkBD5u-gJc1DTDIEuxM_qymKsrWtLagFKeEn__2rFgU,16009
6
+ hockey_blast_common_lib/assign_skater_skill.py,sha256=p-0fbodGpM8BCjKHDpxNb7BH2FcIlBsJwON844KNrUY,1817
7
+ hockey_blast_common_lib/db_connection.py,sha256=HvPxDvOj7j5H85RfslGvHVNevfg7mKCd0syJ6NX21mU,1890
8
+ hockey_blast_common_lib/dump_sample_db.sh,sha256=MHPA-Ciod7wsvAlMbRtXFiyajgnEqU1xR59sJQ9UWR0,738
9
+ hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=fO5SsdPB6XYptPLx1rD2VVSTySLnJmyubeSS0A0HGyw,1033692
10
+ hockey_blast_common_lib/models.py,sha256=ebRnnvDOVNDfqAp8CA8u7uk3LCOfI3iUwOpHgzoBy0U,15984
11
+ hockey_blast_common_lib/options.py,sha256=6na8fo-5A2RBPpd_h-7dsqetOLSLoNEJg1QMYgl4jNs,792
12
+ hockey_blast_common_lib/restore_sample_db.sh,sha256=u2zKazC6vNMULkpYzI64nlneCWaGUtDHPBAU-gWgRbw,1861
13
+ hockey_blast_common_lib/skills_in_divisions.py,sha256=RR-x-D7V_lQX--2a2GHEYHtATtIOj2ACpvcEUDzVgkY,7487
14
+ hockey_blast_common_lib/skills_propagation.py,sha256=x6yy7fJ6IX3YiHqiP_v7-p_S2Expb8JJ-mWuajEFBdY,16388
15
+ hockey_blast_common_lib/stats_models.py,sha256=qvkt-XRFb4ZW7yBj7vltedzUS_YwWagm_efMRcsioSA,25120
16
+ hockey_blast_common_lib/stats_utils.py,sha256=Uv7xv9Eph2g7kQFOpTJujMm8P-UB42IDbAw5WkjJA0g,267
17
+ hockey_blast_common_lib/utils.py,sha256=odDJWCK0BgbResXeoUzxbVChjaxcXr168ZxbrAw3L_8,3752
18
+ hockey_blast_common_lib/wsgi.py,sha256=7LGUzioigviJp-EUhSEaQcd4jBae0mxbkyBscQfZhlc,730
19
+ hockey_blast_common_lib-0.1.33.dist-info/METADATA,sha256=AqXHHG_rYxzkm7oBWB7W1Ew94nm51nTCN1YCetrOX6Q,318
20
+ hockey_blast_common_lib-0.1.33.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
21
+ hockey_blast_common_lib-0.1.33.dist-info/top_level.txt,sha256=wIR4LIkE40npoA2QlOdfCYlgFeGbsHR8Z6r0h46Vtgc,24
22
+ hockey_blast_common_lib-0.1.33.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- hockey_blast_common_lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- hockey_blast_common_lib/aggregate_goalie_stats.py,sha256=d2qav46Rg2DNIYRj_Ubj1kpQmoPUJHKiwEWOVU25nD4,8742
3
- hockey_blast_common_lib/aggregate_human_stats.py,sha256=88OMhTgQjzc9xIakf6kW9_lZwbSXkpsZy8C0pX-Wlq8,14229
4
- hockey_blast_common_lib/aggregate_referee_stats.py,sha256=A0PTyEbPUjqfXxlJCDOVioFaQk9AyjjhiWEuRuu35v0,11036
5
- hockey_blast_common_lib/aggregate_skater_stats.py,sha256=jkBD5u-gJc1DTDIEuxM_qymKsrWtLagFKeEn__2rFgU,16009
6
- hockey_blast_common_lib/assign_skater_skill.py,sha256=YQYYWKArkvqPsu7shlRzggE-p_7rcEMO3VtnzKQHPG0,1657
7
- hockey_blast_common_lib/db_connection.py,sha256=HvPxDvOj7j5H85RfslGvHVNevfg7mKCd0syJ6NX21mU,1890
8
- hockey_blast_common_lib/dump_sample_db.sh,sha256=MHPA-Ciod7wsvAlMbRtXFiyajgnEqU1xR59sJQ9UWR0,738
9
- hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=Flm7M1rDuz-psuBg_gtlo1JhjjJRjnBMGy-gTwgMhjM,1033685
10
- hockey_blast_common_lib/models.py,sha256=2YkFHgzvxLSU2-fURsyIqdaXjcQwZ_wjauYR9wWQtDw,15924
11
- hockey_blast_common_lib/options.py,sha256=6na8fo-5A2RBPpd_h-7dsqetOLSLoNEJg1QMYgl4jNs,792
12
- hockey_blast_common_lib/restore_sample_db.sh,sha256=u2zKazC6vNMULkpYzI64nlneCWaGUtDHPBAU-gWgRbw,1861
13
- hockey_blast_common_lib/skills_in_divisions.py,sha256=tQYSikIV9AN_Cr-770KRy4reaiiR-2vvtwaNPJ8Lvws,7524
14
- hockey_blast_common_lib/skills_propagation.py,sha256=x6yy7fJ6IX3YiHqiP_v7-p_S2Expb8JJ-mWuajEFBdY,16388
15
- hockey_blast_common_lib/stats_models.py,sha256=35-6iz1r8MJcmzlyIlJy0uHgWh8oltyf-3H61ocal3o,23048
16
- hockey_blast_common_lib/utils.py,sha256=odDJWCK0BgbResXeoUzxbVChjaxcXr168ZxbrAw3L_8,3752
17
- hockey_blast_common_lib/wsgi.py,sha256=7LGUzioigviJp-EUhSEaQcd4jBae0mxbkyBscQfZhlc,730
18
- hockey_blast_common_lib-0.1.31.dist-info/METADATA,sha256=F19EMmlAfN34YH4YhXsATH-TdZ3ROGICcIU-T10A5yw,318
19
- hockey_blast_common_lib-0.1.31.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
20
- hockey_blast_common_lib-0.1.31.dist-info/top_level.txt,sha256=wIR4LIkE40npoA2QlOdfCYlgFeGbsHR8Z6r0h46Vtgc,24
21
- hockey_blast_common_lib-0.1.31.dist-info/RECORD,,