hockey-blast-common-lib 0.1.63__py3-none-any.whl → 0.1.64__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hockey_blast_common_lib/aggregate_all_stats.py +7 -4
- hockey_blast_common_lib/aggregate_goalie_stats.py +301 -107
- hockey_blast_common_lib/aggregate_h2h_stats.py +64 -33
- hockey_blast_common_lib/aggregate_human_stats.py +565 -280
- hockey_blast_common_lib/aggregate_referee_stats.py +286 -135
- hockey_blast_common_lib/aggregate_s2s_stats.py +85 -25
- hockey_blast_common_lib/aggregate_scorekeeper_stats.py +228 -113
- hockey_blast_common_lib/aggregate_skater_stats.py +561 -238
- hockey_blast_common_lib/assign_skater_skill.py +21 -11
- hockey_blast_common_lib/db_connection.py +59 -8
- hockey_blast_common_lib/embedding_utils.py +309 -0
- hockey_blast_common_lib/h2h_models.py +150 -56
- hockey_blast_common_lib/models.py +305 -150
- hockey_blast_common_lib/options.py +30 -15
- hockey_blast_common_lib/progress_utils.py +21 -13
- hockey_blast_common_lib/skills_in_divisions.py +170 -33
- hockey_blast_common_lib/skills_propagation.py +164 -70
- hockey_blast_common_lib/stats_models.py +489 -245
- hockey_blast_common_lib/stats_utils.py +6 -3
- hockey_blast_common_lib/utils.py +89 -25
- hockey_blast_common_lib/wsgi.py +7 -5
- {hockey_blast_common_lib-0.1.63.dist-info → hockey_blast_common_lib-0.1.64.dist-info}/METADATA +1 -1
- hockey_blast_common_lib-0.1.64.dist-info/RECORD +29 -0
- hockey_blast_common_lib-0.1.63.dist-info/RECORD +0 -28
- {hockey_blast_common_lib-0.1.63.dist-info → hockey_blast_common_lib-0.1.64.dist-info}/WHEEL +0 -0
- {hockey_blast_common_lib-0.1.63.dist-info → hockey_blast_common_lib-0.1.64.dist-info}/top_level.txt +0 -0
|
@@ -1,48 +1,66 @@
|
|
|
1
|
-
import
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
2
3
|
from datetime import datetime
|
|
3
4
|
|
|
4
5
|
# Add the package directory to the Python path
|
|
5
6
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
6
7
|
|
|
7
|
-
from
|
|
8
|
-
from
|
|
8
|
+
from sqlalchemy import types
|
|
9
|
+
from sqlalchemy.sql import func
|
|
10
|
+
|
|
9
11
|
from hockey_blast_common_lib.db_connection import create_session
|
|
12
|
+
from hockey_blast_common_lib.h2h_models import H2HStats, H2HStatsMeta
|
|
13
|
+
from hockey_blast_common_lib.models import Game, GameRoster, Goal, Penalty
|
|
10
14
|
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
|
11
|
-
from sqlalchemy.sql import func
|
|
12
|
-
from sqlalchemy import types
|
|
13
15
|
|
|
14
16
|
# Max games to process (set to None to process all)
|
|
15
17
|
MAX_GAMES_TO_PROCESS = None # Set to None to process all games
|
|
16
18
|
|
|
19
|
+
|
|
17
20
|
def aggregate_h2h_stats():
|
|
18
21
|
session = create_session("boss")
|
|
19
|
-
meta = None
|
|
22
|
+
meta = None # session.query(H2HStatsMeta).order_by(H2HStatsMeta.id.desc()).first()
|
|
20
23
|
h2h_stats_dict = {} # (h1, h2) -> H2HStats instance
|
|
21
|
-
if
|
|
24
|
+
if (
|
|
25
|
+
meta is None
|
|
26
|
+
or meta.last_run_timestamp is None
|
|
27
|
+
or meta.last_processed_game_id is None
|
|
28
|
+
):
|
|
22
29
|
# Full run: delete all existing stats and process all games
|
|
23
30
|
session.query(H2HStats).delete()
|
|
24
31
|
session.commit()
|
|
25
32
|
games_query = session.query(Game).order_by(Game.date, Game.time, Game.id)
|
|
26
|
-
print(
|
|
33
|
+
print(
|
|
34
|
+
"No previous run found, deleted all existing H2H stats, processing all games..."
|
|
35
|
+
)
|
|
27
36
|
else:
|
|
28
37
|
# Incremental: only process games after last processed
|
|
29
38
|
# Load all existing stats into memory
|
|
30
39
|
for stat in session.query(H2HStats).all():
|
|
31
40
|
h2h_stats_dict[(stat.human1_id, stat.human2_id)] = stat
|
|
32
|
-
last_game =
|
|
41
|
+
last_game = (
|
|
42
|
+
session.query(Game).filter(Game.id == meta.last_processed_game_id).first()
|
|
43
|
+
)
|
|
33
44
|
if last_game:
|
|
34
45
|
last_dt = datetime.combine(last_game.date, last_game.time)
|
|
35
|
-
games_query =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
games_query = (
|
|
47
|
+
session.query(Game)
|
|
48
|
+
.filter(
|
|
49
|
+
func.cast(func.concat(Game.date, " ", Game.time), types.TIMESTAMP())
|
|
50
|
+
> last_dt
|
|
51
|
+
)
|
|
52
|
+
.order_by(Game.date, Game.time, Game.id)
|
|
53
|
+
)
|
|
54
|
+
print(
|
|
55
|
+
f"Resuming from game after id {meta.last_processed_game_id} ({last_dt})..."
|
|
56
|
+
)
|
|
39
57
|
else:
|
|
40
58
|
games_query = session.query(Game).order_by(Game.date, Game.time, Game.id)
|
|
41
59
|
print("Previous game id not found, processing all games...")
|
|
42
60
|
|
|
43
61
|
total_games = games_query.count()
|
|
44
62
|
print(f"Total games to process: {total_games}")
|
|
45
|
-
|
|
63
|
+
|
|
46
64
|
# Create progress tracker
|
|
47
65
|
progress = create_progress_tracker(total_games, "Processing H2H stats")
|
|
48
66
|
processed = 0
|
|
@@ -64,21 +82,21 @@ def aggregate_h2h_stats():
|
|
|
64
82
|
# Add goalies from Game table (home/visitor)
|
|
65
83
|
if game.home_goalie_id:
|
|
66
84
|
all_humans.add(game.home_goalie_id)
|
|
67
|
-
human_roles.setdefault(game.home_goalie_id, set()).add(
|
|
85
|
+
human_roles.setdefault(game.home_goalie_id, set()).add("G")
|
|
68
86
|
if game.visitor_goalie_id:
|
|
69
87
|
all_humans.add(game.visitor_goalie_id)
|
|
70
|
-
human_roles.setdefault(game.visitor_goalie_id, set()).add(
|
|
88
|
+
human_roles.setdefault(game.visitor_goalie_id, set()).add("G")
|
|
71
89
|
# Add referees from Game table (NOT from roster!)
|
|
72
90
|
if game.referee_1_id:
|
|
73
91
|
all_humans.add(game.referee_1_id)
|
|
74
|
-
human_roles.setdefault(game.referee_1_id, set()).add(
|
|
92
|
+
human_roles.setdefault(game.referee_1_id, set()).add("R")
|
|
75
93
|
if game.referee_2_id:
|
|
76
94
|
all_humans.add(game.referee_2_id)
|
|
77
|
-
human_roles.setdefault(game.referee_2_id, set()).add(
|
|
95
|
+
human_roles.setdefault(game.referee_2_id, set()).add("R")
|
|
78
96
|
# --- Build all pairs of humans in this game ---
|
|
79
97
|
all_humans = list(all_humans)
|
|
80
98
|
for i in range(len(all_humans)):
|
|
81
|
-
for j in range(i+1, len(all_humans)):
|
|
99
|
+
for j in range(i + 1, len(all_humans)):
|
|
82
100
|
h1, h2 = sorted([all_humans[i], all_humans[j]])
|
|
83
101
|
key = (h1, h2)
|
|
84
102
|
h2h = h2h_stats_dict.get(key)
|
|
@@ -128,7 +146,7 @@ def aggregate_h2h_stats():
|
|
|
128
146
|
h1_shootout_attempts_vs_h2_goalie=0,
|
|
129
147
|
h1_shootout_goals_vs_h2_goalie=0,
|
|
130
148
|
h2_shootout_attempts_vs_h1_goalie=0,
|
|
131
|
-
h2_shootout_goals_vs_h1_goalie=0
|
|
149
|
+
h2_shootout_goals_vs_h1_goalie=0,
|
|
132
150
|
)
|
|
133
151
|
h2h_stats_dict[key] = h2h
|
|
134
152
|
# Update first/last game ids
|
|
@@ -163,34 +181,40 @@ def aggregate_h2h_stats():
|
|
|
163
181
|
if _is_tie(game):
|
|
164
182
|
h2h.games_tied_against += 1
|
|
165
183
|
# --- Role-specific stats ---
|
|
166
|
-
if
|
|
184
|
+
if "G" in h1_roles:
|
|
167
185
|
h2h.games_h1_goalie += 1
|
|
168
|
-
if
|
|
186
|
+
if "G" in h2_roles:
|
|
169
187
|
h2h.games_h2_goalie += 1
|
|
170
|
-
if
|
|
188
|
+
if "R" in h1_roles:
|
|
171
189
|
h2h.games_h1_ref += 1
|
|
172
|
-
if
|
|
190
|
+
if "R" in h2_roles:
|
|
173
191
|
h2h.games_h2_ref += 1
|
|
174
|
-
if
|
|
192
|
+
if "R" in h1_roles and "R" in h2_roles:
|
|
175
193
|
h2h.games_both_referees += 1
|
|
176
194
|
# --- Goals, assists, penalties ---
|
|
177
195
|
# Goals
|
|
178
196
|
goals = session.query(Goal).filter(Goal.game_id == game.id).all()
|
|
179
197
|
for goal in goals:
|
|
180
|
-
if goal.goal_scorer_id == h1 and (
|
|
198
|
+
if goal.goal_scorer_id == h1 and (
|
|
199
|
+
goal.assist_1_id == h2 or goal.assist_2_id == h2
|
|
200
|
+
):
|
|
181
201
|
h2h.goals_h1_when_together += 1
|
|
182
|
-
if goal.goal_scorer_id == h2 and (
|
|
202
|
+
if goal.goal_scorer_id == h2 and (
|
|
203
|
+
goal.assist_1_id == h1 or goal.assist_2_id == h1
|
|
204
|
+
):
|
|
183
205
|
h2h.goals_h2_when_together += 1
|
|
184
206
|
# Penalties
|
|
185
|
-
penalties =
|
|
207
|
+
penalties = (
|
|
208
|
+
session.query(Penalty).filter(Penalty.game_id == game.id).all()
|
|
209
|
+
)
|
|
186
210
|
for pen in penalties:
|
|
187
211
|
if pen.penalized_player_id == h1:
|
|
188
212
|
h2h.penalties_h1_when_together += 1
|
|
189
|
-
if pen.penalty_minutes and
|
|
213
|
+
if pen.penalty_minutes and "GM" in pen.penalty_minutes:
|
|
190
214
|
h2h.gm_penalties_h1_when_together += 1
|
|
191
215
|
if pen.penalized_player_id == h2:
|
|
192
216
|
h2h.penalties_h2_when_together += 1
|
|
193
|
-
if pen.penalty_minutes and
|
|
217
|
+
if pen.penalty_minutes and "GM" in pen.penalty_minutes:
|
|
194
218
|
h2h.gm_penalties_h2_when_together += 1
|
|
195
219
|
# --- TODO: Add more detailed logic for goalie/skater, referee/player, shootouts, etc. ---
|
|
196
220
|
latest_game_id = game.id
|
|
@@ -202,13 +226,13 @@ def aggregate_h2h_stats():
|
|
|
202
226
|
session.commit()
|
|
203
227
|
# Save/update meta
|
|
204
228
|
meta = H2HStatsMeta(
|
|
205
|
-
last_run_timestamp=datetime.utcnow(),
|
|
206
|
-
last_processed_game_id=latest_game_id
|
|
229
|
+
last_run_timestamp=datetime.utcnow(), last_processed_game_id=latest_game_id
|
|
207
230
|
)
|
|
208
231
|
session.add(meta)
|
|
209
232
|
session.commit()
|
|
210
233
|
print("H2H aggregation complete.")
|
|
211
234
|
|
|
235
|
+
|
|
212
236
|
# --- Helper functions for win/loss/tie ---
|
|
213
237
|
def _is_win(game, team_id):
|
|
214
238
|
if team_id == game.home_team_id:
|
|
@@ -217,6 +241,7 @@ def _is_win(game, team_id):
|
|
|
217
241
|
return (game.visitor_final_score or 0) > (game.home_final_score or 0)
|
|
218
242
|
return False
|
|
219
243
|
|
|
244
|
+
|
|
220
245
|
def _is_loss(game, team_id):
|
|
221
246
|
if team_id == game.home_team_id:
|
|
222
247
|
return (game.home_final_score or 0) < (game.visitor_final_score or 0)
|
|
@@ -224,8 +249,14 @@ def _is_loss(game, team_id):
|
|
|
224
249
|
return (game.visitor_final_score or 0) < (game.home_final_score or 0)
|
|
225
250
|
return False
|
|
226
251
|
|
|
252
|
+
|
|
227
253
|
def _is_tie(game):
|
|
228
|
-
return (
|
|
254
|
+
return (
|
|
255
|
+
game.home_final_score is not None
|
|
256
|
+
and game.visitor_final_score is not None
|
|
257
|
+
and game.home_final_score == game.visitor_final_score
|
|
258
|
+
)
|
|
259
|
+
|
|
229
260
|
|
|
230
261
|
if __name__ == "__main__":
|
|
231
262
|
aggregate_h2h_stats()
|