hockey-blast-common-lib 0.1.63__py3-none-any.whl → 0.1.65__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/hockey_blast_sample_backup.sql.gz +0 -0
- 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 +91 -25
- hockey_blast_common_lib/wsgi.py +7 -5
- {hockey_blast_common_lib-0.1.63.dist-info → hockey_blast_common_lib-0.1.65.dist-info}/METADATA +1 -1
- hockey_blast_common_lib-0.1.65.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.65.dist-info}/WHEEL +0 -0
- {hockey_blast_common_lib-0.1.63.dist-info → hockey_blast_common_lib-0.1.65.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
|
|
2
3
|
from .models import Human
|
|
3
4
|
|
|
4
5
|
MAX_HUMAN_SEARCH_RESULTS = 25
|
|
@@ -7,38 +8,52 @@ MIN_GAMES_FOR_ORG_STATS = 1
|
|
|
7
8
|
MIN_GAMES_FOR_DIVISION_STATS = 1
|
|
8
9
|
MIN_GAMES_FOR_LEVEL_STATS = 1
|
|
9
10
|
|
|
10
|
-
orgs = {
|
|
11
|
+
orgs = {"caha", "sharksice", "tvice"}
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
def parse_args():
|
|
13
|
-
parser = argparse.ArgumentParser(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
parser.add_argument(
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
description="Process data for a specific organization."
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"org",
|
|
20
|
+
choices=orgs,
|
|
21
|
+
help="The organization to process (e.g., 'caha', 'sharksice', 'tvice').",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--reprocess", action="store_true", help="Reprocess existing data."
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--pre_process", action="store_true", help="Pre-Process existing data."
|
|
28
|
+
)
|
|
17
29
|
return parser.parse_args()
|
|
18
30
|
|
|
31
|
+
|
|
19
32
|
def get_or_create_empty_net_human(session, game_date):
|
|
20
33
|
"""Get or create the special 'Empty Net' human record for tracking pulled goalies.
|
|
21
|
-
|
|
34
|
+
|
|
22
35
|
Args:
|
|
23
36
|
session: Database session
|
|
24
37
|
game_date: Date of the game (used for first_date/last_date if creating)
|
|
25
|
-
|
|
38
|
+
|
|
26
39
|
Returns:
|
|
27
40
|
int: The human_id of the Empty Net special record
|
|
28
41
|
"""
|
|
29
|
-
empty_net_human =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
empty_net_human = (
|
|
43
|
+
session.query(Human)
|
|
44
|
+
.filter_by(first_name="Empty", middle_name="", last_name="Net")
|
|
45
|
+
.first()
|
|
46
|
+
)
|
|
47
|
+
|
|
33
48
|
if not empty_net_human:
|
|
34
49
|
empty_net_human = Human(
|
|
35
|
-
first_name="Empty",
|
|
36
|
-
middle_name="",
|
|
50
|
+
first_name="Empty",
|
|
51
|
+
middle_name="",
|
|
37
52
|
last_name="Net",
|
|
38
53
|
first_date=game_date,
|
|
39
|
-
last_date=game_date
|
|
54
|
+
last_date=game_date,
|
|
40
55
|
)
|
|
41
56
|
session.add(empty_net_human)
|
|
42
57
|
session.commit()
|
|
43
|
-
|
|
58
|
+
|
|
44
59
|
return empty_net_human.id
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import time
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
|
|
4
4
|
class ProgressTracker:
|
|
5
5
|
"""
|
|
@@ -14,7 +14,7 @@ class ProgressTracker:
|
|
|
14
14
|
self.processed_items = 0
|
|
15
15
|
self.last_update_time = self.start_time
|
|
16
16
|
self.custom_counters = custom_counters if custom_counters else {}
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
def update(self, processed_count=None, **kwargs):
|
|
19
19
|
"""
|
|
20
20
|
Update progress. If processed_count is None, increment by 1.
|
|
@@ -33,23 +33,26 @@ class ProgressTracker:
|
|
|
33
33
|
current_time = time.time()
|
|
34
34
|
|
|
35
35
|
# Only update display if at least 0.1 seconds have passed (to avoid spamming)
|
|
36
|
-
if
|
|
36
|
+
if (
|
|
37
|
+
current_time - self.last_update_time >= 0.1
|
|
38
|
+
or self.processed_items == self.total_items
|
|
39
|
+
):
|
|
37
40
|
self._display_progress()
|
|
38
41
|
self.last_update_time = current_time
|
|
39
|
-
|
|
42
|
+
|
|
40
43
|
def _display_progress(self):
|
|
41
44
|
"""
|
|
42
45
|
Display progress with percentage, ETA, and elapsed time.
|
|
43
46
|
"""
|
|
44
47
|
current_time = time.time()
|
|
45
48
|
elapsed_time = current_time - self.start_time
|
|
46
|
-
|
|
49
|
+
|
|
47
50
|
if self.processed_items == 0:
|
|
48
51
|
percentage = 0.0
|
|
49
52
|
eta_str = "calculating..."
|
|
50
53
|
else:
|
|
51
54
|
percentage = (self.processed_items / self.total_items) * 100
|
|
52
|
-
|
|
55
|
+
|
|
53
56
|
# Calculate ETA
|
|
54
57
|
if self.processed_items == self.total_items:
|
|
55
58
|
eta_str = "completed!"
|
|
@@ -58,7 +61,7 @@ class ProgressTracker:
|
|
|
58
61
|
remaining_items = self.total_items - self.processed_items
|
|
59
62
|
eta_seconds = avg_time_per_item * remaining_items
|
|
60
63
|
eta_str = self._format_time(eta_seconds)
|
|
61
|
-
|
|
64
|
+
|
|
62
65
|
elapsed_str = self._format_time(elapsed_time)
|
|
63
66
|
|
|
64
67
|
progress_msg = f"\r{self.description}: {self.processed_items}/{self.total_items} ({percentage:.1f}%) | "
|
|
@@ -66,15 +69,17 @@ class ProgressTracker:
|
|
|
66
69
|
|
|
67
70
|
# Add custom counters to the display
|
|
68
71
|
if self.custom_counters:
|
|
69
|
-
counter_parts = [
|
|
72
|
+
counter_parts = [
|
|
73
|
+
f"{key}: {value}" for key, value in self.custom_counters.items()
|
|
74
|
+
]
|
|
70
75
|
progress_msg += " | " + ", ".join(counter_parts)
|
|
71
76
|
|
|
72
77
|
print(progress_msg, end="", flush=True)
|
|
73
|
-
|
|
78
|
+
|
|
74
79
|
# Add newline when complete
|
|
75
80
|
if self.processed_items == self.total_items:
|
|
76
81
|
print() # Newline to finish the progress line
|
|
77
|
-
|
|
82
|
+
|
|
78
83
|
def _format_time(self, seconds):
|
|
79
84
|
"""
|
|
80
85
|
Format seconds into a human-readable string.
|
|
@@ -89,7 +94,7 @@ class ProgressTracker:
|
|
|
89
94
|
hours = int(seconds // 3600)
|
|
90
95
|
minutes = int((seconds % 3600) // 60)
|
|
91
96
|
return f"{hours}h {minutes}m"
|
|
92
|
-
|
|
97
|
+
|
|
93
98
|
def finish(self):
|
|
94
99
|
"""
|
|
95
100
|
Mark progress as complete and add final newline.
|
|
@@ -97,9 +102,12 @@ class ProgressTracker:
|
|
|
97
102
|
self.processed_items = self.total_items
|
|
98
103
|
self._display_progress()
|
|
99
104
|
|
|
100
|
-
|
|
105
|
+
|
|
106
|
+
def create_progress_tracker(
|
|
107
|
+
total_items, description="Processing", custom_counters=None
|
|
108
|
+
):
|
|
101
109
|
"""
|
|
102
110
|
Factory function to create a progress tracker.
|
|
103
111
|
custom_counters: dict of counter_name: initial_value for additional metrics to track
|
|
104
112
|
"""
|
|
105
|
-
return ProgressTracker(total_items, description, custom_counters)
|
|
113
|
+
return ProgressTracker(total_items, description, custom_counters)
|
|
@@ -1,20 +1,31 @@
|
|
|
1
|
-
import sys
|
|
2
1
|
import os
|
|
2
|
+
import sys
|
|
3
3
|
from collections import defaultdict
|
|
4
4
|
|
|
5
5
|
# Add the project root directory to the Python path
|
|
6
6
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
7
7
|
|
|
8
|
-
from hockey_blast_common_lib.models import Game, Division, Level, Season, League
|
|
9
8
|
from hockey_blast_common_lib.db_connection import create_session
|
|
9
|
+
from hockey_blast_common_lib.models import Division, Game, League, Level, Season
|
|
10
10
|
from hockey_blast_common_lib.utils import get_fake_level
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
def analyze_levels(org):
|
|
13
14
|
session = create_session(org)
|
|
14
15
|
|
|
15
16
|
# Query to get games and their divisions for season_number 33 and 35 with league_number 1
|
|
16
|
-
games_season_33 =
|
|
17
|
-
|
|
17
|
+
games_season_33 = (
|
|
18
|
+
session.query(Game.home_team_id, Game.visitor_team_id, Division.level)
|
|
19
|
+
.join(Division, Game.division_id == Division.id)
|
|
20
|
+
.filter(Division.season_number == 33, Division.league_number == 1)
|
|
21
|
+
.all()
|
|
22
|
+
)
|
|
23
|
+
games_season_35 = (
|
|
24
|
+
session.query(Game.home_team_id, Game.visitor_team_id, Division.level)
|
|
25
|
+
.join(Division, Game.division_id == Division.id)
|
|
26
|
+
.filter(Division.season_number == 35, Division.league_number == 1)
|
|
27
|
+
.all()
|
|
28
|
+
)
|
|
18
29
|
|
|
19
30
|
# Dictionary to store levels for each team by season
|
|
20
31
|
team_levels_season_33 = defaultdict(set)
|
|
@@ -43,32 +54,131 @@ def analyze_levels(org):
|
|
|
43
54
|
for new_level in sorted(level_connections.keys()):
|
|
44
55
|
connections = level_connections[new_level]
|
|
45
56
|
connections_list = sorted(connections.items(), key=lambda x: x[0])
|
|
46
|
-
connections_str = ", ".join(
|
|
57
|
+
connections_str = ", ".join(
|
|
58
|
+
[f"{old_level}: {count}" for old_level, count in connections_list]
|
|
59
|
+
)
|
|
47
60
|
print(f"{new_level}: {connections_str}")
|
|
48
61
|
|
|
49
62
|
session.close()
|
|
50
63
|
|
|
64
|
+
|
|
51
65
|
def fill_seed_levels():
|
|
52
66
|
session = create_session("boss")
|
|
53
67
|
|
|
54
68
|
# List of Skill objects based on the provided comments
|
|
55
69
|
levels = [
|
|
56
|
-
Level(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
Level(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Level(
|
|
71
|
-
|
|
70
|
+
Level(
|
|
71
|
+
is_seed=True,
|
|
72
|
+
org_id=1,
|
|
73
|
+
skill_value=10.0,
|
|
74
|
+
level_name="Adult Division 1",
|
|
75
|
+
level_alternative_name="Senior A",
|
|
76
|
+
),
|
|
77
|
+
Level(
|
|
78
|
+
is_seed=True,
|
|
79
|
+
org_id=1,
|
|
80
|
+
skill_value=20.0,
|
|
81
|
+
level_name="Adult Division 2",
|
|
82
|
+
level_alternative_name="Senior B",
|
|
83
|
+
),
|
|
84
|
+
Level(
|
|
85
|
+
is_seed=True,
|
|
86
|
+
org_id=1,
|
|
87
|
+
skill_value=30.0,
|
|
88
|
+
level_name="Adult Division 3A",
|
|
89
|
+
level_alternative_name="Senior BB",
|
|
90
|
+
),
|
|
91
|
+
Level(
|
|
92
|
+
is_seed=True,
|
|
93
|
+
org_id=1,
|
|
94
|
+
skill_value=35.0,
|
|
95
|
+
level_name="Adult Division 3B",
|
|
96
|
+
level_alternative_name="Senior C",
|
|
97
|
+
),
|
|
98
|
+
Level(
|
|
99
|
+
is_seed=True,
|
|
100
|
+
org_id=1,
|
|
101
|
+
skill_value=40.0,
|
|
102
|
+
level_name="Adult Division 4A",
|
|
103
|
+
level_alternative_name="Senior CC",
|
|
104
|
+
),
|
|
105
|
+
Level(
|
|
106
|
+
is_seed=True,
|
|
107
|
+
org_id=1,
|
|
108
|
+
skill_value=45.0,
|
|
109
|
+
level_name="Adult Division 4B",
|
|
110
|
+
level_alternative_name="Senior CCC,Senior CCCC",
|
|
111
|
+
),
|
|
112
|
+
Level(
|
|
113
|
+
is_seed=True,
|
|
114
|
+
org_id=1,
|
|
115
|
+
skill_value=50.0,
|
|
116
|
+
level_name="Adult Division 5A",
|
|
117
|
+
level_alternative_name="Senior D,Senior DD",
|
|
118
|
+
),
|
|
119
|
+
Level(
|
|
120
|
+
is_seed=True,
|
|
121
|
+
org_id=1,
|
|
122
|
+
skill_value=55.0,
|
|
123
|
+
level_name="Adult Division 5B",
|
|
124
|
+
level_alternative_name="Senior DDD",
|
|
125
|
+
),
|
|
126
|
+
Level(
|
|
127
|
+
is_seed=True,
|
|
128
|
+
org_id=1,
|
|
129
|
+
skill_value=60.0,
|
|
130
|
+
level_name="Adult Division 6A",
|
|
131
|
+
level_alternative_name="Senior DDDD",
|
|
132
|
+
),
|
|
133
|
+
Level(
|
|
134
|
+
is_seed=True,
|
|
135
|
+
org_id=1,
|
|
136
|
+
skill_value=65.0,
|
|
137
|
+
level_name="Adult Division 6B",
|
|
138
|
+
level_alternative_name="Senior DDDDD",
|
|
139
|
+
),
|
|
140
|
+
Level(
|
|
141
|
+
is_seed=True,
|
|
142
|
+
org_id=1,
|
|
143
|
+
skill_value=70.0,
|
|
144
|
+
level_name="Adult Division 7A",
|
|
145
|
+
level_alternative_name="Senior E",
|
|
146
|
+
),
|
|
147
|
+
Level(
|
|
148
|
+
is_seed=True,
|
|
149
|
+
org_id=1,
|
|
150
|
+
skill_value=75.0,
|
|
151
|
+
level_name="Adult Division 7B",
|
|
152
|
+
level_alternative_name="Senior EE",
|
|
153
|
+
),
|
|
154
|
+
Level(
|
|
155
|
+
is_seed=True,
|
|
156
|
+
org_id=1,
|
|
157
|
+
skill_value=80.0,
|
|
158
|
+
level_name="Adult Division 8",
|
|
159
|
+
level_alternative_name="Senior EEE",
|
|
160
|
+
),
|
|
161
|
+
Level(
|
|
162
|
+
is_seed=True,
|
|
163
|
+
org_id=1,
|
|
164
|
+
skill_value=80.0,
|
|
165
|
+
level_name="Adult Division 8A",
|
|
166
|
+
level_alternative_name="Senior EEE",
|
|
167
|
+
),
|
|
168
|
+
Level(
|
|
169
|
+
is_seed=True,
|
|
170
|
+
org_id=1,
|
|
171
|
+
skill_value=85.0,
|
|
172
|
+
level_name="Adult Division 8B",
|
|
173
|
+
level_alternative_name="Senior EEEE",
|
|
174
|
+
),
|
|
175
|
+
Level(
|
|
176
|
+
is_seed=True,
|
|
177
|
+
org_id=1,
|
|
178
|
+
skill_value=90.0,
|
|
179
|
+
level_name="Adult Division 9",
|
|
180
|
+
level_alternative_name="Senior EEEEE",
|
|
181
|
+
),
|
|
72
182
|
]
|
|
73
183
|
|
|
74
184
|
for skill in levels:
|
|
@@ -77,6 +187,7 @@ def fill_seed_levels():
|
|
|
77
187
|
|
|
78
188
|
print("Seed skills have been populated into the database.")
|
|
79
189
|
|
|
190
|
+
|
|
80
191
|
def assign_fake_level_to_divisions(session, fake_level):
|
|
81
192
|
# Assign the special fake Skill to every existing Division
|
|
82
193
|
divisions = session.query(Division).all()
|
|
@@ -85,45 +196,71 @@ def assign_fake_level_to_divisions(session, fake_level):
|
|
|
85
196
|
session.commit()
|
|
86
197
|
print("Assigned special fake Skill to all Division records.")
|
|
87
198
|
|
|
199
|
+
|
|
88
200
|
def delete_all_levels():
|
|
89
201
|
session = create_session("boss")
|
|
90
202
|
fake_level = get_fake_level(session)
|
|
91
203
|
assign_fake_level_to_divisions(session, fake_level)
|
|
92
204
|
# Delete all Skill records except the fake skill
|
|
93
|
-
session.query(Level).filter(Level.id != fake_level.id).delete(
|
|
205
|
+
session.query(Level).filter(Level.id != fake_level.id).delete(
|
|
206
|
+
synchronize_session=False
|
|
207
|
+
)
|
|
94
208
|
session.commit()
|
|
95
209
|
print("All Skill records except the fake skill have been deleted.")
|
|
96
210
|
|
|
211
|
+
|
|
97
212
|
def populate_season_ids():
|
|
98
213
|
session = create_session("boss")
|
|
99
214
|
divisions = session.query(Division).all()
|
|
100
215
|
for division in divisions:
|
|
101
216
|
# Find the Season record that matches the season_number
|
|
102
|
-
season =
|
|
217
|
+
season = (
|
|
218
|
+
session.query(Season)
|
|
219
|
+
.filter_by(
|
|
220
|
+
season_number=division.season_number,
|
|
221
|
+
org_id=division.org_id,
|
|
222
|
+
league_number=division.league_number,
|
|
223
|
+
)
|
|
224
|
+
.first()
|
|
225
|
+
)
|
|
103
226
|
if season:
|
|
104
227
|
division.season_id = season.id
|
|
105
|
-
print(
|
|
228
|
+
print(
|
|
229
|
+
f"Assigned season_id {season.id} for Division with season_number {division.season_number}"
|
|
230
|
+
)
|
|
106
231
|
else:
|
|
107
|
-
print(
|
|
232
|
+
print(
|
|
233
|
+
f"Season not found for Division with season_number {division.season_number}"
|
|
234
|
+
)
|
|
108
235
|
session.commit()
|
|
109
236
|
print("Season IDs have been populated into the Division table.")
|
|
110
237
|
|
|
238
|
+
|
|
111
239
|
def populate_league_ids():
|
|
112
240
|
session = create_session("boss")
|
|
113
241
|
seasons = session.query(Season).all()
|
|
114
242
|
for season in seasons:
|
|
115
243
|
# Find the League record that matches the league_number and org_id
|
|
116
|
-
league =
|
|
244
|
+
league = (
|
|
245
|
+
session.query(League)
|
|
246
|
+
.filter_by(league_number=season.league_number, org_id=season.org_id)
|
|
247
|
+
.first()
|
|
248
|
+
)
|
|
117
249
|
if league:
|
|
118
250
|
season.league_id = league.id
|
|
119
|
-
print(
|
|
251
|
+
print(
|
|
252
|
+
f"Assigned league_id {league.id} for Season with league_number {season.league_number}"
|
|
253
|
+
)
|
|
120
254
|
else:
|
|
121
|
-
print(
|
|
255
|
+
print(
|
|
256
|
+
f"League not found for Season with league_number {season.league_number}"
|
|
257
|
+
)
|
|
122
258
|
session.commit()
|
|
123
259
|
print("League IDs have been populated into the Season table.")
|
|
124
260
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
261
|
+
|
|
262
|
+
# if __name__ == "__main__":
|
|
263
|
+
# delete_all_levels()
|
|
264
|
+
# fill_seed_levels()
|
|
265
|
+
# populate_season_ids() # Call the function to populate season_ids
|
|
266
|
+
# populate_league_ids() # Call the new function to populate league_ids
|