hockey-blast-common-lib 0.1.32__tar.gz → 0.1.33__tar.gz

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.
Files changed (29) hide show
  1. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/PKG-INFO +1 -1
  2. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/aggregate_goalie_stats.py +89 -34
  3. hockey_blast_common_lib-0.1.33/hockey_blast_common_lib/aggregate_human_stats.py +454 -0
  4. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/aggregate_referee_stats.py +55 -48
  5. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/skills_in_divisions.py +1 -1
  6. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/stats_models.py +53 -2
  7. hockey_blast_common_lib-0.1.33/hockey_blast_common_lib/stats_utils.py +4 -0
  8. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib.egg-info/PKG-INFO +1 -1
  9. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib.egg-info/SOURCES.txt +1 -0
  10. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/setup.py +1 -1
  11. hockey_blast_common_lib-0.1.32/hockey_blast_common_lib/aggregate_human_stats.py +0 -295
  12. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/MANIFEST.in +0 -0
  13. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/README.md +0 -0
  14. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/__init__.py +0 -0
  15. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/aggregate_skater_stats.py +0 -0
  16. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/assign_skater_skill.py +0 -0
  17. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/db_connection.py +0 -0
  18. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/dump_sample_db.sh +0 -0
  19. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz +0 -0
  20. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/models.py +0 -0
  21. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/options.py +0 -0
  22. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/restore_sample_db.sh +0 -0
  23. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/skills_propagation.py +0 -0
  24. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/utils.py +0 -0
  25. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib/wsgi.py +0 -0
  26. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib.egg-info/dependency_links.txt +0 -0
  27. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib.egg-info/requires.txt +0 -0
  28. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/hockey_blast_common_lib.egg-info/top_level.txt +0 -0
  29. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.33}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hockey-blast-common-lib
3
- Version: 0.1.32
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
@@ -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)
@@ -0,0 +1,454 @@
1
+ import sys, os
2
+
3
+ # Add the package directory to the Python path
4
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
5
+
6
+
7
+ from datetime import datetime, timedelta
8
+ import sqlalchemy
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
+ from hockey_blast_common_lib.db_connection import create_session
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
15
+ from hockey_blast_common_lib.stats_utils import assign_ranks
16
+
17
+ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_filter_out, human_id_filter=None, aggregation_window=None):
18
+ human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
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}...")
23
+ if aggregation_window == 'Daily':
24
+ StatsModel = OrgStatsDailyHuman
25
+ elif aggregation_window == 'Weekly':
26
+ StatsModel = OrgStatsWeeklyHuman
27
+ else:
28
+ StatsModel = OrgStatsHuman
29
+ min_games = MIN_GAMES_FOR_ORG_STATS
30
+ filter_condition = Game.org_id == aggregation_id
31
+ elif aggregation_type == 'division':
32
+ if aggregation_window == 'Daily':
33
+ StatsModel = DivisionStatsDailyHuman
34
+ elif aggregation_window == 'Weekly':
35
+ StatsModel = DivisionStatsWeeklyHuman
36
+ else:
37
+ StatsModel = DivisionStatsHuman
38
+ min_games = MIN_GAMES_FOR_DIVISION_STATS
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
48
+ else:
49
+ raise ValueError("Invalid aggregation type")
50
+
51
+ # Apply aggregation window filter
52
+ if aggregation_window:
53
+ last_game_datetime = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
54
+ if last_game_datetime:
55
+ last_game_datetime = datetime.strptime(last_game_datetime, '%Y-%m-%d %H:%M:%S')
56
+ if aggregation_window == 'Daily':
57
+ start_datetime = last_game_datetime - timedelta(days=1)
58
+ elif aggregation_window == 'Weekly':
59
+ start_datetime = last_game_datetime - timedelta(weeks=1)
60
+ else:
61
+ start_datetime = None
62
+ if start_datetime:
63
+ game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime)
64
+ filter_condition = filter_condition & game_window_filter
65
+
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(
87
+ GameRoster.human_id,
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()
91
+
92
+ # Aggregate referee and scorekeeper games from Game table
93
+ referee_stats = session.query(
94
+ Game.referee_1_id.label('human_id'),
95
+ func.count(func.distinct(Game.id)).label('games_referee'),
96
+ func.array_agg(func.distinct(Game.id)).label('referee_game_ids')
97
+ ).join(Division, Game.division_id == Division.id).filter(filter_condition, game_status_filter, *human_filter).group_by(Game.referee_1_id).all()
98
+
99
+ referee_stats_2 = session.query(
100
+ Game.referee_2_id.label('human_id'),
101
+ func.count(func.distinct(Game.id)).label('games_referee'),
102
+ func.array_agg(func.distinct(Game.id)).label('referee_game_ids')
103
+ ).join(Division, Game.division_id == Division.id).filter(filter_condition, game_status_filter, *human_filter).group_by(Game.referee_2_id).all()
104
+
105
+ scorekeeper_stats = session.query(
106
+ Game.scorekeeper_id.label('human_id'),
107
+ func.count(func.distinct(Game.id)).label('games_scorekeeper'),
108
+ func.array_agg(func.distinct(Game.id)).label('scorekeeper_game_ids')
109
+ ).join(Division, Game.division_id == Division.id).filter(filter_condition, game_status_filter, *human_filter).group_by(Game.scorekeeper_id).all()
110
+
111
+ # Combine the results
112
+ stats_dict = {}
113
+ for stat in skater_stats:
114
+ if stat.human_id in human_ids_to_filter:
115
+ continue
116
+ key = (aggregation_id, stat.human_id)
117
+ stats_dict[key] = {
118
+ 'games_total': stat.games_skater,
119
+ 'games_skater': stat.games_skater,
120
+ 'games_goalie': 0,
121
+ 'games_referee': 0,
122
+ 'games_scorekeeper': 0,
123
+ 'skater_game_ids': stat.skater_game_ids,
124
+ 'goalie_game_ids': [],
125
+ 'referee_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
135
+ }
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
+
166
+ for stat in referee_stats:
167
+ if stat.human_id in human_ids_to_filter:
168
+ continue
169
+ key = (aggregation_id, stat.human_id)
170
+ if key not in stats_dict:
171
+ stats_dict[key] = {
172
+ 'games_total': stat.games_referee,
173
+ 'games_skater': 0,
174
+ 'games_goalie': 0,
175
+ 'games_referee': stat.games_referee,
176
+ 'games_scorekeeper': 0,
177
+ 'skater_game_ids': [],
178
+ 'goalie_game_ids': [],
179
+ 'referee_game_ids': stat.referee_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
189
+ }
190
+ else:
191
+ stats_dict[key]['games_referee'] += stat.games_referee
192
+ stats_dict[key]['games_total'] += stat.games_referee
193
+ stats_dict[key]['referee_game_ids'] += stat.referee_game_ids
194
+
195
+ for stat in referee_stats_2:
196
+ if stat.human_id in human_ids_to_filter:
197
+ continue
198
+ key = (aggregation_id, stat.human_id)
199
+ if key not in stats_dict:
200
+ stats_dict[key] = {
201
+ 'games_total': stat.games_referee,
202
+ 'games_skater': 0,
203
+ 'games_goalie': 0,
204
+ 'games_referee': stat.games_referee,
205
+ 'games_scorekeeper': 0,
206
+ 'skater_game_ids': [],
207
+ 'goalie_game_ids': [],
208
+ 'referee_game_ids': stat.referee_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
218
+ }
219
+ else:
220
+ stats_dict[key]['games_referee'] += stat.games_referee
221
+ stats_dict[key]['games_total'] += stat.games_referee
222
+ stats_dict[key]['referee_game_ids'] += stat.referee_game_ids
223
+
224
+ for stat in scorekeeper_stats:
225
+ if stat.human_id in human_ids_to_filter:
226
+ continue
227
+ key = (aggregation_id, stat.human_id)
228
+ if key not in stats_dict:
229
+ stats_dict[key] = {
230
+ 'games_total': stat.games_scorekeeper,
231
+ 'games_skater': 0,
232
+ 'games_goalie': 0,
233
+ 'games_referee': 0,
234
+ 'games_scorekeeper': stat.games_scorekeeper,
235
+ 'skater_game_ids': [],
236
+ 'goalie_game_ids': [],
237
+ 'referee_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
247
+ }
248
+ else:
249
+ stats_dict[key]['games_scorekeeper'] += stat.games_scorekeeper
250
+ stats_dict[key]['games_total'] += stat.games_scorekeeper
251
+ stats_dict[key]['scorekeeper_game_ids'] += stat.scorekeeper_game_ids
252
+
253
+ # Ensure all keys have valid human_id values
254
+ stats_dict = {key: value for key, value in stats_dict.items() if key[1] is not None}
255
+
256
+ # Calculate total_in_rank
257
+ total_in_rank = len(stats_dict)
258
+
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}
267
+
268
+ # Assign ranks
269
+ assign_ranks(stats_dict, 'games_total')
270
+ assign_ranks(stats_dict, 'games_skater')
271
+ assign_ranks(stats_dict, 'games_goalie')
272
+ assign_ranks(stats_dict, 'games_referee')
273
+ assign_ranks(stats_dict, 'games_scorekeeper')
274
+
275
+ # Populate first_game_id and last_game_id for each role
276
+ for key, stat in stats_dict.items():
277
+ all_game_ids = stat['skater_game_ids'] + stat['goalie_game_ids'] + stat['referee_game_ids'] + stat['scorekeeper_game_ids']
278
+ if all_game_ids:
279
+ first_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date, Game.time).first()
280
+ last_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date.desc(), Game.time.desc()).first()
281
+ stat['first_game_id'] = first_game.id if first_game else None
282
+ stat['last_game_id'] = last_game.id if last_game else None
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
+
308
+ # Insert aggregated stats into the appropriate table with progress output
309
+ batch_size = 1000
310
+ for i, (key, stat) in enumerate(stats_dict.items(), 1):
311
+ aggregation_id, human_id = key
312
+ if human_id_filter and human_id != human_id_filter:
313
+ continue
314
+
315
+ human_stat = StatsModel(
316
+ aggregation_id=aggregation_id,
317
+ human_id=human_id,
318
+ games_total=stat['games_total'],
319
+ games_total_rank=stat['games_total_rank'],
320
+ games_skater=stat['games_skater'],
321
+ games_skater_rank=stat['games_skater_rank'],
322
+ games_goalie=stat['games_goalie'],
323
+ games_goalie_rank=stat['games_goalie_rank'],
324
+ games_referee=stat['games_referee'],
325
+ games_referee_rank=stat['games_referee_rank'],
326
+ games_scorekeeper=stat['games_scorekeeper'],
327
+ games_scorekeeper_rank=stat['games_scorekeeper_rank'],
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,
333
+ first_game_id=stat['first_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']
343
+ )
344
+ session.add(human_stat)
345
+ # Commit in batches
346
+ if i % batch_size == 0:
347
+ session.commit()
348
+ session.commit()
349
+
350
+ # Fetch fake human ID for overall stats
351
+ fake_human_id = get_fake_human_for_stats(session)
352
+
353
+ # Calculate overall stats
354
+ overall_stats = {
355
+ 'games_total': sum(stat['games_total'] for stat in stats_dict.values()),
356
+ 'games_skater': sum(stat['games_skater'] for stat in stats_dict.values()),
357
+ 'games_goalie': sum(stat['games_goalie'] for stat in stats_dict.values()),
358
+ 'games_referee': sum(stat['games_referee'] for stat in stats_dict.values()),
359
+ 'games_scorekeeper': sum(stat['games_scorekeeper'] for stat in stats_dict.values()),
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,
365
+ 'first_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
375
+ }
376
+
377
+ # Populate first_game_id and last_game_id for overall stats
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']]
379
+ if all_game_ids:
380
+ first_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date, Game.time).first()
381
+ last_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date.desc(), Game.time.desc()).first()
382
+ overall_stats['first_game_id'] = first_game.id if first_game else None
383
+ overall_stats['last_game_id'] = last_game.id if last_game else None
384
+
385
+ # Insert overall stats for the fake human
386
+ overall_human_stat = StatsModel(
387
+ aggregation_id=aggregation_id,
388
+ human_id=fake_human_id,
389
+ games_total=overall_stats['games_total'],
390
+ games_total_rank=0, # Overall stats do not need a rank
391
+ games_skater=overall_stats['games_skater'],
392
+ games_skater_rank=0, # Overall stats do not need a rank
393
+ games_goalie=overall_stats['games_goalie'],
394
+ games_goalie_rank=0, # Overall stats do not need a rank
395
+ games_referee=overall_stats['games_referee'],
396
+ games_referee_rank=0, # Overall stats do not need a rank
397
+ games_scorekeeper=overall_stats['games_scorekeeper'],
398
+ games_scorekeeper_rank=0, # Overall stats do not need a rank
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'],
404
+ first_game_id=overall_stats['first_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']
414
+ )
415
+ session.add(overall_human_stat)
416
+ session.commit()
417
+
418
+ if __name__ == "__main__":
419
+ session = create_session("boss")
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)