hockey-blast-common-lib 0.1.55__py3-none-any.whl → 0.1.57__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.
@@ -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, Goal, Penalty, GameRoster, Organization, Division, Human, Level
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, case
13
- from hockey_blast_common_lib.options import not_human_names, parse_args, MIN_GAMES_FOR_ORG_STATS, MIN_GAMES_FOR_DIVISION_STATS, MIN_GAMES_FOR_LEVEL_STATS
14
- from hockey_blast_common_lib.utils import get_org_id_from_alias, get_human_ids_by_names, get_division_ids_for_last_season_in_all_leagues, get_all_division_ids_for_org, get_start_datetime
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
- human_filter = []
78
- # if debug_human_id:
79
- # human_filter = [GameRoster.human_id == debug_human_id]
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
+ human_ids_to_filter = get_human_ids_by_names(session, names_to_filter_out)
56
+
57
+ if aggregation_type == 'org':
58
+ # Only process scorekeeper stats for ALL_ORGS_ID - skip individual organizations
59
+ if aggregation_id != ALL_ORGS_ID:
60
+ return # Skip processing for individual organization IDs
61
+
62
+ aggregation_name = "All Orgs"
63
+ filter_condition = sqlalchemy.true() # No filter for organization
64
+ print(f"Aggregating scorekeeper stats for {aggregation_name} with window {aggregation_window}...")
65
+ if aggregation_window == 'Daily':
66
+ StatsModel = OrgStatsDailyScorekeeper
67
+ elif aggregation_window == 'Weekly':
68
+ StatsModel = OrgStatsWeeklyScorekeeper
69
+ else:
70
+ StatsModel = OrgStatsScorekeeper
71
+ min_games = MIN_GAMES_FOR_ORG_STATS
72
+ else:
73
+ raise ValueError("Invalid aggregation type")
74
+
75
+ # Delete existing items from the stats table
76
+ session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
77
+ session.commit()
78
+
79
+ # Apply aggregation window filter
80
+ if aggregation_window:
81
+ last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
82
+ start_datetime = get_start_datetime(last_game_datetime_str, aggregation_window)
83
+ if start_datetime:
84
+ game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime_str)
85
+ filter_condition = filter_condition & game_window_filter
86
+ else:
87
+ return
88
+
89
+
90
+ # Aggregate scorekeeper quality data for each human
91
+ scorekeeper_quality_stats = session.query(
92
+ ScorekeeperSaveQuality.scorekeeper_id.label('human_id'),
93
+ func.count(ScorekeeperSaveQuality.game_id).label('games_recorded'),
94
+ func.sum(ScorekeeperSaveQuality.total_saves_recorded).label('total_saves_recorded'),
95
+ func.avg(ScorekeeperSaveQuality.total_saves_recorded).label('avg_saves_per_game'),
96
+ func.avg(ScorekeeperSaveQuality.max_saves_per_5sec).label('avg_max_saves_per_5sec'),
97
+ func.avg(ScorekeeperSaveQuality.max_saves_per_20sec).label('avg_max_saves_per_20sec'),
98
+ func.max(ScorekeeperSaveQuality.max_saves_per_5sec).label('peak_max_saves_per_5sec'),
99
+ func.max(ScorekeeperSaveQuality.max_saves_per_20sec).label('peak_max_saves_per_20sec'),
100
+ func.array_agg(ScorekeeperSaveQuality.game_id).label('game_ids')
101
+ ).join(Game, Game.id == ScorekeeperSaveQuality.game_id)
102
+
103
+
104
+ scorekeeper_quality_stats = scorekeeper_quality_stats.filter(filter_condition).group_by(ScorekeeperSaveQuality.scorekeeper_id).all()
105
+
106
+ # Combine the results
107
+ stats_dict = {}
108
+ for stat in scorekeeper_quality_stats:
109
+ if stat.human_id in human_ids_to_filter or stat.human_id is None:
110
+ continue
111
+ key = (aggregation_id, stat.human_id)
112
+
113
+ # Calculate quality score
114
+ quality_score = calculate_quality_score(
115
+ stat.avg_max_saves_per_5sec or 0.0,
116
+ stat.avg_max_saves_per_20sec or 0.0,
117
+ stat.peak_max_saves_per_5sec or 0,
118
+ stat.peak_max_saves_per_20sec or 0
119
+ )
120
+
121
+ stats_dict[key] = {
122
+ 'games_recorded': stat.games_recorded,
123
+ 'sog_given': stat.total_saves_recorded, # Legacy field name mapping
124
+ 'sog_per_game': stat.avg_saves_per_game or 0.0, # Legacy field name mapping
125
+ 'total_saves_recorded': stat.total_saves_recorded,
126
+ 'avg_saves_per_game': stat.avg_saves_per_game or 0.0,
127
+ 'avg_max_saves_per_5sec': stat.avg_max_saves_per_5sec or 0.0,
128
+ 'avg_max_saves_per_20sec': stat.avg_max_saves_per_20sec or 0.0,
129
+ 'peak_max_saves_per_5sec': stat.peak_max_saves_per_5sec or 0,
130
+ 'peak_max_saves_per_20sec': stat.peak_max_saves_per_20sec or 0,
131
+ 'quality_score': quality_score,
132
+ 'game_ids': stat.game_ids,
133
+ 'first_game_id': None,
134
+ 'last_game_id': None
135
+ }
136
+
137
+ # Filter out entries with games_recorded less than min_games
138
+ stats_dict = {key: value for key, value in stats_dict.items() if value['games_recorded'] >= min_games}
139
+
140
+ # Populate first_game_id and last_game_id
141
+ for key, stat in stats_dict.items():
142
+ all_game_ids = stat['game_ids']
143
+ if all_game_ids:
144
+ first_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date, Game.time).first()
145
+ last_game = session.query(Game).filter(Game.id.in_(all_game_ids)).order_by(Game.date.desc(), Game.time.desc()).first()
146
+ stat['first_game_id'] = first_game.id if first_game else None
147
+ stat['last_game_id'] = last_game.id if last_game else None
148
+
149
+ # Calculate total_in_rank
150
+ total_in_rank = len(stats_dict)
151
+
152
+ # Assign ranks - note: for quality metrics, lower values are better (reverse_rank=True for avg and peak clicking)
153
+ assign_ranks(stats_dict, 'games_recorded')
154
+ assign_ranks(stats_dict, 'sog_given') # Legacy field
155
+ assign_ranks(stats_dict, 'sog_per_game') # Legacy field
156
+ assign_ranks(stats_dict, 'total_saves_recorded')
157
+ assign_ranks(stats_dict, 'avg_saves_per_game')
158
+ assign_ranks(stats_dict, 'avg_max_saves_per_5sec', reverse_rank=True) # Lower is better (less clicking)
159
+ assign_ranks(stats_dict, 'avg_max_saves_per_20sec', reverse_rank=True) # Lower is better
160
+ assign_ranks(stats_dict, 'peak_max_saves_per_5sec', reverse_rank=True) # Lower is better
161
+ assign_ranks(stats_dict, 'peak_max_saves_per_20sec', reverse_rank=True) # Lower is better
162
+ assign_ranks(stats_dict, 'quality_score', reverse_rank=True) # Lower is better (less problematic)
163
+
164
+ # Insert aggregated stats into the appropriate table with progress output
165
+ total_items = len(stats_dict)
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()
@@ -104,14 +104,18 @@ class Goal(db.Model):
104
104
  class Human(db.Model):
105
105
  __tablename__ = 'humans'
106
106
  id = db.Column(db.Integer, primary_key=True)
107
- first_name = db.Column(db.String(100))
108
- middle_name = db.Column(db.String(100))
109
- last_name = db.Column(db.String(100))
107
+ # All name components now declared non-nullable at the ORM level. Ensure data cleanup
108
+ # (convert existing NULLs to '') BEFORE applying a DB migration that enforces NOT NULL.
109
+ # middle_name and suffix may be logically "empty" but must not be NULL; use '' for absence.
110
+ first_name = db.Column(db.String(100), nullable=False, default='')
111
+ middle_name = db.Column(db.String(100), nullable=False, default='')
112
+ last_name = db.Column(db.String(100), nullable=False, default='')
113
+ suffix = db.Column(db.String(100), nullable=False, default='')
110
114
  first_date = db.Column(db.Date)
111
115
  last_date = db.Column(db.Date)
112
116
  skater_skill_value = db.Column(db.Float, nullable=True)
113
117
  __table_args__ = (
114
- db.UniqueConstraint('first_name', 'middle_name', 'last_name', name='_human_name_uc'),
118
+ db.UniqueConstraint('first_name', 'middle_name', 'last_name', 'suffix', name='_human_name_uc'),
115
119
  )
116
120
 
117
121
  class HumanAlias(db.Model):
@@ -121,10 +125,11 @@ class HumanAlias(db.Model):
121
125
  first_name = db.Column(db.String(100))
122
126
  middle_name = db.Column(db.String(100))
123
127
  last_name = db.Column(db.String(100))
128
+ suffix = db.Column(db.String(100))
124
129
  first_date = db.Column(db.Date)
125
130
  last_date = db.Column(db.Date)
126
131
  __table_args__ = (
127
- db.UniqueConstraint('human_id', 'first_name', 'middle_name', 'last_name', name='_human_alias_uc'),
132
+ db.UniqueConstraint('human_id', 'first_name', 'middle_name', 'last_name', 'suffix', name='_human_alias_uc'),
128
133
  )
129
134
 
130
135
  class HumanInTTS(db.Model):
@@ -15,7 +15,9 @@ not_human_names = [
15
15
  (None, "Unknown", None),
16
16
  ("Not", None , None),
17
17
  (None , None, "Goalie"),
18
- ("Unassigned",None , None)
18
+ ("Unassigned",None , None),
19
+ # Critical addition - primary cause of 93.4% of roster insertion errors
20
+ ("Not", "Signed", "In")
19
21
  ]
20
22
 
21
23
  def parse_args():
@@ -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'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hockey-blast-common-lib
3
- Version: 0.1.55
3
+ Version: 0.1.57
4
4
  Summary: Common library for shared functionality and DB models
5
5
  Author: Pavel Kletskov
6
6
  Author-email: kletskov@gmail.com
@@ -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=2bOj2BW0k3ZPQR1NH04upnkIfO9SastzTz7XwO3ujYo,1104
3
- hockey_blast_common_lib/aggregate_goalie_stats.py,sha256=0pJOT6nKgFYmSTWA3HY-BKARgc7l6czqvzWfwNQMad0,14346
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=bE7pjSgMi35r9hX6qqsH_r38zJ0DXSr9unb1nyL2Jlw,11722
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=hWju95cPNMukwns66g0CTK3XUFShrMDN-FzRjpzqLPQ,4648902
14
- hockey_blast_common_lib/models.py,sha256=NBF0u4wTAwxFe5UaUObFo-L9Az2jLPXbKejtcJRuGQ0,18440
15
- hockey_blast_common_lib/options.py,sha256=alOXCYF7Jmw4Z97ae6tAsDIS0VNideV_-GKJ4-v4Pjo,1689
14
+ hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=B7MqFsTHYSi1HTIb5jV7xuIu1TTFlvoeMu7_lC-6n9E,4648905
15
+ hockey_blast_common_lib/models.py,sha256=JMwQE1QgNkuWuKakxuHIlbEHfk8Z7GAYktCppCWA6LE,18928
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=uBNQSqCMXurzS-tD13OoV5WqurYYGHMZMHk1CeA5jgI,26104
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.55.dist-info/METADATA,sha256=fl85rh700VSbNViKgFuvQFhsvJbJUq3BwYlzQeTzko8,318
25
- hockey_blast_common_lib-0.1.55.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
26
- hockey_blast_common_lib-0.1.55.dist-info/top_level.txt,sha256=wIR4LIkE40npoA2QlOdfCYlgFeGbsHR8Z6r0h46Vtgc,24
27
- hockey_blast_common_lib-0.1.55.dist-info/RECORD,,
25
+ hockey_blast_common_lib-0.1.57.dist-info/METADATA,sha256=JDBDQSrlR8qwho-9RInI8J9XosXE_D5ZlISRqfyhSlU,318
26
+ hockey_blast_common_lib-0.1.57.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
27
+ hockey_blast_common_lib-0.1.57.dist-info/top_level.txt,sha256=wIR4LIkE40npoA2QlOdfCYlgFeGbsHR8Z6r0h46Vtgc,24
28
+ hockey_blast_common_lib-0.1.57.dist-info/RECORD,,