hockey-blast-common-lib 0.1.56__py3-none-any.whl → 0.1.58__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 +5 -0
- hockey_blast_common_lib/aggregate_goalie_stats.py +17 -19
- hockey_blast_common_lib/aggregate_scorekeeper_stats.py +222 -0
- hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz +0 -0
- hockey_blast_common_lib/stats_models.py +25 -37
- {hockey_blast_common_lib-0.1.56.dist-info → hockey_blast_common_lib-0.1.58.dist-info}/METADATA +1 -1
- {hockey_blast_common_lib-0.1.56.dist-info → hockey_blast_common_lib-0.1.58.dist-info}/RECORD +9 -8
- {hockey_blast_common_lib-0.1.56.dist-info → hockey_blast_common_lib-0.1.58.dist-info}/WHEEL +0 -0
- {hockey_blast_common_lib-0.1.56.dist-info → hockey_blast_common_lib-0.1.58.dist-info}/top_level.txt +0 -0
@@ -7,6 +7,7 @@ from hockey_blast_common_lib.aggregate_human_stats import run_aggregate_human_st
|
|
7
7
|
from hockey_blast_common_lib.aggregate_skater_stats import run_aggregate_skater_stats
|
8
8
|
from hockey_blast_common_lib.aggregate_goalie_stats import run_aggregate_goalie_stats
|
9
9
|
from hockey_blast_common_lib.aggregate_referee_stats import run_aggregate_referee_stats
|
10
|
+
from hockey_blast_common_lib.aggregate_scorekeeper_stats import run_aggregate_scorekeeper_stats
|
10
11
|
|
11
12
|
if __name__ == "__main__":
|
12
13
|
print("Running aggregate_skater_stats...", flush=True)
|
@@ -21,6 +22,10 @@ if __name__ == "__main__":
|
|
21
22
|
run_aggregate_referee_stats()
|
22
23
|
print("Finished running aggregate_referee_stats\n")
|
23
24
|
|
25
|
+
print("Running aggregate_scorekeeper_stats...", flush=True)
|
26
|
+
run_aggregate_scorekeeper_stats()
|
27
|
+
print("Finished running aggregate_scorekeeper_stats\n")
|
28
|
+
|
24
29
|
print("Running aggregate_human_stats...", flush=True)
|
25
30
|
run_aggregate_human_stats()
|
26
31
|
print("Finished running aggregate_human_stats\n")
|
@@ -6,15 +6,13 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
6
6
|
from datetime import datetime, timedelta
|
7
7
|
import sqlalchemy
|
8
8
|
|
9
|
-
from hockey_blast_common_lib.models import Game,
|
9
|
+
from hockey_blast_common_lib.models import Game, Organization, Division, Human, GoalieSaves
|
10
10
|
from hockey_blast_common_lib.stats_models import OrgStatsGoalie, DivisionStatsGoalie, OrgStatsWeeklyGoalie, OrgStatsDailyGoalie, DivisionStatsWeeklyGoalie, DivisionStatsDailyGoalie, LevelStatsGoalie
|
11
11
|
from hockey_blast_common_lib.db_connection import create_session
|
12
|
-
from sqlalchemy.sql import func
|
13
|
-
from hockey_blast_common_lib.options import not_human_names,
|
14
|
-
from hockey_blast_common_lib.utils import
|
12
|
+
from sqlalchemy.sql import func
|
13
|
+
from hockey_blast_common_lib.options import not_human_names, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
|
14
|
+
from hockey_blast_common_lib.utils import get_human_ids_by_names, get_all_division_ids_for_org, get_start_datetime
|
15
15
|
from hockey_blast_common_lib.utils import assign_ranks
|
16
|
-
from sqlalchemy import func, case, and_
|
17
|
-
from collections import defaultdict
|
18
16
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
19
17
|
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
20
18
|
|
@@ -73,19 +71,20 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
|
|
73
71
|
return
|
74
72
|
|
75
73
|
|
74
|
+
# Aggregate games played, goals allowed, and shots faced for each goalie using GoalieSaves table
|
75
|
+
query = session.query(
|
76
|
+
GoalieSaves.goalie_id.label('human_id'),
|
77
|
+
func.count(GoalieSaves.game_id).label('games_played'),
|
78
|
+
func.sum(GoalieSaves.goals_allowed).label('goals_allowed'),
|
79
|
+
func.sum(GoalieSaves.shots_against).label('shots_faced'),
|
80
|
+
func.array_agg(GoalieSaves.game_id).label('game_ids')
|
81
|
+
).join(Game, GoalieSaves.game_id == Game.id).join(Division, Game.division_id == Division.id).filter(filter_condition)
|
82
|
+
|
76
83
|
# Filter for specific human_id if provided
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# Aggregate games played, goals allowed, and shots faced for each goalie
|
82
|
-
goalie_stats = session.query(
|
83
|
-
GameRoster.human_id,
|
84
|
-
func.count(Game.id).label('games_played'),
|
85
|
-
func.sum(case((GameRoster.team_id == Game.home_team_id, Game.visitor_final_score), else_=Game.home_final_score)).label('goals_allowed'),
|
86
|
-
func.sum(case((GameRoster.team_id == Game.home_team_id, Game.visitor_period_1_shots + Game.visitor_period_2_shots + Game.visitor_period_3_shots + Game.visitor_ot_shots + Game.visitor_so_shots), else_=Game.home_period_1_shots + Game.home_period_2_shots + Game.home_period_3_shots + Game.home_ot_shots + Game.home_so_shots)).label('shots_faced'),
|
87
|
-
func.array_agg(Game.id).label('game_ids')
|
88
|
-
).join(Game, GameRoster.game_id == Game.id).join(Division, Game.division_id == Division.id).filter(filter_condition, GameRoster.role.ilike('g')).group_by(GameRoster.human_id).all()
|
84
|
+
if debug_human_id:
|
85
|
+
query = query.filter(GoalieSaves.goalie_id == debug_human_id)
|
86
|
+
|
87
|
+
goalie_stats = query.group_by(GoalieSaves.goalie_id).all()
|
89
88
|
|
90
89
|
# Combine the results
|
91
90
|
stats_dict = {}
|
@@ -152,7 +151,6 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
|
|
152
151
|
print(f"{k}: {v}")
|
153
152
|
|
154
153
|
# Insert aggregated stats into the appropriate table with progress output
|
155
|
-
total_items = len(stats_dict)
|
156
154
|
batch_size = 1000
|
157
155
|
for i, (key, stat) in enumerate(stats_dict.items(), 1):
|
158
156
|
aggregation_id, human_id = key
|
@@ -0,0 +1,222 @@
|
|
1
|
+
import sys, os
|
2
|
+
|
3
|
+
# Add the package directory to the Python path
|
4
|
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
5
|
+
|
6
|
+
from datetime import datetime, timedelta
|
7
|
+
import sqlalchemy
|
8
|
+
|
9
|
+
from hockey_blast_common_lib.models import Game, ScorekeeperSaveQuality
|
10
|
+
from hockey_blast_common_lib.stats_models import OrgStatsScorekeeper, OrgStatsWeeklyScorekeeper, OrgStatsDailyScorekeeper
|
11
|
+
from hockey_blast_common_lib.db_connection import create_session
|
12
|
+
from sqlalchemy.sql import func, case
|
13
|
+
from hockey_blast_common_lib.options import parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, not_human_names
|
14
|
+
from hockey_blast_common_lib.utils import get_org_id_from_alias, get_human_ids_by_names
|
15
|
+
from hockey_blast_common_lib.utils import assign_ranks
|
16
|
+
from hockey_blast_common_lib.utils import get_start_datetime
|
17
|
+
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
18
|
+
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
19
|
+
|
20
|
+
def calculate_quality_score(avg_max_saves_5sec, avg_max_saves_20sec, peak_max_saves_5sec, peak_max_saves_20sec):
|
21
|
+
"""
|
22
|
+
Calculate a quality score based on excessive clicking patterns.
|
23
|
+
Lower scores are better (less problematic clicking).
|
24
|
+
|
25
|
+
Logic:
|
26
|
+
- Penalize high average clicking rates in windows
|
27
|
+
- Heavily penalize peak clicking incidents
|
28
|
+
- Score ranges from 0 (perfect) to higher values (problematic)
|
29
|
+
"""
|
30
|
+
# Convert to float to handle Decimal types from database
|
31
|
+
avg_max_saves_5sec = float(avg_max_saves_5sec or 0.0)
|
32
|
+
avg_max_saves_20sec = float(avg_max_saves_20sec or 0.0)
|
33
|
+
peak_max_saves_5sec = float(peak_max_saves_5sec or 0)
|
34
|
+
peak_max_saves_20sec = float(peak_max_saves_20sec or 0)
|
35
|
+
|
36
|
+
if avg_max_saves_5sec == 0 and avg_max_saves_20sec == 0:
|
37
|
+
return 0.0
|
38
|
+
|
39
|
+
# Weight factors (can be tuned based on analysis)
|
40
|
+
avg_5sec_weight = 2.0 # Average clicking in 5sec windows
|
41
|
+
avg_20sec_weight = 1.0 # Average clicking in 20sec windows
|
42
|
+
peak_5sec_weight = 5.0 # Peak 5sec incidents are heavily penalized
|
43
|
+
peak_20sec_weight = 3.0 # Peak 20sec incidents are moderately penalized
|
44
|
+
|
45
|
+
score = (
|
46
|
+
(avg_max_saves_5sec * avg_5sec_weight) +
|
47
|
+
(avg_max_saves_20sec * avg_20sec_weight) +
|
48
|
+
(peak_max_saves_5sec * peak_5sec_weight) +
|
49
|
+
(peak_max_saves_20sec * peak_20sec_weight)
|
50
|
+
)
|
51
|
+
|
52
|
+
return round(score, 2)
|
53
|
+
|
54
|
+
def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, names_to_filter_out, aggregation_window=None):
|
55
|
+
# Only process scorekeeper stats for ALL_ORGS_ID - skip individual organizations
|
56
|
+
# This prevents redundant processing when upstream logic calls with all organization IDs
|
57
|
+
if aggregation_type == 'org' and aggregation_id != ALL_ORGS_ID:
|
58
|
+
return # Do nothing for individual organization IDs
|
59
|
+
|
60
|
+
human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
|
61
|
+
|
62
|
+
if aggregation_type == 'org':
|
63
|
+
aggregation_name = "All Orgs"
|
64
|
+
filter_condition = sqlalchemy.true() # No filter for organization
|
65
|
+
print(f"Aggregating scorekeeper stats for {aggregation_name} with window {aggregation_window}...")
|
66
|
+
if aggregation_window == 'Daily':
|
67
|
+
StatsModel = OrgStatsDailyScorekeeper
|
68
|
+
elif aggregation_window == 'Weekly':
|
69
|
+
StatsModel = OrgStatsWeeklyScorekeeper
|
70
|
+
else:
|
71
|
+
StatsModel = OrgStatsScorekeeper
|
72
|
+
min_games = MIN_GAMES_FOR_ORG_STATS
|
73
|
+
else:
|
74
|
+
raise ValueError("Invalid aggregation type")
|
75
|
+
|
76
|
+
# Delete existing items from the stats table
|
77
|
+
session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
|
78
|
+
session.commit()
|
79
|
+
|
80
|
+
# Apply aggregation window filter
|
81
|
+
if aggregation_window:
|
82
|
+
last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
|
83
|
+
start_datetime = get_start_datetime(last_game_datetime_str, aggregation_window)
|
84
|
+
if start_datetime:
|
85
|
+
game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime_str)
|
86
|
+
filter_condition = filter_condition & game_window_filter
|
87
|
+
else:
|
88
|
+
return
|
89
|
+
|
90
|
+
|
91
|
+
# Aggregate scorekeeper quality data for each human
|
92
|
+
scorekeeper_quality_stats = session.query(
|
93
|
+
ScorekeeperSaveQuality.scorekeeper_id.label('human_id'),
|
94
|
+
func.count(ScorekeeperSaveQuality.game_id).label('games_recorded'),
|
95
|
+
func.sum(ScorekeeperSaveQuality.total_saves_recorded).label('total_saves_recorded'),
|
96
|
+
func.avg(ScorekeeperSaveQuality.total_saves_recorded).label('avg_saves_per_game'),
|
97
|
+
func.avg(ScorekeeperSaveQuality.max_saves_per_5sec).label('avg_max_saves_per_5sec'),
|
98
|
+
func.avg(ScorekeeperSaveQuality.max_saves_per_20sec).label('avg_max_saves_per_20sec'),
|
99
|
+
func.max(ScorekeeperSaveQuality.max_saves_per_5sec).label('peak_max_saves_per_5sec'),
|
100
|
+
func.max(ScorekeeperSaveQuality.max_saves_per_20sec).label('peak_max_saves_per_20sec'),
|
101
|
+
func.array_agg(ScorekeeperSaveQuality.game_id).label('game_ids')
|
102
|
+
).join(Game, Game.id == ScorekeeperSaveQuality.game_id)
|
103
|
+
|
104
|
+
|
105
|
+
scorekeeper_quality_stats = scorekeeper_quality_stats.filter(filter_condition).group_by(ScorekeeperSaveQuality.scorekeeper_id).all()
|
106
|
+
|
107
|
+
# Combine the results
|
108
|
+
stats_dict = {}
|
109
|
+
for stat in scorekeeper_quality_stats:
|
110
|
+
if stat.human_id in human_ids_to_filter or stat.human_id is None:
|
111
|
+
continue
|
112
|
+
key = (aggregation_id, stat.human_id)
|
113
|
+
|
114
|
+
# Calculate quality score
|
115
|
+
quality_score = calculate_quality_score(
|
116
|
+
stat.avg_max_saves_per_5sec or 0.0,
|
117
|
+
stat.avg_max_saves_per_20sec or 0.0,
|
118
|
+
stat.peak_max_saves_per_5sec or 0,
|
119
|
+
stat.peak_max_saves_per_20sec or 0
|
120
|
+
)
|
121
|
+
|
122
|
+
stats_dict[key] = {
|
123
|
+
'games_recorded': stat.games_recorded,
|
124
|
+
'sog_given': stat.total_saves_recorded, # Legacy field name mapping
|
125
|
+
'sog_per_game': stat.avg_saves_per_game or 0.0, # Legacy field name mapping
|
126
|
+
'total_saves_recorded': stat.total_saves_recorded,
|
127
|
+
'avg_saves_per_game': stat.avg_saves_per_game or 0.0,
|
128
|
+
'avg_max_saves_per_5sec': stat.avg_max_saves_per_5sec or 0.0,
|
129
|
+
'avg_max_saves_per_20sec': stat.avg_max_saves_per_20sec or 0.0,
|
130
|
+
'peak_max_saves_per_5sec': stat.peak_max_saves_per_5sec or 0,
|
131
|
+
'peak_max_saves_per_20sec': stat.peak_max_saves_per_20sec or 0,
|
132
|
+
'quality_score': quality_score,
|
133
|
+
'game_ids': stat.game_ids,
|
134
|
+
'first_game_id': None,
|
135
|
+
'last_game_id': None
|
136
|
+
}
|
137
|
+
|
138
|
+
# Filter out entries with games_recorded less than min_games
|
139
|
+
stats_dict = {key: value for key, value in stats_dict.items() if value['games_recorded'] >= min_games}
|
140
|
+
|
141
|
+
# Populate first_game_id and last_game_id
|
142
|
+
for key, stat in stats_dict.items():
|
143
|
+
all_game_ids = stat['game_ids']
|
144
|
+
if all_game_ids:
|
145
|
+
first_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date, Game.time).first()
|
146
|
+
last_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date.desc(), Game.time.desc()).first()
|
147
|
+
stat['first_game_id'] = first_game.id if first_game else None
|
148
|
+
stat['last_game_id'] = last_game.id if last_game else None
|
149
|
+
|
150
|
+
# Calculate total_in_rank
|
151
|
+
total_in_rank = len(stats_dict)
|
152
|
+
|
153
|
+
# Assign ranks - note: for quality metrics, lower values are better (reverse_rank=True for avg and peak clicking)
|
154
|
+
assign_ranks(stats_dict, 'games_recorded')
|
155
|
+
assign_ranks(stats_dict, 'sog_given') # Legacy field
|
156
|
+
assign_ranks(stats_dict, 'sog_per_game') # Legacy field
|
157
|
+
assign_ranks(stats_dict, 'total_saves_recorded')
|
158
|
+
assign_ranks(stats_dict, 'avg_saves_per_game')
|
159
|
+
assign_ranks(stats_dict, 'avg_max_saves_per_5sec', reverse_rank=True) # Lower is better (less clicking)
|
160
|
+
assign_ranks(stats_dict, 'avg_max_saves_per_20sec', reverse_rank=True) # Lower is better
|
161
|
+
assign_ranks(stats_dict, 'peak_max_saves_per_5sec', reverse_rank=True) # Lower is better
|
162
|
+
assign_ranks(stats_dict, 'peak_max_saves_per_20sec', reverse_rank=True) # Lower is better
|
163
|
+
assign_ranks(stats_dict, 'quality_score', reverse_rank=True) # Lower is better (less problematic)
|
164
|
+
|
165
|
+
# Insert aggregated stats into the appropriate table with progress output
|
166
|
+
batch_size = 1000
|
167
|
+
for i, (key, stat) in enumerate(stats_dict.items(), 1):
|
168
|
+
aggregation_id, human_id = key
|
169
|
+
scorekeeper_stat = StatsModel(
|
170
|
+
aggregation_id=aggregation_id,
|
171
|
+
human_id=human_id,
|
172
|
+
games_recorded=stat['games_recorded'],
|
173
|
+
sog_given=stat['sog_given'], # Legacy field mapping
|
174
|
+
sog_per_game=stat['sog_per_game'], # Legacy field mapping
|
175
|
+
total_saves_recorded=stat['total_saves_recorded'],
|
176
|
+
total_saves_recorded_rank=stat['total_saves_recorded_rank'],
|
177
|
+
avg_saves_per_game=stat['avg_saves_per_game'],
|
178
|
+
avg_saves_per_game_rank=stat['avg_saves_per_game_rank'],
|
179
|
+
avg_max_saves_per_5sec=stat['avg_max_saves_per_5sec'],
|
180
|
+
avg_max_saves_per_5sec_rank=stat['avg_max_saves_per_5sec_rank'],
|
181
|
+
avg_max_saves_per_20sec=stat['avg_max_saves_per_20sec'],
|
182
|
+
avg_max_saves_per_20sec_rank=stat['avg_max_saves_per_20sec_rank'],
|
183
|
+
peak_max_saves_per_5sec=stat['peak_max_saves_per_5sec'],
|
184
|
+
peak_max_saves_per_5sec_rank=stat['peak_max_saves_per_5sec_rank'],
|
185
|
+
peak_max_saves_per_20sec=stat['peak_max_saves_per_20sec'],
|
186
|
+
peak_max_saves_per_20sec_rank=stat['peak_max_saves_per_20sec_rank'],
|
187
|
+
quality_score=stat['quality_score'],
|
188
|
+
quality_score_rank=stat['quality_score_rank'],
|
189
|
+
games_recorded_rank=stat['games_recorded_rank'],
|
190
|
+
sog_given_rank=stat['sog_given_rank'], # Legacy field
|
191
|
+
sog_per_game_rank=stat['sog_per_game_rank'], # Legacy field
|
192
|
+
total_in_rank=total_in_rank,
|
193
|
+
first_game_id=stat['first_game_id'],
|
194
|
+
last_game_id=stat['last_game_id']
|
195
|
+
)
|
196
|
+
session.add(scorekeeper_stat)
|
197
|
+
# Commit in batches
|
198
|
+
if i % batch_size == 0:
|
199
|
+
session.commit()
|
200
|
+
session.commit()
|
201
|
+
|
202
|
+
def run_aggregate_scorekeeper_stats():
|
203
|
+
session = create_session("boss")
|
204
|
+
human_id_to_debug = None
|
205
|
+
|
206
|
+
# Process ALL_ORGS scorekeeper stats only (cross-organizational)
|
207
|
+
# Scorekeeper quality should be measured across all organizations they work for
|
208
|
+
if human_id_to_debug is None:
|
209
|
+
org_progress = create_progress_tracker(3, f"Processing cross-organizational scorekeeper stats")
|
210
|
+
aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=ALL_ORGS_ID, names_to_filter_out=not_human_names)
|
211
|
+
org_progress.update(1)
|
212
|
+
aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=ALL_ORGS_ID, names_to_filter_out=not_human_names, aggregation_window='Weekly')
|
213
|
+
org_progress.update(2)
|
214
|
+
aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=ALL_ORGS_ID, names_to_filter_out=not_human_names, aggregation_window='Daily')
|
215
|
+
org_progress.update(3)
|
216
|
+
else:
|
217
|
+
aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=ALL_ORGS_ID, names_to_filter_out=not_human_names)
|
218
|
+
aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=ALL_ORGS_ID, names_to_filter_out=not_human_names, aggregation_window='Weekly')
|
219
|
+
aggregate_scorekeeper_stats(session, aggregation_type='org', aggregation_id=ALL_ORGS_ID, names_to_filter_out=not_human_names, aggregation_window='Daily')
|
220
|
+
|
221
|
+
if __name__ == "__main__":
|
222
|
+
run_aggregate_scorekeeper_stats()
|
Binary file
|
@@ -176,6 +176,23 @@ class BaseStatsScorekeeper(db.Model):
|
|
176
176
|
sog_given_rank = db.Column(db.Integer, default=0)
|
177
177
|
sog_per_game = db.Column(db.Float, default=0.0)
|
178
178
|
sog_per_game_rank = db.Column(db.Integer, default=0)
|
179
|
+
|
180
|
+
# Quality metrics fields
|
181
|
+
total_saves_recorded = db.Column(db.Integer, default=0)
|
182
|
+
total_saves_recorded_rank = db.Column(db.Integer, default=0)
|
183
|
+
avg_saves_per_game = db.Column(db.Float, default=0.0)
|
184
|
+
avg_saves_per_game_rank = db.Column(db.Integer, default=0)
|
185
|
+
avg_max_saves_per_5sec = db.Column(db.Float, default=0.0)
|
186
|
+
avg_max_saves_per_5sec_rank = db.Column(db.Integer, default=0)
|
187
|
+
avg_max_saves_per_20sec = db.Column(db.Float, default=0.0)
|
188
|
+
avg_max_saves_per_20sec_rank = db.Column(db.Integer, default=0)
|
189
|
+
peak_max_saves_per_5sec = db.Column(db.Integer, default=0)
|
190
|
+
peak_max_saves_per_5sec_rank = db.Column(db.Integer, default=0)
|
191
|
+
peak_max_saves_per_20sec = db.Column(db.Integer, default=0)
|
192
|
+
peak_max_saves_per_20sec_rank = db.Column(db.Integer, default=0)
|
193
|
+
quality_score = db.Column(db.Float, default=0.0)
|
194
|
+
quality_score_rank = db.Column(db.Integer, default=0)
|
195
|
+
|
179
196
|
total_in_rank = db.Column(db.Integer, default=0)
|
180
197
|
first_game_id = db.Column(db.Integer, db.ForeignKey('games.id'))
|
181
198
|
last_game_id = db.Column(db.Integer, db.ForeignKey('games.id'))
|
@@ -186,7 +203,14 @@ class BaseStatsScorekeeper(db.Model):
|
|
186
203
|
db.UniqueConstraint('human_id', cls.get_aggregation_column(), name=f'_human_{cls.aggregation_type}_uc_scorekeeper1'),
|
187
204
|
db.Index(f'idx_{cls.aggregation_type}_games_recorded1', cls.get_aggregation_column(), 'games_recorded'),
|
188
205
|
db.Index(f'idx_{cls.aggregation_type}_sog_given1', cls.get_aggregation_column(), 'sog_given'),
|
189
|
-
db.Index(f'idx_{cls.aggregation_type}_sog_per_game1', cls.get_aggregation_column(), 'sog_per_game')
|
206
|
+
db.Index(f'idx_{cls.aggregation_type}_sog_per_game1', cls.get_aggregation_column(), 'sog_per_game'),
|
207
|
+
db.Index(f'idx_{cls.aggregation_type}_total_saves_recorded1', cls.get_aggregation_column(), 'total_saves_recorded'),
|
208
|
+
db.Index(f'idx_{cls.aggregation_type}_avg_saves_per_game1', cls.get_aggregation_column(), 'avg_saves_per_game'),
|
209
|
+
db.Index(f'idx_{cls.aggregation_type}_avg_max_saves_per_5sec1', cls.get_aggregation_column(), 'avg_max_saves_per_5sec'),
|
210
|
+
db.Index(f'idx_{cls.aggregation_type}_avg_max_saves_per_20sec1', cls.get_aggregation_column(), 'avg_max_saves_per_20sec'),
|
211
|
+
db.Index(f'idx_{cls.aggregation_type}_peak_max_saves_per_5sec1', cls.get_aggregation_column(), 'peak_max_saves_per_5sec'),
|
212
|
+
db.Index(f'idx_{cls.aggregation_type}_peak_max_saves_per_20sec1', cls.get_aggregation_column(), 'peak_max_saves_per_20sec'),
|
213
|
+
db.Index(f'idx_{cls.aggregation_type}_quality_score1', cls.get_aggregation_column(), 'quality_score')
|
190
214
|
)
|
191
215
|
|
192
216
|
@classmethod
|
@@ -365,18 +389,6 @@ class OrgStatsScorekeeper(BaseStatsScorekeeper):
|
|
365
389
|
def get_aggregation_column(cls):
|
366
390
|
return 'org_id'
|
367
391
|
|
368
|
-
class DivisionStatsScorekeeper(BaseStatsScorekeeper):
|
369
|
-
__tablename__ = 'division_stats_scorekeeper'
|
370
|
-
division_id = db.Column(db.Integer, db.ForeignKey('divisions.id'), nullable=False)
|
371
|
-
aggregation_id = synonym('division_id')
|
372
|
-
|
373
|
-
@declared_attr
|
374
|
-
def aggregation_type(cls):
|
375
|
-
return 'division'
|
376
|
-
|
377
|
-
@classmethod
|
378
|
-
def get_aggregation_column(cls):
|
379
|
-
return 'division_id'
|
380
392
|
|
381
393
|
class OrgStatsDailyHuman(BaseStatsHuman):
|
382
394
|
__tablename__ = 'org_stats_daily_human'
|
@@ -612,31 +624,7 @@ class OrgStatsWeeklyScorekeeper(BaseStatsScorekeeper):
|
|
612
624
|
def get_aggregation_column(cls):
|
613
625
|
return 'org_id'
|
614
626
|
|
615
|
-
class DivisionStatsDailyScorekeeper(BaseStatsScorekeeper):
|
616
|
-
__tablename__ = 'division_stats_daily_scorekeeper'
|
617
|
-
division_id = db.Column(db.Integer, db.ForeignKey('divisions.id'), nullable=False)
|
618
|
-
aggregation_id = synonym('division_id')
|
619
|
-
|
620
|
-
@declared_attr
|
621
|
-
def aggregation_type(cls):
|
622
|
-
return 'division_daily'
|
623
|
-
|
624
|
-
@classmethod
|
625
|
-
def get_aggregation_column(cls):
|
626
|
-
return 'division_id'
|
627
|
-
|
628
|
-
class DivisionStatsWeeklyScorekeeper(BaseStatsScorekeeper):
|
629
|
-
__tablename__ = 'division_stats_weekly_scorekeeper'
|
630
|
-
division_id = db.Column(db.Integer, db.ForeignKey('divisions.id'), nullable=False)
|
631
|
-
aggregation_id = synonym('division_id')
|
632
|
-
|
633
|
-
@declared_attr
|
634
|
-
def aggregation_type(cls):
|
635
|
-
return 'division_weekly'
|
636
627
|
|
637
|
-
@classmethod
|
638
|
-
def get_aggregation_column(cls):
|
639
|
-
return 'division_id'
|
640
628
|
|
641
629
|
class LevelsGraphEdge(db.Model):
|
642
630
|
__tablename__ = 'levels_graph_edges'
|
{hockey_blast_common_lib-0.1.56.dist-info → hockey_blast_common_lib-0.1.58.dist-info}/RECORD
RENAMED
@@ -1,27 +1,28 @@
|
|
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=QhuSvGjuk4jVywNRcgxB-9ooJAoAbZRkaLjLe9Q1hEM,1363
|
3
|
+
hockey_blast_common_lib/aggregate_goalie_stats.py,sha256=lJE5TMBtJLqLeG9jxHV5xUcudusZ4t8yHQN6TvPK_0k,13804
|
4
4
|
hockey_blast_common_lib/aggregate_h2h_stats.py,sha256=dC5TcJZGkpIQTiq3z40kOX6EjEhFbGv5EL0P1EClBQ0,11117
|
5
5
|
hockey_blast_common_lib/aggregate_human_stats.py,sha256=ku42TAjUIj49822noM8fEeB8GS4vFeCCNrLupTWmqzg,26043
|
6
6
|
hockey_blast_common_lib/aggregate_referee_stats.py,sha256=mUcTVQH9K4kwmIfgfGsnh_3AqX6Ia3RjfukkYuQas3I,13938
|
7
7
|
hockey_blast_common_lib/aggregate_s2s_stats.py,sha256=urYN0Q06twwLO-XWGlSMVAVOTVR_D2AWdmoGsxIYHXE,6737
|
8
|
+
hockey_blast_common_lib/aggregate_scorekeeper_stats.py,sha256=dtjJunl5QZb97ypqdrZH9N1MUYZRJvVoswXf0kMeZYA,11794
|
8
9
|
hockey_blast_common_lib/aggregate_skater_stats.py,sha256=pA_2pDOGcJyEywISg2ySG8gFCuoLWwqw6a3Gm2wHLyo,23302
|
9
10
|
hockey_blast_common_lib/assign_skater_skill.py,sha256=Asq6iRMPsCMDnvuNSd-M3s4Gee4kDocP9Eznwju_9kA,2749
|
10
11
|
hockey_blast_common_lib/db_connection.py,sha256=HvPxDvOj7j5H85RfslGvHVNevfg7mKCd0syJ6NX21mU,1890
|
11
12
|
hockey_blast_common_lib/dump_sample_db.sh,sha256=MY3lnzTXBoWd76-ZlZr9nWsKMEVgyRsUn-LZ2d1JWZs,810
|
12
13
|
hockey_blast_common_lib/h2h_models.py,sha256=0st4xoJO0U6ONfx3BV03BQvHjZE31e_PqZfphAJMoSU,7968
|
13
|
-
hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=
|
14
|
+
hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=0XOu7BnMN-skRFuOmPTTOPjCYci9UdAZ2yoIpkngtPg,4648906
|
14
15
|
hockey_blast_common_lib/models.py,sha256=JMwQE1QgNkuWuKakxuHIlbEHfk8Z7GAYktCppCWA6LE,18928
|
15
16
|
hockey_blast_common_lib/options.py,sha256=rQaLRYYcaxXrpZoXfUpmvsahC23oVGPEOzEpqtthbIQ,1794
|
16
17
|
hockey_blast_common_lib/progress_utils.py,sha256=H_zRFOsb2qQQpGw56wJghZ1nUe_m6zqGeR9hZ33Y1Uo,3229
|
17
18
|
hockey_blast_common_lib/restore_sample_db.sh,sha256=7W3lzRZeu9zXIu1Bvtnaw8EHc1ulHmFM4mMh86oUQJo,2205
|
18
19
|
hockey_blast_common_lib/skills_in_divisions.py,sha256=m-UEwMwn1KM7wOYvDstgsOEeH57M9V6yrkBoghzGYKE,7005
|
19
20
|
hockey_blast_common_lib/skills_propagation.py,sha256=nUxntyK8M4xWjHpkfze8f0suaBeunxicgDCduGmNJ-A,18468
|
20
|
-
hockey_blast_common_lib/stats_models.py,sha256=
|
21
|
+
hockey_blast_common_lib/stats_models.py,sha256=64sUq_iWhNXi_b_V_1INuQ1RusKaTASjurkRo5gQOs4,26703
|
21
22
|
hockey_blast_common_lib/stats_utils.py,sha256=DXsPO4jw8XsdRUN46TGF_IiBAfz3GCIVBswCGp5ELDk,284
|
22
23
|
hockey_blast_common_lib/utils.py,sha256=PduHp6HoI4sfr5HvJfQAaz7170dy5kTFVdIfWvBR-Jg,5874
|
23
24
|
hockey_blast_common_lib/wsgi.py,sha256=y3NxoJfWjdzX3iP7RGvDEer6zcnPyCanpqSgW1BlXgg,779
|
24
|
-
hockey_blast_common_lib-0.1.
|
25
|
-
hockey_blast_common_lib-0.1.
|
26
|
-
hockey_blast_common_lib-0.1.
|
27
|
-
hockey_blast_common_lib-0.1.
|
25
|
+
hockey_blast_common_lib-0.1.58.dist-info/METADATA,sha256=jCXdoBpO5gfR3UZnUelcFJ8XspPWu3VcvYuCt5QlhQ4,318
|
26
|
+
hockey_blast_common_lib-0.1.58.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
27
|
+
hockey_blast_common_lib-0.1.58.dist-info/top_level.txt,sha256=wIR4LIkE40npoA2QlOdfCYlgFeGbsHR8Z6r0h46Vtgc,24
|
28
|
+
hockey_blast_common_lib-0.1.58.dist-info/RECORD,,
|
File without changes
|
{hockey_blast_common_lib-0.1.56.dist-info → hockey_blast_common_lib-0.1.58.dist-info}/top_level.txt
RENAMED
File without changes
|