hockey-blast-common-lib 0.1.65__py3-none-any.whl → 0.1.66__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 +14 -0
- hockey_blast_common_lib/aggregate_goalie_stats.py +68 -0
- hockey_blast_common_lib/aggregate_referee_stats.py +63 -0
- hockey_blast_common_lib/aggregate_scorekeeper_stats.py +78 -0
- hockey_blast_common_lib/aggregate_skater_stats.py +106 -0
- hockey_blast_common_lib/aggregate_team_goalie_stats.py +251 -0
- hockey_blast_common_lib/aggregate_team_skater_stats.py +299 -0
- hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz +0 -0
- hockey_blast_common_lib/stats_models.py +123 -0
- hockey_blast_common_lib/utils.py +64 -0
- {hockey_blast_common_lib-0.1.65.dist-info → hockey_blast_common_lib-0.1.66.dist-info}/METADATA +1 -1
- {hockey_blast_common_lib-0.1.65.dist-info → hockey_blast_common_lib-0.1.66.dist-info}/RECORD +14 -12
- {hockey_blast_common_lib-0.1.65.dist-info → hockey_blast_common_lib-0.1.66.dist-info}/WHEEL +0 -0
- {hockey_blast_common_lib-0.1.65.dist-info → hockey_blast_common_lib-0.1.66.dist-info}/top_level.txt +0 -0
|
@@ -11,6 +11,12 @@ from hockey_blast_common_lib.aggregate_scorekeeper_stats import (
|
|
|
11
11
|
run_aggregate_scorekeeper_stats,
|
|
12
12
|
)
|
|
13
13
|
from hockey_blast_common_lib.aggregate_skater_stats import run_aggregate_skater_stats
|
|
14
|
+
from hockey_blast_common_lib.aggregate_team_goalie_stats import (
|
|
15
|
+
run_aggregate_team_goalie_stats,
|
|
16
|
+
)
|
|
17
|
+
from hockey_blast_common_lib.aggregate_team_skater_stats import (
|
|
18
|
+
run_aggregate_team_skater_stats,
|
|
19
|
+
)
|
|
14
20
|
|
|
15
21
|
if __name__ == "__main__":
|
|
16
22
|
print("Running aggregate_skater_stats...", flush=True)
|
|
@@ -32,3 +38,11 @@ if __name__ == "__main__":
|
|
|
32
38
|
print("Running aggregate_human_stats...", flush=True)
|
|
33
39
|
run_aggregate_human_stats()
|
|
34
40
|
print("Finished running aggregate_human_stats\n")
|
|
41
|
+
|
|
42
|
+
print("Running aggregate_team_skater_stats...", flush=True)
|
|
43
|
+
run_aggregate_team_skater_stats()
|
|
44
|
+
print("Finished running aggregate_team_skater_stats\n")
|
|
45
|
+
|
|
46
|
+
print("Running aggregate_team_goalie_stats...", flush=True)
|
|
47
|
+
run_aggregate_team_goalie_stats()
|
|
48
|
+
print("Finished running aggregate_team_goalie_stats\n")
|
|
@@ -35,8 +35,10 @@ from hockey_blast_common_lib.stats_models import (
|
|
|
35
35
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
|
36
36
|
from hockey_blast_common_lib.utils import (
|
|
37
37
|
assign_ranks,
|
|
38
|
+
calculate_percentile_value,
|
|
38
39
|
get_all_division_ids_for_org,
|
|
39
40
|
get_non_human_ids,
|
|
41
|
+
get_percentile_human,
|
|
40
42
|
get_start_datetime,
|
|
41
43
|
)
|
|
42
44
|
|
|
@@ -47,6 +49,67 @@ FORFEIT_STATUS = "FORFEIT"
|
|
|
47
49
|
NOEVENTS_STATUS = "NOEVENTS"
|
|
48
50
|
|
|
49
51
|
|
|
52
|
+
def insert_percentile_markers_goalie(
|
|
53
|
+
session, stats_dict, aggregation_id, total_in_rank, StatsModel
|
|
54
|
+
):
|
|
55
|
+
"""Insert percentile marker records for goalie stats.
|
|
56
|
+
|
|
57
|
+
For each stat field, calculate the 25th, 50th, 75th, 90th, and 95th percentile values
|
|
58
|
+
and insert marker records with fake human IDs.
|
|
59
|
+
"""
|
|
60
|
+
if not stats_dict:
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
# Define the stat fields we want to calculate percentiles for
|
|
64
|
+
stat_fields = [
|
|
65
|
+
"games_played",
|
|
66
|
+
"games_participated",
|
|
67
|
+
"games_with_stats",
|
|
68
|
+
"goals_allowed",
|
|
69
|
+
"shots_faced",
|
|
70
|
+
"goals_allowed_per_game",
|
|
71
|
+
"save_percentage",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
percentiles = [25, 50, 75, 90, 95]
|
|
75
|
+
|
|
76
|
+
for percentile in percentiles:
|
|
77
|
+
percentile_human_id = get_percentile_human(session, "Goalie", percentile)
|
|
78
|
+
|
|
79
|
+
percentile_values = {}
|
|
80
|
+
for field in stat_fields:
|
|
81
|
+
values = [stat[field] for stat in stats_dict.values() if field in stat]
|
|
82
|
+
if values:
|
|
83
|
+
percentile_values[field] = calculate_percentile_value(values, percentile)
|
|
84
|
+
else:
|
|
85
|
+
percentile_values[field] = 0
|
|
86
|
+
|
|
87
|
+
goalie_stat = StatsModel(
|
|
88
|
+
aggregation_id=aggregation_id,
|
|
89
|
+
human_id=percentile_human_id,
|
|
90
|
+
games_played=int(percentile_values.get("games_played", 0)),
|
|
91
|
+
games_participated=int(percentile_values.get("games_participated", 0)),
|
|
92
|
+
games_participated_rank=0,
|
|
93
|
+
games_with_stats=int(percentile_values.get("games_with_stats", 0)),
|
|
94
|
+
games_with_stats_rank=0,
|
|
95
|
+
goals_allowed=int(percentile_values.get("goals_allowed", 0)),
|
|
96
|
+
shots_faced=int(percentile_values.get("shots_faced", 0)),
|
|
97
|
+
goals_allowed_per_game=percentile_values.get("goals_allowed_per_game", 0.0),
|
|
98
|
+
save_percentage=percentile_values.get("save_percentage", 0.0),
|
|
99
|
+
games_played_rank=0,
|
|
100
|
+
goals_allowed_rank=0,
|
|
101
|
+
shots_faced_rank=0,
|
|
102
|
+
goals_allowed_per_game_rank=0,
|
|
103
|
+
save_percentage_rank=0,
|
|
104
|
+
total_in_rank=total_in_rank,
|
|
105
|
+
first_game_id=None,
|
|
106
|
+
last_game_id=None,
|
|
107
|
+
)
|
|
108
|
+
session.add(goalie_stat)
|
|
109
|
+
|
|
110
|
+
session.commit()
|
|
111
|
+
|
|
112
|
+
|
|
50
113
|
def aggregate_goalie_stats(
|
|
51
114
|
session,
|
|
52
115
|
aggregation_type,
|
|
@@ -243,6 +306,11 @@ def aggregate_goalie_stats(
|
|
|
243
306
|
assign_ranks(stats_dict, "goals_allowed_per_game", reverse_rank=True)
|
|
244
307
|
assign_ranks(stats_dict, "save_percentage")
|
|
245
308
|
|
|
309
|
+
# Calculate and insert percentile marker records
|
|
310
|
+
insert_percentile_markers_goalie(
|
|
311
|
+
session, stats_dict, aggregation_id, total_in_rank, StatsModel
|
|
312
|
+
)
|
|
313
|
+
|
|
246
314
|
# Debug output for specific human
|
|
247
315
|
if debug_human_id:
|
|
248
316
|
if any(key[1] == debug_human_id for key in stats_dict):
|
|
@@ -29,8 +29,10 @@ from hockey_blast_common_lib.stats_models import (
|
|
|
29
29
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
|
30
30
|
from hockey_blast_common_lib.utils import (
|
|
31
31
|
assign_ranks,
|
|
32
|
+
calculate_percentile_value,
|
|
32
33
|
get_all_division_ids_for_org,
|
|
33
34
|
get_non_human_ids,
|
|
35
|
+
get_percentile_human,
|
|
34
36
|
get_start_datetime,
|
|
35
37
|
)
|
|
36
38
|
|
|
@@ -41,6 +43,62 @@ FORFEIT_STATUS = "FORFEIT"
|
|
|
41
43
|
NOEVENTS_STATUS = "NOEVENTS"
|
|
42
44
|
|
|
43
45
|
|
|
46
|
+
def insert_percentile_markers_referee(
|
|
47
|
+
session, stats_dict, aggregation_id, total_in_rank, StatsModel
|
|
48
|
+
):
|
|
49
|
+
"""Insert percentile marker records for referee stats."""
|
|
50
|
+
if not stats_dict:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
stat_fields = [
|
|
54
|
+
"games_reffed",
|
|
55
|
+
"games_participated",
|
|
56
|
+
"games_with_stats",
|
|
57
|
+
"penalties_given",
|
|
58
|
+
"penalties_per_game",
|
|
59
|
+
"gm_given",
|
|
60
|
+
"gm_per_game",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
percentiles = [25, 50, 75, 90, 95]
|
|
64
|
+
|
|
65
|
+
for percentile in percentiles:
|
|
66
|
+
percentile_human_id = get_percentile_human(session, "Ref", percentile)
|
|
67
|
+
|
|
68
|
+
percentile_values = {}
|
|
69
|
+
for field in stat_fields:
|
|
70
|
+
values = [stat[field] for stat in stats_dict.values() if field in stat]
|
|
71
|
+
if values:
|
|
72
|
+
percentile_values[field] = calculate_percentile_value(values, percentile)
|
|
73
|
+
else:
|
|
74
|
+
percentile_values[field] = 0
|
|
75
|
+
|
|
76
|
+
referee_stat = StatsModel(
|
|
77
|
+
aggregation_id=aggregation_id,
|
|
78
|
+
human_id=percentile_human_id,
|
|
79
|
+
games_reffed=int(percentile_values.get("games_reffed", 0)),
|
|
80
|
+
games_participated=int(percentile_values.get("games_participated", 0)),
|
|
81
|
+
games_participated_rank=0,
|
|
82
|
+
games_with_stats=int(percentile_values.get("games_with_stats", 0)),
|
|
83
|
+
games_with_stats_rank=0,
|
|
84
|
+
penalties_given=int(percentile_values.get("penalties_given", 0)),
|
|
85
|
+
penalties_per_game=percentile_values.get("penalties_per_game", 0.0),
|
|
86
|
+
gm_given=int(percentile_values.get("gm_given", 0)),
|
|
87
|
+
gm_per_game=percentile_values.get("gm_per_game", 0.0),
|
|
88
|
+
games_reffed_rank=0,
|
|
89
|
+
penalties_given_rank=0,
|
|
90
|
+
penalties_per_game_rank=0,
|
|
91
|
+
gm_given_rank=0,
|
|
92
|
+
gm_per_game_rank=0,
|
|
93
|
+
total_in_rank=total_in_rank,
|
|
94
|
+
first_game_id=None,
|
|
95
|
+
last_game_id=None,
|
|
96
|
+
)
|
|
97
|
+
session.add(referee_stat)
|
|
98
|
+
|
|
99
|
+
session.commit()
|
|
100
|
+
|
|
101
|
+
|
|
44
102
|
def aggregate_referee_stats(
|
|
45
103
|
session, aggregation_type, aggregation_id, aggregation_window=None
|
|
46
104
|
):
|
|
@@ -281,6 +339,11 @@ def aggregate_referee_stats(
|
|
|
281
339
|
assign_ranks(stats_dict, "gm_given")
|
|
282
340
|
assign_ranks(stats_dict, "gm_per_game")
|
|
283
341
|
|
|
342
|
+
# Calculate and insert percentile marker records
|
|
343
|
+
insert_percentile_markers_referee(
|
|
344
|
+
session, stats_dict, aggregation_id, total_in_rank, StatsModel
|
|
345
|
+
)
|
|
346
|
+
|
|
284
347
|
# Insert aggregated stats into the appropriate table with progress output
|
|
285
348
|
total_items = len(stats_dict)
|
|
286
349
|
batch_size = 1000
|
|
@@ -22,7 +22,9 @@ from hockey_blast_common_lib.stats_models import (
|
|
|
22
22
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
|
23
23
|
from hockey_blast_common_lib.utils import (
|
|
24
24
|
assign_ranks,
|
|
25
|
+
calculate_percentile_value,
|
|
25
26
|
get_non_human_ids,
|
|
27
|
+
get_percentile_human,
|
|
26
28
|
get_start_datetime,
|
|
27
29
|
)
|
|
28
30
|
|
|
@@ -33,6 +35,77 @@ FORFEIT_STATUS = "FORFEIT"
|
|
|
33
35
|
NOEVENTS_STATUS = "NOEVENTS"
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
def insert_percentile_markers_scorekeeper(
|
|
39
|
+
session, stats_dict, aggregation_id, total_in_rank, StatsModel
|
|
40
|
+
):
|
|
41
|
+
"""Insert percentile marker records for scorekeeper stats."""
|
|
42
|
+
if not stats_dict:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
stat_fields = [
|
|
46
|
+
"games_recorded",
|
|
47
|
+
"games_participated",
|
|
48
|
+
"games_with_stats",
|
|
49
|
+
"sog_given",
|
|
50
|
+
"sog_per_game",
|
|
51
|
+
"total_saves_recorded",
|
|
52
|
+
"avg_saves_per_game",
|
|
53
|
+
"avg_max_saves_per_5sec",
|
|
54
|
+
"avg_max_saves_per_20sec",
|
|
55
|
+
"peak_max_saves_per_5sec",
|
|
56
|
+
"peak_max_saves_per_20sec",
|
|
57
|
+
"quality_score",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
percentiles = [25, 50, 75, 90, 95]
|
|
61
|
+
|
|
62
|
+
for percentile in percentiles:
|
|
63
|
+
percentile_human_id = get_percentile_human(session, "Scorekeeper", percentile)
|
|
64
|
+
|
|
65
|
+
percentile_values = {}
|
|
66
|
+
for field in stat_fields:
|
|
67
|
+
values = [stat[field] for stat in stats_dict.values() if field in stat]
|
|
68
|
+
if values:
|
|
69
|
+
percentile_values[field] = calculate_percentile_value(values, percentile)
|
|
70
|
+
else:
|
|
71
|
+
percentile_values[field] = 0
|
|
72
|
+
|
|
73
|
+
scorekeeper_stat = StatsModel(
|
|
74
|
+
aggregation_id=aggregation_id,
|
|
75
|
+
human_id=percentile_human_id,
|
|
76
|
+
games_recorded=int(percentile_values.get("games_recorded", 0)),
|
|
77
|
+
games_participated=int(percentile_values.get("games_participated", 0)),
|
|
78
|
+
games_participated_rank=0,
|
|
79
|
+
games_with_stats=int(percentile_values.get("games_with_stats", 0)),
|
|
80
|
+
games_with_stats_rank=0,
|
|
81
|
+
sog_given=int(percentile_values.get("sog_given", 0)),
|
|
82
|
+
sog_per_game=percentile_values.get("sog_per_game", 0.0),
|
|
83
|
+
total_saves_recorded=int(percentile_values.get("total_saves_recorded", 0)),
|
|
84
|
+
avg_saves_per_game=percentile_values.get("avg_saves_per_game", 0.0),
|
|
85
|
+
avg_max_saves_per_5sec=percentile_values.get("avg_max_saves_per_5sec", 0.0),
|
|
86
|
+
avg_max_saves_per_20sec=percentile_values.get("avg_max_saves_per_20sec", 0.0),
|
|
87
|
+
peak_max_saves_per_5sec=int(percentile_values.get("peak_max_saves_per_5sec", 0)),
|
|
88
|
+
peak_max_saves_per_20sec=int(percentile_values.get("peak_max_saves_per_20sec", 0)),
|
|
89
|
+
quality_score=percentile_values.get("quality_score", 0.0),
|
|
90
|
+
games_recorded_rank=0,
|
|
91
|
+
sog_given_rank=0,
|
|
92
|
+
sog_per_game_rank=0,
|
|
93
|
+
total_saves_recorded_rank=0,
|
|
94
|
+
avg_saves_per_game_rank=0,
|
|
95
|
+
avg_max_saves_per_5sec_rank=0,
|
|
96
|
+
avg_max_saves_per_20sec_rank=0,
|
|
97
|
+
peak_max_saves_per_5sec_rank=0,
|
|
98
|
+
peak_max_saves_per_20sec_rank=0,
|
|
99
|
+
quality_score_rank=0,
|
|
100
|
+
total_in_rank=total_in_rank,
|
|
101
|
+
first_game_id=None,
|
|
102
|
+
last_game_id=None,
|
|
103
|
+
)
|
|
104
|
+
session.add(scorekeeper_stat)
|
|
105
|
+
|
|
106
|
+
session.commit()
|
|
107
|
+
|
|
108
|
+
|
|
36
109
|
def calculate_quality_score(
|
|
37
110
|
avg_max_saves_5sec, avg_max_saves_20sec, peak_max_saves_5sec, peak_max_saves_20sec
|
|
38
111
|
):
|
|
@@ -252,6 +325,11 @@ def aggregate_scorekeeper_stats(
|
|
|
252
325
|
stats_dict, "quality_score", reverse_rank=True
|
|
253
326
|
) # Lower is better (less problematic)
|
|
254
327
|
|
|
328
|
+
# Calculate and insert percentile marker records
|
|
329
|
+
insert_percentile_markers_scorekeeper(
|
|
330
|
+
session, stats_dict, aggregation_id, total_in_rank, StatsModel
|
|
331
|
+
)
|
|
332
|
+
|
|
255
333
|
# Insert aggregated stats into the appropriate table with progress output
|
|
256
334
|
batch_size = 1000
|
|
257
335
|
for i, (key, stat) in enumerate(stats_dict.items(), 1):
|
|
@@ -37,8 +37,10 @@ from hockey_blast_common_lib.stats_models import (
|
|
|
37
37
|
)
|
|
38
38
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
|
39
39
|
from hockey_blast_common_lib.utils import (
|
|
40
|
+
calculate_percentile_value,
|
|
40
41
|
get_all_division_ids_for_org,
|
|
41
42
|
get_non_human_ids,
|
|
43
|
+
get_percentile_human,
|
|
42
44
|
get_start_datetime,
|
|
43
45
|
)
|
|
44
46
|
|
|
@@ -115,6 +117,105 @@ def calculate_current_point_streak(session, human_id, filter_condition):
|
|
|
115
117
|
return current_streak, avg_points_during_streak
|
|
116
118
|
|
|
117
119
|
|
|
120
|
+
def insert_percentile_markers_skater(
|
|
121
|
+
session, stats_dict, aggregation_id, total_in_rank, StatsModel, aggregation_window
|
|
122
|
+
):
|
|
123
|
+
"""Insert percentile marker records for skater stats.
|
|
124
|
+
|
|
125
|
+
For each stat field, calculate the 25th, 50th, 75th, 90th, and 95th percentile values
|
|
126
|
+
and insert marker records with fake human IDs.
|
|
127
|
+
"""
|
|
128
|
+
if not stats_dict:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
# Define the stat fields we want to calculate percentiles for
|
|
132
|
+
# Each field has percentile calculated SEPARATELY
|
|
133
|
+
stat_fields = [
|
|
134
|
+
"games_played",
|
|
135
|
+
"games_participated",
|
|
136
|
+
"games_with_stats",
|
|
137
|
+
"goals",
|
|
138
|
+
"assists",
|
|
139
|
+
"points",
|
|
140
|
+
"penalties",
|
|
141
|
+
"gm_penalties",
|
|
142
|
+
"goals_per_game",
|
|
143
|
+
"assists_per_game",
|
|
144
|
+
"points_per_game",
|
|
145
|
+
"penalties_per_game",
|
|
146
|
+
"gm_penalties_per_game",
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
# Add streak fields only for all-time stats
|
|
150
|
+
if aggregation_window is None:
|
|
151
|
+
stat_fields.extend(
|
|
152
|
+
["current_point_streak", "current_point_streak_avg_points"]
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# For each percentile (25, 50, 75, 90, 95)
|
|
156
|
+
percentiles = [25, 50, 75, 90, 95]
|
|
157
|
+
|
|
158
|
+
for percentile in percentiles:
|
|
159
|
+
# Get or create the percentile marker human
|
|
160
|
+
percentile_human_id = get_percentile_human(session, "Skater", percentile)
|
|
161
|
+
|
|
162
|
+
# Calculate percentile values for each stat field SEPARATELY
|
|
163
|
+
percentile_values = {}
|
|
164
|
+
for field in stat_fields:
|
|
165
|
+
# Extract all values for this field
|
|
166
|
+
values = [stat[field] for stat in stats_dict.values() if field in stat]
|
|
167
|
+
if values:
|
|
168
|
+
percentile_values[field] = calculate_percentile_value(values, percentile)
|
|
169
|
+
else:
|
|
170
|
+
percentile_values[field] = 0
|
|
171
|
+
|
|
172
|
+
# Create the stats record for this percentile marker
|
|
173
|
+
skater_stat = StatsModel(
|
|
174
|
+
aggregation_id=aggregation_id,
|
|
175
|
+
human_id=percentile_human_id,
|
|
176
|
+
games_played=int(percentile_values.get("games_played", 0)),
|
|
177
|
+
games_participated=int(percentile_values.get("games_participated", 0)),
|
|
178
|
+
games_participated_rank=0, # Percentile markers don't have ranks
|
|
179
|
+
games_with_stats=int(percentile_values.get("games_with_stats", 0)),
|
|
180
|
+
games_with_stats_rank=0,
|
|
181
|
+
goals=int(percentile_values.get("goals", 0)),
|
|
182
|
+
assists=int(percentile_values.get("assists", 0)),
|
|
183
|
+
points=int(percentile_values.get("points", 0)),
|
|
184
|
+
penalties=int(percentile_values.get("penalties", 0)),
|
|
185
|
+
gm_penalties=int(percentile_values.get("gm_penalties", 0)),
|
|
186
|
+
goals_per_game=percentile_values.get("goals_per_game", 0.0),
|
|
187
|
+
points_per_game=percentile_values.get("points_per_game", 0.0),
|
|
188
|
+
assists_per_game=percentile_values.get("assists_per_game", 0.0),
|
|
189
|
+
penalties_per_game=percentile_values.get("penalties_per_game", 0.0),
|
|
190
|
+
gm_penalties_per_game=percentile_values.get("gm_penalties_per_game", 0.0),
|
|
191
|
+
games_played_rank=0,
|
|
192
|
+
goals_rank=0,
|
|
193
|
+
assists_rank=0,
|
|
194
|
+
points_rank=0,
|
|
195
|
+
penalties_rank=0,
|
|
196
|
+
gm_penalties_rank=0,
|
|
197
|
+
goals_per_game_rank=0,
|
|
198
|
+
points_per_game_rank=0,
|
|
199
|
+
assists_per_game_rank=0,
|
|
200
|
+
penalties_per_game_rank=0,
|
|
201
|
+
gm_penalties_per_game_rank=0,
|
|
202
|
+
total_in_rank=total_in_rank,
|
|
203
|
+
current_point_streak=int(
|
|
204
|
+
percentile_values.get("current_point_streak", 0)
|
|
205
|
+
),
|
|
206
|
+
current_point_streak_rank=0,
|
|
207
|
+
current_point_streak_avg_points=percentile_values.get(
|
|
208
|
+
"current_point_streak_avg_points", 0.0
|
|
209
|
+
),
|
|
210
|
+
current_point_streak_avg_points_rank=0,
|
|
211
|
+
first_game_id=None, # Percentile markers don't have game references
|
|
212
|
+
last_game_id=None,
|
|
213
|
+
)
|
|
214
|
+
session.add(skater_stat)
|
|
215
|
+
|
|
216
|
+
session.commit()
|
|
217
|
+
|
|
218
|
+
|
|
118
219
|
def aggregate_skater_stats(
|
|
119
220
|
session,
|
|
120
221
|
aggregation_type,
|
|
@@ -561,6 +662,11 @@ def aggregate_skater_stats(
|
|
|
561
662
|
assign_ranks(stats_dict, "current_point_streak")
|
|
562
663
|
assign_ranks(stats_dict, "current_point_streak_avg_points")
|
|
563
664
|
|
|
665
|
+
# Calculate and insert percentile marker records
|
|
666
|
+
insert_percentile_markers_skater(
|
|
667
|
+
session, stats_dict, aggregation_id, total_in_rank, StatsModel, aggregation_window
|
|
668
|
+
)
|
|
669
|
+
|
|
564
670
|
# Debug output for specific human
|
|
565
671
|
if debug_human_id:
|
|
566
672
|
if any(key[1] == debug_human_id for key in stats_dict):
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aggregate goalie statistics by team.
|
|
3
|
+
|
|
4
|
+
This module aggregates goalie statistics for each team, counting only games
|
|
5
|
+
where the goalie was on that specific team (using GameRoster.team_id).
|
|
6
|
+
|
|
7
|
+
Key difference from regular aggregation:
|
|
8
|
+
- Aggregates by (aggregation_id, team_id, human_id) instead of just (aggregation_id, human_id)
|
|
9
|
+
- Filters to only games where GameRoster.team_id matches the target team
|
|
10
|
+
- Stores results in OrgStatsGoalieTeam / DivisionStatsGoalieTeam
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
17
|
+
|
|
18
|
+
import sqlalchemy
|
|
19
|
+
from sqlalchemy import func
|
|
20
|
+
|
|
21
|
+
from hockey_blast_common_lib.db_connection import create_session
|
|
22
|
+
from hockey_blast_common_lib.models import (
|
|
23
|
+
Division,
|
|
24
|
+
Game,
|
|
25
|
+
GameRoster,
|
|
26
|
+
GoalieSaves,
|
|
27
|
+
Human,
|
|
28
|
+
Organization,
|
|
29
|
+
Team,
|
|
30
|
+
)
|
|
31
|
+
from hockey_blast_common_lib.options import (
|
|
32
|
+
MIN_GAMES_FOR_DIVISION_STATS,
|
|
33
|
+
MIN_GAMES_FOR_ORG_STATS,
|
|
34
|
+
)
|
|
35
|
+
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
|
36
|
+
from hockey_blast_common_lib.stats_models import (
|
|
37
|
+
DivisionStatsGoalieTeam,
|
|
38
|
+
OrgStatsGoalieTeam,
|
|
39
|
+
)
|
|
40
|
+
from hockey_blast_common_lib.utils import (
|
|
41
|
+
calculate_percentile_value,
|
|
42
|
+
get_non_human_ids,
|
|
43
|
+
get_percentile_human,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Import status constants for game filtering
|
|
47
|
+
FINAL_STATUS = "Final"
|
|
48
|
+
FINAL_SO_STATUS = "Final(SO)"
|
|
49
|
+
FORFEIT_STATUS = "FORFEIT"
|
|
50
|
+
NOEVENTS_STATUS = "NOEVENTS"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def aggregate_team_goalie_stats(session, aggregation_type, aggregation_id):
|
|
54
|
+
"""
|
|
55
|
+
Aggregate goalie stats by team for an organization or division.
|
|
56
|
+
|
|
57
|
+
For each team in the aggregation scope, calculates stats for all goalies
|
|
58
|
+
who played for that team, counting only games where they were on that team.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
session: Database session
|
|
62
|
+
aggregation_type: "org" or "division"
|
|
63
|
+
aggregation_id: ID of the organization or division
|
|
64
|
+
"""
|
|
65
|
+
human_ids_to_filter = get_non_human_ids(session)
|
|
66
|
+
|
|
67
|
+
# Determine aggregation details
|
|
68
|
+
if aggregation_type == "org":
|
|
69
|
+
StatsModel = OrgStatsGoalieTeam
|
|
70
|
+
min_games = MIN_GAMES_FOR_ORG_STATS
|
|
71
|
+
aggregation_name = (
|
|
72
|
+
session.query(Organization)
|
|
73
|
+
.filter(Organization.id == aggregation_id)
|
|
74
|
+
.first()
|
|
75
|
+
.organization_name
|
|
76
|
+
)
|
|
77
|
+
filter_condition = Game.org_id == aggregation_id
|
|
78
|
+
elif aggregation_type == "division":
|
|
79
|
+
StatsModel = DivisionStatsGoalieTeam
|
|
80
|
+
min_games = MIN_GAMES_FOR_DIVISION_STATS
|
|
81
|
+
aggregation_name = (
|
|
82
|
+
session.query(Division).filter(Division.id == aggregation_id).first().level
|
|
83
|
+
)
|
|
84
|
+
filter_condition = Game.division_id == aggregation_id
|
|
85
|
+
else:
|
|
86
|
+
raise ValueError(f"Invalid aggregation type: {aggregation_type}")
|
|
87
|
+
|
|
88
|
+
print(f"Aggregating team goalie stats for {aggregation_name}...")
|
|
89
|
+
|
|
90
|
+
# Delete existing stats for this aggregation
|
|
91
|
+
session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
|
|
92
|
+
session.commit()
|
|
93
|
+
|
|
94
|
+
# Get all teams in this aggregation scope
|
|
95
|
+
if aggregation_type == "org":
|
|
96
|
+
teams_query = (
|
|
97
|
+
session.query(Team.id, Team.name)
|
|
98
|
+
.join(Game, (Game.home_team_id == Team.id) | (Game.visitor_team_id == Team.id))
|
|
99
|
+
.filter(Game.org_id == aggregation_id)
|
|
100
|
+
.distinct()
|
|
101
|
+
)
|
|
102
|
+
else: # division
|
|
103
|
+
teams_query = (
|
|
104
|
+
session.query(Team.id, Team.name)
|
|
105
|
+
.join(Game, (Game.home_team_id == Team.id) | (Game.visitor_team_id == Team.id))
|
|
106
|
+
.filter(Game.division_id == aggregation_id)
|
|
107
|
+
.distinct()
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
teams = teams_query.all()
|
|
111
|
+
print(f"Found {len(teams)} teams in {aggregation_name}")
|
|
112
|
+
|
|
113
|
+
# Process each team
|
|
114
|
+
progress = create_progress_tracker(len(teams), description="Processing teams")
|
|
115
|
+
for team_id, team_name in teams:
|
|
116
|
+
progress.update(1)
|
|
117
|
+
|
|
118
|
+
# Aggregate stats for goalies on this team
|
|
119
|
+
# Filter to only games where goalies were on THIS team
|
|
120
|
+
games_played_query = (
|
|
121
|
+
session.query(
|
|
122
|
+
GameRoster.human_id,
|
|
123
|
+
func.count(Game.id).label("games_played"),
|
|
124
|
+
func.count(Game.id).label("games_participated"),
|
|
125
|
+
func.count(Game.id).label("games_with_stats"),
|
|
126
|
+
func.array_agg(Game.id).label("game_ids"),
|
|
127
|
+
)
|
|
128
|
+
.join(Game, Game.id == GameRoster.game_id)
|
|
129
|
+
.filter(
|
|
130
|
+
GameRoster.team_id == team_id, # KEY: Filter by team
|
|
131
|
+
GameRoster.role.ilike("g"), # Only goalies
|
|
132
|
+
GameRoster.human_id.notin_(human_ids_to_filter),
|
|
133
|
+
Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS]),
|
|
134
|
+
filter_condition, # org_id or division_id filter
|
|
135
|
+
)
|
|
136
|
+
.group_by(GameRoster.human_id)
|
|
137
|
+
.having(func.count(Game.id) >= min_games)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
games_played_data = games_played_query.all()
|
|
141
|
+
if not games_played_data:
|
|
142
|
+
continue # No goalies met minimum games for this team
|
|
143
|
+
|
|
144
|
+
# Create stats dictionary
|
|
145
|
+
stats_dict = {}
|
|
146
|
+
for row in games_played_data:
|
|
147
|
+
stats_dict[row.human_id] = {
|
|
148
|
+
"games_played": row.games_played,
|
|
149
|
+
"games_participated": row.games_participated,
|
|
150
|
+
"games_with_stats": row.games_with_stats,
|
|
151
|
+
"game_ids": row.game_ids,
|
|
152
|
+
"first_game_id": row.game_ids[0] if row.game_ids else None,
|
|
153
|
+
"last_game_id": row.game_ids[-1] if row.game_ids else None,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Aggregate goals allowed and shots faced from GoalieSaves table
|
|
157
|
+
goalie_saves_query = (
|
|
158
|
+
session.query(
|
|
159
|
+
GameRoster.human_id,
|
|
160
|
+
func.sum(GoalieSaves.goals_allowed).label("goals_allowed"),
|
|
161
|
+
func.sum(GoalieSaves.shots_against).label("shots_faced"),
|
|
162
|
+
)
|
|
163
|
+
.join(Game, Game.id == GameRoster.game_id)
|
|
164
|
+
.join(
|
|
165
|
+
GoalieSaves,
|
|
166
|
+
(GoalieSaves.game_id == Game.id) & (GoalieSaves.goalie_id == GameRoster.human_id),
|
|
167
|
+
)
|
|
168
|
+
.filter(
|
|
169
|
+
GameRoster.team_id == team_id, # KEY: Filter by team
|
|
170
|
+
GameRoster.role.ilike("g"),
|
|
171
|
+
GameRoster.human_id.in_(stats_dict.keys()),
|
|
172
|
+
Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS]),
|
|
173
|
+
filter_condition,
|
|
174
|
+
)
|
|
175
|
+
.group_by(GameRoster.human_id)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
for row in goalie_saves_query.all():
|
|
179
|
+
if row.human_id in stats_dict:
|
|
180
|
+
stats_dict[row.human_id]["goals_allowed"] = row.goals_allowed or 0
|
|
181
|
+
stats_dict[row.human_id]["shots_faced"] = row.shots_faced or 0
|
|
182
|
+
|
|
183
|
+
# Calculate per-game averages and save percentage
|
|
184
|
+
for human_id, stats in stats_dict.items():
|
|
185
|
+
games_with_stats = stats.get("games_with_stats", 0)
|
|
186
|
+
goals_allowed = stats.get("goals_allowed", 0)
|
|
187
|
+
shots_faced = stats.get("shots_faced", 0)
|
|
188
|
+
|
|
189
|
+
if games_with_stats > 0:
|
|
190
|
+
stats["goals_allowed_per_game"] = goals_allowed / games_with_stats
|
|
191
|
+
else:
|
|
192
|
+
stats["goals_allowed_per_game"] = 0.0
|
|
193
|
+
|
|
194
|
+
if shots_faced > 0:
|
|
195
|
+
saves = shots_faced - goals_allowed
|
|
196
|
+
stats["save_percentage"] = saves / shots_faced
|
|
197
|
+
else:
|
|
198
|
+
stats["save_percentage"] = 0.0
|
|
199
|
+
|
|
200
|
+
# Insert stats for each goalie on this team
|
|
201
|
+
for human_id, stats in stats_dict.items():
|
|
202
|
+
goalie_stat = StatsModel(
|
|
203
|
+
aggregation_id=aggregation_id,
|
|
204
|
+
team_id=team_id,
|
|
205
|
+
human_id=human_id,
|
|
206
|
+
games_played=stats.get("games_played", 0),
|
|
207
|
+
games_participated=stats.get("games_participated", 0),
|
|
208
|
+
games_with_stats=stats.get("games_with_stats", 0),
|
|
209
|
+
goals_allowed=stats.get("goals_allowed", 0),
|
|
210
|
+
shots_faced=stats.get("shots_faced", 0),
|
|
211
|
+
goals_allowed_per_game=stats.get("goals_allowed_per_game", 0.0),
|
|
212
|
+
save_percentage=stats.get("save_percentage", 0.0),
|
|
213
|
+
total_in_rank=len(stats_dict),
|
|
214
|
+
first_game_id=stats.get("first_game_id"),
|
|
215
|
+
last_game_id=stats.get("last_game_id"),
|
|
216
|
+
# Initialize rank fields to 0 (not calculated for team stats)
|
|
217
|
+
games_played_rank=0,
|
|
218
|
+
games_participated_rank=0,
|
|
219
|
+
games_with_stats_rank=0,
|
|
220
|
+
goals_allowed_rank=0,
|
|
221
|
+
shots_faced_rank=0,
|
|
222
|
+
goals_allowed_per_game_rank=0,
|
|
223
|
+
save_percentage_rank=0,
|
|
224
|
+
)
|
|
225
|
+
session.add(goalie_stat)
|
|
226
|
+
|
|
227
|
+
session.commit()
|
|
228
|
+
progress.finish()
|
|
229
|
+
print(f"✓ Team goalie stats aggregation complete for {aggregation_name}")
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def run_aggregate_team_goalie_stats():
|
|
233
|
+
"""
|
|
234
|
+
Run team goalie stats aggregation for all organizations and divisions.
|
|
235
|
+
"""
|
|
236
|
+
from hockey_blast_common_lib.utils import get_all_division_ids_for_org
|
|
237
|
+
|
|
238
|
+
session = create_session("boss")
|
|
239
|
+
|
|
240
|
+
# Get all org_id present in the Organization table
|
|
241
|
+
org_ids = session.query(Organization.id).all()
|
|
242
|
+
org_ids = [org_id[0] for org_id in org_ids]
|
|
243
|
+
|
|
244
|
+
for org_id in org_ids:
|
|
245
|
+
# Aggregate for organization level
|
|
246
|
+
aggregate_team_goalie_stats(session, "org", org_id)
|
|
247
|
+
|
|
248
|
+
# Aggregate for all divisions in this organization
|
|
249
|
+
division_ids = get_all_division_ids_for_org(session, org_id)
|
|
250
|
+
for division_id in division_ids:
|
|
251
|
+
aggregate_team_goalie_stats(session, "division", division_id)
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aggregate skater statistics by team.
|
|
3
|
+
|
|
4
|
+
This module aggregates player statistics for each team, counting only games
|
|
5
|
+
where the player was on that specific team (using GameRoster.team_id).
|
|
6
|
+
|
|
7
|
+
Key difference from regular aggregation:
|
|
8
|
+
- Aggregates by (aggregation_id, team_id, human_id) instead of just (aggregation_id, human_id)
|
|
9
|
+
- Filters to only games where GameRoster.team_id matches the target team
|
|
10
|
+
- Stores results in OrgStatsSkaterTeam / DivisionStatsSkaterTeam
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
17
|
+
|
|
18
|
+
import sqlalchemy
|
|
19
|
+
from sqlalchemy import and_, case, func
|
|
20
|
+
|
|
21
|
+
from hockey_blast_common_lib.db_connection import create_session
|
|
22
|
+
from hockey_blast_common_lib.models import (
|
|
23
|
+
Division,
|
|
24
|
+
Game,
|
|
25
|
+
GameRoster,
|
|
26
|
+
Goal,
|
|
27
|
+
Human,
|
|
28
|
+
Organization,
|
|
29
|
+
Penalty,
|
|
30
|
+
Team,
|
|
31
|
+
)
|
|
32
|
+
from hockey_blast_common_lib.options import (
|
|
33
|
+
MIN_GAMES_FOR_DIVISION_STATS,
|
|
34
|
+
MIN_GAMES_FOR_ORG_STATS,
|
|
35
|
+
)
|
|
36
|
+
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
|
37
|
+
from hockey_blast_common_lib.stats_models import (
|
|
38
|
+
DivisionStatsSkaterTeam,
|
|
39
|
+
OrgStatsSkaterTeam,
|
|
40
|
+
)
|
|
41
|
+
from hockey_blast_common_lib.utils import (
|
|
42
|
+
calculate_percentile_value,
|
|
43
|
+
get_non_human_ids,
|
|
44
|
+
get_percentile_human,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Import status constants for game filtering
|
|
48
|
+
FINAL_STATUS = "Final"
|
|
49
|
+
FINAL_SO_STATUS = "Final(SO)"
|
|
50
|
+
FORFEIT_STATUS = "FORFEIT"
|
|
51
|
+
NOEVENTS_STATUS = "NOEVENTS"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def aggregate_team_skater_stats(session, aggregation_type, aggregation_id):
|
|
55
|
+
"""
|
|
56
|
+
Aggregate skater stats by team for an organization or division.
|
|
57
|
+
|
|
58
|
+
For each team in the aggregation scope, calculates stats for all players
|
|
59
|
+
who played for that team, counting only games where they were on that team.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
session: Database session
|
|
63
|
+
aggregation_type: "org" or "division"
|
|
64
|
+
aggregation_id: ID of the organization or division
|
|
65
|
+
"""
|
|
66
|
+
human_ids_to_filter = get_non_human_ids(session)
|
|
67
|
+
|
|
68
|
+
# Determine aggregation details
|
|
69
|
+
if aggregation_type == "org":
|
|
70
|
+
StatsModel = OrgStatsSkaterTeam
|
|
71
|
+
min_games = MIN_GAMES_FOR_ORG_STATS
|
|
72
|
+
aggregation_name = (
|
|
73
|
+
session.query(Organization)
|
|
74
|
+
.filter(Organization.id == aggregation_id)
|
|
75
|
+
.first()
|
|
76
|
+
.organization_name
|
|
77
|
+
)
|
|
78
|
+
filter_condition = Game.org_id == aggregation_id
|
|
79
|
+
elif aggregation_type == "division":
|
|
80
|
+
StatsModel = DivisionStatsSkaterTeam
|
|
81
|
+
min_games = MIN_GAMES_FOR_DIVISION_STATS
|
|
82
|
+
aggregation_name = (
|
|
83
|
+
session.query(Division).filter(Division.id == aggregation_id).first().level
|
|
84
|
+
)
|
|
85
|
+
filter_condition = Game.division_id == aggregation_id
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError(f"Invalid aggregation type: {aggregation_type}")
|
|
88
|
+
|
|
89
|
+
print(f"Aggregating team skater stats for {aggregation_name}...")
|
|
90
|
+
|
|
91
|
+
# Delete existing stats for this aggregation
|
|
92
|
+
session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
|
|
93
|
+
session.commit()
|
|
94
|
+
|
|
95
|
+
# Get all teams in this aggregation scope
|
|
96
|
+
if aggregation_type == "org":
|
|
97
|
+
teams_query = (
|
|
98
|
+
session.query(Team.id, Team.name)
|
|
99
|
+
.join(Game, (Game.home_team_id == Team.id) | (Game.visitor_team_id == Team.id))
|
|
100
|
+
.filter(Game.org_id == aggregation_id)
|
|
101
|
+
.distinct()
|
|
102
|
+
)
|
|
103
|
+
else: # division
|
|
104
|
+
teams_query = (
|
|
105
|
+
session.query(Team.id, Team.name)
|
|
106
|
+
.join(Game, (Game.home_team_id == Team.id) | (Game.visitor_team_id == Team.id))
|
|
107
|
+
.filter(Game.division_id == aggregation_id)
|
|
108
|
+
.distinct()
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
teams = teams_query.all()
|
|
112
|
+
print(f"Found {len(teams)} teams in {aggregation_name}")
|
|
113
|
+
|
|
114
|
+
# Process each team
|
|
115
|
+
progress = create_progress_tracker(len(teams), description="Processing teams")
|
|
116
|
+
for team_id, team_name in teams:
|
|
117
|
+
progress.update(1)
|
|
118
|
+
|
|
119
|
+
# Aggregate stats for this team
|
|
120
|
+
# Filter to only games where players were on THIS team
|
|
121
|
+
games_played_query = (
|
|
122
|
+
session.query(
|
|
123
|
+
GameRoster.human_id,
|
|
124
|
+
func.count(Game.id).label("games_played"),
|
|
125
|
+
func.count(Game.id).label("games_participated"),
|
|
126
|
+
func.count(Game.id).label("games_with_stats"),
|
|
127
|
+
func.array_agg(Game.id).label("game_ids"),
|
|
128
|
+
)
|
|
129
|
+
.join(Game, Game.id == GameRoster.game_id)
|
|
130
|
+
.filter(
|
|
131
|
+
GameRoster.team_id == team_id, # KEY: Filter by team
|
|
132
|
+
~GameRoster.role.ilike("g"), # Exclude goalies
|
|
133
|
+
GameRoster.human_id.notin_(human_ids_to_filter),
|
|
134
|
+
Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS]),
|
|
135
|
+
filter_condition, # org_id or division_id filter
|
|
136
|
+
)
|
|
137
|
+
.group_by(GameRoster.human_id)
|
|
138
|
+
.having(func.count(Game.id) >= min_games)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
games_played_data = games_played_query.all()
|
|
142
|
+
if not games_played_data:
|
|
143
|
+
continue # No players met minimum games for this team
|
|
144
|
+
|
|
145
|
+
# Create stats dictionary
|
|
146
|
+
stats_dict = {}
|
|
147
|
+
for row in games_played_data:
|
|
148
|
+
stats_dict[row.human_id] = {
|
|
149
|
+
"games_played": row.games_played,
|
|
150
|
+
"games_participated": row.games_participated,
|
|
151
|
+
"games_with_stats": row.games_with_stats,
|
|
152
|
+
"game_ids": row.game_ids,
|
|
153
|
+
"first_game_id": row.game_ids[0] if row.game_ids else None,
|
|
154
|
+
"last_game_id": row.game_ids[-1] if row.game_ids else None,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# Aggregate goals, assists, points
|
|
158
|
+
goals_assists_query = (
|
|
159
|
+
session.query(
|
|
160
|
+
GameRoster.human_id,
|
|
161
|
+
func.count(func.distinct(case((Goal.goal_scorer_id == GameRoster.human_id, Goal.id)))).label("goals"),
|
|
162
|
+
func.count(
|
|
163
|
+
func.distinct(
|
|
164
|
+
case(
|
|
165
|
+
(
|
|
166
|
+
(Goal.assist_1_id == GameRoster.human_id) | (Goal.assist_2_id == GameRoster.human_id),
|
|
167
|
+
Goal.id,
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
).label("assists"),
|
|
172
|
+
)
|
|
173
|
+
.join(Game, Game.id == GameRoster.game_id)
|
|
174
|
+
.outerjoin(Goal, Game.id == Goal.game_id)
|
|
175
|
+
.filter(
|
|
176
|
+
GameRoster.team_id == team_id, # KEY: Filter by team
|
|
177
|
+
~GameRoster.role.ilike("g"),
|
|
178
|
+
GameRoster.human_id.in_(stats_dict.keys()),
|
|
179
|
+
Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS]),
|
|
180
|
+
filter_condition,
|
|
181
|
+
)
|
|
182
|
+
.group_by(GameRoster.human_id)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
for row in goals_assists_query.all():
|
|
186
|
+
if row.human_id in stats_dict:
|
|
187
|
+
stats_dict[row.human_id]["goals"] = row.goals
|
|
188
|
+
stats_dict[row.human_id]["assists"] = row.assists
|
|
189
|
+
stats_dict[row.human_id]["points"] = row.goals + row.assists
|
|
190
|
+
|
|
191
|
+
# Aggregate penalties
|
|
192
|
+
penalties_query = (
|
|
193
|
+
session.query(
|
|
194
|
+
GameRoster.human_id,
|
|
195
|
+
func.count(Penalty.id).label("penalties"),
|
|
196
|
+
func.sum(case((Penalty.penalty_minutes == "GM", 1), else_=0)).label("gm_penalties"),
|
|
197
|
+
)
|
|
198
|
+
.join(Game, Game.id == GameRoster.game_id)
|
|
199
|
+
.outerjoin(Penalty, and_(Game.id == Penalty.game_id, Penalty.penalized_player_id == GameRoster.human_id))
|
|
200
|
+
.filter(
|
|
201
|
+
GameRoster.team_id == team_id, # KEY: Filter by team
|
|
202
|
+
~GameRoster.role.ilike("g"),
|
|
203
|
+
GameRoster.human_id.in_(stats_dict.keys()),
|
|
204
|
+
Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS]),
|
|
205
|
+
filter_condition,
|
|
206
|
+
)
|
|
207
|
+
.group_by(GameRoster.human_id)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
for row in penalties_query.all():
|
|
211
|
+
if row.human_id in stats_dict:
|
|
212
|
+
stats_dict[row.human_id]["penalties"] = row.penalties
|
|
213
|
+
stats_dict[row.human_id]["gm_penalties"] = row.gm_penalties
|
|
214
|
+
|
|
215
|
+
# Calculate per-game averages
|
|
216
|
+
for human_id, stats in stats_dict.items():
|
|
217
|
+
games_with_stats = stats.get("games_with_stats", 0)
|
|
218
|
+
if games_with_stats > 0:
|
|
219
|
+
stats["goals_per_game"] = stats.get("goals", 0) / games_with_stats
|
|
220
|
+
stats["assists_per_game"] = stats.get("assists", 0) / games_with_stats
|
|
221
|
+
stats["points_per_game"] = stats.get("points", 0) / games_with_stats
|
|
222
|
+
stats["penalties_per_game"] = stats.get("penalties", 0) / games_with_stats
|
|
223
|
+
stats["gm_penalties_per_game"] = stats.get("gm_penalties", 0) / games_with_stats
|
|
224
|
+
else:
|
|
225
|
+
stats["goals_per_game"] = 0.0
|
|
226
|
+
stats["assists_per_game"] = 0.0
|
|
227
|
+
stats["points_per_game"] = 0.0
|
|
228
|
+
stats["penalties_per_game"] = 0.0
|
|
229
|
+
stats["gm_penalties_per_game"] = 0.0
|
|
230
|
+
|
|
231
|
+
# Insert stats for each player on this team
|
|
232
|
+
for human_id, stats in stats_dict.items():
|
|
233
|
+
skater_stat = StatsModel(
|
|
234
|
+
aggregation_id=aggregation_id,
|
|
235
|
+
team_id=team_id,
|
|
236
|
+
human_id=human_id,
|
|
237
|
+
games_played=stats.get("games_played", 0),
|
|
238
|
+
games_participated=stats.get("games_participated", 0),
|
|
239
|
+
games_with_stats=stats.get("games_with_stats", 0),
|
|
240
|
+
goals=stats.get("goals", 0),
|
|
241
|
+
assists=stats.get("assists", 0),
|
|
242
|
+
points=stats.get("points", 0),
|
|
243
|
+
penalties=stats.get("penalties", 0),
|
|
244
|
+
gm_penalties=stats.get("gm_penalties", 0),
|
|
245
|
+
goals_per_game=stats.get("goals_per_game", 0.0),
|
|
246
|
+
assists_per_game=stats.get("assists_per_game", 0.0),
|
|
247
|
+
points_per_game=stats.get("points_per_game", 0.0),
|
|
248
|
+
penalties_per_game=stats.get("penalties_per_game", 0.0),
|
|
249
|
+
gm_penalties_per_game=stats.get("gm_penalties_per_game", 0.0),
|
|
250
|
+
total_in_rank=len(stats_dict),
|
|
251
|
+
first_game_id=stats.get("first_game_id"),
|
|
252
|
+
last_game_id=stats.get("last_game_id"),
|
|
253
|
+
# Initialize streak fields to 0 (not calculated for team stats)
|
|
254
|
+
current_point_streak=0,
|
|
255
|
+
current_point_streak_avg_points=0.0,
|
|
256
|
+
# Ranks will be assigned later if needed
|
|
257
|
+
games_played_rank=0,
|
|
258
|
+
games_participated_rank=0,
|
|
259
|
+
games_with_stats_rank=0,
|
|
260
|
+
goals_rank=0,
|
|
261
|
+
assists_rank=0,
|
|
262
|
+
points_rank=0,
|
|
263
|
+
penalties_rank=0,
|
|
264
|
+
gm_penalties_rank=0,
|
|
265
|
+
goals_per_game_rank=0,
|
|
266
|
+
assists_per_game_rank=0,
|
|
267
|
+
points_per_game_rank=0,
|
|
268
|
+
penalties_per_game_rank=0,
|
|
269
|
+
gm_penalties_per_game_rank=0,
|
|
270
|
+
current_point_streak_rank=0,
|
|
271
|
+
current_point_streak_avg_points_rank=0,
|
|
272
|
+
)
|
|
273
|
+
session.add(skater_stat)
|
|
274
|
+
|
|
275
|
+
session.commit()
|
|
276
|
+
progress.finish()
|
|
277
|
+
print(f"✓ Team skater stats aggregation complete for {aggregation_name}")
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def run_aggregate_team_skater_stats():
|
|
281
|
+
"""
|
|
282
|
+
Run team skater stats aggregation for all organizations and divisions.
|
|
283
|
+
"""
|
|
284
|
+
from hockey_blast_common_lib.utils import get_all_division_ids_for_org
|
|
285
|
+
|
|
286
|
+
session = create_session("boss")
|
|
287
|
+
|
|
288
|
+
# Get all org_id present in the Organization table
|
|
289
|
+
org_ids = session.query(Organization.id).all()
|
|
290
|
+
org_ids = [org_id[0] for org_id in org_ids]
|
|
291
|
+
|
|
292
|
+
for org_id in org_ids:
|
|
293
|
+
# Aggregate for organization level
|
|
294
|
+
aggregate_team_skater_stats(session, "org", org_id)
|
|
295
|
+
|
|
296
|
+
# Aggregate for all divisions in this organization
|
|
297
|
+
division_ids = get_all_division_ids_for_org(session, org_id)
|
|
298
|
+
for division_id in division_ids:
|
|
299
|
+
aggregate_team_skater_stats(session, "division", division_id)
|
|
Binary file
|
|
@@ -922,3 +922,126 @@ class SkillValuePPGRatio(db.Model):
|
|
|
922
922
|
"from_skill_value", "to_skill_value", name="_from_to_skill_value_uc"
|
|
923
923
|
),
|
|
924
924
|
)
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
# Team-based statistics models (inherit from existing bases, add team_id field)
|
|
928
|
+
class OrgStatsSkaterTeam(BaseStatsSkater):
|
|
929
|
+
__tablename__ = "org_stats_skater_team"
|
|
930
|
+
org_id = db.Column(db.Integer, db.ForeignKey("organizations.id"), nullable=False)
|
|
931
|
+
team_id = db.Column(db.Integer, db.ForeignKey("teams.id"), nullable=False)
|
|
932
|
+
aggregation_id = synonym("org_id")
|
|
933
|
+
|
|
934
|
+
@declared_attr
|
|
935
|
+
def aggregation_type(cls):
|
|
936
|
+
return "org_team"
|
|
937
|
+
|
|
938
|
+
@classmethod
|
|
939
|
+
def get_aggregation_column(cls):
|
|
940
|
+
return "org_id"
|
|
941
|
+
|
|
942
|
+
@declared_attr
|
|
943
|
+
def __table_args__(cls):
|
|
944
|
+
return (
|
|
945
|
+
db.UniqueConstraint(
|
|
946
|
+
"org_id",
|
|
947
|
+
"team_id",
|
|
948
|
+
"human_id",
|
|
949
|
+
name="_org_team_human_uc_skater_team1",
|
|
950
|
+
),
|
|
951
|
+
db.Index("idx_org_team_team_id", "team_id"),
|
|
952
|
+
db.Index("idx_org_team_human_id", "human_id"),
|
|
953
|
+
db.Index("idx_org_team_goals_per_game", "org_id", "goals_per_game"),
|
|
954
|
+
db.Index("idx_org_team_points_per_game", "org_id", "points_per_game"),
|
|
955
|
+
db.Index("idx_org_team_assists_per_game", "org_id", "assists_per_game"),
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
class DivisionStatsSkaterTeam(BaseStatsSkater):
|
|
960
|
+
__tablename__ = "division_stats_skater_team"
|
|
961
|
+
division_id = db.Column(db.Integer, db.ForeignKey("divisions.id"), nullable=False)
|
|
962
|
+
team_id = db.Column(db.Integer, db.ForeignKey("teams.id"), nullable=False)
|
|
963
|
+
aggregation_id = synonym("division_id")
|
|
964
|
+
|
|
965
|
+
@declared_attr
|
|
966
|
+
def aggregation_type(cls):
|
|
967
|
+
return "division_team"
|
|
968
|
+
|
|
969
|
+
@classmethod
|
|
970
|
+
def get_aggregation_column(cls):
|
|
971
|
+
return "division_id"
|
|
972
|
+
|
|
973
|
+
@declared_attr
|
|
974
|
+
def __table_args__(cls):
|
|
975
|
+
return (
|
|
976
|
+
db.UniqueConstraint(
|
|
977
|
+
"division_id",
|
|
978
|
+
"team_id",
|
|
979
|
+
"human_id",
|
|
980
|
+
name="_division_team_human_uc_skater_team1",
|
|
981
|
+
),
|
|
982
|
+
db.Index("idx_division_team_team_id", "team_id"),
|
|
983
|
+
db.Index("idx_division_team_human_id", "human_id"),
|
|
984
|
+
db.Index("idx_division_team_goals_per_game", "division_id", "goals_per_game"),
|
|
985
|
+
db.Index("idx_division_team_points_per_game", "division_id", "points_per_game"),
|
|
986
|
+
db.Index("idx_division_team_assists_per_game", "division_id", "assists_per_game"),
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
class OrgStatsGoalieTeam(BaseStatsGoalie):
|
|
991
|
+
__tablename__ = "org_stats_goalie_team"
|
|
992
|
+
org_id = db.Column(db.Integer, db.ForeignKey("organizations.id"), nullable=False)
|
|
993
|
+
team_id = db.Column(db.Integer, db.ForeignKey("teams.id"), nullable=False)
|
|
994
|
+
aggregation_id = synonym("org_id")
|
|
995
|
+
|
|
996
|
+
@declared_attr
|
|
997
|
+
def aggregation_type(cls):
|
|
998
|
+
return "org_team"
|
|
999
|
+
|
|
1000
|
+
@classmethod
|
|
1001
|
+
def get_aggregation_column(cls):
|
|
1002
|
+
return "org_id"
|
|
1003
|
+
|
|
1004
|
+
@declared_attr
|
|
1005
|
+
def __table_args__(cls):
|
|
1006
|
+
return (
|
|
1007
|
+
db.UniqueConstraint(
|
|
1008
|
+
"org_id",
|
|
1009
|
+
"team_id",
|
|
1010
|
+
"human_id",
|
|
1011
|
+
name="_org_team_human_uc_goalie_team1",
|
|
1012
|
+
),
|
|
1013
|
+
db.Index("idx_org_team_goalie_team_id", "team_id"),
|
|
1014
|
+
db.Index("idx_org_team_goalie_human_id", "human_id"),
|
|
1015
|
+
db.Index("idx_org_team_goalie_save_pct", "org_id", "save_percentage"),
|
|
1016
|
+
db.Index("idx_org_team_goalie_gaa", "org_id", "goals_allowed_per_game"),
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
class DivisionStatsGoalieTeam(BaseStatsGoalie):
|
|
1021
|
+
__tablename__ = "division_stats_goalie_team"
|
|
1022
|
+
division_id = db.Column(db.Integer, db.ForeignKey("divisions.id"), nullable=False)
|
|
1023
|
+
team_id = db.Column(db.Integer, db.ForeignKey("teams.id"), nullable=False)
|
|
1024
|
+
aggregation_id = synonym("division_id")
|
|
1025
|
+
|
|
1026
|
+
@declared_attr
|
|
1027
|
+
def aggregation_type(cls):
|
|
1028
|
+
return "division_team"
|
|
1029
|
+
|
|
1030
|
+
@classmethod
|
|
1031
|
+
def get_aggregation_column(cls):
|
|
1032
|
+
return "division_id"
|
|
1033
|
+
|
|
1034
|
+
@declared_attr
|
|
1035
|
+
def __table_args__(cls):
|
|
1036
|
+
return (
|
|
1037
|
+
db.UniqueConstraint(
|
|
1038
|
+
"division_id",
|
|
1039
|
+
"team_id",
|
|
1040
|
+
"human_id",
|
|
1041
|
+
name="_division_team_human_uc_goalie_team1",
|
|
1042
|
+
),
|
|
1043
|
+
db.Index("idx_division_team_goalie_team_id", "team_id"),
|
|
1044
|
+
db.Index("idx_division_team_goalie_human_id", "human_id"),
|
|
1045
|
+
db.Index("idx_division_team_goalie_save_pct", "division_id", "save_percentage"),
|
|
1046
|
+
db.Index("idx_division_team_goalie_gaa", "division_id", "goals_allowed_per_game"),
|
|
1047
|
+
)
|
hockey_blast_common_lib/utils.py
CHANGED
|
@@ -85,6 +85,7 @@ def get_non_human_ids(session):
|
|
|
85
85
|
|
|
86
86
|
Returns set of human_ids that should be filtered out from statistics.
|
|
87
87
|
Filters out placeholder names like "Home", "Away", "Unknown", etc.
|
|
88
|
+
Also excludes percentile marker humans.
|
|
88
89
|
"""
|
|
89
90
|
not_human_names = [
|
|
90
91
|
("Home", None, None),
|
|
@@ -97,7 +98,9 @@ def get_non_human_ids(session):
|
|
|
97
98
|
("Incognito", None, None),
|
|
98
99
|
("Empty", None , "Net"),
|
|
99
100
|
("Fake", "Stats", "Human"),
|
|
101
|
+
(None, None, "Percentile"),
|
|
100
102
|
]
|
|
103
|
+
|
|
101
104
|
return get_human_ids_by_names(session, not_human_names)
|
|
102
105
|
|
|
103
106
|
|
|
@@ -208,6 +211,67 @@ def get_fake_level(session):
|
|
|
208
211
|
return fake_skill
|
|
209
212
|
|
|
210
213
|
|
|
214
|
+
def get_percentile_human(session, entity_type, percentile):
|
|
215
|
+
"""Get or create a human record representing a percentile marker.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
session: Database session
|
|
219
|
+
entity_type: One of "Skater", "Goalie", "Ref", "Scorekeeper"
|
|
220
|
+
percentile: One of 25, 50, 75, 90, 95
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
human_id of the percentile marker record
|
|
224
|
+
"""
|
|
225
|
+
first_name = entity_type
|
|
226
|
+
middle_name = str(percentile)
|
|
227
|
+
last_name = "Percentile"
|
|
228
|
+
|
|
229
|
+
# Check if the human already exists
|
|
230
|
+
existing_human = (
|
|
231
|
+
session.query(Human)
|
|
232
|
+
.filter_by(first_name=first_name, middle_name=middle_name, last_name=last_name)
|
|
233
|
+
.first()
|
|
234
|
+
)
|
|
235
|
+
if existing_human:
|
|
236
|
+
return existing_human.id
|
|
237
|
+
|
|
238
|
+
# Create a new human
|
|
239
|
+
human = Human(first_name=first_name, middle_name=middle_name, last_name=last_name)
|
|
240
|
+
session.add(human)
|
|
241
|
+
session.commit()
|
|
242
|
+
|
|
243
|
+
return human.id
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def calculate_percentile_value(values, percentile):
|
|
247
|
+
"""Calculate the percentile value from a list of values.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
values: List of numeric values
|
|
251
|
+
percentile: Percentile to calculate (e.g., 25, 50, 75, 90, 95)
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
The value at the given percentile
|
|
255
|
+
"""
|
|
256
|
+
if not values:
|
|
257
|
+
return 0
|
|
258
|
+
|
|
259
|
+
sorted_values = sorted(values)
|
|
260
|
+
n = len(sorted_values)
|
|
261
|
+
|
|
262
|
+
# Calculate index (using linear interpolation method)
|
|
263
|
+
index = (percentile / 100.0) * (n - 1)
|
|
264
|
+
lower_index = int(index)
|
|
265
|
+
upper_index = min(lower_index + 1, n - 1)
|
|
266
|
+
|
|
267
|
+
# Interpolate if needed
|
|
268
|
+
fraction = index - lower_index
|
|
269
|
+
lower_value = sorted_values[lower_index]
|
|
270
|
+
upper_value = sorted_values[upper_index]
|
|
271
|
+
|
|
272
|
+
return lower_value + fraction * (upper_value - lower_value)
|
|
273
|
+
|
|
274
|
+
|
|
211
275
|
# TEST DB CONNECTION, PERMISSIONS...
|
|
212
276
|
# from hockey_blast_common_lib.db_connection import create_session
|
|
213
277
|
# session = create_session("frontend")
|
{hockey_blast_common_lib-0.1.65.dist-info → hockey_blast_common_lib-0.1.66.dist-info}/RECORD
RENAMED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
hockey_blast_common_lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
hockey_blast_common_lib/aggregate_all_stats.py,sha256=
|
|
3
|
-
hockey_blast_common_lib/aggregate_goalie_stats.py,sha256=
|
|
2
|
+
hockey_blast_common_lib/aggregate_all_stats.py,sha256=lWDhdYMYFEdNFTM3FmAKWiHFYSkb0OLjTkagguHlwls,1914
|
|
3
|
+
hockey_blast_common_lib/aggregate_goalie_stats.py,sha256=Z_xRHR-C6_2KO67LmIW8uKVD4tpEXXvFhfo0DHouuHo,19871
|
|
4
4
|
hockey_blast_common_lib/aggregate_h2h_stats.py,sha256=nStyIm_be25pKDYbPCaOSHFTjbaMLFxFAa2mTU1tL_k,11486
|
|
5
5
|
hockey_blast_common_lib/aggregate_human_stats.py,sha256=uoGBkROBKh8n18TyzZ6vHX_viCTpHbRsiVLyflJq92g,29247
|
|
6
|
-
hockey_blast_common_lib/aggregate_referee_stats.py,sha256=
|
|
6
|
+
hockey_blast_common_lib/aggregate_referee_stats.py,sha256=VZVqiTfcHKtpyqUjvFBlypz2P8bIVg9wp0OK-MOu7O8,19580
|
|
7
7
|
hockey_blast_common_lib/aggregate_s2s_stats.py,sha256=gB3Oi1emtBWL3bKojUhHH01gAbQTSLvgqO1WcvLI6F8,7449
|
|
8
|
-
hockey_blast_common_lib/aggregate_scorekeeper_stats.py,sha256=
|
|
9
|
-
hockey_blast_common_lib/aggregate_skater_stats.py,sha256=
|
|
8
|
+
hockey_blast_common_lib/aggregate_scorekeeper_stats.py,sha256=NVCL5QzeIodTKs_OvDlcKDtGKnyxA_ZMlKTGfeO4H6Y,17829
|
|
9
|
+
hockey_blast_common_lib/aggregate_skater_stats.py,sha256=pU9ULO90165QqWWMz5leSHD9iJb5rJegBzGjZpiKYGw,34870
|
|
10
|
+
hockey_blast_common_lib/aggregate_team_goalie_stats.py,sha256=Yy06zDrgLDsI2QqjFzPck3mYWNhyTxSVpZHSdpDSkRE,9569
|
|
11
|
+
hockey_blast_common_lib/aggregate_team_skater_stats.py,sha256=cGP8eLTDD0lEkkTmUlBvSwqYfAGIfEOQREy_xtHxHKI,11962
|
|
10
12
|
hockey_blast_common_lib/assign_skater_skill.py,sha256=it3jiSyUq7XpKqxzs88lyB5t1c3t1idIS_JRwq_FQoo,2810
|
|
11
13
|
hockey_blast_common_lib/db_connection.py,sha256=KACyHaOMeTX9zPNztYy8uOeB1ubIUenZcEKAeD5gC24,3333
|
|
12
14
|
hockey_blast_common_lib/dump_sample_db.sh,sha256=MY3lnzTXBoWd76-ZlZr9nWsKMEVgyRsUn-LZ2d1JWZs,810
|
|
13
15
|
hockey_blast_common_lib/embedding_utils.py,sha256=XbJvJlq6BKE6_oLzhUKcCrx6-TM8P-xl-S1SVLr_teU,10222
|
|
14
16
|
hockey_blast_common_lib/h2h_models.py,sha256=DEmQnmuacBVRNWvpRvq2RlwmhQYrT7XPOSTDNVtchr0,8597
|
|
15
|
-
hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=
|
|
17
|
+
hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=sh_-vvwEIiKDCtmIIzyiyaNs67gUHCTu6CGCCdBcT6Q,4648833
|
|
16
18
|
hockey_blast_common_lib/models.py,sha256=RQGUq8C8eJqUB2o3QCSs14W-9B4lMTUNvwNDM-Lc6j4,21687
|
|
17
19
|
hockey_blast_common_lib/options.py,sha256=wzfGWKK_dHBA_PfiOvbP_-HtdoJCR0E7DkA5_cYDb_k,1578
|
|
18
20
|
hockey_blast_common_lib/progress_utils.py,sha256=7Txjpx5G4vHbnPTvNYuBA_WtrY0QFA4mDEYUDuZyY1E,3923
|
|
19
21
|
hockey_blast_common_lib/restore_sample_db.sh,sha256=7W3lzRZeu9zXIu1Bvtnaw8EHc1ulHmFM4mMh86oUQJo,2205
|
|
20
22
|
hockey_blast_common_lib/skills_in_divisions.py,sha256=9sGtU6SLj8BXb5R74ue1oPWa2nbk4JfJz5VmcuxetzA,8542
|
|
21
23
|
hockey_blast_common_lib/skills_propagation.py,sha256=qBK84nzkn8ZQHum0bdxFQwLvdgVE7DtWoPP9cdbOmRo,20201
|
|
22
|
-
hockey_blast_common_lib/stats_models.py,sha256=
|
|
24
|
+
hockey_blast_common_lib/stats_models.py,sha256=EU7Uw9ANPNNEx4vORgJkMFdgyTXQGmGLOF7YiY8jeBY,35488
|
|
23
25
|
hockey_blast_common_lib/stats_utils.py,sha256=PTZvykl1zfEcojnzDFa1J3V3F5gREmoFG1lQHLnYHgo,300
|
|
24
|
-
hockey_blast_common_lib/utils.py,sha256=
|
|
26
|
+
hockey_blast_common_lib/utils.py,sha256=xHgA3Xh40i4CBVArvfW2j123XGdgrMTFqTudPQHwkho,8997
|
|
25
27
|
hockey_blast_common_lib/wsgi.py,sha256=oL9lPWccKLTAYIKPJkKZV5keVE-Dgosv74CBi770NNc,786
|
|
26
|
-
hockey_blast_common_lib-0.1.
|
|
27
|
-
hockey_blast_common_lib-0.1.
|
|
28
|
-
hockey_blast_common_lib-0.1.
|
|
29
|
-
hockey_blast_common_lib-0.1.
|
|
28
|
+
hockey_blast_common_lib-0.1.66.dist-info/METADATA,sha256=ANT3HdUqzzFk-q_2ujzu9oPPmohLJFXXZdYAqj-P0Yg,318
|
|
29
|
+
hockey_blast_common_lib-0.1.66.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
30
|
+
hockey_blast_common_lib-0.1.66.dist-info/top_level.txt,sha256=wIR4LIkE40npoA2QlOdfCYlgFeGbsHR8Z6r0h46Vtgc,24
|
|
31
|
+
hockey_blast_common_lib-0.1.66.dist-info/RECORD,,
|
|
File without changes
|
{hockey_blast_common_lib-0.1.65.dist-info → hockey_blast_common_lib-0.1.66.dist-info}/top_level.txt
RENAMED
|
File without changes
|