hockey-blast-common-lib 0.1.67__tar.gz → 0.1.68__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.
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/PKG-INFO +1 -1
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_skater_stats.py +130 -26
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib.egg-info/PKG-INFO +1 -1
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/setup.py +1 -1
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/MANIFEST.in +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/README.md +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/__init__.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_all_stats.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_game_stats_all.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_game_stats_goalie.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_game_stats_skater.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_goalie_stats.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_h2h_stats.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_human_stats.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_referee_stats.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_s2s_stats.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_scorekeeper_stats.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_team_goalie_stats.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/aggregate_team_skater_stats.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/assign_skater_skill.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/db_connection.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/dump_sample_db.sh +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/embedding_utils.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/h2h_models.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/models.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/options.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/progress_utils.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/restore_sample_db.sh +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/skills_in_divisions.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/skills_propagation.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/stats_models.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/stats_utils.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/utils.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/wsgi.py +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib.egg-info/SOURCES.txt +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib.egg-info/dependency_links.txt +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib.egg-info/requires.txt +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib.egg-info/top_level.txt +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/pyproject.toml +0 -0
- {hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/setup.cfg +0 -0
|
@@ -118,6 +118,118 @@ def calculate_current_point_streak(session, human_id, filter_condition):
|
|
|
118
118
|
return current_streak, avg_points_during_streak
|
|
119
119
|
|
|
120
120
|
|
|
121
|
+
def calculate_all_point_streaks_batch(session, human_ids, filter_condition):
|
|
122
|
+
"""
|
|
123
|
+
Calculate point streaks for ALL players in one batch query.
|
|
124
|
+
This is MUCH faster than calling calculate_current_point_streak() for each player.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
session: Database session
|
|
128
|
+
human_ids: List of all human_ids to calculate streaks for (e.g., 150 players in a division)
|
|
129
|
+
filter_condition: SQLAlchemy filter condition (org/division/level + time window)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Dict[human_id] -> (streak_length, avg_points_per_game)
|
|
133
|
+
|
|
134
|
+
Performance:
|
|
135
|
+
- Old: N queries (one per player)
|
|
136
|
+
- New: 1 query for all players
|
|
137
|
+
- Speedup: ~150x for typical division
|
|
138
|
+
"""
|
|
139
|
+
if not human_ids:
|
|
140
|
+
return {}
|
|
141
|
+
|
|
142
|
+
# Single query for ALL players at once
|
|
143
|
+
game_points = (
|
|
144
|
+
session.query(
|
|
145
|
+
GameRoster.human_id,
|
|
146
|
+
Game.id.label("game_id"),
|
|
147
|
+
Game.date,
|
|
148
|
+
Game.time,
|
|
149
|
+
func.sum(
|
|
150
|
+
case((Goal.goal_scorer_id == GameRoster.human_id, 1), else_=0)
|
|
151
|
+
).label("goals"),
|
|
152
|
+
func.sum(
|
|
153
|
+
case(
|
|
154
|
+
(
|
|
155
|
+
(Goal.assist_1_id == GameRoster.human_id) |
|
|
156
|
+
(Goal.assist_2_id == GameRoster.human_id),
|
|
157
|
+
1
|
|
158
|
+
),
|
|
159
|
+
else_=0
|
|
160
|
+
)
|
|
161
|
+
).label("assists"),
|
|
162
|
+
)
|
|
163
|
+
.join(Game, GameRoster.game_id == Game.id)
|
|
164
|
+
.outerjoin(Goal, Game.id == Goal.game_id)
|
|
165
|
+
.filter(
|
|
166
|
+
GameRoster.human_id.in_(human_ids), # ← ALL players at once!
|
|
167
|
+
~GameRoster.role.ilike("g"), # Exclude goalie games
|
|
168
|
+
filter_condition,
|
|
169
|
+
(Game.status.like("Final%")) | (Game.status == "NOEVENTS"),
|
|
170
|
+
)
|
|
171
|
+
.group_by(GameRoster.human_id, Game.id, Game.date, Game.time)
|
|
172
|
+
.order_by(
|
|
173
|
+
GameRoster.human_id, # Group by player first
|
|
174
|
+
Game.date.desc(), # Then most recent games first
|
|
175
|
+
Game.time.desc()
|
|
176
|
+
)
|
|
177
|
+
.all()
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Process results in Python to calculate streaks
|
|
181
|
+
streaks_by_human = {}
|
|
182
|
+
processed_humans = set() # Track which humans we've finished processing
|
|
183
|
+
current_human = None
|
|
184
|
+
current_streak = 0
|
|
185
|
+
total_points = 0
|
|
186
|
+
|
|
187
|
+
for row in game_points:
|
|
188
|
+
# Skip if we've already processed this human (streak already broken)
|
|
189
|
+
if row.human_id in processed_humans:
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
# New player?
|
|
193
|
+
if row.human_id != current_human:
|
|
194
|
+
# Save previous player's streak (if any)
|
|
195
|
+
if current_human is not None:
|
|
196
|
+
avg = total_points / current_streak if current_streak > 0 else 0.0
|
|
197
|
+
streaks_by_human[current_human] = (current_streak, avg)
|
|
198
|
+
processed_humans.add(current_human)
|
|
199
|
+
|
|
200
|
+
# Reset for new player
|
|
201
|
+
current_human = row.human_id
|
|
202
|
+
current_streak = 0
|
|
203
|
+
total_points = 0
|
|
204
|
+
|
|
205
|
+
# Calculate points for this game
|
|
206
|
+
points = (row.goals or 0) + (row.assists or 0)
|
|
207
|
+
|
|
208
|
+
if points > 0:
|
|
209
|
+
# Streak continues
|
|
210
|
+
current_streak += 1
|
|
211
|
+
total_points += points
|
|
212
|
+
else:
|
|
213
|
+
# Streak broken - save and mark as processed
|
|
214
|
+
avg = total_points / current_streak if current_streak > 0 else 0.0
|
|
215
|
+
streaks_by_human[current_human] = (current_streak, avg)
|
|
216
|
+
processed_humans.add(current_human)
|
|
217
|
+
# Reset current_human so we skip remaining games for this player
|
|
218
|
+
current_human = None
|
|
219
|
+
|
|
220
|
+
# Don't forget the last player (if still in a streak)
|
|
221
|
+
if current_human is not None and current_human not in processed_humans:
|
|
222
|
+
avg = total_points / current_streak if current_streak > 0 else 0.0
|
|
223
|
+
streaks_by_human[current_human] = (current_streak, avg)
|
|
224
|
+
|
|
225
|
+
# Ensure all requested human_ids have a result (even if 0,0)
|
|
226
|
+
for human_id in human_ids:
|
|
227
|
+
if human_id not in streaks_by_human:
|
|
228
|
+
streaks_by_human[human_id] = (0, 0.0)
|
|
229
|
+
|
|
230
|
+
return streaks_by_human
|
|
231
|
+
|
|
232
|
+
|
|
121
233
|
def insert_percentile_markers_skater(
|
|
122
234
|
session, stats_dict, aggregation_id, total_in_rank, StatsModel, aggregation_window
|
|
123
235
|
):
|
|
@@ -605,34 +717,26 @@ def aggregate_skater_stats(
|
|
|
605
717
|
stat["last_game_id"] = last_game.id if last_game else None
|
|
606
718
|
|
|
607
719
|
# Calculate current point streak (only for all-time stats)
|
|
720
|
+
# OPTIMIZED: Use batch calculation instead of N+1 queries
|
|
608
721
|
if aggregation_window is None:
|
|
609
722
|
total_players = len(stats_dict)
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
progress.update(idx + 1)
|
|
628
|
-
else:
|
|
629
|
-
for key, stat in stats_dict.items():
|
|
630
|
-
agg_id, human_id = key
|
|
631
|
-
streak_length, avg_points = calculate_current_point_streak(
|
|
632
|
-
session, human_id, filter_condition
|
|
633
|
-
)
|
|
634
|
-
stat["current_point_streak"] = streak_length
|
|
635
|
-
stat["current_point_streak_avg_points"] = avg_points
|
|
723
|
+
|
|
724
|
+
# Extract all human_ids from stats_dict
|
|
725
|
+
all_human_ids = [key[1] for key in stats_dict.keys()]
|
|
726
|
+
|
|
727
|
+
# Calculate all point streaks in ONE batch query (instead of N queries)
|
|
728
|
+
print(f"Calculating point streaks for {total_players} players using batch query...")
|
|
729
|
+
all_streaks = calculate_all_point_streaks_batch(
|
|
730
|
+
session, all_human_ids, filter_condition
|
|
731
|
+
)
|
|
732
|
+
print(f"✓ Point streaks calculated for {len(all_streaks)} players")
|
|
733
|
+
|
|
734
|
+
# Assign streak values to stats_dict
|
|
735
|
+
for key, stat in stats_dict.items():
|
|
736
|
+
agg_id, human_id = key
|
|
737
|
+
streak_length, avg_points = all_streaks.get(human_id, (0, 0.0))
|
|
738
|
+
stat["current_point_streak"] = streak_length
|
|
739
|
+
stat["current_point_streak_avg_points"] = avg_points
|
|
636
740
|
|
|
637
741
|
# Calculate total_in_rank
|
|
638
742
|
total_in_rank = len(stats_dict)
|
|
Binary file
|
|
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="hockey-blast-common-lib", # The name of your package
|
|
5
|
-
version="0.1.
|
|
5
|
+
version="0.1.68",
|
|
6
6
|
description="Common library for shared functionality and DB models",
|
|
7
7
|
author="Pavel Kletskov",
|
|
8
8
|
author_email="kletskov@gmail.com",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/models.py
RENAMED
|
File without changes
|
{hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/options.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/utils.py
RENAMED
|
File without changes
|
{hockey_blast_common_lib-0.1.67 → hockey_blast_common_lib-0.1.68}/hockey_blast_common_lib/wsgi.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|