hockey-blast-common-lib 0.1.49__py3-none-any.whl → 0.1.51__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.
@@ -56,6 +56,10 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
56
56
  else:
57
57
  raise ValueError("Invalid aggregation type")
58
58
 
59
+ # Delete existing items from the stats table
60
+ session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
61
+ session.commit()
62
+
59
63
  # Apply aggregation window filter
60
64
  if aggregation_window:
61
65
  last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
@@ -63,10 +67,10 @@ def aggregate_goalie_stats(session, aggregation_type, aggregation_id, names_to_f
63
67
  if start_datetime:
64
68
  game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime_str)
65
69
  filter_condition = filter_condition & game_window_filter
70
+ else:
71
+ #print(f"Warning: No valid start datetime for aggregation window '{aggregation_window}' for {aggregation_name}. No games will be included.")
72
+ return
66
73
 
67
- # Delete existing items from the stats table
68
- session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
69
- session.commit()
70
74
 
71
75
  # Filter for specific human_id if provided
72
76
  human_filter = []
@@ -0,0 +1,230 @@
1
+ import sys, os
2
+ from datetime import datetime
3
+
4
+ # Add the package directory to the Python path
5
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
6
+
7
+ from hockey_blast_common_lib.models import Game, GameRoster, Goal, Penalty
8
+ from hockey_blast_common_lib.h2h_models import H2HStats, H2HStatsMeta
9
+ from hockey_blast_common_lib.db_connection import create_session
10
+ from sqlalchemy.sql import func
11
+ from sqlalchemy import types
12
+
13
+ # Max games to process (set to None to process all)
14
+ MAX_GAMES_TO_PROCESS = None # Set to None to process all games
15
+
16
+ def aggregate_h2h_stats():
17
+ session = create_session("boss")
18
+ meta = None #session.query(H2HStatsMeta).order_by(H2HStatsMeta.id.desc()).first()
19
+ h2h_stats_dict = {} # (h1, h2) -> H2HStats instance
20
+ if meta is None or meta.last_run_timestamp is None or meta.last_processed_game_id is None:
21
+ # Full run: delete all existing stats and process all games
22
+ session.query(H2HStats).delete()
23
+ session.commit()
24
+ games_query = session.query(Game).order_by(Game.date, Game.time, Game.id)
25
+ print("No previous run found, deleted all existing H2H stats, processing all games...")
26
+ else:
27
+ # Incremental: only process games after last processed
28
+ # Load all existing stats into memory
29
+ for stat in session.query(H2HStats).all():
30
+ h2h_stats_dict[(stat.human1_id, stat.human2_id)] = stat
31
+ last_game = session.query(Game).filter(Game.id == meta.last_processed_game_id).first()
32
+ if last_game:
33
+ last_dt = datetime.combine(last_game.date, last_game.time)
34
+ games_query = session.query(Game).filter(
35
+ func.cast(func.concat(Game.date, ' ', Game.time), types.TIMESTAMP()) > last_dt
36
+ ).order_by(Game.date, Game.time, Game.id)
37
+ print(f"Resuming from game after id {meta.last_processed_game_id} ({last_dt})...")
38
+ else:
39
+ games_query = session.query(Game).order_by(Game.date, Game.time, Game.id)
40
+ print("Previous game id not found, processing all games...")
41
+
42
+ total_games = games_query.count()
43
+ print(f"Total games to process: {total_games}")
44
+ processed = 0
45
+ latest_game_id = None
46
+ for game in games_query:
47
+ if MAX_GAMES_TO_PROCESS is not None and processed >= MAX_GAMES_TO_PROCESS:
48
+ break
49
+ # --- Gather all relevant data for this game ---
50
+ # Get all GameRoster entries for this game
51
+ rosters = session.query(GameRoster).filter(GameRoster.game_id == game.id).all()
52
+ # Map: team_id -> set of human_ids (players)
53
+ team_to_players = {}
54
+ human_roles = {} # human_id -> set of roles in this game
55
+ for roster in rosters:
56
+ team_to_players.setdefault(roster.team_id, set()).add(roster.human_id)
57
+ human_roles.setdefault(roster.human_id, set()).add(roster.role)
58
+ # Get all human_ids in this game
59
+ all_humans = set(human_roles.keys())
60
+ # Add goalies from Game table (home/visitor)
61
+ if game.home_goalie_id:
62
+ all_humans.add(game.home_goalie_id)
63
+ human_roles.setdefault(game.home_goalie_id, set()).add('G')
64
+ if game.visitor_goalie_id:
65
+ all_humans.add(game.visitor_goalie_id)
66
+ human_roles.setdefault(game.visitor_goalie_id, set()).add('G')
67
+ # Add referees from Game table (NOT from roster!)
68
+ if game.referee_1_id:
69
+ all_humans.add(game.referee_1_id)
70
+ human_roles.setdefault(game.referee_1_id, set()).add('R')
71
+ if game.referee_2_id:
72
+ all_humans.add(game.referee_2_id)
73
+ human_roles.setdefault(game.referee_2_id, set()).add('R')
74
+ # --- Build all pairs of humans in this game ---
75
+ all_humans = list(all_humans)
76
+ for i in range(len(all_humans)):
77
+ for j in range(i+1, len(all_humans)):
78
+ h1, h2 = sorted([all_humans[i], all_humans[j]])
79
+ key = (h1, h2)
80
+ h2h = h2h_stats_dict.get(key)
81
+ if not h2h:
82
+ h2h = H2HStats(
83
+ human1_id=h1,
84
+ human2_id=h2,
85
+ first_game_id=game.id,
86
+ last_game_id=game.id,
87
+ games_together=0,
88
+ games_against=0,
89
+ games_tied_together=0,
90
+ games_tied_against=0,
91
+ wins_together=0,
92
+ losses_together=0,
93
+ h1_wins_vs_h2=0,
94
+ h2_wins_vs_h1=0,
95
+ games_h1_goalie=0,
96
+ games_h2_goalie=0,
97
+ games_h1_ref=0,
98
+ games_h2_ref=0,
99
+ games_both_referees=0,
100
+ goals_h1_when_together=0,
101
+ goals_h2_when_together=0,
102
+ assists_h1_when_together=0,
103
+ assists_h2_when_together=0,
104
+ penalties_h1_when_together=0,
105
+ penalties_h2_when_together=0,
106
+ gm_penalties_h1_when_together=0,
107
+ gm_penalties_h2_when_together=0,
108
+ h1_goalie_h2_scorer_goals=0,
109
+ h2_goalie_h1_scorer_goals=0,
110
+ shots_faced_h1_goalie_vs_h2=0,
111
+ shots_faced_h2_goalie_vs_h1=0,
112
+ goals_allowed_h1_goalie_vs_h2=0,
113
+ goals_allowed_h2_goalie_vs_h1=0,
114
+ save_percentage_h1_goalie_vs_h2=0.0,
115
+ save_percentage_h2_goalie_vs_h1=0.0,
116
+ h1_ref_h2_player_games=0,
117
+ h2_ref_h1_player_games=0,
118
+ h1_ref_penalties_on_h2=0,
119
+ h2_ref_penalties_on_h1=0,
120
+ h1_ref_gm_penalties_on_h2=0,
121
+ h2_ref_gm_penalties_on_h1=0,
122
+ penalties_given_both_refs=0,
123
+ gm_penalties_given_both_refs=0,
124
+ h1_shootout_attempts_vs_h2_goalie=0,
125
+ h1_shootout_goals_vs_h2_goalie=0,
126
+ h2_shootout_attempts_vs_h1_goalie=0,
127
+ h2_shootout_goals_vs_h1_goalie=0
128
+ )
129
+ h2h_stats_dict[key] = h2h
130
+ # Update first/last game ids
131
+ if game.id < h2h.first_game_id:
132
+ h2h.first_game_id = game.id
133
+ if game.id > h2h.last_game_id:
134
+ h2h.last_game_id = game.id
135
+ # --- Determine roles and teams ---
136
+ h1_roles = human_roles.get(h1, set())
137
+ h2_roles = human_roles.get(h2, set())
138
+ h1_team = None
139
+ h2_team = None
140
+ for team_id, players in team_to_players.items():
141
+ if h1 in players:
142
+ h1_team = team_id
143
+ if h2 in players:
144
+ h2_team = team_id
145
+ # --- General stats ---
146
+ h2h.games_together += 1 # Both present in this game
147
+ if h1_team and h2_team:
148
+ if h1_team == h2_team:
149
+ h2h.wins_together += int(_is_win(game, h1_team))
150
+ h2h.losses_together += int(_is_loss(game, h1_team))
151
+ if _is_tie(game):
152
+ h2h.games_tied_together += 1
153
+ else:
154
+ h2h.games_against += 1
155
+ if _is_win(game, h1_team):
156
+ h2h.h1_wins_vs_h2 += 1
157
+ if _is_win(game, h2_team):
158
+ h2h.h2_wins_vs_h1 += 1
159
+ if _is_tie(game):
160
+ h2h.games_tied_against += 1
161
+ # --- Role-specific stats ---
162
+ if 'G' in h1_roles:
163
+ h2h.games_h1_goalie += 1
164
+ if 'G' in h2_roles:
165
+ h2h.games_h2_goalie += 1
166
+ if 'R' in h1_roles:
167
+ h2h.games_h1_ref += 1
168
+ if 'R' in h2_roles:
169
+ h2h.games_h2_ref += 1
170
+ if 'R' in h1_roles and 'R' in h2_roles:
171
+ h2h.games_both_referees += 1
172
+ # --- Goals, assists, penalties ---
173
+ # Goals
174
+ goals = session.query(Goal).filter(Goal.game_id == game.id).all()
175
+ for goal in goals:
176
+ if goal.goal_scorer_id == h1 and (goal.assist_1_id == h2 or goal.assist_2_id == h2):
177
+ h2h.goals_h1_when_together += 1
178
+ if goal.goal_scorer_id == h2 and (goal.assist_1_id == h1 or goal.assist_2_id == h1):
179
+ h2h.goals_h2_when_together += 1
180
+ # Penalties
181
+ penalties = session.query(Penalty).filter(Penalty.game_id == game.id).all()
182
+ for pen in penalties:
183
+ if pen.penalized_player_id == h1:
184
+ h2h.penalties_h1_when_together += 1
185
+ if pen.penalty_minutes and 'GM' in pen.penalty_minutes:
186
+ h2h.gm_penalties_h1_when_together += 1
187
+ if pen.penalized_player_id == h2:
188
+ h2h.penalties_h2_when_together += 1
189
+ if pen.penalty_minutes and 'GM' in pen.penalty_minutes:
190
+ h2h.gm_penalties_h2_when_together += 1
191
+ # --- TODO: Add more detailed logic for goalie/skater, referee/player, shootouts, etc. ---
192
+ latest_game_id = game.id
193
+ processed += 1
194
+ if processed % 10 == 0 or processed == total_games:
195
+ print(f"\rProcessed {processed}/{total_games} games ({(processed/total_games)*100:.2f}%)", end="")
196
+ sys.stdout.flush()
197
+ # Commit all stats at once
198
+ session.query(H2HStats).delete()
199
+ session.add_all(list(h2h_stats_dict.values()))
200
+ session.commit()
201
+ print(f"\rProcessed {processed}/{total_games} games (100.00%)")
202
+ # Save/update meta
203
+ meta = H2HStatsMeta(
204
+ last_run_timestamp=datetime.utcnow(),
205
+ last_processed_game_id=latest_game_id
206
+ )
207
+ session.add(meta)
208
+ session.commit()
209
+ print("H2H aggregation complete.")
210
+
211
+ # --- Helper functions for win/loss/tie ---
212
+ def _is_win(game, team_id):
213
+ if team_id == game.home_team_id:
214
+ return (game.home_final_score or 0) > (game.visitor_final_score or 0)
215
+ if team_id == game.visitor_team_id:
216
+ return (game.visitor_final_score or 0) > (game.home_final_score or 0)
217
+ return False
218
+
219
+ def _is_loss(game, team_id):
220
+ if team_id == game.home_team_id:
221
+ return (game.home_final_score or 0) < (game.visitor_final_score or 0)
222
+ if team_id == game.visitor_team_id:
223
+ return (game.visitor_final_score or 0) < (game.home_final_score or 0)
224
+ return False
225
+
226
+ def _is_tie(game):
227
+ return (game.home_final_score is not None and game.visitor_final_score is not None and game.home_final_score == game.visitor_final_score)
228
+
229
+ if __name__ == "__main__":
230
+ aggregate_h2h_stats()
@@ -54,6 +54,10 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
54
54
  else:
55
55
  raise ValueError("Invalid aggregation type")
56
56
 
57
+ # Delete existing items from the stats table
58
+ session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
59
+ session.commit()
60
+
57
61
  # Apply aggregation window filter
58
62
  if aggregation_window:
59
63
  last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
@@ -61,10 +65,9 @@ def aggregate_human_stats(session, aggregation_type, aggregation_id, names_to_fi
61
65
  if start_datetime:
62
66
  game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime_str)
63
67
  filter_condition = filter_condition & game_window_filter
64
-
65
- # Delete existing items from the stats table
66
- session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
67
- session.commit()
68
+ else:
69
+ #print(f"Warning: No valid start datetime for aggregation window '{aggregation_window}' for {aggregation_name}. No games will be included.")
70
+ return
68
71
 
69
72
  # Filter for specific human_id if provided
70
73
  human_filter = []
@@ -48,12 +48,16 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_
48
48
  min_games = MIN_GAMES_FOR_LEVEL_STATS
49
49
  filter_condition = Division.level_id == aggregation_id
50
50
  # Add filter to only include games for the last 5 years
51
- five_years_ago = datetime.now() - timedelta(days=5*365)
52
- level_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP) >= five_years_ago
53
- filter_condition = filter_condition & level_window_filter
51
+ # five_years_ago = datetime.now() - timedelta(days=5*365)
52
+ # level_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP) >= five_years_ago
53
+ # filter_condition = filter_condition & level_window_filter
54
54
  else:
55
55
  raise ValueError("Invalid aggregation type")
56
56
 
57
+ # Delete existing items from the stats table
58
+ session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
59
+ session.commit()
60
+
57
61
  # Apply aggregation window filter
58
62
  if aggregation_window:
59
63
  last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
@@ -61,11 +65,11 @@ def aggregate_referee_stats(session, aggregation_type, aggregation_id, names_to_
61
65
  if start_datetime:
62
66
  game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime_str)
63
67
  filter_condition = filter_condition & game_window_filter
68
+ else:
69
+ #print(f"Warning: No valid start datetime for aggregation window '{aggregation_window}' for {aggregation_name}. No games will be included.")
70
+ return
64
71
 
65
- # Delete existing items from the stats table
66
- session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
67
- session.commit()
68
-
72
+ filter_condition = filter_condition & (Division.id == Game.division_id)
69
73
  # Aggregate games reffed for each referee
70
74
  games_reffed_stats = session.query(
71
75
  Game.referee_1_id.label('human_id'),
@@ -239,4 +243,4 @@ def run_aggregate_referee_stats():
239
243
  aggregate_referee_stats(session, aggregation_type='level', aggregation_id=level_id, names_to_filter_out=not_human_names)
240
244
 
241
245
  if __name__ == "__main__":
242
- run_aggregate_referee_stats()
246
+ run_aggregate_referee_stats()
@@ -65,6 +65,10 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, names_to_f
65
65
  else:
66
66
  raise ValueError("Invalid aggregation type")
67
67
 
68
+ # Delete existing items from the stats table
69
+ session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
70
+ session.commit()
71
+
68
72
  # Apply aggregation window filter
69
73
  if aggregation_window:
70
74
  last_game_datetime_str = session.query(func.max(func.concat(Game.date, ' ', Game.time))).filter(filter_condition, Game.status.like('Final%')).scalar()
@@ -72,11 +76,10 @@ def aggregate_skater_stats(session, aggregation_type, aggregation_id, names_to_f
72
76
  if start_datetime:
73
77
  game_window_filter = func.cast(func.concat(Game.date, ' ', Game.time), sqlalchemy.types.TIMESTAMP).between(start_datetime, last_game_datetime_str)
74
78
  filter_condition = filter_condition & game_window_filter
75
-
76
- # Delete existing items from the stats table
77
- session.query(StatsModel).filter(StatsModel.aggregation_id == aggregation_id).delete()
78
- session.commit()
79
-
79
+ else:
80
+ #print(f"Warning: No valid start datetime for aggregation window '{aggregation_window}' for {aggregation_name}. No games will be included.")
81
+ return
82
+
80
83
  # Filter for specific human_id if provided
81
84
  human_filter = []
82
85
  # if debug_human_id:
@@ -0,0 +1,77 @@
1
+ from hockey_blast_common_lib.models import db
2
+
3
+ class H2HStats(db.Model):
4
+ __tablename__ = 'h2h_stats'
5
+ id = db.Column(db.Integer, primary_key=True)
6
+ human1_id = db.Column(db.Integer, db.ForeignKey('humans.id'), nullable=False)
7
+ human2_id = db.Column(db.Integer, db.ForeignKey('humans.id'), nullable=False)
8
+ # Always store with human1_id < human2_id for uniqueness
9
+ __table_args__ = (
10
+ db.UniqueConstraint('human1_id', 'human2_id', name='_h2h_human_pair_uc'),
11
+ db.Index('ix_h2h_human_pair', 'human1_id', 'human2_id'), # Composite index for fast lookup
12
+ )
13
+
14
+ # General
15
+ games_together = db.Column(db.Integer, default=0, nullable=False) # Games where both played (any role, any team)
16
+ games_against = db.Column(db.Integer, default=0, nullable=False) # Games where both played on opposing teams
17
+ games_tied_together = db.Column(db.Integer, default=0, nullable=False)
18
+ games_tied_against = db.Column(db.Integer, default=0, nullable=False) # Games against each other that ended in a tie
19
+ wins_together = db.Column(db.Integer, default=0, nullable=False) # Games both played on same team and won
20
+ losses_together = db.Column(db.Integer, default=0, nullable=False) # Games both played on same team and lost
21
+ h1_wins_vs_h2 = db.Column(db.Integer, default=0, nullable=False) # Games h1's team won vs h2's team
22
+ h2_wins_vs_h1 = db.Column(db.Integer, default=0, nullable=False) # Games h2's team won vs h1's team
23
+
24
+ # Role-specific counts
25
+ games_h1_goalie = db.Column(db.Integer, default=0, nullable=False) # Games where h1 was a goalie and h2 played
26
+ games_h2_goalie = db.Column(db.Integer, default=0, nullable=False) # Games where h2 was a goalie and h1 played
27
+ games_h1_ref = db.Column(db.Integer, default=0, nullable=False) # Games where h1 was a referee and h2 played
28
+ games_h2_ref = db.Column(db.Integer, default=0, nullable=False) # Games where h2 was a referee and h1 played
29
+ games_both_referees = db.Column(db.Integer, default=0, nullable=False) # Games where both were referees
30
+
31
+ # Goals/Assists/Penalties (when both played)
32
+ goals_h1_when_together = db.Column(db.Integer, default=0, nullable=False) # Goals by h1 when both played
33
+ goals_h2_when_together = db.Column(db.Integer, default=0, nullable=False) # Goals by h2 when both played
34
+ assists_h1_when_together = db.Column(db.Integer, default=0, nullable=False) # Assists by h1 when both played
35
+ assists_h2_when_together = db.Column(db.Integer, default=0, nullable=False) # Assists by h2 when both played
36
+ penalties_h1_when_together = db.Column(db.Integer, default=0, nullable=False) # Penalties on h1 when both played
37
+ penalties_h2_when_together = db.Column(db.Integer, default=0, nullable=False) # Penalties on h2 when both played
38
+ gm_penalties_h1_when_together = db.Column(db.Integer, default=0, nullable=False) # GM penalties on h1 when both played
39
+ gm_penalties_h2_when_together = db.Column(db.Integer, default=0, nullable=False) # GM penalties on h2 when both played
40
+
41
+ # Goalie/Skater head-to-head (when one is goalie, other is skater on opposing team)
42
+ h1_goalie_h2_scorer_goals = db.Column(db.Integer, default=0, nullable=False) # Goals scored by h2 against h1 as goalie
43
+ h2_goalie_h1_scorer_goals = db.Column(db.Integer, default=0, nullable=False) # Goals scored by h1 against h2 as goalie
44
+ shots_faced_h1_goalie_vs_h2 = db.Column(db.Integer, default=0, nullable=False) # Shots faced by h1 as goalie vs h2 as skater
45
+ shots_faced_h2_goalie_vs_h1 = db.Column(db.Integer, default=0, nullable=False) # Shots faced by h2 as goalie vs h1 as skater
46
+ goals_allowed_h1_goalie_vs_h2 = db.Column(db.Integer, default=0, nullable=False) # Goals allowed by h1 as goalie vs h2 as skater
47
+ goals_allowed_h2_goalie_vs_h1 = db.Column(db.Integer, default=0, nullable=False) # Goals allowed by h2 as goalie vs h1 as skater
48
+ save_percentage_h1_goalie_vs_h2 = db.Column(db.Float, default=0.0, nullable=False) # Save % by h1 as goalie vs h2 as skater
49
+ save_percentage_h2_goalie_vs_h1 = db.Column(db.Float, default=0.0, nullable=False) # Save % by h2 as goalie vs h1 as skater
50
+
51
+ # Referee/Player
52
+ h1_ref_h2_player_games = db.Column(db.Integer, default=0, nullable=False) # Games h1 was referee, h2 was player
53
+ h2_ref_h1_player_games = db.Column(db.Integer, default=0, nullable=False) # Games h2 was referee, h1 was player
54
+ h1_ref_penalties_on_h2 = db.Column(db.Integer, default=0, nullable=False) # Penalties given by h1 (as ref) to h2
55
+ h2_ref_penalties_on_h1 = db.Column(db.Integer, default=0, nullable=False) # Penalties given by h2 (as ref) to h1
56
+ h1_ref_gm_penalties_on_h2 = db.Column(db.Integer, default=0, nullable=False) # GM penalties given by h1 (as ref) to h2
57
+ h2_ref_gm_penalties_on_h1 = db.Column(db.Integer, default=0, nullable=False) # GM penalties given by h2 (as ref) to h1
58
+
59
+ # Both referees (when both are referees in the same game)
60
+ penalties_given_both_refs = db.Column(db.Integer, default=0, nullable=False) # Total penalties given by both
61
+ gm_penalties_given_both_refs = db.Column(db.Integer, default=0, nullable=False) # Total GM penalties given by both
62
+
63
+ # Shootouts
64
+ h1_shootout_attempts_vs_h2_goalie = db.Column(db.Integer, default=0, nullable=False) # h1 shootout attempts vs h2 as goalie
65
+ h1_shootout_goals_vs_h2_goalie = db.Column(db.Integer, default=0, nullable=False) # h1 shootout goals vs h2 as goalie
66
+ h2_shootout_attempts_vs_h1_goalie = db.Column(db.Integer, default=0, nullable=False) # h2 shootout attempts vs h1 as goalie
67
+ h2_shootout_goals_vs_h1_goalie = db.Column(db.Integer, default=0, nullable=False) # h2 shootout goals vs h1 as goalie
68
+
69
+ # First and last game IDs where both were present
70
+ first_game_id = db.Column(db.Integer, nullable=False) # Game.id of the first game where both were present
71
+ last_game_id = db.Column(db.Integer, nullable=False) # Game.id of the most recent game where both were present
72
+
73
+ class H2HStatsMeta(db.Model):
74
+ __tablename__ = 'h2h_stats_meta'
75
+ id = db.Column(db.Integer, primary_key=True)
76
+ last_run_timestamp = db.Column(db.DateTime, nullable=True) # When the h2h stats were last updated
77
+ last_processed_game_id = db.Column(db.Integer, nullable=True) # Game.id of the latest processed game
@@ -85,13 +85,22 @@ def get_fake_human_for_stats(session):
85
85
  return human.id
86
86
 
87
87
  def get_start_datetime(last_game_datetime_str, aggregation_window):
88
+ if aggregation_window == 'Weekly':
89
+ if last_game_datetime_str:
90
+ last_game_datetime = datetime.strptime(last_game_datetime_str, '%Y-%m-%d %H:%M:%S')
91
+ # Check if the last game datetime is over 1 week from now
92
+ if datetime.now() - last_game_datetime > timedelta(weeks=1):
93
+ return None
94
+ # Use current time as the start of the weekly window
95
+ return datetime.now() - timedelta(weeks=1)
88
96
  if last_game_datetime_str:
89
97
  last_game_datetime = datetime.strptime(last_game_datetime_str, '%Y-%m-%d %H:%M:%S')
90
98
  if aggregation_window == 'Daily':
99
+ # Check if the last game datetime is over 24 hours from now
100
+ if datetime.now() - last_game_datetime > timedelta(hours=24):
101
+ return None
91
102
  # From 10AM till midnight, 14 hours to avoid last day games
92
103
  return last_game_datetime - timedelta(hours=14)
93
- elif aggregation_window == 'Weekly':
94
- return last_game_datetime - timedelta(weeks=1)
95
104
  return None
96
105
 
97
106
  def assign_ranks(stats_dict, field, reverse_rank=False):
@@ -6,6 +6,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
6
6
  from flask_migrate import Migrate
7
7
  from flask import Flask
8
8
  from hockey_blast_common_lib.models import *
9
+ from hockey_blast_common_lib.h2h_models import *
9
10
  from hockey_blast_common_lib.stats_models import *
10
11
  from hockey_blast_common_lib.stats_models import db
11
12
  from hockey_blast_common_lib.db_connection import get_db_params
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hockey-blast-common-lib
3
- Version: 0.1.49
3
+ Version: 0.1.51
4
4
  Summary: Common library for shared functionality and DB models
5
5
  Author: Pavel Kletskov
6
6
  Author-email: kletskov@gmail.com
@@ -1,13 +1,15 @@
1
1
  hockey_blast_common_lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  hockey_blast_common_lib/aggregate_all_stats.py,sha256=2bOj2BW0k3ZPQR1NH04upnkIfO9SastzTz7XwO3ujYo,1104
3
- hockey_blast_common_lib/aggregate_goalie_stats.py,sha256=FcYL40NP0-sPY7UI7F8Ny_RaPKz3mfkmhQnPVbeRtOc,12178
4
- hockey_blast_common_lib/aggregate_human_stats.py,sha256=VmUAMbqtWRPFM64V8ECJ3eJjRXvblNbeVU9HFYNoPp0,23898
5
- hockey_blast_common_lib/aggregate_referee_stats.py,sha256=FszWLTygddDQNcUgbmevQ-eGPrW8Y0nXpRvUluPRKnU,11920
6
- hockey_blast_common_lib/aggregate_skater_stats.py,sha256=FBc6enJNnFXYg7mVPiXssleTA1vZRAXCTFUa-3eOGdo,17199
3
+ hockey_blast_common_lib/aggregate_goalie_stats.py,sha256=_9lP5xNX_QcIge-sHbqIlt7mKcFJBGVBbr33Rtf3LAY,12365
4
+ hockey_blast_common_lib/aggregate_h2h_stats.py,sha256=Hqhca_OyA9BwLOLU488RcPuZZgpAW0gx4G8EhD7Noy8,11166
5
+ hockey_blast_common_lib/aggregate_human_stats.py,sha256=uBNzXbFsK5PhPg-5wjH2iuJ7VPpA7MWKFHx90rMbhjQ,24084
6
+ hockey_blast_common_lib/aggregate_referee_stats.py,sha256=dSl0LEpCzyzpD_tv8yw07NLBImo2RP_xuUQZK_p5kFs,12189
7
+ hockey_blast_common_lib/aggregate_skater_stats.py,sha256=MU4ICUUtDDKhdeb4sc0UPzjREwSB0rFZuJqRtIDedDU,17393
7
8
  hockey_blast_common_lib/assign_skater_skill.py,sha256=p-0fbodGpM8BCjKHDpxNb7BH2FcIlBsJwON844KNrUY,1817
8
9
  hockey_blast_common_lib/db_connection.py,sha256=HvPxDvOj7j5H85RfslGvHVNevfg7mKCd0syJ6NX21mU,1890
9
10
  hockey_blast_common_lib/dump_sample_db.sh,sha256=MY3lnzTXBoWd76-ZlZr9nWsKMEVgyRsUn-LZ2d1JWZs,810
10
- hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=UXr0ka2t3NzBogn8a7J3QiAWaGnhcfyiJ59BbVoLi3E,1256782
11
+ hockey_blast_common_lib/h2h_models.py,sha256=inB_QAm8Unkc0QRVibiw-Wf8yebNk8zhwxF9EZGMNKM,6350
12
+ hockey_blast_common_lib/hockey_blast_sample_backup.sql.gz,sha256=6_atrXLDO31Qyc9CnangK-xywBHIAI3Ztvng-PZXe6U,4648905
11
13
  hockey_blast_common_lib/models.py,sha256=nbJjypa2OBO5_fwjAbWVgBp4WDCuE-RtaELzJ9cvqB4,16468
12
14
  hockey_blast_common_lib/options.py,sha256=2L4J9rKCKr58om34259D3_s7kbPdknMSwoo6IwTNnx0,849
13
15
  hockey_blast_common_lib/restore_sample_db.sh,sha256=7W3lzRZeu9zXIu1Bvtnaw8EHc1ulHmFM4mMh86oUQJo,2205
@@ -15,9 +17,9 @@ hockey_blast_common_lib/skills_in_divisions.py,sha256=m-UEwMwn1KM7wOYvDstgsOEeH5
15
17
  hockey_blast_common_lib/skills_propagation.py,sha256=x6yy7fJ6IX3YiHqiP_v7-p_S2Expb8JJ-mWuajEFBdY,16388
16
18
  hockey_blast_common_lib/stats_models.py,sha256=NWigeIowIJU6o1Sk1cP08kEy4t594LZpecKUnl-O6as,25552
17
19
  hockey_blast_common_lib/stats_utils.py,sha256=DXsPO4jw8XsdRUN46TGF_IiBAfz3GCIVBswCGp5ELDk,284
18
- hockey_blast_common_lib/utils.py,sha256=EORIX_yqvqAHdoP33yI4Uey3JoPg_CBHUL1xAhksRpY,5351
19
- hockey_blast_common_lib/wsgi.py,sha256=7LGUzioigviJp-EUhSEaQcd4jBae0mxbkyBscQfZhlc,730
20
- hockey_blast_common_lib-0.1.49.dist-info/METADATA,sha256=Ezd_kdOghO-mioTD3CtwTYsyIviEQiEVsH5PkP3W1fk,318
21
- hockey_blast_common_lib-0.1.49.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
22
- hockey_blast_common_lib-0.1.49.dist-info/top_level.txt,sha256=wIR4LIkE40npoA2QlOdfCYlgFeGbsHR8Z6r0h46Vtgc,24
23
- hockey_blast_common_lib-0.1.49.dist-info/RECORD,,
20
+ hockey_blast_common_lib/utils.py,sha256=PduHp6HoI4sfr5HvJfQAaz7170dy5kTFVdIfWvBR-Jg,5874
21
+ hockey_blast_common_lib/wsgi.py,sha256=y3NxoJfWjdzX3iP7RGvDEer6zcnPyCanpqSgW1BlXgg,779
22
+ hockey_blast_common_lib-0.1.51.dist-info/METADATA,sha256=wr1jHyXwbpWDlzqSguE3UWILsSlrcu3xgjwZ_IdoQ3A,318
23
+ hockey_blast_common_lib-0.1.51.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
24
+ hockey_blast_common_lib-0.1.51.dist-info/top_level.txt,sha256=wIR4LIkE40npoA2QlOdfCYlgFeGbsHR8Z6r0h46Vtgc,24
25
+ hockey_blast_common_lib-0.1.51.dist-info/RECORD,,