hockey-blast-common-lib 0.1.61__tar.gz → 0.1.63__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/PKG-INFO +1 -1
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/aggregate_goalie_stats.py +29 -7
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/aggregate_human_stats.py +3 -3
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/aggregate_referee_stats.py +38 -9
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/aggregate_scorekeeper_stats.py +24 -3
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/aggregate_skater_stats.py +107 -49
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/models.py +1 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/progress_utils.py +23 -9
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/stats_models.py +20 -4
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib.egg-info/PKG-INFO +1 -1
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/setup.py +1 -1
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/MANIFEST.in +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/README.md +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/__init__.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/aggregate_all_stats.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/aggregate_h2h_stats.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/aggregate_s2s_stats.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/assign_skater_skill.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/db_connection.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/dump_sample_db.sh +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/h2h_models.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/options.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/restore_sample_db.sh +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/skills_in_divisions.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/skills_propagation.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/stats_utils.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/utils.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/wsgi.py +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib.egg-info/SOURCES.txt +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib.egg-info/dependency_links.txt +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib.egg-info/requires.txt +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib.egg-info/top_level.txt +0 -0
- {hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/setup.cfg +0 -0
@@ -9,13 +9,19 @@ import sqlalchemy
|
|
9
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
|
12
|
+
from sqlalchemy.sql import func, case
|
13
13
|
from hockey_blast_common_lib.options import MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
|
14
14
|
from hockey_blast_common_lib.utils import get_non_human_ids, get_all_division_ids_for_org, get_start_datetime
|
15
15
|
from hockey_blast_common_lib.utils import assign_ranks
|
16
16
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
17
17
|
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
18
18
|
|
19
|
+
# Import status constants for game filtering
|
20
|
+
FINAL_STATUS = "Final"
|
21
|
+
FINAL_SO_STATUS = "Final(SO)"
|
22
|
+
FORFEIT_STATUS = "FORFEIT"
|
23
|
+
NOEVENTS_STATUS = "NOEVENTS"
|
24
|
+
|
19
25
|
def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_human_id=None, aggregation_window=None):
|
20
26
|
human_ids_to_filter = get_non_human_ids(session)
|
21
27
|
|
@@ -72,13 +78,19 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
72
78
|
|
73
79
|
|
74
80
|
# Aggregate games played, goals allowed, and shots faced for each goalie using GoalieSaves table
|
81
|
+
# Filter games by status upfront for performance (avoid CASE statements)
|
82
|
+
# Only count games with these statuses: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
75
83
|
query = session.query(
|
76
84
|
GoalieSaves.goalie_id.label('human_id'),
|
77
85
|
func.count(GoalieSaves.game_id).label('games_played'),
|
86
|
+
func.count(GoalieSaves.game_id).label('games_participated'), # Same as games_played after filtering
|
87
|
+
func.count(GoalieSaves.game_id).label('games_with_stats'), # Same as games_played after filtering
|
78
88
|
func.sum(GoalieSaves.goals_allowed).label('goals_allowed'),
|
79
89
|
func.sum(GoalieSaves.shots_against).label('shots_faced'),
|
80
90
|
func.array_agg(GoalieSaves.game_id).label('game_ids')
|
81
|
-
).join(Game, GoalieSaves.game_id == Game.id).
|
91
|
+
).join(Game, GoalieSaves.game_id == Game.id).filter(
|
92
|
+
Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS])
|
93
|
+
).join(Division, Game.division_id == Division.id).filter(filter_condition)
|
82
94
|
|
83
95
|
# Filter for specific human_id if provided
|
84
96
|
if debug_human_id:
|
@@ -94,7 +106,9 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
94
106
|
key = (aggregation_id, stat.human_id)
|
95
107
|
if key not in stats_dict:
|
96
108
|
stats_dict[key] = {
|
97
|
-
'games_played': 0,
|
109
|
+
'games_played': 0, # DEPRECATED - for backward compatibility
|
110
|
+
'games_participated': 0, # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
111
|
+
'games_with_stats': 0, # Games with full stats: FINAL, FINAL_SO only
|
98
112
|
'goals_allowed': 0,
|
99
113
|
'shots_faced': 0,
|
100
114
|
'goals_allowed_per_game': 0.0,
|
@@ -104,6 +118,8 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
104
118
|
'last_game_id': None
|
105
119
|
}
|
106
120
|
stats_dict[key]['games_played'] += stat.games_played
|
121
|
+
stats_dict[key]['games_participated'] += stat.games_participated
|
122
|
+
stats_dict[key]['games_with_stats'] += stat.games_with_stats
|
107
123
|
stats_dict[key]['goals_allowed'] += stat.goals_allowed if stat.goals_allowed is not None else 0
|
108
124
|
stats_dict[key]['shots_faced'] += stat.shots_faced if stat.shots_faced is not None else 0
|
109
125
|
stats_dict[key]['game_ids'].extend(stat.game_ids)
|
@@ -111,10 +127,10 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
111
127
|
# Filter out entries with games_played less than min_games
|
112
128
|
stats_dict = {key: value for key, value in stats_dict.items() if value['games_played'] >= min_games}
|
113
129
|
|
114
|
-
# Calculate per game stats
|
130
|
+
# Calculate per game stats (using games_with_stats as denominator for accuracy)
|
115
131
|
for key, stat in stats_dict.items():
|
116
|
-
if stat['
|
117
|
-
stat['goals_allowed_per_game'] = stat['goals_allowed'] / stat['
|
132
|
+
if stat['games_with_stats'] > 0:
|
133
|
+
stat['goals_allowed_per_game'] = stat['goals_allowed'] / stat['games_with_stats']
|
118
134
|
stat['save_percentage'] = (stat['shots_faced'] - stat['goals_allowed']) / stat['shots_faced'] if stat['shots_faced'] > 0 else 0.0
|
119
135
|
|
120
136
|
# Ensure all keys have valid human_id values
|
@@ -134,6 +150,8 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
134
150
|
|
135
151
|
# Assign ranks within each level
|
136
152
|
assign_ranks(stats_dict, 'games_played')
|
153
|
+
assign_ranks(stats_dict, 'games_participated') # Rank by total participation
|
154
|
+
assign_ranks(stats_dict, 'games_with_stats') # Rank by games with full stats
|
137
155
|
assign_ranks(stats_dict, 'goals_allowed', reverse_rank=True)
|
138
156
|
assign_ranks(stats_dict, 'shots_faced')
|
139
157
|
assign_ranks(stats_dict, 'goals_allowed_per_game', reverse_rank=True)
|
@@ -159,7 +177,11 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
159
177
|
goalie_stat = StatsModel(
|
160
178
|
aggregation_id=aggregation_id,
|
161
179
|
human_id=human_id,
|
162
|
-
games_played=stat['games_played'],
|
180
|
+
games_played=stat['games_played'], # DEPRECATED - for backward compatibility
|
181
|
+
games_participated=stat['games_participated'], # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
182
|
+
games_participated_rank=stat['games_participated_rank'],
|
183
|
+
games_with_stats=stat['games_with_stats'], # Games with full stats: FINAL, FINAL_SO only
|
184
|
+
games_with_stats_rank=stat['games_with_stats_rank'],
|
163
185
|
goals_allowed=stat['goals_allowed'],
|
164
186
|
shots_faced=stat['shots_faced'],
|
165
187
|
goals_allowed_per_game=goals_allowed_per_game,
|
@@ -61,7 +61,7 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, human_id_fi
|
|
61
61
|
|
62
62
|
# Apply aggregation window filter
|
63
63
|
if aggregation_window:
|
64
|
-
last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
|
64
|
+
last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, (Game.status.like('Final%')) | (Game.status == 'NOEVENTS')).scalar()
|
65
65
|
start_datetime = get_start_datetime(last_game_datetime_str, aggregation_window)
|
66
66
|
if start_datetime:
|
67
67
|
game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime_str)
|
@@ -75,8 +75,8 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, human_id_fi
|
|
75
75
|
if human_id_filter:
|
76
76
|
human_filter = [GameRoster.human_id == human_id_filter]
|
77
77
|
|
78
|
-
# Filter games by status
|
79
|
-
game_status_filter = Game.status.like('Final%')
|
78
|
+
# Filter games by status - include both Final and NOEVENTS games
|
79
|
+
game_status_filter = (Game.status.like('Final%')) | (Game.status == 'NOEVENTS')
|
80
80
|
|
81
81
|
# Aggregate skater games played
|
82
82
|
skater_stats = session.query(
|
@@ -17,6 +17,12 @@ from hockey_blast_common_lib.utils import get_start_datetime
|
|
17
17
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
18
18
|
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
19
19
|
|
20
|
+
# Import status constants for game filtering
|
21
|
+
FINAL_STATUS = "Final"
|
22
|
+
FINAL_SO_STATUS = "Final(SO)"
|
23
|
+
FORFEIT_STATUS = "FORFEIT"
|
24
|
+
NOEVENTS_STATUS = "NOEVENTS"
|
25
|
+
|
20
26
|
def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregation_window=None):
|
21
27
|
human_ids_to_filter = get_non_human_ids(session)
|
22
28
|
|
@@ -72,17 +78,26 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
72
78
|
|
73
79
|
filter_condition = filter_condition & (Division.id == Game.division_id)
|
74
80
|
# Aggregate games reffed for each referee
|
81
|
+
# games_participated: Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
82
|
+
# games_with_stats: Count only FINAL, FINAL_SO (for per-game averages)
|
83
|
+
# Filter by game status upfront for performance
|
84
|
+
status_filter = Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS])
|
85
|
+
|
75
86
|
games_reffed_stats = session.query(
|
76
87
|
Game.referee_1_id.label('human_id'),
|
77
88
|
func.count(Game.id).label('games_reffed'),
|
89
|
+
func.count(Game.id).label('games_participated'), # Same as games_reffed after filtering
|
90
|
+
func.count(Game.id).label('games_with_stats'), # Same as games_reffed after filtering
|
78
91
|
func.array_agg(Game.id).label('game_ids')
|
79
|
-
).filter(filter_condition).group_by(Game.referee_1_id).all()
|
92
|
+
).filter(filter_condition, status_filter).group_by(Game.referee_1_id).all()
|
80
93
|
|
81
94
|
games_reffed_stats_2 = session.query(
|
82
95
|
Game.referee_2_id.label('human_id'),
|
83
96
|
func.count(Game.id).label('games_reffed'),
|
97
|
+
func.count(Game.id).label('games_participated'), # Same as games_reffed after filtering
|
98
|
+
func.count(Game.id).label('games_with_stats'), # Same as games_reffed after filtering
|
84
99
|
func.array_agg(Game.id).label('game_ids')
|
85
|
-
).filter(filter_condition).group_by(Game.referee_2_id).all()
|
100
|
+
).filter(filter_condition, status_filter).group_by(Game.referee_2_id).all()
|
86
101
|
|
87
102
|
# Aggregate penalties given for each referee
|
88
103
|
penalties_given_stats = session.query(
|
@@ -101,7 +116,9 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
101
116
|
key = (aggregation_id, stat.human_id)
|
102
117
|
if key not in stats_dict:
|
103
118
|
stats_dict[key] = {
|
104
|
-
'games_reffed': 0,
|
119
|
+
'games_reffed': 0, # DEPRECATED - for backward compatibility
|
120
|
+
'games_participated': 0, # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
121
|
+
'games_with_stats': 0, # Games with full stats: FINAL, FINAL_SO only
|
105
122
|
'penalties_given': 0,
|
106
123
|
'gm_given': 0,
|
107
124
|
'penalties_per_game': 0.0,
|
@@ -111,6 +128,8 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
111
128
|
'last_game_id': None
|
112
129
|
}
|
113
130
|
stats_dict[key]['games_reffed'] += stat.games_reffed
|
131
|
+
stats_dict[key]['games_participated'] += stat.games_participated
|
132
|
+
stats_dict[key]['games_with_stats'] += stat.games_with_stats
|
114
133
|
stats_dict[key]['game_ids'].extend(stat.game_ids)
|
115
134
|
|
116
135
|
for stat in games_reffed_stats_2:
|
@@ -119,7 +138,9 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
119
138
|
key = (aggregation_id, stat.human_id)
|
120
139
|
if key not in stats_dict:
|
121
140
|
stats_dict[key] = {
|
122
|
-
'games_reffed': 0,
|
141
|
+
'games_reffed': 0, # DEPRECATED - for backward compatibility
|
142
|
+
'games_participated': 0, # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
143
|
+
'games_with_stats': 0, # Games with full stats: FINAL, FINAL_SO only
|
123
144
|
'penalties_given': 0,
|
124
145
|
'gm_given': 0,
|
125
146
|
'penalties_per_game': 0.0,
|
@@ -129,6 +150,8 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
129
150
|
'last_game_id': None
|
130
151
|
}
|
131
152
|
stats_dict[key]['games_reffed'] += stat.games_reffed
|
153
|
+
stats_dict[key]['games_participated'] += stat.games_participated
|
154
|
+
stats_dict[key]['games_with_stats'] += stat.games_with_stats
|
132
155
|
stats_dict[key]['game_ids'].extend(stat.game_ids)
|
133
156
|
|
134
157
|
# Filter out entries with games_reffed less than min_games
|
@@ -149,11 +172,11 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
149
172
|
stats_dict[key]['gm_given'] += stat.gm_given / 2
|
150
173
|
stats_dict[key]['game_ids'].append(stat.game_id)
|
151
174
|
|
152
|
-
# Calculate per game stats
|
175
|
+
# Calculate per game stats (using games_with_stats as denominator for accuracy)
|
153
176
|
for key, stat in stats_dict.items():
|
154
|
-
if stat['
|
155
|
-
stat['penalties_per_game'] = stat['penalties_given'] / stat['
|
156
|
-
stat['gm_per_game'] = stat['gm_given'] / stat['
|
177
|
+
if stat['games_with_stats'] > 0:
|
178
|
+
stat['penalties_per_game'] = stat['penalties_given'] / stat['games_with_stats']
|
179
|
+
stat['gm_per_game'] = stat['gm_given'] / stat['games_with_stats']
|
157
180
|
|
158
181
|
# Ensure all keys have valid human_id values
|
159
182
|
stats_dict = {key: value for key, value in stats_dict.items() if key[1] is not None}
|
@@ -172,6 +195,8 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
172
195
|
|
173
196
|
# Assign ranks
|
174
197
|
assign_ranks(stats_dict, 'games_reffed')
|
198
|
+
assign_ranks(stats_dict, 'games_participated') # Rank by total participation
|
199
|
+
assign_ranks(stats_dict, 'games_with_stats') # Rank by games with full stats
|
175
200
|
assign_ranks(stats_dict, 'penalties_given')
|
176
201
|
assign_ranks(stats_dict, 'penalties_per_game')
|
177
202
|
assign_ranks(stats_dict, 'gm_given')
|
@@ -185,7 +210,11 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, aggregati
|
|
185
210
|
referee_stat = StatsModel(
|
186
211
|
aggregation_id=aggregation_id,
|
187
212
|
human_id=human_id,
|
188
|
-
games_reffed=stat['games_reffed'],
|
213
|
+
games_reffed=stat['games_reffed'], # DEPRECATED - for backward compatibility
|
214
|
+
games_participated=stat['games_participated'], # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
215
|
+
games_participated_rank=stat['games_participated_rank'],
|
216
|
+
games_with_stats=stat['games_with_stats'], # Games with full stats: FINAL, FINAL_SO only
|
217
|
+
games_with_stats_rank=stat['games_with_stats_rank'],
|
189
218
|
penalties_given=stat['penalties_given'],
|
190
219
|
penalties_per_game=stat['penalties_per_game'],
|
191
220
|
gm_given=stat['gm_given'],
|
@@ -17,6 +17,12 @@ from hockey_blast_common_lib.utils import get_start_datetime
|
|
17
17
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
18
18
|
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
19
19
|
|
20
|
+
# Import status constants for game filtering
|
21
|
+
FINAL_STATUS = "Final"
|
22
|
+
FINAL_SO_STATUS = "Final(SO)"
|
23
|
+
FORFEIT_STATUS = "FORFEIT"
|
24
|
+
NOEVENTS_STATUS = "NOEVENTS"
|
25
|
+
|
20
26
|
def calculate_quality_score(avg_max_saves_5sec, avg_max_saves_20sec, peak_max_saves_5sec, peak_max_saves_20sec):
|
21
27
|
"""
|
22
28
|
Calculate a quality score based on excessive clicking patterns.
|
@@ -89,9 +95,14 @@ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, aggre
|
|
89
95
|
|
90
96
|
|
91
97
|
# Aggregate scorekeeper quality data for each human
|
98
|
+
# games_participated: Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
99
|
+
# games_with_stats: Count only FINAL, FINAL_SO (for per-game averages)
|
100
|
+
# Filter by game status upfront for performance
|
92
101
|
scorekeeper_quality_stats = session.query(
|
93
102
|
ScorekeeperSaveQuality.scorekeeper_id.label('human_id'),
|
94
103
|
func.count(ScorekeeperSaveQuality.game_id).label('games_recorded'),
|
104
|
+
func.count(ScorekeeperSaveQuality.game_id).label('games_participated'), # Same as games_recorded after filtering
|
105
|
+
func.count(ScorekeeperSaveQuality.game_id).label('games_with_stats'), # Same as games_recorded after filtering
|
95
106
|
func.sum(ScorekeeperSaveQuality.total_saves_recorded).label('total_saves_recorded'),
|
96
107
|
func.avg(ScorekeeperSaveQuality.total_saves_recorded).label('avg_saves_per_game'),
|
97
108
|
func.avg(ScorekeeperSaveQuality.max_saves_per_5sec).label('avg_max_saves_per_5sec'),
|
@@ -99,7 +110,9 @@ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, aggre
|
|
99
110
|
func.max(ScorekeeperSaveQuality.max_saves_per_5sec).label('peak_max_saves_per_5sec'),
|
100
111
|
func.max(ScorekeeperSaveQuality.max_saves_per_20sec).label('peak_max_saves_per_20sec'),
|
101
112
|
func.array_agg(ScorekeeperSaveQuality.game_id).label('game_ids')
|
102
|
-
).join(Game, Game.id == ScorekeeperSaveQuality.game_id)
|
113
|
+
).join(Game, Game.id == ScorekeeperSaveQuality.game_id).filter(
|
114
|
+
Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS])
|
115
|
+
)
|
103
116
|
|
104
117
|
|
105
118
|
scorekeeper_quality_stats = scorekeeper_quality_stats.filter(filter_condition).group_by(ScorekeeperSaveQuality.scorekeeper_id).all()
|
@@ -120,7 +133,9 @@ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, aggre
|
|
120
133
|
)
|
121
134
|
|
122
135
|
stats_dict[key] = {
|
123
|
-
'games_recorded': stat.games_recorded,
|
136
|
+
'games_recorded': stat.games_recorded, # DEPRECATED - for backward compatibility
|
137
|
+
'games_participated': stat.games_participated, # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
138
|
+
'games_with_stats': stat.games_with_stats, # Games with full stats: FINAL, FINAL_SO only
|
124
139
|
'sog_given': stat.total_saves_recorded, # Legacy field name mapping
|
125
140
|
'sog_per_game': stat.avg_saves_per_game or 0.0, # Legacy field name mapping
|
126
141
|
'total_saves_recorded': stat.total_saves_recorded,
|
@@ -152,6 +167,8 @@ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, aggre
|
|
152
167
|
|
153
168
|
# Assign ranks - note: for quality metrics, lower values are better (reverse_rank=True for avg and peak clicking)
|
154
169
|
assign_ranks(stats_dict, 'games_recorded')
|
170
|
+
assign_ranks(stats_dict, 'games_participated') # Rank by total participation
|
171
|
+
assign_ranks(stats_dict, 'games_with_stats') # Rank by games with full stats
|
155
172
|
assign_ranks(stats_dict, 'sog_given') # Legacy field
|
156
173
|
assign_ranks(stats_dict, 'sog_per_game') # Legacy field
|
157
174
|
assign_ranks(stats_dict, 'total_saves_recorded')
|
@@ -169,7 +186,11 @@ def aggregate_scorekeeper_stats(session, aggregation_type, aggregation_id, aggre
|
|
169
186
|
scorekeeper_stat = StatsModel(
|
170
187
|
aggregation_id=aggregation_id,
|
171
188
|
human_id=human_id,
|
172
|
-
games_recorded=stat['games_recorded'],
|
189
|
+
games_recorded=stat['games_recorded'], # DEPRECATED - for backward compatibility
|
190
|
+
games_participated=stat['games_participated'], # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
191
|
+
games_participated_rank=stat['games_participated_rank'],
|
192
|
+
games_with_stats=stat['games_with_stats'], # Games with full stats: FINAL, FINAL_SO only
|
193
|
+
games_with_stats_rank=stat['games_with_stats_rank'],
|
173
194
|
sog_given=stat['sog_given'], # Legacy field mapping
|
174
195
|
sog_per_game=stat['sog_per_game'], # Legacy field mapping
|
175
196
|
total_saves_recorded=stat['total_saves_recorded'],
|
@@ -18,50 +18,65 @@ from collections import defaultdict
|
|
18
18
|
from hockey_blast_common_lib.stats_utils import ALL_ORGS_ID
|
19
19
|
from hockey_blast_common_lib.progress_utils import create_progress_tracker
|
20
20
|
|
21
|
+
# Import status constants for game filtering
|
22
|
+
FINAL_STATUS = "Final"
|
23
|
+
FINAL_SO_STATUS = "Final(SO)"
|
24
|
+
FORFEIT_STATUS = "FORFEIT"
|
25
|
+
NOEVENTS_STATUS = "NOEVENTS"
|
26
|
+
|
21
27
|
def calculate_current_point_streak(session, human_id, filter_condition):
|
22
28
|
"""
|
23
29
|
Calculate the current point streak for a player.
|
24
30
|
A point streak is consecutive games (from the most recent game backward) where the player had at least one point.
|
25
31
|
Returns a tuple: (streak_length, average_points_during_streak)
|
32
|
+
|
33
|
+
Optimized to use CASE statements for conditional aggregation in a single query.
|
26
34
|
"""
|
27
|
-
# Get all games
|
28
|
-
|
35
|
+
# Get all games with their point totals in ONE query using CASE for conditional counting
|
36
|
+
game_points = session.query(
|
37
|
+
Game.id,
|
38
|
+
Game.date,
|
39
|
+
Game.time,
|
40
|
+
func.sum(case((Goal.goal_scorer_id == human_id, 1), else_=0)).label('goals'),
|
41
|
+
func.sum(case(
|
42
|
+
((Goal.assist_1_id == human_id) | (Goal.assist_2_id == human_id), 1),
|
43
|
+
else_=0
|
44
|
+
)).label('assists')
|
45
|
+
).join(
|
46
|
+
GameRoster, Game.id == GameRoster.game_id
|
47
|
+
).outerjoin(
|
48
|
+
Goal, Game.id == Goal.game_id
|
49
|
+
).filter(
|
29
50
|
GameRoster.human_id == human_id,
|
30
51
|
~GameRoster.role.ilike('g'), # Exclude goalie games
|
31
52
|
filter_condition,
|
32
|
-
Game.status.like('Final%') #
|
33
|
-
).
|
34
|
-
|
35
|
-
|
53
|
+
(Game.status.like('Final%')) | (Game.status == 'NOEVENTS') # Include Final and NOEVENTS games
|
54
|
+
).group_by(
|
55
|
+
Game.id, Game.date, Game.time
|
56
|
+
).order_by(
|
57
|
+
Game.date.desc(), Game.time.desc()
|
58
|
+
).all()
|
59
|
+
|
60
|
+
if not game_points:
|
36
61
|
return 0, 0.0
|
37
|
-
|
62
|
+
|
38
63
|
current_streak = 0
|
39
64
|
total_points_in_streak = 0
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
Goal.goal_scorer_id == human_id
|
46
|
-
).count()
|
47
|
-
|
48
|
-
assists = session.query(Goal).filter(
|
49
|
-
Goal.game_id == game.id,
|
50
|
-
((Goal.assist_1_id == human_id) | (Goal.assist_2_id == human_id))
|
51
|
-
).count()
|
52
|
-
|
53
|
-
total_points = goals + assists
|
54
|
-
|
65
|
+
|
66
|
+
# Iterate through games from most recent to oldest
|
67
|
+
for game in game_points:
|
68
|
+
total_points = (game.goals or 0) + (game.assists or 0)
|
69
|
+
|
55
70
|
if total_points > 0:
|
56
71
|
current_streak += 1
|
57
72
|
total_points_in_streak += total_points
|
58
73
|
else:
|
59
74
|
# Streak is broken, stop counting
|
60
75
|
break
|
61
|
-
|
76
|
+
|
62
77
|
# Calculate average points during streak
|
63
78
|
avg_points_during_streak = total_points_in_streak / current_streak if current_streak > 0 else 0.0
|
64
|
-
|
79
|
+
|
65
80
|
return current_streak, avg_points_during_streak
|
66
81
|
|
67
82
|
def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_human_id=None, aggregation_window=None):
|
@@ -123,7 +138,7 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
123
138
|
|
124
139
|
# Apply aggregation window filter
|
125
140
|
if aggregation_window:
|
126
|
-
last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
|
141
|
+
last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, (Game.status.like('Final%')) | (Game.status == 'NOEVENTS')).scalar()
|
127
142
|
start_datetime = get_start_datetime(last_game_datetime_str, aggregation_window)
|
128
143
|
if start_datetime:
|
129
144
|
game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime_str)
|
@@ -138,16 +153,22 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
138
153
|
# human_filter = [GameRoster.human_id == debug_human_id]
|
139
154
|
|
140
155
|
# Aggregate games played for each human in each division, excluding goalies
|
156
|
+
# Filter games by status upfront for performance (avoid CASE statements on 200K+ rows)
|
157
|
+
# Only count games with these statuses: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
141
158
|
games_played_query = session.query(
|
142
159
|
GameRoster.human_id,
|
143
160
|
func.count(Game.id).label('games_played'),
|
161
|
+
func.count(Game.id).label('games_participated'), # Same as games_played after filtering
|
162
|
+
func.count(Game.id).label('games_with_stats'), # Same as games_played after filtering
|
144
163
|
func.array_agg(Game.id).label('game_ids')
|
145
|
-
).join(Game, Game.id == GameRoster.game_id)
|
146
|
-
|
164
|
+
).join(Game, Game.id == GameRoster.game_id).filter(
|
165
|
+
Game.status.in_([FINAL_STATUS, FINAL_SO_STATUS, FORFEIT_STATUS, NOEVENTS_STATUS])
|
166
|
+
)
|
167
|
+
|
147
168
|
# Only join Division if not level aggregation (since we filter on Game.division_id directly for levels)
|
148
169
|
if aggregation_type != 'level':
|
149
170
|
games_played_query = games_played_query.join(Division, Game.division_id == Division.id)
|
150
|
-
|
171
|
+
|
151
172
|
games_played_stats = games_played_query.filter(filter_condition, ~GameRoster.role.ilike('g'), *human_filter).group_by(GameRoster.human_id).all()
|
152
173
|
|
153
174
|
# Aggregate goals for each human in each division, excluding goalies
|
@@ -206,7 +227,9 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
206
227
|
key = (aggregation_id, stat.human_id)
|
207
228
|
if key not in stats_dict:
|
208
229
|
stats_dict[key] = {
|
209
|
-
'games_played': 0,
|
230
|
+
'games_played': 0, # DEPRECATED - for backward compatibility
|
231
|
+
'games_participated': 0, # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
232
|
+
'games_with_stats': 0, # Games with full stats: FINAL, FINAL_SO only
|
210
233
|
'goals': 0,
|
211
234
|
'assists': 0,
|
212
235
|
'penalties': 0,
|
@@ -224,6 +247,8 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
224
247
|
'last_game_id': None
|
225
248
|
}
|
226
249
|
stats_dict[key]['games_played'] += stat.games_played
|
250
|
+
stats_dict[key]['games_participated'] += stat.games_participated
|
251
|
+
stats_dict[key]['games_with_stats'] += stat.games_with_stats
|
227
252
|
stats_dict[key]['game_ids'].extend(stat.game_ids)
|
228
253
|
|
229
254
|
# Filter out entries with games_played less than min_games
|
@@ -253,34 +278,61 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
253
278
|
stats_dict[key]['penalties'] += stat.penalties
|
254
279
|
stats_dict[key]['gm_penalties'] += stat.gm_penalties # Update GM penalties
|
255
280
|
|
256
|
-
# Calculate per game stats
|
281
|
+
# Calculate per game stats (using games_with_stats as denominator for accuracy)
|
257
282
|
for key, stat in stats_dict.items():
|
258
|
-
if stat['
|
259
|
-
stat['goals_per_game'] = stat['goals'] / stat['
|
260
|
-
stat['points_per_game'] = stat['points'] / stat['
|
261
|
-
stat['assists_per_game'] = stat['assists'] / stat['
|
262
|
-
stat['penalties_per_game'] = stat['penalties'] / stat['
|
263
|
-
stat['gm_penalties_per_game'] = stat['gm_penalties'] / stat['
|
283
|
+
if stat['games_with_stats'] > 0:
|
284
|
+
stat['goals_per_game'] = stat['goals'] / stat['games_with_stats']
|
285
|
+
stat['points_per_game'] = stat['points'] / stat['games_with_stats']
|
286
|
+
stat['assists_per_game'] = stat['assists'] / stat['games_with_stats']
|
287
|
+
stat['penalties_per_game'] = stat['penalties'] / stat['games_with_stats']
|
288
|
+
stat['gm_penalties_per_game'] = stat['gm_penalties'] / stat['games_with_stats'] # Calculate GM penalties per game
|
264
289
|
|
265
290
|
# Ensure all keys have valid human_id values
|
266
291
|
stats_dict = {key: value for key, value in stats_dict.items() if key[1] is not None}
|
267
292
|
|
268
293
|
# Populate first_game_id and last_game_id
|
269
|
-
for
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
stat['
|
275
|
-
|
294
|
+
# Only show progress for "All Orgs" with no window (all-time stats) - the slowest case
|
295
|
+
total_players = len(stats_dict)
|
296
|
+
if aggregation_type == 'org' and aggregation_id == ALL_ORGS_ID and aggregation_window is None and total_players > 1000:
|
297
|
+
progress = create_progress_tracker(total_players, f"Processing {total_players} players for {aggregation_name}")
|
298
|
+
for idx, (key, stat) in enumerate(stats_dict.items()):
|
299
|
+
all_game_ids = stat['game_ids']
|
300
|
+
if all_game_ids:
|
301
|
+
first_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date, Game.time).first()
|
302
|
+
last_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date.desc(), Game.time.desc()).first()
|
303
|
+
stat['first_game_id'] = first_game.id if first_game else None
|
304
|
+
stat['last_game_id'] = last_game.id if last_game else None
|
305
|
+
if (idx + 1) % 100 == 0 or (idx + 1) == total_players: # Update every 100 players
|
306
|
+
progress.update(idx + 1)
|
307
|
+
else:
|
308
|
+
# No progress tracking for all other cases
|
309
|
+
for key, stat in stats_dict.items():
|
310
|
+
all_game_ids = stat['game_ids']
|
311
|
+
if all_game_ids:
|
312
|
+
first_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date, Game.time).first()
|
313
|
+
last_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date.desc(), Game.time.desc()).first()
|
314
|
+
stat['first_game_id'] = first_game.id if first_game else None
|
315
|
+
stat['last_game_id'] = last_game.id if last_game else None
|
276
316
|
|
277
317
|
# Calculate current point streak (only for all-time stats)
|
278
318
|
if aggregation_window is None:
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
stat
|
319
|
+
total_players = len(stats_dict)
|
320
|
+
# Show progress for All Orgs - this is the slowest part
|
321
|
+
if aggregation_type == 'org' and aggregation_id == ALL_ORGS_ID and total_players > 1000:
|
322
|
+
progress = create_progress_tracker(total_players, f"Calculating point streaks for {total_players} players")
|
323
|
+
for idx, (key, stat) in enumerate(stats_dict.items()):
|
324
|
+
agg_id, human_id = key
|
325
|
+
streak_length, avg_points = calculate_current_point_streak(session, human_id, filter_condition)
|
326
|
+
stat['current_point_streak'] = streak_length
|
327
|
+
stat['current_point_streak_avg_points'] = avg_points
|
328
|
+
if (idx + 1) % 100 == 0 or (idx + 1) == total_players:
|
329
|
+
progress.update(idx + 1)
|
330
|
+
else:
|
331
|
+
for key, stat in stats_dict.items():
|
332
|
+
agg_id, human_id = key
|
333
|
+
streak_length, avg_points = calculate_current_point_streak(session, human_id, filter_condition)
|
334
|
+
stat['current_point_streak'] = streak_length
|
335
|
+
stat['current_point_streak_avg_points'] = avg_points
|
284
336
|
|
285
337
|
# Calculate total_in_rank
|
286
338
|
total_in_rank = len(stats_dict)
|
@@ -292,6 +344,8 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
292
344
|
stats_dict[key][f'{field}_rank'] = rank
|
293
345
|
|
294
346
|
assign_ranks(stats_dict, 'games_played')
|
347
|
+
assign_ranks(stats_dict, 'games_participated') # Rank by total participation
|
348
|
+
assign_ranks(stats_dict, 'games_with_stats') # Rank by games with full stats
|
295
349
|
assign_ranks(stats_dict, 'goals')
|
296
350
|
assign_ranks(stats_dict, 'assists')
|
297
351
|
assign_ranks(stats_dict, 'points')
|
@@ -330,7 +384,11 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, debug_huma
|
|
330
384
|
skater_stat = StatsModel(
|
331
385
|
aggregation_id=aggregation_id,
|
332
386
|
human_id=human_id,
|
333
|
-
games_played=stat['games_played'],
|
387
|
+
games_played=stat['games_played'], # DEPRECATED - for backward compatibility
|
388
|
+
games_participated=stat['games_participated'], # Total games: FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
389
|
+
games_participated_rank=stat['games_participated_rank'],
|
390
|
+
games_with_stats=stat['games_with_stats'], # Games with full stats: FINAL, FINAL_SO only
|
391
|
+
games_with_stats_rank=stat['games_with_stats_rank'],
|
334
392
|
goals=stat['goals'],
|
335
393
|
assists=stat['assists'],
|
336
394
|
points=stat['goals'] + stat['assists'],
|
Binary file
|
{hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/models.py
RENAMED
@@ -99,6 +99,7 @@ class Goal(db.Model):
|
|
99
99
|
sequence_number = db.Column(db.Integer)
|
100
100
|
__table_args__ = (
|
101
101
|
db.UniqueConstraint('game_id', 'scoring_team_id', 'sequence_number', name='_goal_team_sequence_uc'),
|
102
|
+
db.UniqueConstraint('game_id', 'period', 'time', 'goal_scorer_id', 'scoring_team_id', name='uq_goals_no_duplicates'),
|
102
103
|
)
|
103
104
|
|
104
105
|
class Human(db.Model):
|
@@ -4,26 +4,34 @@ from datetime import datetime, timedelta
|
|
4
4
|
class ProgressTracker:
|
5
5
|
"""
|
6
6
|
Reusable progress tracker with ETA calculation for stats aggregation processes.
|
7
|
+
Supports optional custom counters for tracking additional metrics.
|
7
8
|
"""
|
8
|
-
|
9
|
-
def __init__(self, total_items, description="Processing"):
|
9
|
+
|
10
|
+
def __init__(self, total_items, description="Processing", custom_counters=None):
|
10
11
|
self.total_items = total_items
|
11
12
|
self.description = description
|
12
13
|
self.start_time = time.time()
|
13
14
|
self.processed_items = 0
|
14
15
|
self.last_update_time = self.start_time
|
16
|
+
self.custom_counters = custom_counters if custom_counters else {}
|
15
17
|
|
16
|
-
def update(self, processed_count=None):
|
18
|
+
def update(self, processed_count=None, **kwargs):
|
17
19
|
"""
|
18
20
|
Update progress. If processed_count is None, increment by 1.
|
21
|
+
kwargs can be used to update custom counters.
|
19
22
|
"""
|
20
23
|
if processed_count is not None:
|
21
24
|
self.processed_items = processed_count
|
22
25
|
else:
|
23
26
|
self.processed_items += 1
|
24
|
-
|
27
|
+
|
28
|
+
# Update custom counters
|
29
|
+
for key, value in kwargs.items():
|
30
|
+
if key in self.custom_counters:
|
31
|
+
self.custom_counters[key] = value
|
32
|
+
|
25
33
|
current_time = time.time()
|
26
|
-
|
34
|
+
|
27
35
|
# Only update display if at least 0.1 seconds have passed (to avoid spamming)
|
28
36
|
if current_time - self.last_update_time >= 0.1 or self.processed_items == self.total_items:
|
29
37
|
self._display_progress()
|
@@ -52,10 +60,15 @@ class ProgressTracker:
|
|
52
60
|
eta_str = self._format_time(eta_seconds)
|
53
61
|
|
54
62
|
elapsed_str = self._format_time(elapsed_time)
|
55
|
-
|
63
|
+
|
56
64
|
progress_msg = f"\r{self.description}: {self.processed_items}/{self.total_items} ({percentage:.1f}%) | "
|
57
65
|
progress_msg += f"Elapsed: {elapsed_str} | ETA: {eta_str}"
|
58
|
-
|
66
|
+
|
67
|
+
# Add custom counters to the display
|
68
|
+
if self.custom_counters:
|
69
|
+
counter_parts = [f"{key}: {value}" for key, value in self.custom_counters.items()]
|
70
|
+
progress_msg += " | " + ", ".join(counter_parts)
|
71
|
+
|
59
72
|
print(progress_msg, end="", flush=True)
|
60
73
|
|
61
74
|
# Add newline when complete
|
@@ -84,8 +97,9 @@ class ProgressTracker:
|
|
84
97
|
self.processed_items = self.total_items
|
85
98
|
self._display_progress()
|
86
99
|
|
87
|
-
def create_progress_tracker(total_items, description="Processing"):
|
100
|
+
def create_progress_tracker(total_items, description="Processing", custom_counters=None):
|
88
101
|
"""
|
89
102
|
Factory function to create a progress tracker.
|
103
|
+
custom_counters: dict of counter_name: initial_value for additional metrics to track
|
90
104
|
"""
|
91
|
-
return ProgressTracker(total_items, description)
|
105
|
+
return ProgressTracker(total_items, description, custom_counters)
|
@@ -52,8 +52,12 @@ class BaseStatsSkater(db.Model):
|
|
52
52
|
__abstract__ = True
|
53
53
|
id = db.Column(db.Integer, primary_key=True)
|
54
54
|
human_id = db.Column(db.Integer, db.ForeignKey('humans.id'), nullable=False)
|
55
|
-
games_played = db.Column(db.Integer, default=0)
|
55
|
+
games_played = db.Column(db.Integer, default=0) # DEPRECATED - use games_participated instead
|
56
56
|
games_played_rank = db.Column(db.Integer, default=0)
|
57
|
+
games_participated = db.Column(db.Integer, default=0) # Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
58
|
+
games_participated_rank = db.Column(db.Integer, default=0)
|
59
|
+
games_with_stats = db.Column(db.Integer, default=0) # Count only FINAL, FINAL_SO
|
60
|
+
games_with_stats_rank = db.Column(db.Integer, default=0)
|
57
61
|
goals = db.Column(db.Integer, default=0)
|
58
62
|
goals_rank = db.Column(db.Integer, default=0)
|
59
63
|
assists = db.Column(db.Integer, default=0)
|
@@ -104,8 +108,12 @@ class BaseStatsGoalie(db.Model):
|
|
104
108
|
__abstract__ = True
|
105
109
|
id = db.Column(db.Integer, primary_key=True)
|
106
110
|
human_id = db.Column(db.Integer, db.ForeignKey('humans.id'), nullable=False)
|
107
|
-
games_played = db.Column(db.Integer, default=0)
|
111
|
+
games_played = db.Column(db.Integer, default=0) # DEPRECATED - use games_participated instead
|
108
112
|
games_played_rank = db.Column(db.Integer, default=0)
|
113
|
+
games_participated = db.Column(db.Integer, default=0) # Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
114
|
+
games_participated_rank = db.Column(db.Integer, default=0)
|
115
|
+
games_with_stats = db.Column(db.Integer, default=0) # Count only FINAL, FINAL_SO
|
116
|
+
games_with_stats_rank = db.Column(db.Integer, default=0)
|
109
117
|
goals_allowed = db.Column(db.Integer, default=0)
|
110
118
|
goals_allowed_rank = db.Column(db.Integer, default=0)
|
111
119
|
goals_allowed_per_game = db.Column(db.Float, default=0.0)
|
@@ -137,8 +145,12 @@ class BaseStatsReferee(db.Model):
|
|
137
145
|
__abstract__ = True
|
138
146
|
id = db.Column(db.Integer, primary_key=True)
|
139
147
|
human_id = db.Column(db.Integer, db.ForeignKey('humans.id'), nullable=False)
|
140
|
-
games_reffed = db.Column(db.Integer, default=0)
|
148
|
+
games_reffed = db.Column(db.Integer, default=0) # DEPRECATED - use games_participated instead
|
141
149
|
games_reffed_rank = db.Column(db.Integer, default=0)
|
150
|
+
games_participated = db.Column(db.Integer, default=0) # Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
151
|
+
games_participated_rank = db.Column(db.Integer, default=0)
|
152
|
+
games_with_stats = db.Column(db.Integer, default=0) # Count only FINAL, FINAL_SO (for per-game averages)
|
153
|
+
games_with_stats_rank = db.Column(db.Integer, default=0)
|
142
154
|
penalties_given = db.Column(db.Integer, default=0)
|
143
155
|
penalties_given_rank = db.Column(db.Integer, default=0)
|
144
156
|
penalties_per_game = db.Column(db.Float, default=0.0)
|
@@ -170,8 +182,12 @@ class BaseStatsScorekeeper(db.Model):
|
|
170
182
|
__abstract__ = True
|
171
183
|
id = db.Column(db.Integer, primary_key=True)
|
172
184
|
human_id = db.Column(db.Integer, db.ForeignKey('humans.id'), nullable=False)
|
173
|
-
games_recorded = db.Column(db.Integer, default=0)
|
185
|
+
games_recorded = db.Column(db.Integer, default=0) # DEPRECATED - use games_participated instead
|
174
186
|
games_recorded_rank = db.Column(db.Integer, default=0)
|
187
|
+
games_participated = db.Column(db.Integer, default=0) # Count FINAL, FINAL_SO, FORFEIT, NOEVENTS
|
188
|
+
games_participated_rank = db.Column(db.Integer, default=0)
|
189
|
+
games_with_stats = db.Column(db.Integer, default=0) # Count only FINAL, FINAL_SO (for per-game averages)
|
190
|
+
games_with_stats_rank = db.Column(db.Integer, default=0)
|
175
191
|
sog_given = db.Column(db.Integer, default=0)
|
176
192
|
sog_given_rank = db.Column(db.Integer, default=0)
|
177
193
|
sog_per_game = db.Column(db.Float, default=0.0)
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name='hockey-blast-common-lib', # The name of your package
|
5
|
-
version='0.1.
|
5
|
+
version='0.1.63',
|
6
6
|
description='Common library for shared functionality and DB models',
|
7
7
|
author='Pavel Kletskov',
|
8
8
|
author_email='kletskov@gmail.com',
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/options.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/utils.py
RENAMED
File without changes
|
{hockey_blast_common_lib-0.1.61 → hockey_blast_common_lib-0.1.63}/hockey_blast_common_lib/wsgi.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|