hockey-blast-common-lib 0.1.50__py3-none-any.whl → 0.1.53__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_goalie_stats.py +44 -25
- hockey_blast_common_lib/aggregate_h2h_stats.py +231 -0
- hockey_blast_common_lib/aggregate_human_stats.py +45 -26
- hockey_blast_common_lib/aggregate_referee_stats.py +44 -25
- hockey_blast_common_lib/aggregate_s2s_stats.py +143 -0
- hockey_blast_common_lib/aggregate_skater_stats.py +149 -36
- hockey_blast_common_lib/assign_skater_skill.py +27 -17
- hockey_blast_common_lib/h2h_models.py +107 -0
- hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz +0 -0
- hockey_blast_common_lib/models.py +1 -0
- hockey_blast_common_lib/progress_utils.py +91 -0
- hockey_blast_common_lib/skills_propagation.py +56 -30
- hockey_blast_common_lib/stats_models.py +6 -0
- hockey_blast_common_lib/wsgi.py +1 -0
- {hockey_blast_common_lib-0.1.50.dist-info → hockey_blast_common_lib-0.1.53.dist-info}/METADATA +1 -1
- hockey_blast_common_lib-0.1.53.dist-info/RECORD +27 -0
- hockey_blast_common_lib-0.1.50.dist-info/RECORD +0 -23
- {hockey_blast_common_lib-0.1.50.dist-info → hockey_blast_common_lib-0.1.53.dist-info}/WHEEL +0 -0
- {hockey_blast_common_lib-0.1.50.dist-info → hockey_blast_common_lib-0.1.53.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,7 @@ from hockey_blast_common_lib.utils import assign_ranks
|
|
16
16
|
from sqlalchemy import func, case, and_
|
17
17
|
from collections import defaultdict
|
18
18
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
19
|
+
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
19
20
|
|
20
21
|
def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_filter_out, debug_human_id=None, aggregation_window=None):
|
21
22
|
human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
|
@@ -190,33 +191,51 @@ def run_aggregate_goalie_stats():
|
|
190
191
|
|
191
192
|
for org_id in org_ids:
|
192
193
|
division_ids = get_all_division_ids_for_org(session, org_id)
|
193
|
-
|
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')
|
194
|
+
org_name = session.query(Organization.organization_name).filter(Organization.id == org_id).scalar() or f"org_id {org_id}"
|
207
195
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
196
|
+
if human_id_to_debug is None and division_ids:
|
197
|
+
# Process divisions with progress tracking
|
198
|
+
progress = create_progress_tracker(len(division_ids), f"Processing {len(division_ids)} divisions for {org_name}")
|
199
|
+
for i, division_id in enumerate(division_ids):
|
200
|
+
aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
|
201
|
+
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')
|
202
|
+
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')
|
203
|
+
progress.update(i + 1)
|
204
|
+
else:
|
205
|
+
# Debug mode or no divisions - process without progress tracking
|
206
|
+
for division_id in division_ids:
|
207
|
+
aggregate_goalie_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
|
208
|
+
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')
|
209
|
+
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')
|
210
|
+
|
211
|
+
# Process org-level stats with progress tracking
|
216
212
|
if human_id_to_debug is None:
|
217
|
-
|
218
|
-
|
219
|
-
|
213
|
+
org_progress = create_progress_tracker(3, f"Processing org-level stats for {org_name}")
|
214
|
+
aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
|
215
|
+
org_progress.update(1)
|
216
|
+
aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug, aggregation_window='Weekly')
|
217
|
+
org_progress.update(2)
|
218
|
+
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')
|
219
|
+
org_progress.update(3)
|
220
|
+
else:
|
221
|
+
aggregate_goalie_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, debug_human_id=human_id_to_debug)
|
222
|
+
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')
|
223
|
+
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')
|
224
|
+
|
225
|
+
# Aggregate by level
|
226
|
+
level_ids = session.query(Division.level_id).distinct().all()
|
227
|
+
level_ids = [level_id[0] for level_id in level_ids if level_id[0] is not None]
|
228
|
+
|
229
|
+
if human_id_to_debug is None and level_ids:
|
230
|
+
# Process levels with progress tracking
|
231
|
+
level_progress = create_progress_tracker(len(level_ids), f"Processing {len(level_ids)} skill levels")
|
232
|
+
for i, level_id in enumerate(level_ids):
|
233
|
+
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)
|
234
|
+
level_progress.update(i + 1)
|
235
|
+
else:
|
236
|
+
# Debug mode or no levels - process without progress tracking
|
237
|
+
for level_id in level_ids:
|
238
|
+
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)
|
220
239
|
|
221
240
|
if __name__ == "__main__":
|
222
241
|
run_aggregate_goalie_stats()
|
@@ -0,0 +1,231 @@
|
|
1
|
+
import sys, os
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
# Add the package directory to the Python path
|
5
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
6
|
+
|
7
|
+
from hockey_blast_common_lib.models import Game, GameRoster, Goal, Penalty
|
8
|
+
from hockey_blast_common_lib.h2h_models import H2HStats, H2HStatsMeta
|
9
|
+
from hockey_blast_common_lib.db_connection import create_session
|
10
|
+
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
11
|
+
from sqlalchemy.sql import func
|
12
|
+
from sqlalchemy import types
|
13
|
+
|
14
|
+
# Max games to process (set to None to process all)
|
15
|
+
MAX_GAMES_TO_PROCESS = None # Set to None to process all games
|
16
|
+
|
17
|
+
def aggregate_h2h_stats():
|
18
|
+
session = create_session("boss")
|
19
|
+
meta = None #session.query(H2HStatsMeta).order_by(H2HStatsMeta.id.desc()).first()
|
20
|
+
h2h_stats_dict = {} # (h1, h2) -> H2HStats instance
|
21
|
+
if meta is None or meta.last_run_timestamp is None or meta.last_processed_game_id is None:
|
22
|
+
# Full run: delete all existing stats and process all games
|
23
|
+
session.query(H2HStats).delete()
|
24
|
+
session.commit()
|
25
|
+
games_query = session.query(Game).order_by(Game.date, Game.time, Game.id)
|
26
|
+
print("No previous run found, deleted all existing H2H stats, processing all games...")
|
27
|
+
else:
|
28
|
+
# Incremental: only process games after last processed
|
29
|
+
# Load all existing stats into memory
|
30
|
+
for stat in session.query(H2HStats).all():
|
31
|
+
h2h_stats_dict[(stat.human1_id, stat.human2_id)] = stat
|
32
|
+
last_game = session.query(Game).filter(Game.id == meta.last_processed_game_id).first()
|
33
|
+
if last_game:
|
34
|
+
last_dt = datetime.combine(last_game.date, last_game.time)
|
35
|
+
games_query = session.query(Game).filter(
|
36
|
+
func.cast(func.concat(Game.date, ' ', Game.time), types.TIMESTAMP()) > last_dt
|
37
|
+
).order_by(Game.date, Game.time, Game.id)
|
38
|
+
print(f"Resuming from game after id {meta.last_processed_game_id} ({last_dt})...")
|
39
|
+
else:
|
40
|
+
games_query = session.query(Game).order_by(Game.date, Game.time, Game.id)
|
41
|
+
print("Previous game id not found, processing all games...")
|
42
|
+
|
43
|
+
total_games = games_query.count()
|
44
|
+
print(f"Total games to process: {total_games}")
|
45
|
+
|
46
|
+
# Create progress tracker
|
47
|
+
progress = create_progress_tracker(total_games, "Processing H2H stats")
|
48
|
+
processed = 0
|
49
|
+
latest_game_id = None
|
50
|
+
for game in games_query:
|
51
|
+
if MAX_GAMES_TO_PROCESS is not None and processed >= MAX_GAMES_TO_PROCESS:
|
52
|
+
break
|
53
|
+
# --- Gather all relevant data for this game ---
|
54
|
+
# Get all GameRoster entries for this game
|
55
|
+
rosters = session.query(GameRoster).filter(GameRoster.game_id == game.id).all()
|
56
|
+
# Map: team_id -> set of human_ids (players)
|
57
|
+
team_to_players = {}
|
58
|
+
human_roles = {} # human_id -> set of roles in this game
|
59
|
+
for roster in rosters:
|
60
|
+
team_to_players.setdefault(roster.team_id, set()).add(roster.human_id)
|
61
|
+
human_roles.setdefault(roster.human_id, set()).add(roster.role)
|
62
|
+
# Get all human_ids in this game
|
63
|
+
all_humans = set(human_roles.keys())
|
64
|
+
# Add goalies from Game table (home/visitor)
|
65
|
+
if game.home_goalie_id:
|
66
|
+
all_humans.add(game.home_goalie_id)
|
67
|
+
human_roles.setdefault(game.home_goalie_id, set()).add('G')
|
68
|
+
if game.visitor_goalie_id:
|
69
|
+
all_humans.add(game.visitor_goalie_id)
|
70
|
+
human_roles.setdefault(game.visitor_goalie_id, set()).add('G')
|
71
|
+
# Add referees from Game table (NOT from roster!)
|
72
|
+
if game.referee_1_id:
|
73
|
+
all_humans.add(game.referee_1_id)
|
74
|
+
human_roles.setdefault(game.referee_1_id, set()).add('R')
|
75
|
+
if game.referee_2_id:
|
76
|
+
all_humans.add(game.referee_2_id)
|
77
|
+
human_roles.setdefault(game.referee_2_id, set()).add('R')
|
78
|
+
# --- Build all pairs of humans in this game ---
|
79
|
+
all_humans = list(all_humans)
|
80
|
+
for i in range(len(all_humans)):
|
81
|
+
for j in range(i+1, len(all_humans)):
|
82
|
+
h1, h2 = sorted([all_humans[i], all_humans[j]])
|
83
|
+
key = (h1, h2)
|
84
|
+
h2h = h2h_stats_dict.get(key)
|
85
|
+
if not h2h:
|
86
|
+
h2h = H2HStats(
|
87
|
+
human1_id=h1,
|
88
|
+
human2_id=h2,
|
89
|
+
first_game_id=game.id,
|
90
|
+
last_game_id=game.id,
|
91
|
+
games_together=0,
|
92
|
+
games_against=0,
|
93
|
+
games_tied_together=0,
|
94
|
+
games_tied_against=0,
|
95
|
+
wins_together=0,
|
96
|
+
losses_together=0,
|
97
|
+
h1_wins_vs_h2=0,
|
98
|
+
h2_wins_vs_h1=0,
|
99
|
+
games_h1_goalie=0,
|
100
|
+
games_h2_goalie=0,
|
101
|
+
games_h1_ref=0,
|
102
|
+
games_h2_ref=0,
|
103
|
+
games_both_referees=0,
|
104
|
+
goals_h1_when_together=0,
|
105
|
+
goals_h2_when_together=0,
|
106
|
+
assists_h1_when_together=0,
|
107
|
+
assists_h2_when_together=0,
|
108
|
+
penalties_h1_when_together=0,
|
109
|
+
penalties_h2_when_together=0,
|
110
|
+
gm_penalties_h1_when_together=0,
|
111
|
+
gm_penalties_h2_when_together=0,
|
112
|
+
h1_goalie_h2_scorer_goals=0,
|
113
|
+
h2_goalie_h1_scorer_goals=0,
|
114
|
+
shots_faced_h1_goalie_vs_h2=0,
|
115
|
+
shots_faced_h2_goalie_vs_h1=0,
|
116
|
+
goals_allowed_h1_goalie_vs_h2=0,
|
117
|
+
goals_allowed_h2_goalie_vs_h1=0,
|
118
|
+
save_percentage_h1_goalie_vs_h2=0.0,
|
119
|
+
save_percentage_h2_goalie_vs_h1=0.0,
|
120
|
+
h1_ref_h2_player_games=0,
|
121
|
+
h2_ref_h1_player_games=0,
|
122
|
+
h1_ref_penalties_on_h2=0,
|
123
|
+
h2_ref_penalties_on_h1=0,
|
124
|
+
h1_ref_gm_penalties_on_h2=0,
|
125
|
+
h2_ref_gm_penalties_on_h1=0,
|
126
|
+
penalties_given_both_refs=0,
|
127
|
+
gm_penalties_given_both_refs=0,
|
128
|
+
h1_shootout_attempts_vs_h2_goalie=0,
|
129
|
+
h1_shootout_goals_vs_h2_goalie=0,
|
130
|
+
h2_shootout_attempts_vs_h1_goalie=0,
|
131
|
+
h2_shootout_goals_vs_h1_goalie=0
|
132
|
+
)
|
133
|
+
h2h_stats_dict[key] = h2h
|
134
|
+
# Update first/last game ids
|
135
|
+
if game.id < h2h.first_game_id:
|
136
|
+
h2h.first_game_id = game.id
|
137
|
+
if game.id > h2h.last_game_id:
|
138
|
+
h2h.last_game_id = game.id
|
139
|
+
# --- Determine roles and teams ---
|
140
|
+
h1_roles = human_roles.get(h1, set())
|
141
|
+
h2_roles = human_roles.get(h2, set())
|
142
|
+
h1_team = None
|
143
|
+
h2_team = None
|
144
|
+
for team_id, players in team_to_players.items():
|
145
|
+
if h1 in players:
|
146
|
+
h1_team = team_id
|
147
|
+
if h2 in players:
|
148
|
+
h2_team = team_id
|
149
|
+
# --- General stats ---
|
150
|
+
h2h.games_together += 1 # Both present in this game
|
151
|
+
if h1_team and h2_team:
|
152
|
+
if h1_team == h2_team:
|
153
|
+
h2h.wins_together += int(_is_win(game, h1_team))
|
154
|
+
h2h.losses_together += int(_is_loss(game, h1_team))
|
155
|
+
if _is_tie(game):
|
156
|
+
h2h.games_tied_together += 1
|
157
|
+
else:
|
158
|
+
h2h.games_against += 1
|
159
|
+
if _is_win(game, h1_team):
|
160
|
+
h2h.h1_wins_vs_h2 += 1
|
161
|
+
if _is_win(game, h2_team):
|
162
|
+
h2h.h2_wins_vs_h1 += 1
|
163
|
+
if _is_tie(game):
|
164
|
+
h2h.games_tied_against += 1
|
165
|
+
# --- Role-specific stats ---
|
166
|
+
if 'G' in h1_roles:
|
167
|
+
h2h.games_h1_goalie += 1
|
168
|
+
if 'G' in h2_roles:
|
169
|
+
h2h.games_h2_goalie += 1
|
170
|
+
if 'R' in h1_roles:
|
171
|
+
h2h.games_h1_ref += 1
|
172
|
+
if 'R' in h2_roles:
|
173
|
+
h2h.games_h2_ref += 1
|
174
|
+
if 'R' in h1_roles and 'R' in h2_roles:
|
175
|
+
h2h.games_both_referees += 1
|
176
|
+
# --- Goals, assists, penalties ---
|
177
|
+
# Goals
|
178
|
+
goals = session.query(Goal).filter(Goal.game_id == game.id).all()
|
179
|
+
for goal in goals:
|
180
|
+
if goal.goal_scorer_id == h1 and (goal.assist_1_id == h2 or goal.assist_2_id == h2):
|
181
|
+
h2h.goals_h1_when_together += 1
|
182
|
+
if goal.goal_scorer_id == h2 and (goal.assist_1_id == h1 or goal.assist_2_id == h1):
|
183
|
+
h2h.goals_h2_when_together += 1
|
184
|
+
# Penalties
|
185
|
+
penalties = session.query(Penalty).filter(Penalty.game_id == game.id).all()
|
186
|
+
for pen in penalties:
|
187
|
+
if pen.penalized_player_id == h1:
|
188
|
+
h2h.penalties_h1_when_together += 1
|
189
|
+
if pen.penalty_minutes and 'GM' in pen.penalty_minutes:
|
190
|
+
h2h.gm_penalties_h1_when_together += 1
|
191
|
+
if pen.penalized_player_id == h2:
|
192
|
+
h2h.penalties_h2_when_together += 1
|
193
|
+
if pen.penalty_minutes and 'GM' in pen.penalty_minutes:
|
194
|
+
h2h.gm_penalties_h2_when_together += 1
|
195
|
+
# --- TODO: Add more detailed logic for goalie/skater, referee/player, shootouts, etc. ---
|
196
|
+
latest_game_id = game.id
|
197
|
+
processed += 1
|
198
|
+
progress.update(processed)
|
199
|
+
# Commit all stats at once
|
200
|
+
session.query(H2HStats).delete()
|
201
|
+
session.add_all(list(h2h_stats_dict.values()))
|
202
|
+
session.commit()
|
203
|
+
# Save/update meta
|
204
|
+
meta = H2HStatsMeta(
|
205
|
+
last_run_timestamp=datetime.utcnow(),
|
206
|
+
last_processed_game_id=latest_game_id
|
207
|
+
)
|
208
|
+
session.add(meta)
|
209
|
+
session.commit()
|
210
|
+
print("H2H aggregation complete.")
|
211
|
+
|
212
|
+
# --- Helper functions for win/loss/tie ---
|
213
|
+
def _is_win(game, team_id):
|
214
|
+
if team_id == game.home_team_id:
|
215
|
+
return (game.home_final_score or 0) > (game.visitor_final_score or 0)
|
216
|
+
if team_id == game.visitor_team_id:
|
217
|
+
return (game.visitor_final_score or 0) > (game.home_final_score or 0)
|
218
|
+
return False
|
219
|
+
|
220
|
+
def _is_loss(game, team_id):
|
221
|
+
if team_id == game.home_team_id:
|
222
|
+
return (game.home_final_score or 0) < (game.visitor_final_score or 0)
|
223
|
+
if team_id == game.visitor_team_id:
|
224
|
+
return (game.visitor_final_score or 0) < (game.home_final_score or 0)
|
225
|
+
return False
|
226
|
+
|
227
|
+
def _is_tie(game):
|
228
|
+
return (game.home_final_score is not None and game.visitor_final_score is not None and game.home_final_score == game.visitor_final_score)
|
229
|
+
|
230
|
+
if __name__ == "__main__":
|
231
|
+
aggregate_h2h_stats()
|
@@ -15,6 +15,7 @@ from hockey_blast_common_lib.utils import get_fake_human_for_stats, get_org_id_f
|
|
15
15
|
from hockey_blast_common_lib.utils import assign_ranks
|
16
16
|
from hockey_blast_common_lib.utils import get_start_datetime
|
17
17
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
18
|
+
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
18
19
|
|
19
20
|
def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_filter_out, human_id_filter=None, aggregation_window=None):
|
20
21
|
human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
|
@@ -425,35 +426,53 @@ def run_aggregate_human_stats():
|
|
425
426
|
org_ids = session.query(Organization.id).all()
|
426
427
|
org_ids = [org_id[0] for org_id in org_ids]
|
427
428
|
|
428
|
-
for org_id in
|
429
|
+
for org_id in org_ids:
|
429
430
|
division_ids = get_all_division_ids_for_org(session, org_id)
|
430
|
-
|
431
|
-
total_divisions = len(division_ids)
|
432
|
-
processed_divisions = 0
|
433
|
-
for division_id in division_ids:
|
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)
|
435
|
-
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')
|
436
|
-
aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Daily')
|
437
|
-
processed_divisions += 1
|
438
|
-
if human_id_to_debug is None:
|
439
|
-
print(f"\rProcessed {processed_divisions}/{total_divisions} divisions ({(processed_divisions/total_divisions)*100:.2f}%)", end="")
|
440
|
-
print("")
|
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)
|
442
|
-
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')
|
443
|
-
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')
|
431
|
+
org_name = session.query(Organization.organization_name).filter(Organization.id == org_id).scalar() or f"org_id {org_id}"
|
444
432
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
433
|
+
if human_id_to_debug is None and division_ids:
|
434
|
+
# Process divisions with progress tracking
|
435
|
+
progress = create_progress_tracker(len(division_ids), f"Processing {len(division_ids)} divisions for {org_name}")
|
436
|
+
for i, division_id in enumerate(division_ids):
|
437
|
+
aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
|
438
|
+
aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
|
439
|
+
aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Daily')
|
440
|
+
progress.update(i + 1)
|
441
|
+
else:
|
442
|
+
# Debug mode or no divisions - process without progress tracking
|
443
|
+
for division_id in division_ids:
|
444
|
+
aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
|
445
|
+
aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
|
446
|
+
aggregate_human_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Daily')
|
447
|
+
|
448
|
+
# Process org-level stats with progress tracking
|
453
449
|
if human_id_to_debug is None:
|
454
|
-
|
455
|
-
|
456
|
-
|
450
|
+
org_progress = create_progress_tracker(3, f"Processing org-level stats for {org_name}")
|
451
|
+
aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
|
452
|
+
org_progress.update(1)
|
453
|
+
aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
|
454
|
+
org_progress.update(2)
|
455
|
+
aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Daily')
|
456
|
+
org_progress.update(3)
|
457
|
+
else:
|
458
|
+
aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
|
459
|
+
aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Weekly')
|
460
|
+
aggregate_human_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug, aggregation_window='Daily')
|
461
|
+
|
462
|
+
# Aggregate by level
|
463
|
+
level_ids = session.query(Division.level_id).distinct().all()
|
464
|
+
level_ids = [level_id[0] for level_id in level_ids if level_id[0] is not None]
|
465
|
+
|
466
|
+
if human_id_to_debug is None and level_ids:
|
467
|
+
# Process levels with progress tracking
|
468
|
+
level_progress = create_progress_tracker(len(level_ids), f"Processing {len(level_ids)} skill levels")
|
469
|
+
for i, level_id in enumerate(level_ids):
|
470
|
+
aggregate_human_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
|
471
|
+
level_progress.update(i + 1)
|
472
|
+
else:
|
473
|
+
# Debug mode or no levels - process without progress tracking
|
474
|
+
for level_id in level_ids:
|
475
|
+
aggregate_human_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names, human_id_filter=human_id_to_debug)
|
457
476
|
|
458
477
|
if __name__ == "__main__":
|
459
478
|
run_aggregate_human_stats()
|
@@ -15,6 +15,7 @@ from hockey_blast_common_lib.utils import get_org_id_from_alias, get_human_ids_b
|
|
15
15
|
from hockey_blast_common_lib.utils import assign_ranks
|
16
16
|
from hockey_blast_common_lib.utils import get_start_datetime
|
17
17
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
18
|
+
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
18
19
|
|
19
20
|
def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_filter_out, aggregation_window=None):
|
20
21
|
human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
|
@@ -214,33 +215,51 @@ def run_aggregate_referee_stats():
|
|
214
215
|
|
215
216
|
for org_id in org_ids:
|
216
217
|
division_ids = get_all_division_ids_for_org(session, org_id)
|
217
|
-
|
218
|
-
total_divisions = len(division_ids)
|
219
|
-
processed_divisions = 0
|
220
|
-
for division_id in division_ids:
|
221
|
-
aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names)
|
222
|
-
aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
|
223
|
-
aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
|
224
|
-
processed_divisions += 1
|
225
|
-
if human_id_to_debug is None:
|
226
|
-
print(f"\rProcessed {processed_divisions}/{total_divisions} divisions ({(processed_divisions/total_divisions)*100:.2f}%)", end="")
|
227
|
-
|
228
|
-
aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names)
|
229
|
-
aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
|
230
|
-
aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
|
218
|
+
org_name = session.query(Organization.organization_name).filter(Organization.id == org_id).scalar() or f"org_id {org_id}"
|
231
219
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
220
|
+
if human_id_to_debug is None and division_ids:
|
221
|
+
# Process divisions with progress tracking
|
222
|
+
progress = create_progress_tracker(len(division_ids), f"Processing {len(division_ids)} divisions for {org_name}")
|
223
|
+
for i, division_id in enumerate(division_ids):
|
224
|
+
aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names)
|
225
|
+
aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
|
226
|
+
aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
|
227
|
+
progress.update(i + 1)
|
228
|
+
else:
|
229
|
+
# Debug mode or no divisions - process without progress tracking
|
230
|
+
for division_id in division_ids:
|
231
|
+
aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names)
|
232
|
+
aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
|
233
|
+
aggregate_referee_stats(session, aggregation_type='division', aggregation_id=division_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
|
234
|
+
|
235
|
+
# Process org-level stats with progress tracking
|
240
236
|
if human_id_to_debug is None:
|
241
|
-
|
242
|
-
|
243
|
-
|
237
|
+
org_progress = create_progress_tracker(3, f"Processing org-level stats for {org_name}")
|
238
|
+
aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names)
|
239
|
+
org_progress.update(1)
|
240
|
+
aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
|
241
|
+
org_progress.update(2)
|
242
|
+
aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
|
243
|
+
org_progress.update(3)
|
244
|
+
else:
|
245
|
+
aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names)
|
246
|
+
aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Weekly')
|
247
|
+
aggregate_referee_stats(session, aggregation_type='org', aggregation_id=org_id, names_to_filter_out=not_human_names, aggregation_window='Daily')
|
248
|
+
|
249
|
+
# Aggregate by level
|
250
|
+
level_ids = session.query(Division.level_id).distinct().all()
|
251
|
+
level_ids = [level_id[0] for level_id in level_ids if level_id[0] is not None]
|
252
|
+
|
253
|
+
if human_id_to_debug is None and level_ids:
|
254
|
+
# Process levels with progress tracking
|
255
|
+
level_progress = create_progress_tracker(len(level_ids), f"Processing {len(level_ids)} skill levels")
|
256
|
+
for i, level_id in enumerate(level_ids):
|
257
|
+
aggregate_referee_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names)
|
258
|
+
level_progress.update(i + 1)
|
259
|
+
else:
|
260
|
+
# Debug mode or no levels - process without progress tracking
|
261
|
+
for level_id in level_ids:
|
262
|
+
aggregate_referee_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names)
|
244
263
|
|
245
264
|
if __name__ == "__main__":
|
246
265
|
run_aggregate_referee_stats()
|
@@ -0,0 +1,143 @@
|
|
1
|
+
import sys, os
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
# Add the package directory to the Python path
|
5
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
6
|
+
|
7
|
+
from hockey_blast_common_lib.models import Game, Goal, Penalty, GameRoster
|
8
|
+
from hockey_blast_common_lib.h2h_models import SkaterToSkaterStats, SkaterToSkaterStatsMeta
|
9
|
+
from hockey_blast_common_lib.db_connection import create_session
|
10
|
+
from sqlalchemy.sql import func
|
11
|
+
from sqlalchemy import types
|
12
|
+
|
13
|
+
# Optional: Limit processing to a specific human_id
|
14
|
+
LIMIT_HUMAN_ID = None
|
15
|
+
|
16
|
+
def aggregate_s2s_stats():
|
17
|
+
session = create_session("boss")
|
18
|
+
meta = session.query(SkaterToSkaterStatsMeta).order_by(SkaterToSkaterStatsMeta.id.desc()).first()
|
19
|
+
s2s_stats_dict = {} # (skater1_id, skater2_id) -> SkaterToSkaterStats instance
|
20
|
+
|
21
|
+
if meta is None or meta.last_run_timestamp is None or meta.last_processed_game_id is None:
|
22
|
+
# Full run: delete all existing stats and process all games
|
23
|
+
session.query(SkaterToSkaterStats).delete()
|
24
|
+
session.commit()
|
25
|
+
games_query = session.query(Game).order_by(Game.date, Game.time, Game.id)
|
26
|
+
print("No previous run found, deleted all existing Skater-to-Skater stats, processing all games...")
|
27
|
+
else:
|
28
|
+
# Incremental: only process games after last processed
|
29
|
+
for stat in session.query(SkaterToSkaterStats).all():
|
30
|
+
s2s_stats_dict[(stat.skater1_id, stat.skater2_id)] = stat
|
31
|
+
last_game = session.query(Game).filter(Game.id == meta.last_processed_game_id).first()
|
32
|
+
if last_game:
|
33
|
+
last_dt = datetime.combine(last_game.date, last_game.time)
|
34
|
+
games_query = session.query(Game).filter(
|
35
|
+
func.cast(func.concat(Game.date, ' ', Game.time), types.TIMESTAMP()) > last_dt
|
36
|
+
).order_by(Game.date, Game.time, Game.id)
|
37
|
+
print(f"Resuming from game after id {meta.last_processed_game_id} ({last_dt})...")
|
38
|
+
else:
|
39
|
+
games_query = session.query(Game).order_by(Game.date, Game.time, Game.id)
|
40
|
+
print("Previous game id not found, processing all games...")
|
41
|
+
|
42
|
+
total_games = games_query.count()
|
43
|
+
print(f"Total games to process: {total_games}")
|
44
|
+
processed = 0
|
45
|
+
latest_game_id = None
|
46
|
+
|
47
|
+
for game in games_query:
|
48
|
+
# Separate skaters into home and away rosters (exclude goalies)
|
49
|
+
home_skaters = [entry.human_id for entry in session.query(GameRoster).filter(GameRoster.game_id == game.id, GameRoster.team_id == game.home_team_id, ~GameRoster.role.ilike('g')).all()]
|
50
|
+
away_skaters = [entry.human_id for entry in session.query(GameRoster).filter(GameRoster.game_id == game.id, GameRoster.team_id == game.visitor_team_id, ~GameRoster.role.ilike('g')).all()]
|
51
|
+
|
52
|
+
if LIMIT_HUMAN_ID is not None and LIMIT_HUMAN_ID not in home_skaters + away_skaters:
|
53
|
+
continue
|
54
|
+
|
55
|
+
# Create pairs of skaters from different rosters
|
56
|
+
for h_skater in home_skaters:
|
57
|
+
for a_skater in away_skaters:
|
58
|
+
if LIMIT_HUMAN_ID is not None and LIMIT_HUMAN_ID not in [h_skater, a_skater]:
|
59
|
+
continue
|
60
|
+
|
61
|
+
s1, s2 = sorted([h_skater, a_skater])
|
62
|
+
key = (s1, s2)
|
63
|
+
s2s = s2s_stats_dict.get(key)
|
64
|
+
if not s2s:
|
65
|
+
s2s = SkaterToSkaterStats(
|
66
|
+
skater1_id=s1,
|
67
|
+
skater2_id=s2,
|
68
|
+
games_against=0,
|
69
|
+
games_tied_against=0,
|
70
|
+
skater1_wins_vs_skater2=0,
|
71
|
+
skater2_wins_vs_skater1=0,
|
72
|
+
skater1_goals_against_skater2=0,
|
73
|
+
skater2_goals_against_skater1=0,
|
74
|
+
skater1_assists_against_skater2=0,
|
75
|
+
skater2_assists_against_skater1=0,
|
76
|
+
skater1_penalties_against_skater2=0,
|
77
|
+
skater2_penalties_against_skater1=0
|
78
|
+
)
|
79
|
+
s2s_stats_dict[key] = s2s
|
80
|
+
|
81
|
+
# Update stats
|
82
|
+
s2s.games_against += 1
|
83
|
+
if _is_tie(game):
|
84
|
+
s2s.games_tied_against += 1
|
85
|
+
elif _is_win(game, s1, game.home_team_id):
|
86
|
+
s2s.skater1_wins_vs_skater2 += 1
|
87
|
+
elif _is_win(game, s2, game.visitor_team_id):
|
88
|
+
s2s.skater2_wins_vs_skater1 += 1
|
89
|
+
|
90
|
+
# Goals and assists
|
91
|
+
goals_stats = session.query(Goal).filter(Goal.game_id == game.id).all()
|
92
|
+
for goal in goals_stats:
|
93
|
+
if goal.goal_scorer_id == s1:
|
94
|
+
s2s.skater1_goals_against_skater2 += 1
|
95
|
+
if goal.goal_scorer_id == s2:
|
96
|
+
s2s.skater2_goals_against_skater1 += 1
|
97
|
+
if goal.assist_1_id == s1 or goal.assist_2_id == s1:
|
98
|
+
s2s.skater1_assists_against_skater2 += 1
|
99
|
+
if goal.assist_1_id == s2 or goal.assist_2_id == s2:
|
100
|
+
s2s.skater2_assists_against_skater1 += 1
|
101
|
+
|
102
|
+
# Penalties
|
103
|
+
penalties_stats = session.query(Penalty).filter(Penalty.game_id == game.id).all()
|
104
|
+
for penalty in penalties_stats:
|
105
|
+
if penalty.penalized_player_id == s1:
|
106
|
+
s2s.skater1_penalties_against_skater2 += 1
|
107
|
+
if penalty.penalized_player_id == s2:
|
108
|
+
s2s.skater2_penalties_against_skater1 += 1
|
109
|
+
|
110
|
+
latest_game_id = game.id
|
111
|
+
processed += 1
|
112
|
+
if processed % 10 == 0 or processed == total_games:
|
113
|
+
print(f"\rProcessed {processed}/{total_games} games ({(processed/total_games)*100:.2f}%)", end="")
|
114
|
+
sys.stdout.flush()
|
115
|
+
|
116
|
+
# Commit all stats at once
|
117
|
+
session.query(SkaterToSkaterStats).delete()
|
118
|
+
session.add_all(list(s2s_stats_dict.values()))
|
119
|
+
session.commit()
|
120
|
+
print(f"\rProcessed {processed}/{total_games} games (100.00%)")
|
121
|
+
|
122
|
+
# Save/update meta
|
123
|
+
meta = SkaterToSkaterStatsMeta(
|
124
|
+
last_run_timestamp=datetime.utcnow(),
|
125
|
+
last_processed_game_id=latest_game_id
|
126
|
+
)
|
127
|
+
session.add(meta)
|
128
|
+
session.commit()
|
129
|
+
print("Skater-to-Skater aggregation complete.")
|
130
|
+
|
131
|
+
# --- Helper functions for win/loss/tie ---
|
132
|
+
def _is_win(game, skater_id, team_id):
|
133
|
+
if team_id == game.home_team_id:
|
134
|
+
return (game.home_final_score or 0) > (game.visitor_final_score or 0)
|
135
|
+
if team_id == game.visitor_team_id:
|
136
|
+
return (game.visitor_final_score or 0) > (game.home_final_score or 0)
|
137
|
+
return False
|
138
|
+
|
139
|
+
def _is_tie(game):
|
140
|
+
return (game.home_final_score is not None and game.visitor_final_score is not None and game.home_final_score == game.visitor_final_score)
|
141
|
+
|
142
|
+
if __name__ == "__main__":
|
143
|
+
aggregate_s2s_stats()
|