hockey-blast-common-lib 0.1.32__tar.gz → 0.1.34__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 (31) hide show
  1. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/PKG-INFO +1 -1
  2. hockey_blast_common_lib-0.1.34/hockey_blast_common_lib/aggregate_all_stats.py +26 -0
  3. hockey_blast_common_lib-0.1.34/hockey_blast_common_lib/aggregate_goalie_stats.py +215 -0
  4. hockey_blast_common_lib-0.1.34/hockey_blast_common_lib/aggregate_human_stats.py +451 -0
  5. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/aggregate_referee_stats.py +65 -64
  6. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/aggregate_skater_stats.py +14 -19
  7. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz +0 -0
  8. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/models.py +1 -0
  9. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/skills_in_divisions.py +1 -1
  10. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/stats_models.py +53 -2
  11. hockey_blast_common_lib-0.1.34/hockey_blast_common_lib/stats_utils.py +0 -0
  12. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/utils.py +20 -4
  13. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib.egg-info/PKG-INFO +1 -1
  14. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib.egg-info/SOURCES.txt +2 -0
  15. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/setup.py +1 -1
  16. hockey_blast_common_lib-0.1.32/hockey_blast_common_lib/aggregate_goalie_stats.py +0 -164
  17. hockey_blast_common_lib-0.1.32/hockey_blast_common_lib/aggregate_human_stats.py +0 -295
  18. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/MANIFEST.in +0 -0
  19. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/README.md +0 -0
  20. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/__init__.py +0 -0
  21. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/assign_skater_skill.py +0 -0
  22. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/db_connection.py +0 -0
  23. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/dump_sample_db.sh +0 -0
  24. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/options.py +0 -0
  25. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/restore_sample_db.sh +0 -0
  26. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/skills_propagation.py +0 -0
  27. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib/wsgi.py +0 -0
  28. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib.egg-info/dependency_links.txt +0 -0
  29. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib.egg-info/requires.txt +0 -0
  30. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/hockey_blast_common_lib.egg-info/top_level.txt +0 -0
  31. {hockey_blast_common_lib-0.1.32 → hockey_blast_common_lib-0.1.34}/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.34
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,26 @@
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
+ from hockey_blast_common_lib.aggregate_human_stats import run_aggregate_human_stats
7
+ from hockey_blast_common_lib.aggregate_skater_stats import run_aggregate_skater_stats
8
+ from hockey_blast_common_lib.aggregate_goalie_stats import run_aggregate_goalie_stats
9
+ from hockey_blast_common_lib.aggregate_referee_stats import run_aggregate_referee_stats
10
+
11
+ if __name__ == "__main__":
12
+ print("Running aggregate_human_stats...")
13
+ run_aggregate_human_stats()
14
+ print("Finished running aggregate_human_stats\n")
15
+
16
+ print("Running aggregate_skater_stats...")
17
+ run_aggregate_skater_stats()
18
+ print("Finished running aggregate_skater_stats\n")
19
+
20
+ print("Running aggregate_goalie_stats...")
21
+ run_aggregate_goalie_stats()
22
+ print("Finished running aggregate_goalie_stats\n")
23
+
24
+ print("Running aggregate_referee_stats...")
25
+ run_aggregate_referee_stats()
26
+ print("Finished running aggregate_referee_stats\n")
@@ -0,0 +1,215 @@
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
+ from datetime import datetime, timedelta
7
+ import sqlalchemy
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
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 not_human_names, parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
14
+ from hockey_blast_common_lib.utils import get_org_id_from_alias, get_human_ids_by_names, get_division_ids_for_last_season_in_all_leagues, get_all_division_ids_for_org, get_start_datetime
15
+ from hockey_blast_common_lib.utils import assign_ranks
16
+ from sqlalchemy import func, case, and_
17
+ from collections import defaultdict
18
+
19
+ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_filter_out, debug_human_id=None, aggregation_window=None):
20
+ human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
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
+
33
+ if aggregation_type == 'org':
34
+ if aggregation_window == 'Daily':
35
+ StatsModel = OrgStatsDailyGoalie
36
+ elif aggregation_window == 'Weekly':
37
+ StatsModel = OrgStatsWeeklyGoalie
38
+ else:
39
+ StatsModel = OrgStatsGoalie
40
+ min_games = MIN_GAMES_FOR_ORG_STATS
41
+ filter_condition = Game.org_id == aggregation_id
42
+ elif aggregation_type == 'division':
43
+ if aggregation_window == 'Daily':
44
+ StatsModel = DivisionStatsDailyGoalie
45
+ elif aggregation_window == 'Weekly':
46
+ StatsModel = DivisionStatsWeeklyGoalie
47
+ else:
48
+ StatsModel = DivisionStatsGoalie
49
+ min_games = MIN_GAMES_FOR_DIVISION_STATS
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
59
+ else:
60
+ raise ValueError("Invalid aggregation type")
61
+
62
+ # Apply aggregation window filter
63
+ if aggregation_window:
64
+ last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
65
+ start_datetime = get_start_datetime(last_game_datetime_str, aggregation_window)
66
+ if start_datetime:
67
+ game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime_str)
68
+ filter_condition = filter_condition & game_window_filter
69
+
70
+ # Delete existing items from the stats table
71
+ session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
72
+ session.commit()
73
+
74
+ # Filter for specific human_id if provided
75
+ human_filter = []
76
+ # if debug_human_id:
77
+ # human_filter = [GameRoster.human_id == debug_human_id]
78
+
79
+ # Aggregate games played, goals allowed, and shots faced for each goalie
80
+ goalie_stats = session.query(
81
+ GameRoster.human_id,
82
+ func.count(Game.id).label('games_played'),
83
+ func.sum(case((GameRoster.team_id == Game.home_team_id, Game.visitor_final_score), else_=Game.home_final_score)).label('goals_allowed'),
84
+ 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'),
85
+ func.array_agg(Game.id).label('game_ids')
86
+ ).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()
87
+
88
+ # Combine the results
89
+ stats_dict = {}
90
+ for stat in goalie_stats:
91
+ if stat.human_id in human_ids_to_filter:
92
+ continue
93
+ key = (aggregation_id, stat.human_id)
94
+ if stat.games_played < min_games:
95
+ continue
96
+ stats_dict[key] = {
97
+ 'games_played': stat.games_played,
98
+ 'goals_allowed': stat.goals_allowed if stat.goals_allowed is not None else 0,
99
+ 'shots_faced': stat.shots_faced if stat.shots_faced is not None else 0,
100
+ 'goals_allowed_per_game': 0.0,
101
+ 'save_percentage': 0.0,
102
+ 'game_ids': stat.game_ids,
103
+ 'first_game_id': None,
104
+ 'last_game_id': None
105
+ }
106
+
107
+ # Calculate per game stats
108
+ for key, stat in stats_dict.items():
109
+ if stat['games_played'] > 0:
110
+ stat['goals_allowed_per_game'] = stat['goals_allowed'] / stat['games_played']
111
+ stat['save_percentage'] = (stat['shots_faced'] - stat['goals_allowed']) / stat['shots_faced'] if stat['shots_faced'] > 0 else 0.0
112
+
113
+ # Ensure all keys have valid human_id values
114
+ stats_dict = {key: value for key, value in stats_dict.items() if key[1] is not None}
115
+
116
+ # Populate first_game_id and last_game_id
117
+ for key, stat in stats_dict.items():
118
+ all_game_ids = stat['game_ids']
119
+ if all_game_ids:
120
+ first_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date, Game.time).first()
121
+ last_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date.desc(), Game.time.desc()).first()
122
+ stat['first_game_id'] = first_game.id if first_game else None
123
+ stat['last_game_id'] = last_game.id if last_game else None
124
+
125
+ # Calculate total_in_rank
126
+ total_in_rank = len(stats_dict)
127
+
128
+ # Assign ranks within each level
129
+ assign_ranks(stats_dict, 'games_played')
130
+ assign_ranks(stats_dict, 'goals_allowed', reverse_rank=True)
131
+ assign_ranks(stats_dict, 'shots_faced')
132
+ assign_ranks(stats_dict, 'goals_allowed_per_game', reverse_rank=True)
133
+ assign_ranks(stats_dict, 'save_percentage')
134
+
135
+ # Debug output for specific human
136
+ if debug_human_id:
137
+ if any(key[1] == debug_human_id for key in stats_dict):
138
+ human = session.query(Human).filter(Human.id == debug_human_id).first()
139
+ human_name = f"{human.first_name} {human.last_name}" if human else "Unknown"
140
+ 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}:")
141
+ for key, stat in stats_dict.items():
142
+ if key[1] == debug_human_id:
143
+ for k, v in stat.items():
144
+ print(f"{k}: {v}")
145
+
146
+ # Insert aggregated stats into the appropriate table with progress output
147
+ total_items = len(stats_dict)
148
+ batch_size = 1000
149
+ for i, (key, stat) in enumerate(stats_dict.items(), 1):
150
+ aggregation_id, human_id = key
151
+ goals_allowed_per_game = stat['goals_allowed'] / stat['games_played'] if stat['games_played'] > 0 else 0.0
152
+ save_percentage = (stat['shots_faced'] - stat['goals_allowed']) / stat['shots_faced'] if stat['shots_faced'] > 0 else 0.0
153
+ goalie_stat = StatsModel(
154
+ aggregation_id=aggregation_id,
155
+ human_id=human_id,
156
+ games_played=stat['games_played'],
157
+ goals_allowed=stat['goals_allowed'],
158
+ shots_faced=stat['shots_faced'],
159
+ goals_allowed_per_game=goals_allowed_per_game,
160
+ save_percentage=save_percentage,
161
+ games_played_rank=stat['games_played_rank'],
162
+ goals_allowed_rank=stat['goals_allowed_rank'],
163
+ shots_faced_rank=stat['shots_faced_rank'],
164
+ goals_allowed_per_game_rank=stat['goals_allowed_per_game_rank'],
165
+ save_percentage_rank=stat['save_percentage_rank'],
166
+ total_in_rank=total_in_rank,
167
+ first_game_id=stat['first_game_id'],
168
+ last_game_id=stat['last_game_id']
169
+ )
170
+ session.add(goalie_stat)
171
+ # Commit in batches
172
+ if i % batch_size == 0:
173
+ session.commit()
174
+ session.commit()
175
+
176
+ def run_aggregate_goalie_stats():
177
+ session = create_session("boss")
178
+ human_id_to_debug = None
179
+
180
+ # Get all org_id present in the Organization table
181
+ org_ids = session.query(Organization.id).all()
182
+ org_ids = [org_id[0] for org_id in org_ids]
183
+
184
+ for org_id in org_ids:
185
+ division_ids = get_all_division_ids_for_org(session, org_id)
186
+ print(f"Aggregating goalie stats for {len(division_ids)} divisions in org_id {org_id}...")
187
+ total_divisions = len(division_ids)
188
+ processed_divisions = 0
189
+ for division_id in division_ids:
190
+ 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)
191
+ 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')
192
+ 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')
193
+ processed_divisions += 1
194
+ if human_id_to_debug is None:
195
+ print(f"\rProcessed {processed_divisions}/{total_divisions} divisions ({(processed_divisions/total_divisions)*100:.2f}%)", end="")
196
+
197
+ 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)
198
+ 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')
199
+ 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')
200
+
201
+ # Aggregate by level
202
+ level_ids = session.query(Division.level_id).distinct().all()
203
+ level_ids = [level_id[0] for level_id in level_ids]
204
+ total_levels = len(level_ids)
205
+ processed_levels = 0
206
+ for level_id in level_ids:
207
+ if level_id is None:
208
+ continue
209
+ if human_id_to_debug is None:
210
+ print(f"\rProcessed {processed_levels}/{total_levels} levels ({(processed_levels/total_levels)*100:.2f}%)", end="")
211
+ processed_levels += 1
212
+ 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)
213
+
214
+ if __name__ == "__main__":
215
+ run_aggregate_goalie_stats()