From 06794c27a1770a24f85f1972664334d1d7f9203d Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Thu, 5 Mar 2026 13:34:43 -0600 Subject: [PATCH] fix: remove commented-out dead code blocks (#31) Co-Authored-By: Claude Sonnet 4.6 --- app/db_engine.py | 1305 +++++++++++++++++++------------- app/dependencies.py | 252 +++--- app/main.py | 66 +- app/routers_v3/battingstats.py | 317 +++++--- app/routers_v3/decisions.py | 158 ++-- 5 files changed, 1249 insertions(+), 849 deletions(-) diff --git a/app/db_engine.py b/app/db_engine.py index 3b7af32..a0df464 100644 --- a/app/db_engine.py +++ b/app/db_engine.py @@ -11,51 +11,32 @@ from peewee import ModelSelect from playhouse.shortcuts import model_to_dict # Database configuration - supports both SQLite and PostgreSQL -DATABASE_TYPE = os.environ.get('DATABASE_TYPE', 'sqlite') +DATABASE_TYPE = os.environ.get("DATABASE_TYPE", "sqlite") -if DATABASE_TYPE.lower() == 'postgresql': +if DATABASE_TYPE.lower() == "postgresql": from playhouse.pool import PooledPostgresqlDatabase + db = PooledPostgresqlDatabase( - os.environ.get('POSTGRES_DB', 'sba_master'), - user=os.environ.get('POSTGRES_USER', 'sba_admin'), - password=os.environ.get('POSTGRES_PASSWORD', 'sba_dev_password_2024'), - host=os.environ.get('POSTGRES_HOST', 'sba_postgres'), - port=int(os.environ.get('POSTGRES_PORT', '5432')), + os.environ.get("POSTGRES_DB", "sba_master"), + user=os.environ.get("POSTGRES_USER", "sba_admin"), + password=os.environ.get("POSTGRES_PASSWORD", "sba_dev_password_2024"), + host=os.environ.get("POSTGRES_HOST", "sba_postgres"), + port=int(os.environ.get("POSTGRES_PORT", "5432")), max_connections=20, stale_timeout=300, # 5 minutes timeout=0, autoconnect=True, - autorollback=True # Automatically rollback failed transactions + autorollback=True, # Automatically rollback failed transactions ) else: # Default SQLite configuration db = SqliteDatabase( - 'storage/sba_master.db', - pragmas={ - 'journal_mode': 'wal', - 'cache_size': -1 * 64000, - 'synchronous': 0 - } + "storage/sba_master.db", + pragmas={"journal_mode": "wal", "cache_size": -1 * 64000, "synchronous": 0}, ) -date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' -logger = logging.getLogger('discord_app') - - -""" -Per season updates: - Result: regular_season & post_season - set season length - update_standings - confirm division alignments and records - Standings: recalculate - e_number function, set season length - - wildcard section, set league abbrevs -""" - - -WEEK_NUMS = { - 'regular': { - - } -} +date = f"{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}" +logger = logging.getLogger("discord_app") def model_csv_headers(this_obj, exclude=None) -> List: @@ -70,7 +51,7 @@ def model_to_csv(this_obj, exclude=None) -> List: def query_to_csv(all_items: ModelSelect, exclude=None): if all_items.count() == 0: - data_list = [['No data found']] + data_list = [["No data found"]] else: data_list = [model_csv_headers(all_items[0], exclude=exclude)] for x in all_items: @@ -81,29 +62,29 @@ def query_to_csv(all_items: ModelSelect, exclude=None): def complex_data_to_csv(complex_data: List): if len(complex_data) == 0: - data_list = [['No data found']] + data_list = [["No data found"]] else: data_list = [[x for x in complex_data[0].keys()]] for line in complex_data: - logger.info(f'line: {line}') + logger.info(f"line: {line}") this_row = [] for key in line: - logger.info(f'key: {key}') + logger.info(f"key: {key}") if line[key] is None: - this_row.append('') + this_row.append("") elif isinstance(line[key], dict): - if 'name' in line[key]: - this_row.append(line[key]['name']) - elif 'abbrev' in line[key]: - this_row.append(line[key]['abbrev']) + if "name" in line[key]: + this_row.append(line[key]["name"]) + elif "abbrev" in line[key]: + this_row.append(line[key]["abbrev"]) else: - this_row.append(line[key]['id']) + this_row.append(line[key]["id"]) elif isinstance(line[key], int) and line[key] > 100000000: this_row.append(f"'{line[key]}") - elif isinstance(line[key], str) and ',' in line[key]: + elif isinstance(line[key], str) and "," in line[key]: this_row.append(line[key].replace(",", "-_-")) else: @@ -114,37 +95,38 @@ def complex_data_to_csv(complex_data: List): return DataFrame(data_list).to_csv(header=False, index=False) -def per_season_weeks(season: int, s_type: Literal['regular', 'post', 'total']): +def per_season_weeks(season: int, s_type: Literal["regular", "post", "total"]): if season == 1: - if s_type == 'regular': - return {'start': 1, 'end': 20} - elif s_type == 'post': - return {'start': 21, 'end': 22} + if s_type == "regular": + return {"start": 1, "end": 20} + elif s_type == "post": + return {"start": 21, "end": 22} else: - return {'start': 1, 'end': 22} + return {"start": 1, "end": 22} elif season in [3, 4, 5, 6, 7]: - if s_type == 'regular': - return {'start': 1, 'end': 22} - elif s_type == 'post': - return {'start': 23, 'end': 25} + if s_type == "regular": + return {"start": 1, "end": 22} + elif s_type == "post": + return {"start": 23, "end": 25} else: - return {'start': 1, 'end': 25} + return {"start": 1, "end": 25} # Season 2, 8, and beyond else: - if s_type == 'regular': - return {'start': 1, 'end': 18} - elif s_type == 'post': - return {'start': 19, 'end': 21} + if s_type == "regular": + return {"start": 1, "end": 18} + elif s_type == "post": + return {"start": 19, "end": 21} else: - return {'start': 1, 'end': 21} + return {"start": 1, "end": 21} def win_pct(this_team_stan): if this_team_stan.wins + this_team_stan.losses == 0: return 0 else: - return (this_team_stan.wins / (this_team_stan.wins + this_team_stan.losses)) + \ - (this_team_stan.run_diff * .000001) + return (this_team_stan.wins / (this_team_stan.wins + this_team_stan.losses)) + ( + this_team_stan.run_diff * 0.000001 + ) def games_back(leader, chaser): @@ -189,40 +171,26 @@ class Division(BaseModel): season = IntegerField(default=0) def abbrev(self): - league_short = self.league_abbrev + ' ' if self.league_abbrev else '' - return f'{league_short}{self.division_abbrev}' + league_short = self.league_abbrev + " " if self.league_abbrev else "" + return f"{league_short}{self.division_abbrev}" def short_name(self): - league_short = self.league_abbrev + ' ' if self.league_abbrev else '' - return f'{league_short}{self.division_name}' + league_short = self.league_abbrev + " " if self.league_abbrev else "" + return f"{league_short}{self.division_name}" def full_name(self): - league_long = self.league_name + ' ' if self.league_name else '' - return f'{league_long}{self.division_name}' + league_long = self.league_name + " " if self.league_name else "" + return f"{league_long}{self.division_name}" def sort_division(self, season): - div_query = Standings.select_season(season).where(Standings.team.division == self) + div_query = Standings.select_season(season).where( + Standings.team.division == self + ) div_teams = [team_stan for team_stan in div_query] div_teams.sort(key=lambda team: win_pct(team), reverse=True) # Assign div_gb and e_num for x in range(len(div_teams)): - # # Used for two playoff teams per divsion - # # Special calculations for the division leader - # if x == 0: - # div_teams[0].div_gb = -games_back(div_teams[0], div_teams[2]) - # div_teams[0].div_e_num = None - # div_teams[0].wc_gb = None - # div_teams[0].wc_e_num = None - # elif x == 1: - # div_teams[1].div_gb = 0 - # div_teams[1].div_e_num = None - # div_teams[1].wc_gb = None - # div_teams[1].wc_e_num = None - # else: - # div_teams[x].div_gb = games_back(div_teams[1], div_teams[x]) - # div_teams[x].div_e_num = e_number(div_teams[1], div_teams[x]) - # Used for one playoff team per division if x == 0: div_teams[0].div_gb = None div_teams[0].div_e_num = None @@ -303,27 +271,40 @@ class Team(BaseModel): @staticmethod def get_season(name_or_abbrev, season): - team = Team.get_or_none(fn.Upper(Team.abbrev) == name_or_abbrev.upper(), Team.season == season) + team = Team.get_or_none( + fn.Upper(Team.abbrev) == name_or_abbrev.upper(), Team.season == season + ) if not team: - team = Team.get_or_none(fn.Lower(Team.sname) == name_or_abbrev.lower(), Team.season == season) + team = Team.get_or_none( + fn.Lower(Team.sname) == name_or_abbrev.lower(), Team.season == season + ) if not team: - team = Team.get_or_none(fn.Lower(Team.lname) == name_or_abbrev.lower(), Team.season == season) + team = Team.get_or_none( + fn.Lower(Team.lname) == name_or_abbrev.lower(), + Team.season == season, + ) return team def get_record(self, week): wins = Result.select_season(Current.latest().season).where( - (((Result.hometeam == self) & (Result.homescore > Result.awayscore)) | - ((Result.awayteam == self) & (Result.awayscore > Result.homescore))) & (Result.week <= week) + ( + ((Result.hometeam == self) & (Result.homescore > Result.awayscore)) + | ((Result.awayteam == self) & (Result.awayscore > Result.homescore)) + ) + & (Result.week <= week) ) losses = Result.select_season(Current.latest().season).where( - (((Result.awayteam == self) & (Result.homescore > Result.awayscore)) | - ((Result.hometeam == self) & (Result.awayscore > Result.homescore))) & (Result.week <= week) + ( + ((Result.awayteam == self) & (Result.homescore > Result.awayscore)) + | ((Result.hometeam == self) & (Result.awayscore > Result.homescore)) + ) + & (Result.week <= week) ) if wins.count() + losses.count() > 0: pct = wins.count() / (wins.count() + losses.count()) else: pct = 0 - return {'w': wins.count(), 'l': losses.count(), 'pct': pct} + return {"w": wins.count(), "l": losses.count(), "pct": pct} def get_gms(self): if self.gmid2: @@ -332,17 +313,35 @@ class Team(BaseModel): return [self.gmid] def get_this_week(self): - active_team = Player.select_season(self.season).where(Player.team == self).order_by(Player.wara) + active_team = ( + Player.select_season(self.season) + .where(Player.team == self) + .order_by(Player.wara) + ) - active_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0, - 'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []} + active_roster = { + "C": 0, + "1B": 0, + "2B": 0, + "3B": 0, + "SS": 0, + "LF": 0, + "CF": 0, + "RF": 0, + "DH": 0, + "SP": 0, + "RP": 0, + "CP": 0, + "WARa": 0, + "players": [], + } combo_pitchers = 0 for guy in active_team: - active_roster['WARa'] += guy.wara - active_roster['players'].append(guy) + active_roster["WARa"] += guy.wara + active_roster["players"].append(guy) guy_pos = guy.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers += 1 else: try: @@ -353,276 +352,374 @@ class Team(BaseModel): pass if combo_pitchers > 0: - if active_roster['SP'] < 5: - if 5 - active_roster['SP'] <= combo_pitchers: - delta = 5 - active_roster['SP'] + if active_roster["SP"] < 5: + if 5 - active_roster["SP"] <= combo_pitchers: + delta = 5 - active_roster["SP"] else: delta = combo_pitchers - active_roster['SP'] += delta + active_roster["SP"] += delta combo_pitchers -= delta if combo_pitchers > 0: - active_roster['RP'] += combo_pitchers + active_roster["RP"] += combo_pitchers - short_il = Player.select_season(self.season).join(Team).where(Player.team.abbrev == f'{self.abbrev}IL') + short_il = ( + Player.select_season(self.season) + .join(Team) + .where(Player.team.abbrev == f"{self.abbrev}IL") + ) - short_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0, - 'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []} + short_roster = { + "C": 0, + "1B": 0, + "2B": 0, + "3B": 0, + "SS": 0, + "LF": 0, + "CF": 0, + "RF": 0, + "DH": 0, + "SP": 0, + "RP": 0, + "CP": 0, + "WARa": 0, + "players": [], + } combo_pitchers = 0 for guy in short_il: - short_roster['WARa'] += guy.wara - short_roster['players'].append(guy) + short_roster["WARa"] += guy.wara + short_roster["players"].append(guy) guy_pos = guy.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers += 1 else: for pos in guy_pos: short_roster[pos] += 1 if combo_pitchers > 0: - if short_roster['SP'] < 5: - if 5 - short_roster['SP'] <= combo_pitchers: - delta = 5 - short_roster['SP'] + if short_roster["SP"] < 5: + if 5 - short_roster["SP"] <= combo_pitchers: + delta = 5 - short_roster["SP"] else: delta = combo_pitchers - short_roster['SP'] += delta + short_roster["SP"] += delta combo_pitchers -= delta if combo_pitchers > 0: - short_roster['RP'] += combo_pitchers + short_roster["RP"] += combo_pitchers - long_il = Player.select_season(self.season).join(Team).where(Player.team.abbrev == f'{self.abbrev}MiL') + long_il = ( + Player.select_season(self.season) + .join(Team) + .where(Player.team.abbrev == f"{self.abbrev}MiL") + ) - long_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0, - 'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []} + long_roster = { + "C": 0, + "1B": 0, + "2B": 0, + "3B": 0, + "SS": 0, + "LF": 0, + "CF": 0, + "RF": 0, + "DH": 0, + "SP": 0, + "RP": 0, + "CP": 0, + "WARa": 0, + "players": [], + } combo_pitchers = 0 for guy in long_il: - long_roster['WARa'] += guy.wara - long_roster['players'].append(guy) + long_roster["WARa"] += guy.wara + long_roster["players"].append(guy) guy_pos = guy.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers += 1 else: for pos in guy_pos: long_roster[pos] += 1 if combo_pitchers > 0: - if long_roster['SP'] < 5: - if 5 - long_roster['SP'] <= combo_pitchers: - delta = 5 - long_roster['SP'] + if long_roster["SP"] < 5: + if 5 - long_roster["SP"] <= combo_pitchers: + delta = 5 - long_roster["SP"] else: delta = combo_pitchers - long_roster['SP'] += delta + long_roster["SP"] += delta combo_pitchers -= delta if combo_pitchers > 0: - long_roster['RP'] += combo_pitchers + long_roster["RP"] += combo_pitchers - return {'active': active_roster, 'shortil': short_roster, 'longil': long_roster} + return {"active": active_roster, "shortil": short_roster, "longil": long_roster} def get_next_week(self): current = Current.latest() active_team = Player.select_season(current.season).where(Player.team == self) - active_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0, - 'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []} + active_roster = { + "C": 0, + "1B": 0, + "2B": 0, + "3B": 0, + "SS": 0, + "LF": 0, + "CF": 0, + "RF": 0, + "DH": 0, + "SP": 0, + "RP": 0, + "CP": 0, + "WARa": 0, + "players": [], + } combo_pitchers = 0 for guy in active_team: - active_roster['WARa'] += guy.wara - active_roster['players'].append(guy) + active_roster["WARa"] += guy.wara + active_roster["players"].append(guy) guy_pos = guy.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers += 1 else: for pos in guy_pos: active_roster[pos] += 1 all_drops = Transaction.select_season(Current.latest().season).where( - (Transaction.oldteam == self) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0) + (Transaction.oldteam == self) + & (Transaction.week == current.week + 1) + & (Transaction.cancelled == 0) ) all_adds = Transaction.select_season(Current.latest().season).where( - (Transaction.newteam == self) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0) + (Transaction.newteam == self) + & (Transaction.week == current.week + 1) + & (Transaction.cancelled == 0) ) for move in all_drops: guy_pos = move.player.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers -= 1 else: for pos in guy_pos: active_roster[pos] -= 1 # print(f'dropping {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa') - active_roster['WARa'] -= move.player.wara + active_roster["WARa"] -= move.player.wara try: - active_roster['players'].remove(move.player) + active_roster["players"].remove(move.player) except: - print(f'I could not drop {move.player.name}') + print(f"I could not drop {move.player.name}") for move in all_adds: guy_pos = move.player.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers += 1 else: for pos in guy_pos: active_roster[pos] += 1 # print(f'adding {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa') - active_roster['WARa'] += move.player.wara - active_roster['players'].append(move.player) + active_roster["WARa"] += move.player.wara + active_roster["players"].append(move.player) if combo_pitchers > 0: - if active_roster['SP'] < 5: - if 5 - active_roster['SP'] <= combo_pitchers: - delta = 5 - active_roster['SP'] + if active_roster["SP"] < 5: + if 5 - active_roster["SP"] <= combo_pitchers: + delta = 5 - active_roster["SP"] else: delta = combo_pitchers - active_roster['SP'] += delta + active_roster["SP"] += delta combo_pitchers -= delta if combo_pitchers > 0: - active_roster['RP'] += combo_pitchers + active_roster["RP"] += combo_pitchers - short_il = Player.select_season(current.season).join(Team).where(Player.team.abbrev == f'{self.abbrev}SIL') + short_il = ( + Player.select_season(current.season) + .join(Team) + .where(Player.team.abbrev == f"{self.abbrev}SIL") + ) - short_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0, - 'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []} + short_roster = { + "C": 0, + "1B": 0, + "2B": 0, + "3B": 0, + "SS": 0, + "LF": 0, + "CF": 0, + "RF": 0, + "DH": 0, + "SP": 0, + "RP": 0, + "CP": 0, + "WARa": 0, + "players": [], + } combo_pitchers = 0 for guy in short_il: - short_roster['WARa'] += guy.wara - short_roster['players'].append(guy) + short_roster["WARa"] += guy.wara + short_roster["players"].append(guy) guy_pos = guy.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers += 1 else: for pos in guy_pos: short_roster[pos] += 1 - sil_team = Team.get_season(f'{self.abbrev}SIL', current.season) + sil_team = Team.get_season(f"{self.abbrev}SIL", current.season) all_drops = Transaction.select_season(Current.latest().season).where( - (Transaction.oldteam == sil_team) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0) + (Transaction.oldteam == sil_team) + & (Transaction.week == current.week + 1) + & (Transaction.cancelled == 0) ) all_adds = Transaction.select_season(Current.latest().season).where( - (Transaction.newteam == sil_team) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0) + (Transaction.newteam == sil_team) + & (Transaction.week == current.week + 1) + & (Transaction.cancelled == 0) ) for move in all_drops: guy_pos = move.player.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers -= 1 else: for pos in guy_pos: short_roster[pos] -= 1 - short_roster['WARa'] -= move.player.wara + short_roster["WARa"] -= move.player.wara # print(f'SIL dropping {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa') try: - short_roster['players'].remove(move.player) + short_roster["players"].remove(move.player) except: - print(f'I could not drop {move.player.name}') + print(f"I could not drop {move.player.name}") for move in all_adds: guy_pos = move.player.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers += 1 else: for pos in guy_pos: short_roster[pos] += 1 # print(f'SIL adding {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa') - short_roster['WARa'] += move.player.wara - short_roster['players'].append(move.player) + short_roster["WARa"] += move.player.wara + short_roster["players"].append(move.player) if combo_pitchers > 0: - if short_roster['SP'] < 5: - if 5 - short_roster['SP'] <= combo_pitchers: - delta = 5 - short_roster['SP'] + if short_roster["SP"] < 5: + if 5 - short_roster["SP"] <= combo_pitchers: + delta = 5 - short_roster["SP"] else: delta = combo_pitchers - short_roster['SP'] += delta + short_roster["SP"] += delta combo_pitchers -= delta if combo_pitchers > 0: - short_roster['RP'] += combo_pitchers + short_roster["RP"] += combo_pitchers - long_il = Player.select_season(current.season).join(Team).where(Player.team.abbrev == f'{self.abbrev}MiL') + long_il = ( + Player.select_season(current.season) + .join(Team) + .where(Player.team.abbrev == f"{self.abbrev}MiL") + ) - long_roster = {'C': 0, '1B': 0, '2B': 0, '3B': 0, 'SS': 0, 'LF': 0, 'CF': 0, 'RF': 0, 'DH': 0, - 'SP': 0, 'RP': 0, 'CP': 0, 'WARa': 0, 'players': []} + long_roster = { + "C": 0, + "1B": 0, + "2B": 0, + "3B": 0, + "SS": 0, + "LF": 0, + "CF": 0, + "RF": 0, + "DH": 0, + "SP": 0, + "RP": 0, + "CP": 0, + "WARa": 0, + "players": [], + } combo_pitchers = 0 for guy in long_il: - long_roster['WARa'] += guy.wara - long_roster['players'].append(guy) + long_roster["WARa"] += guy.wara + long_roster["players"].append(guy) guy_pos = guy.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers += 1 else: for pos in guy_pos: long_roster[pos] += 1 - lil_team = Team.get_season(f'{self.abbrev}LIL', current.season) + lil_team = Team.get_season(f"{self.abbrev}LIL", current.season) all_drops = Transaction.select_season(Current.latest().season).where( - (Transaction.oldteam == lil_team) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0) + (Transaction.oldteam == lil_team) + & (Transaction.week == current.week + 1) + & (Transaction.cancelled == 0) ) all_adds = Transaction.select_season(Current.latest().season).where( - (Transaction.newteam == lil_team) & (Transaction.week == current.week + 1) & (Transaction.cancelled == 0) + (Transaction.newteam == lil_team) + & (Transaction.week == current.week + 1) + & (Transaction.cancelled == 0) ) for move in all_drops: guy_pos = move.player.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers -= 1 else: for pos in guy_pos: long_roster[pos] -= 1 - long_roster['WARa'] -= move.player.wara + long_roster["WARa"] -= move.player.wara # print(f'LIL dropping {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa') try: - long_roster['players'].remove(move.player) + long_roster["players"].remove(move.player) except: - print(f'I could not drop {move.player.name}') + print(f"I could not drop {move.player.name}") for move in all_adds: guy_pos = move.player.get_positions() - if 'SP' in guy_pos and 'RP' in guy_pos: + if "SP" in guy_pos and "RP" in guy_pos: combo_pitchers += 1 else: for pos in guy_pos: long_roster[pos] += 1 # print(f'LIL adding {move.player.name} id ({move.player.get_id()}) for {move.player.wara} WARa') - long_roster['WARa'] += move.player.wara - long_roster['players'].append(move.player) + long_roster["WARa"] += move.player.wara + long_roster["players"].append(move.player) if combo_pitchers > 0: - if long_roster['SP'] < 5: - if 5 - long_roster['SP'] <= combo_pitchers: - delta = 5 - long_roster['SP'] + if long_roster["SP"] < 5: + if 5 - long_roster["SP"] <= combo_pitchers: + delta = 5 - long_roster["SP"] else: delta = combo_pitchers - long_roster['SP'] += delta + long_roster["SP"] += delta combo_pitchers -= delta if combo_pitchers > 0: - long_roster['RP'] += combo_pitchers + long_roster["RP"] += combo_pitchers - return {'active': active_roster, 'shortil': short_roster, 'longil': long_roster} + return {"active": active_roster, "shortil": short_roster, "longil": long_roster} def run_pythag_last8(self): team_stan = Standings.get_or_none(Standings.team == self) runs_scored, runs_allowed = 0, 0 away_games = StratGame.select( - fn.SUM(StratGame.away_score).alias('r_scored'), - fn.SUM(StratGame.home_score).alias('r_allowed') + fn.SUM(StratGame.away_score).alias("r_scored"), + fn.SUM(StratGame.home_score).alias("r_allowed"), ).where((StratGame.away_team == self) & StratGame.game_num.is_null(False)) if away_games.count() > 0: runs_scored += away_games[0].r_scored runs_allowed += away_games[0].r_allowed home_games = StratGame.select( - fn.SUM(StratGame.home_score).alias('r_scored'), - fn.SUM(StratGame.away_score).alias('r_allowed') + fn.SUM(StratGame.home_score).alias("r_scored"), + fn.SUM(StratGame.away_score).alias("r_allowed"), ).where((StratGame.home_team == self) & StratGame.game_num.is_null(False)) if home_games.count() > 0: runs_scored += home_games[0].r_scored @@ -633,17 +730,23 @@ class Team(BaseModel): elif runs_scored == 0: pythag_win_pct = 0 else: - pythag_win_pct = runs_scored ** 1.83 / ((runs_scored ** 1.83) + (runs_allowed ** 1.83)) + pythag_win_pct = runs_scored**1.83 / ( + (runs_scored**1.83) + (runs_allowed**1.83) + ) games_played = team_stan.wins + team_stan.losses team_stan.pythag_wins = round(games_played * pythag_win_pct) team_stan.pythag_losses = games_played - team_stan.pythag_wins - last_games = StratGame.select().where( - ((StratGame.home_team == self) | (StratGame.away_team == self)) & (StratGame.game_num.is_null(False)) - ).order_by( - -StratGame.season, -StratGame.week, -StratGame.game_num - ).limit(8) + last_games = ( + StratGame.select() + .where( + ((StratGame.home_team == self) | (StratGame.away_team == self)) + & (StratGame.game_num.is_null(False)) + ) + .order_by(-StratGame.season, -StratGame.week, -StratGame.game_num) + .limit(8) + ) for game in last_games: if game.home_score > game.away_score: @@ -885,9 +988,11 @@ class Player(BaseModel): def get_season(name, num): player = None try: - player = Player.get(fn.Lower(Player.name) == name.lower(), Player.season == num) + player = Player.get( + fn.Lower(Player.name) == name.lower(), Player.season == num + ) except Exception as e: - print(f'**Error** (db_engine player): {e}') + print(f"**Error** (db_engine player): {e}") finally: return player @@ -999,14 +1104,23 @@ class BattingStat(BaseModel): Return: ModelSelect object for season's regular season """ if season == 1: - return BattingStat.select().where((BattingStat.season == 1) & (BattingStat.week < 21))\ + return ( + BattingStat.select() + .where((BattingStat.season == 1) & (BattingStat.week < 21)) .order_by(BattingStat.week) + ) elif season == 2: - return BattingStat.select().where((BattingStat.season == 2) & (BattingStat.week < 19))\ + return ( + BattingStat.select() + .where((BattingStat.season == 2) & (BattingStat.week < 19)) .order_by(BattingStat.week) + ) elif season > 2: - return BattingStat.select().where((BattingStat.season == season) & (BattingStat.week < 23))\ + return ( + BattingStat.select() + .where((BattingStat.season == season) & (BattingStat.week < 23)) .order_by(BattingStat.week) + ) else: return None @@ -1017,137 +1131,176 @@ class BattingStat(BaseModel): Return: ModelSelect object for season's post season """ if season == 1: - return BattingStat.select().where((BattingStat.season == 1) & (BattingStat.week >= 21)) + return BattingStat.select().where( + (BattingStat.season == 1) & (BattingStat.week >= 21) + ) elif season == 2: - return BattingStat.select().where((BattingStat.season == 2) & (BattingStat.week >= 19)) + return BattingStat.select().where( + (BattingStat.season == 2) & (BattingStat.week >= 19) + ) elif season > 2: - return BattingStat.select().where((BattingStat.season == season) & (BattingStat.week >= 23)) + return BattingStat.select().where( + (BattingStat.season == season) & (BattingStat.week >= 23) + ) else: return None @staticmethod def team_season(team, season): - b_stats = BattingStat.regular_season(season).join(Player).select( - fn.SUM(BattingStat.pa).alias('pas'), - fn.SUM(BattingStat.ab).alias('abs'), - fn.SUM(BattingStat.run).alias('runs'), - fn.SUM(BattingStat.hit).alias('hits'), - fn.SUM(BattingStat.rbi).alias('rbis'), - fn.SUM(BattingStat.double).alias('doubles'), - fn.SUM(BattingStat.triple).alias('triples'), - fn.SUM(BattingStat.hr).alias('hrs'), - fn.SUM(BattingStat.bb).alias('bbs'), - fn.SUM(BattingStat.so).alias('sos'), - fn.SUM(BattingStat.hbp).alias('hbps'), - fn.SUM(BattingStat.sac).alias('sacs'), - fn.SUM(BattingStat.ibb).alias('ibbs'), - fn.SUM(BattingStat.gidp).alias('gidps'), - fn.SUM(BattingStat.sb).alias('sbs'), - fn.SUM(BattingStat.cs).alias('css'), - fn.SUM(BattingStat.bphr).alias('bphr'), - fn.SUM(BattingStat.bpfo).alias('bpfo'), - fn.SUM(BattingStat.bp1b).alias('bp1b'), - fn.SUM(BattingStat.bplo).alias('bplo'), - # fn.SUM(BattingStat.xba).alias('xba'), - # fn.SUM(BattingStat.xbt).alias('xbt'), - fn.COUNT(BattingStat.game).alias('games'), - ).where(BattingStat.team == team) + b_stats = ( + BattingStat.regular_season(season) + .join(Player) + .select( + fn.SUM(BattingStat.pa).alias("pas"), + fn.SUM(BattingStat.ab).alias("abs"), + fn.SUM(BattingStat.run).alias("runs"), + fn.SUM(BattingStat.hit).alias("hits"), + fn.SUM(BattingStat.rbi).alias("rbis"), + fn.SUM(BattingStat.double).alias("doubles"), + fn.SUM(BattingStat.triple).alias("triples"), + fn.SUM(BattingStat.hr).alias("hrs"), + fn.SUM(BattingStat.bb).alias("bbs"), + fn.SUM(BattingStat.so).alias("sos"), + fn.SUM(BattingStat.hbp).alias("hbps"), + fn.SUM(BattingStat.sac).alias("sacs"), + fn.SUM(BattingStat.ibb).alias("ibbs"), + fn.SUM(BattingStat.gidp).alias("gidps"), + fn.SUM(BattingStat.sb).alias("sbs"), + fn.SUM(BattingStat.cs).alias("css"), + fn.SUM(BattingStat.bphr).alias("bphr"), + fn.SUM(BattingStat.bpfo).alias("bpfo"), + fn.SUM(BattingStat.bp1b).alias("bp1b"), + fn.SUM(BattingStat.bplo).alias("bplo"), + # fn.SUM(BattingStat.xba).alias('xba'), + # fn.SUM(BattingStat.xbt).alias('xbt'), + fn.COUNT(BattingStat.game).alias("games"), + ) + .where(BattingStat.team == team) + ) total = { - 'game': b_stats[0].games if b_stats[0].games else 0, - 'pa': b_stats[0].pas if b_stats[0].pas else 0, - 'ab': b_stats[0].abs if b_stats[0].abs else 0, - 'run': b_stats[0].runs if b_stats[0].runs else 0, - 'hit': b_stats[0].hits if b_stats[0].hits else 0, - 'rbi': b_stats[0].rbis if b_stats[0].rbis else 0, - 'double': b_stats[0].doubles if b_stats[0].doubles else 0, - 'triple': b_stats[0].triples if b_stats[0].triples else 0, - 'hr': b_stats[0].hrs if b_stats[0].hrs else 0, - 'bb': b_stats[0].bbs if b_stats[0].bbs else 0, - 'so': b_stats[0].sos if b_stats[0].sos else 0, - 'hbp': b_stats[0].hbps if b_stats[0].hbps else 0, - 'sac': b_stats[0].sacs if b_stats[0].sacs else 0, - 'ibb': b_stats[0].ibbs if b_stats[0].ibbs else 0, - 'gidp': b_stats[0].gidps if b_stats[0].gidps else 0, - 'sb': b_stats[0].sbs if b_stats[0].sbs else 0, - 'cs': b_stats[0].css if b_stats[0].css else 0, - 'ba': 0, - 'obp': 0, - 'slg': 0, - 'woba': 0, - 'kpct': 0, - 'bphr': b_stats[0].bphr if b_stats[0].bphr else 0, - 'bpfo': b_stats[0].bpfo if b_stats[0].bpfo else 0, - 'bp1b': b_stats[0].bp1b if b_stats[0].bp1b else 0, - 'bplo': b_stats[0].bplo if b_stats[0].bplo else 0, + "game": b_stats[0].games if b_stats[0].games else 0, + "pa": b_stats[0].pas if b_stats[0].pas else 0, + "ab": b_stats[0].abs if b_stats[0].abs else 0, + "run": b_stats[0].runs if b_stats[0].runs else 0, + "hit": b_stats[0].hits if b_stats[0].hits else 0, + "rbi": b_stats[0].rbis if b_stats[0].rbis else 0, + "double": b_stats[0].doubles if b_stats[0].doubles else 0, + "triple": b_stats[0].triples if b_stats[0].triples else 0, + "hr": b_stats[0].hrs if b_stats[0].hrs else 0, + "bb": b_stats[0].bbs if b_stats[0].bbs else 0, + "so": b_stats[0].sos if b_stats[0].sos else 0, + "hbp": b_stats[0].hbps if b_stats[0].hbps else 0, + "sac": b_stats[0].sacs if b_stats[0].sacs else 0, + "ibb": b_stats[0].ibbs if b_stats[0].ibbs else 0, + "gidp": b_stats[0].gidps if b_stats[0].gidps else 0, + "sb": b_stats[0].sbs if b_stats[0].sbs else 0, + "cs": b_stats[0].css if b_stats[0].css else 0, + "ba": 0, + "obp": 0, + "slg": 0, + "woba": 0, + "kpct": 0, + "bphr": b_stats[0].bphr if b_stats[0].bphr else 0, + "bpfo": b_stats[0].bpfo if b_stats[0].bpfo else 0, + "bp1b": b_stats[0].bp1b if b_stats[0].bp1b else 0, + "bplo": b_stats[0].bplo if b_stats[0].bplo else 0, # 'xba': b_stats[0].xba if b_stats[0].xba else 0, # 'xbt': b_stats[0].xbt if b_stats[0].xbt else 0, } if b_stats[0].abs: - total['ba'] = b_stats[0].hits / b_stats[0].abs + total["ba"] = b_stats[0].hits / b_stats[0].abs - total['obp'] = ( - (b_stats[0].bbs + b_stats[0].hits + b_stats[0].hbps + b_stats[0].ibbs) / b_stats[0].pas + total["obp"] = ( + b_stats[0].bbs + b_stats[0].hits + b_stats[0].hbps + b_stats[0].ibbs + ) / b_stats[0].pas + + total["slg"] = ( + (b_stats[0].hrs * 4) + + (b_stats[0].triples * 3) + + (b_stats[0].doubles * 2) + + ( + b_stats[0].hits + - b_stats[0].hrs + - b_stats[0].triples + - b_stats[0].doubles + ) + ) / b_stats[0].abs + + total["woba"] = ( + (b_stats[0].bbs * 0.69) + + (b_stats[0].hbps * 0.722) + + (b_stats[0].doubles * 1.271) + + (b_stats[0].triples * 1.616) + + (b_stats[0].hrs * 2.101) + + ( + ( + b_stats[0].hits + - b_stats[0].hrs + - b_stats[0].triples + - b_stats[0].doubles + ) + * 0.888 + ) + ) / (b_stats[0].pas - b_stats[0].ibbs) + + total["kpct"] = (total["so"] * 100) / total["ab"] + + total_innings = ( + PitchingStat.regular_season(season) + .join(Player) + .select( + fn.SUM(PitchingStat.ip).alias("ips"), + ) + .where(PitchingStat.player.team == team) ) - - total['slg'] = ( - ((b_stats[0].hrs * 4) + (b_stats[0].triples * 3) + (b_stats[0].doubles * 2) + - (b_stats[0].hits - b_stats[0].hrs - b_stats[0].triples - b_stats[0].doubles)) / b_stats[0].abs - ) - - total['woba'] = ( - ((b_stats[0].bbs * .69) + (b_stats[0].hbps * .722) + (b_stats[0].doubles * 1.271) + - (b_stats[0].triples * 1.616) + (b_stats[0].hrs * 2.101) + - ((b_stats[0].hits - b_stats[0].hrs - b_stats[0].triples - b_stats[0].doubles) * .888)) / - (b_stats[0].pas - b_stats[0].ibbs) - ) - - total['kpct'] = (total['so'] * 100) / total['ab'] - - total_innings = PitchingStat.regular_season(season).join(Player).select( - fn.SUM(PitchingStat.ip).alias('ips'), - ).where(PitchingStat.player.team == team) - total['rper9'] = (total['run'] * 9) / total_innings[0].ips + total["rper9"] = (total["run"] * 9) / total_innings[0].ips return total @staticmethod def team_fielding_season(team, season): - f_stats = BattingStat.regular_season(season).select( - fn.SUM(BattingStat.xch).alias('xchs'), - fn.SUM(BattingStat.xhit).alias('xhits'), - fn.SUM(BattingStat.error).alias('errors'), - # fn.SUM(BattingStat.roba).alias('roba'), - # fn.SUM(BattingStat.robs).alias('robs'), - # fn.SUM(BattingStat.raa).alias('raa'), - # fn.SUM(BattingStat.rto).alias('rto'), - fn.SUM(BattingStat.pb).alias('pbs'), - fn.SUM(BattingStat.sbc).alias('sbas'), - fn.SUM(BattingStat.csc).alias('cscs'), - fn.COUNT(BattingStat.game).alias('games'), - ).where(BattingStat.team == team) + f_stats = ( + BattingStat.regular_season(season) + .select( + fn.SUM(BattingStat.xch).alias("xchs"), + fn.SUM(BattingStat.xhit).alias("xhits"), + fn.SUM(BattingStat.error).alias("errors"), + # fn.SUM(BattingStat.roba).alias('roba'), + # fn.SUM(BattingStat.robs).alias('robs'), + # fn.SUM(BattingStat.raa).alias('raa'), + # fn.SUM(BattingStat.rto).alias('rto'), + fn.SUM(BattingStat.pb).alias("pbs"), + fn.SUM(BattingStat.sbc).alias("sbas"), + fn.SUM(BattingStat.csc).alias("cscs"), + fn.COUNT(BattingStat.game).alias("games"), + ) + .where(BattingStat.team == team) + ) total = { - 'game': f_stats[0].games if f_stats[0].games else 0, - 'xch': f_stats[0].xchs if f_stats[0].xchs else 0, - 'xhit': f_stats[0].xhits if f_stats[0].xhits else 0, - 'error': f_stats[0].errors if f_stats[0].errors else 0, + "game": f_stats[0].games if f_stats[0].games else 0, + "xch": f_stats[0].xchs if f_stats[0].xchs else 0, + "xhit": f_stats[0].xhits if f_stats[0].xhits else 0, + "error": f_stats[0].errors if f_stats[0].errors else 0, # 'roba': f_stats[0].roba if f_stats[0].roba else 0, # 'robs': f_stats[0].robs if f_stats[0].robs else 0, # 'raa': f_stats[0].raa if f_stats[0].raa else 0, # 'rto': f_stats[0].rto if f_stats[0].rto else 0, - 'pb': f_stats[0].pbs if f_stats[0].pbs else 0, - 'sbc': f_stats[0].sbas if f_stats[0].sbas else 0, - 'csc': f_stats[0].cscs if f_stats[0].cscs else 0, - 'wfpct': 0, - 'cspct': 0, + "pb": f_stats[0].pbs if f_stats[0].pbs else 0, + "sbc": f_stats[0].sbas if f_stats[0].sbas else 0, + "csc": f_stats[0].cscs if f_stats[0].cscs else 0, + "wfpct": 0, + "cspct": 0, } - if total['xch'] > 0: - total['wfpct'] = (total['xch'] - (total['error'] * .5) - (total['xhit'] * .75)) / (total['xch']) - if total['sbc'] > 0: - total['cspct'] = (total['csc'] / total['sbc']) * 100 + if total["xch"] > 0: + total["wfpct"] = ( + total["xch"] - (total["error"] * 0.5) - (total["xhit"] * 0.75) + ) / (total["xch"]) + if total["sbc"] > 0: + total["cspct"] = (total["csc"] / total["sbc"]) * 100 return total @@ -1184,87 +1337,109 @@ class PitchingStat(BaseModel): @staticmethod def regular_season(season): if season == 1: - return PitchingStat.select().where((PitchingStat.season == 1) & (PitchingStat.week < 21))\ + return ( + PitchingStat.select() + .where((PitchingStat.season == 1) & (PitchingStat.week < 21)) .order_by(PitchingStat.week) + ) elif season == 2: - return PitchingStat.select().where((PitchingStat.season == 2) & (PitchingStat.week < 19))\ + return ( + PitchingStat.select() + .where((PitchingStat.season == 2) & (PitchingStat.week < 19)) .order_by(PitchingStat.week) + ) elif season > 2: - return PitchingStat.select().where((PitchingStat.season == season) & (PitchingStat.week < 23))\ + return ( + PitchingStat.select() + .where((PitchingStat.season == season) & (PitchingStat.week < 23)) .order_by(PitchingStat.week) + ) else: return None @staticmethod def post_season(season): if season == 1: - return PitchingStat.select().where((PitchingStat.season == 1) & (PitchingStat.week >= 21))\ + return ( + PitchingStat.select() + .where((PitchingStat.season == 1) & (PitchingStat.week >= 21)) .order_by(PitchingStat.week) + ) elif season == 2: - return PitchingStat.select().where((PitchingStat.season == 2) & (PitchingStat.week >= 19))\ + return ( + PitchingStat.select() + .where((PitchingStat.season == 2) & (PitchingStat.week >= 19)) .order_by(PitchingStat.week) + ) elif season > 2: - return PitchingStat.select().where((PitchingStat.season == season) & (PitchingStat.week >= 23))\ + return ( + PitchingStat.select() + .where((PitchingStat.season == season) & (PitchingStat.week >= 23)) .order_by(PitchingStat.week) + ) else: return None @staticmethod def team_season(team, season): - p_stats = PitchingStat.regular_season(season).select( - fn.SUM(PitchingStat.ip).alias('ips'), - fn.SUM(PitchingStat.hit).alias('hits'), - fn.SUM(PitchingStat.run).alias('runs'), - fn.SUM(PitchingStat.erun).alias('eruns'), - fn.SUM(PitchingStat.so).alias('sos'), - fn.SUM(PitchingStat.bb).alias('bbs'), - fn.SUM(PitchingStat.hbp).alias('hbps'), - fn.SUM(PitchingStat.wp).alias('wps'), - fn.SUM(PitchingStat.ir).alias('ir'), - fn.SUM(PitchingStat.irs).alias('irs'), - fn.SUM(PitchingStat.balk).alias('balks'), - fn.SUM(PitchingStat.hr).alias('hrs'), - fn.COUNT(PitchingStat.game).alias('games'), - fn.SUM(PitchingStat.gs).alias('gss'), - fn.SUM(PitchingStat.win).alias('wins'), - fn.SUM(PitchingStat.loss).alias('losses'), - fn.SUM(PitchingStat.hold).alias('holds'), - fn.SUM(PitchingStat.sv).alias('saves'), - fn.SUM(PitchingStat.bsv).alias('bsaves'), - ).where(PitchingStat.team == team) + p_stats = ( + PitchingStat.regular_season(season) + .select( + fn.SUM(PitchingStat.ip).alias("ips"), + fn.SUM(PitchingStat.hit).alias("hits"), + fn.SUM(PitchingStat.run).alias("runs"), + fn.SUM(PitchingStat.erun).alias("eruns"), + fn.SUM(PitchingStat.so).alias("sos"), + fn.SUM(PitchingStat.bb).alias("bbs"), + fn.SUM(PitchingStat.hbp).alias("hbps"), + fn.SUM(PitchingStat.wp).alias("wps"), + fn.SUM(PitchingStat.ir).alias("ir"), + fn.SUM(PitchingStat.irs).alias("irs"), + fn.SUM(PitchingStat.balk).alias("balks"), + fn.SUM(PitchingStat.hr).alias("hrs"), + fn.COUNT(PitchingStat.game).alias("games"), + fn.SUM(PitchingStat.gs).alias("gss"), + fn.SUM(PitchingStat.win).alias("wins"), + fn.SUM(PitchingStat.loss).alias("losses"), + fn.SUM(PitchingStat.hold).alias("holds"), + fn.SUM(PitchingStat.sv).alias("saves"), + fn.SUM(PitchingStat.bsv).alias("bsaves"), + ) + .where(PitchingStat.team == team) + ) total = { - 'ip': p_stats[0].ips if p_stats[0].ips else 0, - 'hit': int(p_stats[0].hits) if p_stats[0].hits else 0, - 'run': int(p_stats[0].runs) if p_stats[0].runs else 0, - 'erun': int(p_stats[0].eruns) if p_stats[0].eruns else 0, - 'so': int(p_stats[0].sos) if p_stats[0].sos else 0, - 'bb': int(p_stats[0].bbs) if p_stats[0].bbs else 0, - 'hbp': int(p_stats[0].hbps) if p_stats[0].hbps else 0, - 'wp': int(p_stats[0].wps) if p_stats[0].wps else 0, - 'balk': int(p_stats[0].balks) if p_stats[0].balks else 0, - 'hr': int(p_stats[0].hrs) if p_stats[0].hrs else 0, - 'game': int(p_stats[0].games) if p_stats[0].games else 0, - 'gs': int(p_stats[0].gss) if p_stats[0].gss else 0, - 'win': int(p_stats[0].wins) if p_stats[0].wins else 0, - 'loss': int(p_stats[0].losses) if p_stats[0].losses else 0, - 'hold': int(p_stats[0].holds) if p_stats[0].holds else 0, - 'sv': int(p_stats[0].saves) if p_stats[0].saves else 0, - 'bsv': int(p_stats[0].bsaves) if p_stats[0].bsaves else 0, - 'wl%': 0, - 'era': 0, - 'whip': 0, - 'ir': int(p_stats[0].ir) if p_stats[0].ir else 0, - 'irs': int(p_stats[0].irs) if p_stats[0].irs else 0, + "ip": p_stats[0].ips if p_stats[0].ips else 0, + "hit": int(p_stats[0].hits) if p_stats[0].hits else 0, + "run": int(p_stats[0].runs) if p_stats[0].runs else 0, + "erun": int(p_stats[0].eruns) if p_stats[0].eruns else 0, + "so": int(p_stats[0].sos) if p_stats[0].sos else 0, + "bb": int(p_stats[0].bbs) if p_stats[0].bbs else 0, + "hbp": int(p_stats[0].hbps) if p_stats[0].hbps else 0, + "wp": int(p_stats[0].wps) if p_stats[0].wps else 0, + "balk": int(p_stats[0].balks) if p_stats[0].balks else 0, + "hr": int(p_stats[0].hrs) if p_stats[0].hrs else 0, + "game": int(p_stats[0].games) if p_stats[0].games else 0, + "gs": int(p_stats[0].gss) if p_stats[0].gss else 0, + "win": int(p_stats[0].wins) if p_stats[0].wins else 0, + "loss": int(p_stats[0].losses) if p_stats[0].losses else 0, + "hold": int(p_stats[0].holds) if p_stats[0].holds else 0, + "sv": int(p_stats[0].saves) if p_stats[0].saves else 0, + "bsv": int(p_stats[0].bsaves) if p_stats[0].bsaves else 0, + "wl%": 0, + "era": 0, + "whip": 0, + "ir": int(p_stats[0].ir) if p_stats[0].ir else 0, + "irs": int(p_stats[0].irs) if p_stats[0].irs else 0, } - if total['ip']: - total['era'] = (total['erun'] * 9) / total['ip'] + if total["ip"]: + total["era"] = (total["erun"] * 9) / total["ip"] - total['whip'] = (total['bb'] + total['hit']) / total['ip'] + total["whip"] = (total["bb"] + total["hit"]) / total["ip"] - if total['win'] + total['loss'] > 0: - total['wl%'] = total['win'] / (total['win'] + total['loss']) + if total["win"] + total["loss"] > 0: + total["wl%"] = total["win"] / (total["win"] + total["loss"]) return total @@ -1284,7 +1459,7 @@ class Standings(BaseModel): away_losses = IntegerField(default=0) last8_wins = IntegerField(default=0) last8_losses = IntegerField(default=0) - streak_wl = CharField(default='w') + streak_wl = CharField(default="w") streak_num = IntegerField(default=0) one_run_wins = IntegerField(default=0) one_run_losses = IntegerField(default=0) @@ -1325,9 +1500,15 @@ class Standings(BaseModel): # Iterate through each individual result # for game in Result.select_season(season).where(Result.week <= 22): - for game in StratGame.select().where( - (StratGame.season == season) & (StratGame.week <= 18) & (StratGame.game_num.is_null(False)) - ).order_by(StratGame.week, StratGame.game_num): + for game in ( + StratGame.select() + .where( + (StratGame.season == season) + & (StratGame.week <= 18) + & (StratGame.game_num.is_null(False)) + ) + .order_by(StratGame.week, StratGame.game_num) + ): # tally win and loss for each standings object game.update_standings() @@ -1342,7 +1523,7 @@ class Standings(BaseModel): # Pull each league (filter by not null wc_gb) and sort by win pct # # For one league: - Division.sort_wildcard(season, 'SBa') + Division.sort_wildcard(season, "SBa") # For two leagues # Division.sort_wildcard(season, 'AL') @@ -1383,8 +1564,12 @@ class BattingCareer(BaseModel): line.delete_instance() # For each seasonstat, find career or create new and increment - for this_season in BattingSeason.select().where(BattingSeason.season_type == 'Regular'): - this_career = BattingCareer.get_or_none(BattingCareer.name == this_season.player.name) + for this_season in BattingSeason.select().where( + BattingSeason.season_type == "Regular" + ): + this_career = BattingCareer.get_or_none( + BattingCareer.name == this_season.player.name + ) if not this_career: this_career = BattingCareer(name=this_season.player.name) this_career.save() @@ -1444,8 +1629,12 @@ class PitchingCareer(BaseModel): line.delete_instance() # For each seasonstat, find career or create new and increment - for this_season in PitchingSeason.select().where(PitchingSeason.season_type == 'Regular'): - this_career = PitchingCareer.get_or_none(PitchingCareer.name == this_season.player.name) + for this_season in PitchingSeason.select().where( + PitchingSeason.season_type == "Regular" + ): + this_career = PitchingCareer.get_or_none( + PitchingCareer.name == this_season.player.name + ) if not this_career: this_career = PitchingCareer(name=this_season.player.name) this_career.save() @@ -1494,12 +1683,17 @@ class FieldingCareer(BaseModel): line.delete_instance() # For each seasonstat, find career or create new and increment - for this_season in FieldingSeason.select().where(FieldingSeason.season_type == 'Regular'): + for this_season in FieldingSeason.select().where( + FieldingSeason.season_type == "Regular" + ): this_career = FieldingCareer.get_or_none( - FieldingCareer.name == this_season.player.name, FieldingCareer.pos == this_season.pos + FieldingCareer.name == this_season.player.name, + FieldingCareer.pos == this_season.pos, ) if not this_career: - this_career = FieldingCareer(name=this_season.player.name, pos=this_season.pos) + this_career = FieldingCareer( + name=this_season.player.name, pos=this_season.pos + ) this_career.save() this_career.xch += this_season.xch @@ -1518,7 +1712,7 @@ class FieldingCareer(BaseModel): class BattingSeason(BaseModel): player = ForeignKeyField(Player) season = IntegerField() - season_type = CharField(default='Regular') + season_type = CharField(default="Regular") career = ForeignKeyField(BattingCareer, null=True) pa = FloatField(default=0) ab = FloatField(default=0) @@ -1614,10 +1808,14 @@ class BattingSeason(BaseModel): self.xbt = 0 self.game = 0 - if self.season_type == 'Regular': - all_stats = BattingStat.regular_season(self.season).where(BattingStat.player == self.player) + if self.season_type == "Regular": + all_stats = BattingStat.regular_season(self.season).where( + BattingStat.player == self.player + ) else: - all_stats = BattingStat.post_season(self.season).where(BattingStat.player == self.player) + all_stats = BattingStat.post_season(self.season).where( + BattingStat.player == self.player + ) for line in all_stats: self.pa += line.pa self.ab += line.ab @@ -1650,7 +1848,7 @@ class BattingSeason(BaseModel): class PitchingSeason(BaseModel): player = ForeignKeyField(Player) season = IntegerField() - season_type = CharField(default='Regular') + season_type = CharField(default="Regular") career = ForeignKeyField(PitchingCareer, null=True) ip = FloatField(default=0) hit = FloatField(default=0) @@ -1739,10 +1937,14 @@ class PitchingSeason(BaseModel): self.bsv = 0 self.game = 0 - if self.season_type == 'Regular': - all_stats = PitchingStat.regular_season(self.season).where(PitchingStat.player == self.player) + if self.season_type == "Regular": + all_stats = PitchingStat.regular_season(self.season).where( + PitchingStat.player == self.player + ) else: - all_stats = PitchingStat.post_season(self.season).where(PitchingStat.player == self.player) + all_stats = PitchingStat.post_season(self.season).where( + PitchingStat.player == self.player + ) for line in all_stats: self.ip += line.ip self.hit += line.hit @@ -1771,7 +1973,7 @@ class PitchingSeason(BaseModel): class FieldingSeason(BaseModel): player = ForeignKeyField(Player) season = IntegerField() - season_type = CharField(default='Regular') + season_type = CharField(default="Regular") pos = CharField() career = ForeignKeyField(FieldingCareer, null=True) xch = IntegerField(default=0) @@ -1837,7 +2039,7 @@ class FieldingSeason(BaseModel): self.rto = 0 self.game = 0 - if self.season_type == 'Regular': + if self.season_type == "Regular": all_stats = BattingStat.regular_season(self.season).where( (BattingStat.player == self.player) & (BattingStat.pos == self.pos) ) @@ -1877,7 +2079,9 @@ class DraftPick(BaseModel): @staticmethod def get_season(team, rd, num): - return DraftPick.get(DraftPick.season == num, DraftPick.origowner == team, DraftPick.round == rd) + return DraftPick.get( + DraftPick.season == num, DraftPick.origowner == team, DraftPick.round == rd + ) class DraftData(BaseModel): @@ -1885,7 +2089,7 @@ class DraftData(BaseModel): timer = BooleanField() pick_deadline = DateTimeField(null=True) result_channel = CharField(max_length=20, null=True) # Discord channel ID as string - ping_channel = CharField(max_length=20, null=True) # Discord channel ID as string + ping_channel = CharField(max_length=20, null=True) # Discord channel ID as string pick_minutes = IntegerField(null=True) @@ -1902,7 +2106,7 @@ class Award(BaseModel): class DiceRoll(BaseModel): season = IntegerField(default=12) # Will be updated to current season when needed - week = IntegerField(default=1) # Will be updated to current week when needed + week = IntegerField(default=1) # Will be updated to current week when needed team = ForeignKeyField(Team, null=True) roller = CharField(max_length=20) dsix = IntegerField(null=True) @@ -1939,7 +2143,7 @@ class StratGame(BaseModel): season = IntegerField() week = IntegerField() game_num = IntegerField(null=True) - season_type = CharField(default='regular') + season_type = CharField(default="regular") away_team = ForeignKeyField(Team) home_team = ForeignKeyField(Team) away_score = IntegerField(null=True) @@ -1963,16 +2167,16 @@ class StratGame(BaseModel): away_stan.away_losses += 1 # - update streak wl and num - if home_stan.streak_wl == 'w': + if home_stan.streak_wl == "w": home_stan.streak_num += 1 else: - home_stan.streak_wl = 'w' + home_stan.streak_wl = "w" home_stan.streak_num = 1 - if away_stan.streak_wl == 'l': + if away_stan.streak_wl == "l": away_stan.streak_num += 1 else: - away_stan.streak_wl = 'l' + away_stan.streak_wl = "l" away_stan.streak_num = 1 # - if 1-run, tally accordingly @@ -1998,22 +2202,22 @@ class StratGame(BaseModel): # Used for one league with 4 divisions # - update record v division (check opponent's division) - if away_div.division_abbrev == 'TC': + if away_div.division_abbrev == "TC": home_stan.div1_wins += 1 - elif away_div.division_abbrev == 'ETSOS': + elif away_div.division_abbrev == "ETSOS": home_stan.div2_wins += 1 - elif away_div.division_abbrev == 'APL': + elif away_div.division_abbrev == "APL": home_stan.div3_wins += 1 - elif away_div.division_abbrev == 'BBC': + elif away_div.division_abbrev == "BBC": home_stan.div4_wins += 1 - if home_div.division_abbrev == 'TC': + if home_div.division_abbrev == "TC": away_stan.div1_losses += 1 - elif home_div.division_abbrev == 'ETSOS': + elif home_div.division_abbrev == "ETSOS": away_stan.div2_losses += 1 - elif home_div.division_abbrev == 'APL': + elif home_div.division_abbrev == "APL": away_stan.div3_losses += 1 - elif home_div.division_abbrev == 'BBC': + elif home_div.division_abbrev == "BBC": away_stan.div4_losses += 1 # Used for two league plus divisions @@ -2051,16 +2255,16 @@ class StratGame(BaseModel): away_stan.away_wins += 1 # - update streak wl and num - if home_stan.streak_wl == 'l': + if home_stan.streak_wl == "l": home_stan.streak_num += 1 else: - home_stan.streak_wl = 'l' + home_stan.streak_wl = "l" home_stan.streak_num = 1 - if away_stan.streak_wl == 'w': + if away_stan.streak_wl == "w": away_stan.streak_num += 1 else: - away_stan.streak_wl = 'w' + away_stan.streak_wl = "w" away_stan.streak_num = 1 # - if 1-run, tally accordingly @@ -2086,22 +2290,22 @@ class StratGame(BaseModel): # Used for one league with 4 divisions # - update record v division (check opponent's division) - if away_div.division_abbrev == 'TC': + if away_div.division_abbrev == "TC": home_stan.div1_losses += 1 - elif away_div.division_abbrev == 'ETSOS': + elif away_div.division_abbrev == "ETSOS": home_stan.div2_losses += 1 - elif away_div.division_abbrev == 'APL': + elif away_div.division_abbrev == "APL": home_stan.div3_losses += 1 - elif away_div.division_abbrev == 'BBC': + elif away_div.division_abbrev == "BBC": home_stan.div4_losses += 1 - if home_div.division_abbrev == 'TC': + if home_div.division_abbrev == "TC": away_stan.div1_wins += 1 - elif home_div.division_abbrev == 'ETSOS': + elif home_div.division_abbrev == "ETSOS": away_stan.div2_wins += 1 - elif home_div.division_abbrev == 'APL': + elif home_div.division_abbrev == "APL": away_stan.div3_wins += 1 - elif home_div.division_abbrev == 'BBC': + elif home_div.division_abbrev == "BBC": away_stan.div4_wins += 1 # Used for two league plus divisions @@ -2230,6 +2434,7 @@ class Decision(BaseModel): class CustomCommandCreator(BaseModel): """Model for custom command creators.""" + discord_id = CharField(max_length=20, unique=True) # Discord snowflake ID as string username = CharField(max_length=32) display_name = CharField(max_length=32, null=True) @@ -2238,126 +2443,147 @@ class CustomCommandCreator(BaseModel): active_commands = IntegerField(default=0) class Meta: - table_name = 'custom_command_creators' + table_name = "custom_command_creators" class CustomCommand(BaseModel): """Model for custom commands created by users.""" + name = CharField(max_length=32, unique=True) content = TextField() - creator = ForeignKeyField(CustomCommandCreator, backref='commands') - + creator = ForeignKeyField(CustomCommandCreator, backref="commands") + # Timestamps created_at = DateTimeField() updated_at = DateTimeField(null=True) last_used = DateTimeField(null=True) - + # Usage tracking use_count = IntegerField(default=0) warning_sent = BooleanField(default=False) - + # Metadata is_active = BooleanField(default=True) tags = TextField(null=True) # JSON string for tags list class Meta: - table_name = 'custom_commands' + table_name = "custom_commands" @staticmethod def get_by_name(name: str): """Get a custom command by name (case-insensitive).""" return CustomCommand.get_or_none(fn.Lower(CustomCommand.name) == name.lower()) - + @staticmethod def search_by_name(partial_name: str, limit: int = 25): """Search commands by partial name match.""" - return (CustomCommand - .select() - .where((CustomCommand.is_active == True) & - (fn.Lower(CustomCommand.name).contains(partial_name.lower()))) - .order_by(CustomCommand.name) - .limit(limit)) - + return ( + CustomCommand.select() + .where( + (CustomCommand.is_active == True) + & (fn.Lower(CustomCommand.name).contains(partial_name.lower())) + ) + .order_by(CustomCommand.name) + .limit(limit) + ) + @staticmethod def get_popular(limit: int = 10): """Get most popular commands by usage.""" - return (CustomCommand - .select() - .where(CustomCommand.is_active == True) - .order_by(CustomCommand.use_count.desc()) - .limit(limit)) - + return ( + CustomCommand.select() + .where(CustomCommand.is_active == True) + .order_by(CustomCommand.use_count.desc()) + .limit(limit) + ) + @staticmethod def get_by_creator(creator_id: int, limit: int = 25, offset: int = 0): """Get commands by creator ID.""" - return (CustomCommand - .select() - .where((CustomCommand.creator == creator_id) & (CustomCommand.is_active == True)) - .order_by(CustomCommand.name) - .limit(limit) - .offset(offset)) - + return ( + CustomCommand.select() + .where( + (CustomCommand.creator == creator_id) + & (CustomCommand.is_active == True) + ) + .order_by(CustomCommand.name) + .limit(limit) + .offset(offset) + ) + @staticmethod def get_unused_commands(days: int = 60): """Get commands that haven't been used in specified days.""" from datetime import datetime, timedelta + cutoff_date = datetime.now() - timedelta(days=days) - return (CustomCommand - .select() - .where((CustomCommand.is_active == True) & - ((CustomCommand.last_used.is_null()) | - (CustomCommand.last_used < cutoff_date)))) - + return CustomCommand.select().where( + (CustomCommand.is_active == True) + & ( + (CustomCommand.last_used.is_null()) + | (CustomCommand.last_used < cutoff_date) + ) + ) + @staticmethod def get_commands_needing_warning(): """Get commands that need deletion warning (60+ days unused, no warning sent).""" from datetime import datetime, timedelta + cutoff_date = datetime.now() - timedelta(days=60) - return (CustomCommand - .select() - .where((CustomCommand.is_active == True) & - (CustomCommand.warning_sent == False) & - ((CustomCommand.last_used.is_null()) | - (CustomCommand.last_used < cutoff_date)))) - + return CustomCommand.select().where( + (CustomCommand.is_active == True) + & (CustomCommand.warning_sent == False) + & ( + (CustomCommand.last_used.is_null()) + | (CustomCommand.last_used < cutoff_date) + ) + ) + @staticmethod def get_commands_eligible_for_deletion(): """Get commands eligible for deletion (90+ days unused).""" from datetime import datetime, timedelta + cutoff_date = datetime.now() - timedelta(days=90) - return (CustomCommand - .select() - .where((CustomCommand.is_active == True) & - ((CustomCommand.last_used.is_null()) | - (CustomCommand.last_used < cutoff_date)))) - + return CustomCommand.select().where( + (CustomCommand.is_active == True) + & ( + (CustomCommand.last_used.is_null()) + | (CustomCommand.last_used < cutoff_date) + ) + ) + def execute(self): """Execute the command and update usage statistics.""" from datetime import datetime + self.last_used = datetime.now() self.use_count += 1 self.warning_sent = False # Reset warning on use self.save() - + def mark_warning_sent(self): """Mark that a deletion warning has been sent.""" self.warning_sent = True self.save() - + def get_tags_list(self): """Parse tags JSON string into a list.""" if not self.tags: return [] try: import json + return json.loads(self.tags) except: return [] - + def set_tags_list(self, tags_list): """Set tags from a list to JSON string.""" if tags_list: import json + self.tags = json.dumps(tags_list) else: self.tags = None @@ -2365,6 +2591,7 @@ class CustomCommand(BaseModel): class HelpCommand(BaseModel): """Model for admin-created help topics.""" + name = CharField(max_length=32, unique=True) title = CharField(max_length=200) content = TextField() @@ -2374,7 +2601,9 @@ class HelpCommand(BaseModel): created_by_discord_id = CharField(max_length=20) # Discord snowflake ID as string created_at = DateTimeField() updated_at = DateTimeField(null=True) - last_modified_by = CharField(max_length=20, null=True) # Discord snowflake ID as string + last_modified_by = CharField( + max_length=20, null=True + ) # Discord snowflake ID as string # Status and metrics is_active = BooleanField(default=True) @@ -2382,7 +2611,7 @@ class HelpCommand(BaseModel): display_order = IntegerField(default=0) class Meta: - table_name = 'help_commands' + table_name = "help_commands" @staticmethod def get_by_name(name: str, include_inactive: bool = False): @@ -2395,17 +2624,22 @@ class HelpCommand(BaseModel): @staticmethod def search_by_name(partial_name: str, limit: int = 25): """Search help topics by partial name match.""" - return (HelpCommand - .select() - .where((HelpCommand.is_active == True) & - (fn.Lower(HelpCommand.name).contains(partial_name.lower()))) - .order_by(HelpCommand.display_order, HelpCommand.name) - .limit(limit)) + return ( + HelpCommand.select() + .where( + (HelpCommand.is_active == True) + & (fn.Lower(HelpCommand.name).contains(partial_name.lower())) + ) + .order_by(HelpCommand.display_order, HelpCommand.name) + .limit(limit) + ) @staticmethod def get_by_category(category: str, include_inactive: bool = False): """Get help commands by category.""" - query = HelpCommand.select().where(fn.Lower(HelpCommand.category) == category.lower()) + query = HelpCommand.select().where( + fn.Lower(HelpCommand.category) == category.lower() + ) if not include_inactive: query = query.where(HelpCommand.is_active == True) return query.order_by(HelpCommand.display_order, HelpCommand.name) @@ -2413,19 +2647,21 @@ class HelpCommand(BaseModel): @staticmethod def get_all_active(): """Get all active help topics.""" - return (HelpCommand - .select() - .where(HelpCommand.is_active == True) - .order_by(HelpCommand.display_order, HelpCommand.name)) + return ( + HelpCommand.select() + .where(HelpCommand.is_active == True) + .order_by(HelpCommand.display_order, HelpCommand.name) + ) @staticmethod def get_most_viewed(limit: int = 10): """Get most viewed help topics.""" - return (HelpCommand - .select() - .where(HelpCommand.is_active == True) - .order_by(HelpCommand.view_count.desc()) - .limit(limit)) + return ( + HelpCommand.select() + .where(HelpCommand.is_active == True) + .order_by(HelpCommand.view_count.desc()) + .limit(limit) + ) def increment_view_count(self): """Increment view count for this help topic.""" @@ -2475,30 +2711,35 @@ class SeasonBattingStatsView(BaseModel): hbp = IntegerField() sac = IntegerField() ibb = IntegerField() - + class Meta: - table_name = 'season_batting_stats_view' + table_name = "season_batting_stats_view" primary_key = False - + @staticmethod def get_by_season(season): - return SeasonBattingStatsView.select().where(SeasonBattingStatsView.season == season) - + return SeasonBattingStatsView.select().where( + SeasonBattingStatsView.season == season + ) + @staticmethod def get_team_stats(season, team_id): - return (SeasonBattingStatsView.select() - .where(SeasonBattingStatsView.season == season, - SeasonBattingStatsView.player_team_id == team_id)) - + return SeasonBattingStatsView.select().where( + SeasonBattingStatsView.season == season, + SeasonBattingStatsView.player_team_id == team_id, + ) + @staticmethod - def get_top_hitters(season, stat='avg', limit=10, desc=True): + def get_top_hitters(season, stat="avg", limit=10, desc=True): """Get top hitters by specified stat (avg, hr, rbi, ops, etc.)""" stat_field = getattr(SeasonBattingStatsView, stat, SeasonBattingStatsView.avg) order_field = stat_field.desc() if desc else stat_field.asc() - return (SeasonBattingStatsView.select() - .where(SeasonBattingStatsView.season == season) - .order_by(order_field) - .limit(limit)) + return ( + SeasonBattingStatsView.select() + .where(SeasonBattingStatsView.season == season) + .order_by(order_field) + .limit(limit) + ) class SeasonPitchingStats(BaseModel): @@ -2564,24 +2805,33 @@ class SeasonPitchingStats(BaseModel): lob_2outs = FloatField() rbipercent = FloatField() re24 = FloatField() - + class Meta: - table_name = 'seasonpitchingstats' - primary_key = CompositeKey('player', 'season') - + table_name = "seasonpitchingstats" + primary_key = CompositeKey("player", "season") + @staticmethod def get_team_stats(season, team_id): - return (SeasonPitchingStats.select() - .where(SeasonPitchingStats.season == season, - SeasonPitchingStats.player_team_id == team_id)) - + return SeasonPitchingStats.select().where( + SeasonPitchingStats.season == season, + SeasonPitchingStats.player_team_id == team_id, + ) + @staticmethod - def get_top_pitchers(season: Optional[int] = None, stat: str = 'era', limit: Optional[int] = 200, - desc: bool = False, team_id: Optional[int] = None, player_id: Optional[int] = None, - sbaplayer_id: Optional[int] = None, min_outs: Optional[int] = None, offset: int = 0): + def get_top_pitchers( + season: Optional[int] = None, + stat: str = "era", + limit: Optional[int] = 200, + desc: bool = False, + team_id: Optional[int] = None, + player_id: Optional[int] = None, + sbaplayer_id: Optional[int] = None, + min_outs: Optional[int] = None, + offset: int = 0, + ): """ Get top pitchers by specified stat with optional filtering. - + Args: season: Season to filter by (None for all seasons) stat: Stat field to sort by (default: era) @@ -2595,9 +2845,9 @@ class SeasonPitchingStats(BaseModel): """ stat_field = getattr(SeasonPitchingStats, stat, SeasonPitchingStats.era) order_field = stat_field.desc() if desc else stat_field.asc() - + query = SeasonPitchingStats.select().order_by(order_field) - + # Apply filters if season is not None: query = query.where(SeasonPitchingStats.season == season) @@ -2609,13 +2859,13 @@ class SeasonPitchingStats(BaseModel): query = query.where(SeasonPitchingStats.sbaplayer_id == sbaplayer_id) if min_outs is not None: query = query.where(SeasonPitchingStats.outs >= min_outs) - + # Apply pagination if offset > 0: query = query.offset(offset) if limit is not None and limit > 0: query = query.limit(limit) - + return query @@ -2655,28 +2905,37 @@ class SeasonBattingStats(BaseModel): ops = FloatField() woba = FloatField() k_pct = FloatField() - + # Running stats sb = IntegerField() cs = IntegerField() - + class Meta: - table_name = 'seasonbattingstats' - primary_key = CompositeKey('player', 'season') - + table_name = "seasonbattingstats" + primary_key = CompositeKey("player", "season") + @staticmethod def get_team_stats(season, team_id): - return (SeasonBattingStats.select() - .where(SeasonBattingStats.season == season, - SeasonBattingStats.player_team_id == team_id)) - + return SeasonBattingStats.select().where( + SeasonBattingStats.season == season, + SeasonBattingStats.player_team_id == team_id, + ) + @staticmethod - def get_top_hitters(season: Optional[int] = None, stat: str = 'woba', limit: Optional[int] = 200, - desc: bool = True, team_id: Optional[int] = None, player_id: Optional[int] = None, - sbaplayer_id: Optional[int] = None, min_pa: Optional[int] = None, offset: int = 0): + def get_top_hitters( + season: Optional[int] = None, + stat: str = "woba", + limit: Optional[int] = 200, + desc: bool = True, + team_id: Optional[int] = None, + player_id: Optional[int] = None, + sbaplayer_id: Optional[int] = None, + min_pa: Optional[int] = None, + offset: int = 0, + ): """ Get top hitters by specified stat with optional filtering. - + Args: season: Season to filter by (None for all seasons) stat: Stat field to sort by (default: woba) @@ -2689,9 +2948,9 @@ class SeasonBattingStats(BaseModel): """ stat_field = getattr(SeasonBattingStats, stat, SeasonBattingStats.woba) order_field = stat_field.desc() if desc else stat_field.asc() - + query = SeasonBattingStats.select().order_by(order_field) - + # Apply filters if season is not None: query = query.where(SeasonBattingStats.season == season) @@ -2703,13 +2962,13 @@ class SeasonBattingStats(BaseModel): query = query.where(SeasonBattingStats.sbaplayer_id == sbaplayer_id) if min_pa is not None: query = query.where(SeasonBattingStats.pa >= min_pa) - + # Apply pagination if offset > 0: query = query.offset(offset) if limit is not None and limit > 0: query = query.limit(limit) - + return query diff --git a/app/dependencies.py b/app/dependencies.py index b95747d..61aff56 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -11,22 +11,14 @@ from fastapi import HTTPException, Response from fastapi.security import OAuth2PasswordBearer from redis import Redis -date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' -logger = logging.getLogger('discord_app') - -# date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' -# log_level = logger.info if os.environ.get('LOG_LEVEL') == 'INFO' else 'WARN' -# logging.basicConfig( -# filename=f'logs/database/{date}.log', -# format='%(asctime)s - sba-database - %(levelname)s - %(message)s', -# level=log_level -# ) +date = f"{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}" +logger = logging.getLogger("discord_app") # Redis configuration -REDIS_HOST = os.environ.get('REDIS_HOST', 'localhost') -REDIS_PORT = int(os.environ.get('REDIS_PORT', '6379')) -REDIS_DB = int(os.environ.get('REDIS_DB', '0')) -CACHE_ENABLED = os.environ.get('CACHE_ENABLED', 'true').lower() == 'true' +REDIS_HOST = os.environ.get("REDIS_HOST", "localhost") +REDIS_PORT = int(os.environ.get("REDIS_PORT", "6379")) +REDIS_DB = int(os.environ.get("REDIS_DB", "0")) +CACHE_ENABLED = os.environ.get("CACHE_ENABLED", "true").lower() == "true" # Initialize Redis client with connection error handling if not CACHE_ENABLED: @@ -40,7 +32,7 @@ else: db=REDIS_DB, decode_responses=True, socket_connect_timeout=5, - socket_timeout=5 + socket_timeout=5, ) # Test connection redis_client.ping() @@ -50,12 +42,16 @@ else: redis_client = None oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") -priv_help = False if not os.environ.get('PRIVATE_IN_SCHEMA') else os.environ.get('PRIVATE_IN_SCHEMA').upper() -PRIVATE_IN_SCHEMA = True if priv_help == 'TRUE' else False +priv_help = ( + False + if not os.environ.get("PRIVATE_IN_SCHEMA") + else os.environ.get("PRIVATE_IN_SCHEMA").upper() +) +PRIVATE_IN_SCHEMA = True if priv_help == "TRUE" else False def valid_token(token): - return token == os.environ.get('API_TOKEN') + return token == os.environ.get("API_TOKEN") def update_season_batting_stats(player_ids, season, db_connection): @@ -63,17 +59,19 @@ def update_season_batting_stats(player_ids, season, db_connection): Update season batting stats for specific players in a given season. Recalculates stats from stratplay data and upserts into seasonbattingstats table. """ - + if not player_ids: logger.warning("update_season_batting_stats called with empty player_ids list") return - + # Convert single player_id to list for consistency if isinstance(player_ids, int): player_ids = [player_ids] - - logger.info(f"Updating season batting stats for {len(player_ids)} players in season {season}") - + + logger.info( + f"Updating season batting stats for {len(player_ids)} players in season {season}" + ) + try: # SQL query to recalculate and upsert batting stats query = """ @@ -217,12 +215,14 @@ def update_season_batting_stats(player_ids, season, db_connection): sb = EXCLUDED.sb, cs = EXCLUDED.cs; """ - + # Execute the query with parameters using the passed database connection db_connection.execute_sql(query, [season, player_ids, season, player_ids]) - - logger.info(f"Successfully updated season batting stats for {len(player_ids)} players in season {season}") - + + logger.info( + f"Successfully updated season batting stats for {len(player_ids)} players in season {season}" + ) + except Exception as e: logger.error(f"Error updating season batting stats: {e}") raise @@ -233,17 +233,19 @@ def update_season_pitching_stats(player_ids, season, db_connection): Update season pitching stats for specific players in a given season. Recalculates stats from stratplay and decision data and upserts into seasonpitchingstats table. """ - + if not player_ids: logger.warning("update_season_pitching_stats called with empty player_ids list") return - + # Convert single player_id to list for consistency if isinstance(player_ids, int): player_ids = [player_ids] - - logger.info(f"Updating season pitching stats for {len(player_ids)} players in season {season}") - + + logger.info( + f"Updating season pitching stats for {len(player_ids)} players in season {season}" + ) + try: # SQL query to recalculate and upsert pitching stats query = """ @@ -460,12 +462,14 @@ def update_season_pitching_stats(player_ids, season, db_connection): rbipercent = EXCLUDED.rbipercent, re24 = EXCLUDED.re24; """ - + # Execute the query with parameters using the passed database connection db_connection.execute_sql(query, [season, player_ids, season, player_ids]) - - logger.info(f"Successfully updated season pitching stats for {len(player_ids)} players in season {season}") - + + logger.info( + f"Successfully updated season pitching stats for {len(player_ids)} players in season {season}" + ) + except Exception as e: logger.error(f"Error updating season pitching stats: {e}") raise @@ -474,26 +478,24 @@ def update_season_pitching_stats(player_ids, season, db_connection): def send_webhook_message(message: str) -> bool: """ Send a message to Discord via webhook. - + Args: message: The message content to send - + Returns: bool: True if successful, False otherwise """ webhook_url = "https://discord.com/api/webhooks/1408811717424840876/7RXG_D5IqovA3Jwa9YOobUjVcVMuLc6cQyezABcWuXaHo5Fvz1en10M7J43o3OJ3bzGW" - + try: - payload = { - "content": message - } - + payload = {"content": message} + response = requests.post(webhook_url, json=payload, timeout=10) response.raise_for_status() - + logger.info(f"Webhook message sent successfully: {message[:100]}...") return True - + except requests.exceptions.RequestException as e: logger.error(f"Failed to send webhook message: {e}") return False @@ -502,99 +504,106 @@ def send_webhook_message(message: str) -> bool: return False -def cache_result(ttl: int = 300, key_prefix: str = "api", normalize_params: bool = True): +def cache_result( + ttl: int = 300, key_prefix: str = "api", normalize_params: bool = True +): """ Decorator to cache function results in Redis with parameter normalization. - + Args: ttl: Time to live in seconds (default: 5 minutes) key_prefix: Prefix for cache keys (default: "api") normalize_params: Remove None/empty values to reduce cache variations (default: True) - + Usage: @cache_result(ttl=600, key_prefix="stats") async def get_player_stats(player_id: int, season: Optional[int] = None): # expensive operation return stats - + # These will use the same cache entry when normalize_params=True: # get_player_stats(123, None) and get_player_stats(123) """ + def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): # Skip caching if Redis is not available if redis_client is None: return await func(*args, **kwargs) - + try: # Normalize parameters to reduce cache variations normalized_kwargs = kwargs.copy() if normalize_params: # Remove None values and empty collections normalized_kwargs = { - k: v for k, v in kwargs.items() + k: v + for k, v in kwargs.items() if v is not None and v != [] and v != "" and v != {} } - + # Generate more readable cache key args_str = "_".join(str(arg) for arg in args if arg is not None) - kwargs_str = "_".join([ - f"{k}={v}" for k, v in sorted(normalized_kwargs.items()) - ]) - + kwargs_str = "_".join( + [f"{k}={v}" for k, v in sorted(normalized_kwargs.items())] + ) + # Combine args and kwargs for cache key key_parts = [key_prefix, func.__name__] if args_str: key_parts.append(args_str) if kwargs_str: key_parts.append(kwargs_str) - + cache_key = ":".join(key_parts) - + # Truncate very long cache keys to prevent Redis key size limits if len(cache_key) > 200: cache_key = f"{key_prefix}:{func.__name__}:{hash(cache_key)}" - + # Try to get from cache cached_result = redis_client.get(cache_key) if cached_result is not None: logger.debug(f"Cache hit for key: {cache_key}") return json.loads(cached_result) - + # Cache miss - execute function logger.debug(f"Cache miss for key: {cache_key}") result = await func(*args, **kwargs) - + # Skip caching for Response objects (like CSV downloads) as they can't be properly serialized if not isinstance(result, Response): # Store in cache with TTL redis_client.setex( - cache_key, - ttl, - json.dumps(result, default=str, ensure_ascii=False) + cache_key, + ttl, + json.dumps(result, default=str, ensure_ascii=False), ) else: - logger.debug(f"Skipping cache for Response object from {func.__name__}") - + logger.debug( + f"Skipping cache for Response object from {func.__name__}" + ) + return result - + except Exception as e: # If caching fails, log error and continue without caching logger.error(f"Cache error for {func.__name__}: {e}") return await func(*args, **kwargs) - + return wrapper + return decorator def invalidate_cache(pattern: str = "*"): """ Invalidate cache entries matching a pattern. - + Args: pattern: Redis pattern to match keys (default: "*" for all) - + Usage: invalidate_cache("stats:*") # Clear all stats cache invalidate_cache("api:get_player_*") # Clear specific player cache @@ -602,12 +611,14 @@ def invalidate_cache(pattern: str = "*"): if redis_client is None: logger.warning("Cannot invalidate cache: Redis not available") return 0 - + try: keys = redis_client.keys(pattern) if keys: deleted = redis_client.delete(*keys) - logger.info(f"Invalidated {deleted} cache entries matching pattern: {pattern}") + logger.info( + f"Invalidated {deleted} cache entries matching pattern: {pattern}" + ) return deleted else: logger.debug(f"No cache entries found matching pattern: {pattern}") @@ -620,13 +631,13 @@ def invalidate_cache(pattern: str = "*"): def get_cache_stats() -> dict: """ Get Redis cache statistics. - + Returns: dict: Cache statistics including memory usage, key count, etc. """ if redis_client is None: return {"status": "unavailable", "message": "Redis not connected"} - + try: info = redis_client.info() return { @@ -634,7 +645,7 @@ def get_cache_stats() -> dict: "memory_used": info.get("used_memory_human", "unknown"), "total_keys": redis_client.dbsize(), "connected_clients": info.get("connected_clients", 0), - "uptime_seconds": info.get("uptime_in_seconds", 0) + "uptime_seconds": info.get("uptime_in_seconds", 0), } except Exception as e: logger.error(f"Error getting cache stats: {e}") @@ -642,34 +653,35 @@ def get_cache_stats() -> dict: def add_cache_headers( - max_age: int = 300, + max_age: int = 300, cache_type: str = "public", vary: Optional[str] = None, - etag: bool = False + etag: bool = False, ): """ Decorator to add HTTP cache headers to FastAPI responses. - + Args: max_age: Cache duration in seconds (default: 5 minutes) cache_type: "public", "private", or "no-cache" (default: "public") vary: Vary header value (e.g., "Accept-Encoding, Authorization") etag: Whether to generate ETag based on response content - + Usage: @add_cache_headers(max_age=1800, cache_type="public") async def get_static_data(): return {"data": "static content"} - + @add_cache_headers(max_age=60, cache_type="private", vary="Authorization") async def get_user_data(): return {"data": "user specific"} """ + def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): result = await func(*args, **kwargs) - + # Handle different response types if isinstance(result, Response): response = result @@ -677,38 +689,41 @@ def add_cache_headers( # Convert to Response with JSON content response = Response( content=json.dumps(result, default=str, ensure_ascii=False), - media_type="application/json" + media_type="application/json", ) else: # Handle other response types response = Response(content=str(result)) - + # Build Cache-Control header cache_control_parts = [cache_type] if cache_type != "no-cache" and max_age > 0: cache_control_parts.append(f"max-age={max_age}") - + response.headers["Cache-Control"] = ", ".join(cache_control_parts) - + # Add Vary header if specified if vary: response.headers["Vary"] = vary - + # Add ETag if requested - if etag and (hasattr(result, '__dict__') or isinstance(result, (dict, list))): + if etag and ( + hasattr(result, "__dict__") or isinstance(result, (dict, list)) + ): content_hash = hashlib.md5( json.dumps(result, default=str, sort_keys=True).encode() ).hexdigest() response.headers["ETag"] = f'"{content_hash}"' - + # Add Last-Modified header with current time for dynamic content - response.headers["Last-Modified"] = datetime.datetime.now(datetime.timezone.utc).strftime( - "%a, %d %b %Y %H:%M:%S GMT" - ) - + response.headers["Last-Modified"] = datetime.datetime.now( + datetime.timezone.utc + ).strftime("%a, %d %b %Y %H:%M:%S GMT") + return response - + return wrapper + return decorator @@ -718,52 +733,59 @@ def handle_db_errors(func): Ensures proper cleanup of database connections and provides consistent error handling. Includes comprehensive logging with function context, timing, and stack traces. """ + @wraps(func) async def wrapper(*args, **kwargs): import time import traceback from .db_engine import db # Import here to avoid circular imports - + start_time = time.time() func_name = f"{func.__module__}.{func.__name__}" - + # Sanitize arguments for logging (exclude sensitive data) safe_args = [] safe_kwargs = {} - + try: # Log sanitized arguments (avoid logging tokens, passwords, etc.) for arg in args: - if hasattr(arg, '__dict__') and hasattr(arg, 'url'): # FastAPI Request object - safe_args.append(f"Request({getattr(arg, 'method', 'UNKNOWN')} {getattr(arg, 'url', 'unknown')})") + if hasattr(arg, "__dict__") and hasattr( + arg, "url" + ): # FastAPI Request object + safe_args.append( + f"Request({getattr(arg, 'method', 'UNKNOWN')} {getattr(arg, 'url', 'unknown')})" + ) else: safe_args.append(str(arg)[:100]) # Truncate long values - + for key, value in kwargs.items(): - if key.lower() in ['token', 'password', 'secret', 'key']: - safe_kwargs[key] = '[REDACTED]' + if key.lower() in ["token", "password", "secret", "key"]: + safe_kwargs[key] = "[REDACTED]" else: safe_kwargs[key] = str(value)[:100] # Truncate long values - - logger.info(f"Starting {func_name} - args: {safe_args}, kwargs: {safe_kwargs}") - + + logger.info( + f"Starting {func_name} - args: {safe_args}, kwargs: {safe_kwargs}" + ) + result = await func(*args, **kwargs) - + elapsed_time = time.time() - start_time logger.info(f"Completed {func_name} successfully in {elapsed_time:.3f}s") - + return result - + except Exception as e: elapsed_time = time.time() - start_time error_trace = traceback.format_exc() - + logger.error(f"Database error in {func_name} after {elapsed_time:.3f}s") logger.error(f"Function args: {safe_args}") logger.error(f"Function kwargs: {safe_kwargs}") logger.error(f"Exception: {str(e)}") logger.error(f"Full traceback:\n{error_trace}") - + try: logger.info(f"Attempting database rollback for {func_name}") db.rollback() @@ -775,8 +797,12 @@ def handle_db_errors(func): db.close() logger.info(f"Database connection closed for {func_name}") except Exception as close_error: - logger.error(f"Error closing database connection in {func_name}: {close_error}") - - raise HTTPException(status_code=500, detail=f'Database error in {func_name}: {str(e)}') - + logger.error( + f"Error closing database connection in {func_name}: {close_error}" + ) + + raise HTTPException( + status_code=500, detail=f"Database error in {func_name}: {str(e)}" + ) + return wrapper diff --git a/app/main.py b/app/main.py index ab04918..fdf3bac 100644 --- a/app/main.py +++ b/app/main.py @@ -7,41 +7,58 @@ from fastapi import Depends, FastAPI, Request from fastapi.openapi.docs import get_swagger_ui_html from fastapi.openapi.utils import get_openapi -# from fastapi.openapi.docs import get_swagger_ui_html -# from fastapi.openapi.utils import get_openapi +from .routers_v3 import ( + current, + players, + results, + schedules, + standings, + teams, + transactions, + battingstats, + pitchingstats, + fieldingstats, + draftpicks, + draftlist, + managers, + awards, + draftdata, + keepers, + stratgame, + stratplay, + injuries, + decisions, + divisions, + sbaplayers, + custom_commands, + help_commands, + views, +) -from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, pitchingstats, fieldingstats, draftpicks, draftlist, managers, awards, draftdata, keepers, stratgame, stratplay, injuries, decisions, divisions, sbaplayers, custom_commands, help_commands, views - -# date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' -log_level = logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else logging.WARNING -# logging.basicConfig( -# filename=f'logs/database/{date}.log', -# format='%(asctime)s - sba-database - %(levelname)s - %(message)s', -# level=log_level -# ) -logger = logging.getLogger('discord_app') +log_level = logging.INFO if os.environ.get("LOG_LEVEL") == "INFO" else logging.WARNING +logger = logging.getLogger("discord_app") logger.setLevel(log_level) handler = RotatingFileHandler( - filename='./logs/sba-database.log', + filename="./logs/sba-database.log", # encoding='utf-8', maxBytes=8 * 1024 * 1024, # 8 MiB backupCount=5, # Rotate through 5 files ) -formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) logger.addHandler(handler) app = FastAPI( # root_path='/api', - responses={404: {'description': 'Not found'}}, - docs_url='/api/docs', - redoc_url='/api/redoc' + responses={404: {"description": "Not found"}}, + docs_url="/api/docs", + redoc_url="/api/redoc", ) -logger.info(f'Starting up now...') +logger.info(f"Starting up now...") app.include_router(current.router) @@ -70,20 +87,17 @@ app.include_router(custom_commands.router) app.include_router(help_commands.router) app.include_router(views.router) -logger.info(f'Loaded all routers.') +logger.info(f"Loaded all routers.") @app.get("/api/docs", include_in_schema=False) async def get_docs(req: Request): print(req.scope) - return get_swagger_ui_html(openapi_url=req.scope.get('root_path')+'/openapi.json', title='Swagger') + return get_swagger_ui_html( + openapi_url=req.scope.get("root_path") + "/openapi.json", title="Swagger" + ) @app.get("/api/openapi.json", include_in_schema=False) async def openapi(): - return get_openapi(title='SBa API Docs', version=f'0.1.1', routes=app.routes) - - -# @app.get("/api") -# async def root(): -# return {"message": "Hello Bigger Applications!"} + return get_openapi(title="SBa API Docs", version=f"0.1.1", routes=app.routes) diff --git a/app/routers_v3/battingstats.py b/app/routers_v3/battingstats.py index 7471cf9..1912a8b 100644 --- a/app/routers_v3/battingstats.py +++ b/app/routers_v3/battingstats.py @@ -3,15 +3,27 @@ from typing import List, Optional, Literal import logging import pydantic -from ..db_engine import db, BattingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/battingstats', - tags=['battingstats'] +from ..db_engine import ( + db, + BattingStat, + Team, + Player, + Current, + model_to_dict, + chunked, + fn, + per_season_weeks, ) +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, +) + +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/battingstats", tags=["battingstats"]) class BatStatModel(pydantic.BaseModel): @@ -60,29 +72,37 @@ class BatStatList(pydantic.BaseModel): stats: List[BatStatModel] -@router.get('') +@router.get("") @handle_db_errors async def get_batstats( - season: int, s_type: Optional[str] = 'regular', team_abbrev: list = Query(default=None), - player_name: list = Query(default=None), player_id: list = Query(default=None), - week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None), - position: list = Query(default=None), limit: Optional[int] = None, sort: Optional[str] = None, - short_output: Optional[bool] = True): - if 'post' in s_type.lower(): + season: int, + s_type: Optional[str] = "regular", + team_abbrev: list = Query(default=None), + player_name: list = Query(default=None), + player_id: list = Query(default=None), + week_start: Optional[int] = None, + week_end: Optional[int] = None, + game_num: list = Query(default=None), + position: list = Query(default=None), + limit: Optional[int] = None, + sort: Optional[str] = None, + short_output: Optional[bool] = True, +): + if "post" in s_type.lower(): all_stats = BattingStat.post_season(season) if all_stats.count() == 0: db.close() - return {'count': 0, 'stats': []} - elif s_type.lower() in ['combined', 'total', 'all']: + return {"count": 0, "stats": []} + elif s_type.lower() in ["combined", "total", "all"]: all_stats = BattingStat.combined_season(season) if all_stats.count() == 0: db.close() - return {'count': 0, 'stats': []} + return {"count": 0, "stats": []} else: all_stats = BattingStat.regular_season(season) if all_stats.count() == 0: db.close() - return {'count': 0, 'stats': []} + return {"count": 0, "stats": []} if position is not None: all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position]) @@ -93,7 +113,9 @@ async def get_batstats( if player_id: all_stats = all_stats.where(BattingStat.player_id << player_id) else: - p_query = Player.select_season(season).where(fn.Lower(Player.name) << [x.lower() for x in player_name]) + p_query = Player.select_season(season).where( + fn.Lower(Player.name) << [x.lower() for x in player_name] + ) all_stats = all_stats.where(BattingStat.player << p_query) if game_num: all_stats = all_stats.where(BattingStat.game == game_num) @@ -108,21 +130,19 @@ async def get_batstats( db.close() raise HTTPException( status_code=404, - detail=f'Start week {start} is after end week {end} - cannot pull stats' + detail=f"Start week {start} is after end week {end} - cannot pull stats", ) - all_stats = all_stats.where( - (BattingStat.week >= start) & (BattingStat.week <= end) - ) + all_stats = all_stats.where((BattingStat.week >= start) & (BattingStat.week <= end)) if limit: all_stats = all_stats.limit(limit) if sort: - if sort == 'newest': + if sort == "newest": all_stats = all_stats.order_by(-BattingStat.week, -BattingStat.game) return_stats = { - 'count': all_stats.count(), - 'stats': [model_to_dict(x, recurse=not short_output) for x in all_stats], + "count": all_stats.count(), + "stats": [model_to_dict(x, recurse=not short_output) for x in all_stats], # 'stats': [{'id': x.id} for x in all_stats] } @@ -130,52 +150,82 @@ async def get_batstats( return return_stats -@router.get('/totals') +@router.get("/totals") @handle_db_errors async def get_totalstats( - season: int, s_type: Literal['regular', 'post', 'total', None] = None, team_abbrev: list = Query(default=None), - team_id: list = Query(default=None), player_name: list = Query(default=None), - week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None), - position: list = Query(default=None), sort: Optional[str] = None, player_id: list = Query(default=None), - group_by: Literal['team', 'player', 'playerteam'] = 'player', short_output: Optional[bool] = False, - min_pa: Optional[int] = 1, week: list = Query(default=None)): + season: int, + s_type: Literal["regular", "post", "total", None] = None, + team_abbrev: list = Query(default=None), + team_id: list = Query(default=None), + player_name: list = Query(default=None), + week_start: Optional[int] = None, + week_end: Optional[int] = None, + game_num: list = Query(default=None), + position: list = Query(default=None), + sort: Optional[str] = None, + player_id: list = Query(default=None), + group_by: Literal["team", "player", "playerteam"] = "player", + short_output: Optional[bool] = False, + min_pa: Optional[int] = 1, + week: list = Query(default=None), +): if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1: - raise HTTPException(status_code=400, detail=f'Only one of s_type, week_start/week_end, or week may be used.') + raise HTTPException( + status_code=400, + detail=f"Only one of s_type, week_start/week_end, or week may be used.", + ) # Build SELECT fields conditionally based on group_by to match GROUP BY exactly select_fields = [] - - if group_by == 'player': + + if group_by == "player": select_fields = [BattingStat.player] - elif group_by == 'team': + elif group_by == "team": select_fields = [BattingStat.team] - elif group_by == 'playerteam': + elif group_by == "playerteam": select_fields = [BattingStat.player, BattingStat.team] else: # Default case select_fields = [BattingStat.player] all_stats = ( - BattingStat - .select(*select_fields, - fn.SUM(BattingStat.pa).alias('sum_pa'), fn.SUM(BattingStat.ab).alias('sum_ab'), - fn.SUM(BattingStat.run).alias('sum_run'), fn.SUM(BattingStat.hit).alias('sum_hit'), - fn.SUM(BattingStat.rbi).alias('sum_rbi'), fn.SUM(BattingStat.double).alias('sum_double'), - fn.SUM(BattingStat.triple).alias('sum_triple'), fn.SUM(BattingStat.hr).alias('sum_hr'), - fn.SUM(BattingStat.bb).alias('sum_bb'), fn.SUM(BattingStat.so).alias('sum_so'), - fn.SUM(BattingStat.hbp).alias('sum_hbp'), fn.SUM(BattingStat.sac).alias('sum_sac'), - fn.SUM(BattingStat.ibb).alias('sum_ibb'), fn.SUM(BattingStat.gidp).alias('sum_gidp'), - fn.SUM(BattingStat.sb).alias('sum_sb'), fn.SUM(BattingStat.cs).alias('sum_cs'), - fn.SUM(BattingStat.bphr).alias('sum_bphr'), fn.SUM(BattingStat.bpfo).alias('sum_bpfo'), - fn.SUM(BattingStat.bp1b).alias('sum_bp1b'), fn.SUM(BattingStat.bplo).alias('sum_bplo'), - fn.SUM(BattingStat.xba).alias('sum_xba'), fn.SUM(BattingStat.xbt).alias('sum_xbt'), - fn.SUM(BattingStat.xch).alias('sum_xch'), fn.SUM(BattingStat.xhit).alias('sum_xhit'), - fn.SUM(BattingStat.error).alias('sum_error'), fn.SUM(BattingStat.pb).alias('sum_pb'), - fn.SUM(BattingStat.sbc).alias('sum_sbc'), fn.SUM(BattingStat.csc).alias('sum_csc'), - fn.SUM(BattingStat.roba).alias('sum_roba'), fn.SUM(BattingStat.robs).alias('sum_robs'), - fn.SUM(BattingStat.raa).alias('sum_raa'), fn.SUM(BattingStat.rto).alias('sum_rto')) - .where(BattingStat.season == season) - .having(fn.SUM(BattingStat.pa) >= min_pa) + BattingStat.select( + *select_fields, + fn.SUM(BattingStat.pa).alias("sum_pa"), + fn.SUM(BattingStat.ab).alias("sum_ab"), + fn.SUM(BattingStat.run).alias("sum_run"), + fn.SUM(BattingStat.hit).alias("sum_hit"), + fn.SUM(BattingStat.rbi).alias("sum_rbi"), + fn.SUM(BattingStat.double).alias("sum_double"), + fn.SUM(BattingStat.triple).alias("sum_triple"), + fn.SUM(BattingStat.hr).alias("sum_hr"), + fn.SUM(BattingStat.bb).alias("sum_bb"), + fn.SUM(BattingStat.so).alias("sum_so"), + fn.SUM(BattingStat.hbp).alias("sum_hbp"), + fn.SUM(BattingStat.sac).alias("sum_sac"), + fn.SUM(BattingStat.ibb).alias("sum_ibb"), + fn.SUM(BattingStat.gidp).alias("sum_gidp"), + fn.SUM(BattingStat.sb).alias("sum_sb"), + fn.SUM(BattingStat.cs).alias("sum_cs"), + fn.SUM(BattingStat.bphr).alias("sum_bphr"), + fn.SUM(BattingStat.bpfo).alias("sum_bpfo"), + fn.SUM(BattingStat.bp1b).alias("sum_bp1b"), + fn.SUM(BattingStat.bplo).alias("sum_bplo"), + fn.SUM(BattingStat.xba).alias("sum_xba"), + fn.SUM(BattingStat.xbt).alias("sum_xbt"), + fn.SUM(BattingStat.xch).alias("sum_xch"), + fn.SUM(BattingStat.xhit).alias("sum_xhit"), + fn.SUM(BattingStat.error).alias("sum_error"), + fn.SUM(BattingStat.pb).alias("sum_pb"), + fn.SUM(BattingStat.sbc).alias("sum_sbc"), + fn.SUM(BattingStat.csc).alias("sum_csc"), + fn.SUM(BattingStat.roba).alias("sum_roba"), + fn.SUM(BattingStat.robs).alias("sum_robs"), + fn.SUM(BattingStat.raa).alias("sum_raa"), + fn.SUM(BattingStat.rto).alias("sum_rto"), + ) + .where(BattingStat.season == season) + .having(fn.SUM(BattingStat.pa) >= min_pa) ) if True in [s_type is not None, week_start is not None, week_end is not None]: @@ -185,16 +235,20 @@ async def get_totalstats( elif week_start is not None or week_end is not None: if week_start is None or week_end is None: raise HTTPException( - status_code=400, detail='Both week_start and week_end must be included if either is used.' + status_code=400, + detail="Both week_start and week_end must be included if either is used.", + ) + weeks["start"] = week_start + if week_end < weeks["start"]: + raise HTTPException( + status_code=400, + detail="week_end must be greater than or equal to week_start", ) - weeks['start'] = week_start - if week_end < weeks['start']: - raise HTTPException(status_code=400, detail='week_end must be greater than or equal to week_start') else: - weeks['end'] = week_end + weeks["end"] = week_end all_stats = all_stats.where( - (BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) + (BattingStat.week >= weeks["start"]) & (BattingStat.week <= weeks["end"]) ) elif week is not None: all_stats = all_stats.where(BattingStat.week << week) @@ -204,14 +258,20 @@ async def get_totalstats( if position is not None: p_list = [x.upper() for x in position] all_players = Player.select().where( - (Player.pos_1 << p_list) | (Player.pos_2 << p_list) | (Player.pos_3 << p_list) | ( Player.pos_4 << p_list) | - (Player.pos_5 << p_list) | (Player.pos_6 << p_list) | (Player.pos_7 << p_list) | ( Player.pos_8 << p_list) + (Player.pos_1 << p_list) + | (Player.pos_2 << p_list) + | (Player.pos_3 << p_list) + | (Player.pos_4 << p_list) + | (Player.pos_5 << p_list) + | (Player.pos_6 << p_list) + | (Player.pos_7 << p_list) + | (Player.pos_8 << p_list) ) all_stats = all_stats.where(BattingStat.player << all_players) if sort is not None: - if sort == 'player': + if sort == "player": all_stats = all_stats.order_by(BattingStat.player) - elif sort == 'team': + elif sort == "team": all_stats = all_stats.order_by(BattingStat.team) if group_by is not None: # Use the same fields for GROUP BY as we used for SELECT @@ -227,75 +287,78 @@ async def get_totalstats( all_teams = Team.select().where(Team.id << team_id) all_stats = all_stats.where(BattingStat.team << all_teams) elif team_abbrev is not None: - all_teams = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev]) + all_teams = Team.select().where( + fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev] + ) all_stats = all_stats.where(BattingStat.team << all_teams) if player_name is not None: - all_players = Player.select().where(fn.Lower(Player.name) << [x.lower() for x in player_name]) + all_players = Player.select().where( + fn.Lower(Player.name) << [x.lower() for x in player_name] + ) all_stats = all_stats.where(BattingStat.player << all_players) elif player_id is not None: all_players = Player.select().where(Player.id << player_id) all_stats = all_stats.where(BattingStat.player << all_players) - return_stats = { - 'count': all_stats.count(), - 'stats': [] - } - + return_stats = {"count": all_stats.count(), "stats": []} + for x in all_stats: # Handle player field based on grouping with safe access - this_player = 'TOT' - if 'player' in group_by and hasattr(x, 'player'): - this_player = x.player_id if short_output else model_to_dict(x.player, recurse=False) + this_player = "TOT" + if "player" in group_by and hasattr(x, "player"): + this_player = ( + x.player_id if short_output else model_to_dict(x.player, recurse=False) + ) - # Handle team field based on grouping with safe access - this_team = 'TOT' - if 'team' in group_by and hasattr(x, 'team'): - this_team = x.team_id if short_output else model_to_dict(x.team, recurse=False) - - return_stats['stats'].append({ - 'player': this_player, - 'team': this_team, - 'pa': x.sum_pa, - 'ab': x.sum_ab, - 'run': x.sum_run, - 'hit': x.sum_hit, - 'rbi': x.sum_rbi, - 'double': x.sum_double, - 'triple': x.sum_triple, - 'hr': x.sum_hr, - 'bb': x.sum_bb, - 'so': x.sum_so, - 'hbp': x.sum_hbp, - 'sac': x.sum_sac, - 'ibb': x.sum_ibb, - 'gidp': x.sum_gidp, - 'sb': x.sum_sb, - 'cs': x.sum_cs, - 'bphr': x.sum_bphr, - 'bpfo': x.sum_bpfo, - 'bp1b': x.sum_bp1b, - 'bplo': x.sum_bplo - }) + # Handle team field based on grouping with safe access + this_team = "TOT" + if "team" in group_by and hasattr(x, "team"): + this_team = ( + x.team_id if short_output else model_to_dict(x.team, recurse=False) + ) + + return_stats["stats"].append( + { + "player": this_player, + "team": this_team, + "pa": x.sum_pa, + "ab": x.sum_ab, + "run": x.sum_run, + "hit": x.sum_hit, + "rbi": x.sum_rbi, + "double": x.sum_double, + "triple": x.sum_triple, + "hr": x.sum_hr, + "bb": x.sum_bb, + "so": x.sum_so, + "hbp": x.sum_hbp, + "sac": x.sum_sac, + "ibb": x.sum_ibb, + "gidp": x.sum_gidp, + "sb": x.sum_sb, + "cs": x.sum_cs, + "bphr": x.sum_bphr, + "bpfo": x.sum_bpfo, + "bp1b": x.sum_bp1b, + "bplo": x.sum_bplo, + } + ) db.close() return return_stats -# @router.get('/career/{player_name}') -# async def get_careerstats( -# s_type: Literal['regular', 'post', 'total'] = 'regular', player_name: list = Query(default=None)): -# pass # Keep Career Stats table and recalculate after posting stats - - -@router.patch('/{stat_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{stat_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors -async def patch_batstats(stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme)): +async def patch_batstats( + stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme) +): if not valid_token(token): - logger.warning(f'patch_batstats - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"patch_batstats - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") if BattingStat.get_or_none(BattingStat.id == stat_id) is None: - raise HTTPException(status_code=404, detail=f'Stat ID {stat_id} not found') + raise HTTPException(status_code=404, detail=f"Stat ID {stat_id} not found") BattingStat.update(**new_stats.dict()).where(BattingStat.id == stat_id).execute() r_stat = model_to_dict(BattingStat.get_by_id(stat_id)) @@ -303,12 +366,12 @@ async def patch_batstats(stat_id: int, new_stats: BatStatModel, token: str = Dep return r_stat -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_batstats - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"post_batstats - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") all_stats = [] @@ -316,9 +379,13 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme) team = Team.get_or_none(Team.id == x.team_id) this_player = Player.get_or_none(Player.id == x.player_id) if team is None: - raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found') + raise HTTPException( + status_code=404, detail=f"Team ID {x.team_id} not found" + ) if this_player is None: - raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found') + raise HTTPException( + status_code=404, detail=f"Player ID {x.player_id} not found" + ) all_stats.append(BattingStat(**x.dict())) @@ -329,4 +396,4 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme) # Update career stats db.close() - return f'Added {len(all_stats)} batting lines' + return f"Added {len(all_stats)} batting lines" diff --git a/app/routers_v3/decisions.py b/app/routers_v3/decisions.py index 6170ff2..b2c869f 100644 --- a/app/routers_v3/decisions.py +++ b/app/routers_v3/decisions.py @@ -4,15 +4,26 @@ import copy import logging import pydantic -from ..db_engine import db, Decision, StratGame, Player, model_to_dict, chunked, fn, Team -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/decisions', - tags=['decisions'] +from ..db_engine import ( + db, + Decision, + StratGame, + Player, + model_to_dict, + chunked, + fn, + Team, ) +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, +) + +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/decisions", tags=["decisions"]) class DecisionModel(pydantic.BaseModel): @@ -43,17 +54,31 @@ class DecisionReturnList(pydantic.BaseModel): decisions: list[DecisionModel] -@router.get('') +@router.get("") @handle_db_errors async def get_decisions( - season: list = Query(default=None), week: list = Query(default=None), game_num: list = Query(default=None), - s_type: Literal['regular', 'post', 'all', None] = None, team_id: list = Query(default=None), - week_start: Optional[int] = None, week_end: Optional[int] = None, win: Optional[int] = None, - loss: Optional[int] = None, hold: Optional[int] = None, save: Optional[int] = None, - b_save: Optional[int] = None, irunners: list = Query(default=None), irunners_scored: list = Query(default=None), - game_id: list = Query(default=None), player_id: list = Query(default=None), - limit: Optional[int] = None, short_output: Optional[bool] = False): - all_dec = Decision.select().order_by(-Decision.season, -Decision.week, -Decision.game_num) + season: list = Query(default=None), + week: list = Query(default=None), + game_num: list = Query(default=None), + s_type: Literal["regular", "post", "all", None] = None, + team_id: list = Query(default=None), + week_start: Optional[int] = None, + week_end: Optional[int] = None, + win: Optional[int] = None, + loss: Optional[int] = None, + hold: Optional[int] = None, + save: Optional[int] = None, + b_save: Optional[int] = None, + irunners: list = Query(default=None), + irunners_scored: list = Query(default=None), + game_id: list = Query(default=None), + player_id: list = Query(default=None), + limit: Optional[int] = None, + short_output: Optional[bool] = False, +): + all_dec = Decision.select().order_by( + -Decision.season, -Decision.week, -Decision.game_num + ) if season is not None: all_dec = all_dec.where(Decision.season << season) @@ -65,21 +90,13 @@ async def get_decisions( all_dec = all_dec.where(Decision.game_id << game_id) if player_id is not None: all_dec = all_dec.where(Decision.pitcher << player_id) - # # Need to allow for split-season stats - # if team_id is not None: - # all_teams = Team.select().where(Team.id << team_id) - # all_games = StratGame.select().where( - # (StratGame.away_team << all_teams) | (StratGame.home_team << all_teams)) - # all_dec = all_dec.where(Decision.game << all_games) - # if team_id is not None: - # all_players = Player.select().where(Player.team_id << team_id) - # all_dec = all_dec.where(Decision.pitcher << all_players) if team_id is not None: s8_teams = [int(x) for x in team_id if int(x) <= 350] if season is not None and 8 in season or s8_teams: all_teams = Team.select().where(Team.id << team_id) all_games = StratGame.select().where( - (StratGame.away_team << all_teams) | (StratGame.home_team << all_teams)) + (StratGame.away_team << all_teams) | (StratGame.home_team << all_teams) + ) all_dec = all_dec.where(Decision.game << all_games) else: all_teams = Team.select().where(Team.id << team_id) @@ -87,9 +104,6 @@ async def get_decisions( if s_type is not None: all_games = StratGame.select().where(StratGame.season_type == s_type) all_dec = all_dec.where(Decision.game << all_games) - # if team_id is not None: - # all_players = Player.select().where(Player.team_id << team_id) - # all_dec = all_dec.where(Decision.pitcher << all_players) if week_start is not None: all_dec = all_dec.where(Decision.week >= week_start) if week_end is not None: @@ -115,28 +129,38 @@ async def get_decisions( all_dec = all_dec.limit(limit) return_dec = { - 'count': all_dec.count(), - 'decisions': [model_to_dict(x, recurse=not short_output) for x in all_dec] + "count": all_dec.count(), + "decisions": [model_to_dict(x, recurse=not short_output) for x in all_dec], } db.close() return return_dec -@router.patch('/{decision_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{decision_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def patch_decision( - decision_id: int, win: Optional[int] = None, loss: Optional[int] = None, hold: Optional[int] = None, - save: Optional[int] = None, b_save: Optional[int] = None, irunners: Optional[int] = None, - irunners_scored: Optional[int] = None, rest_ip: Optional[int] = None, rest_required: Optional[int] = None, - token: str = Depends(oauth2_scheme)): + decision_id: int, + win: Optional[int] = None, + loss: Optional[int] = None, + hold: Optional[int] = None, + save: Optional[int] = None, + b_save: Optional[int] = None, + irunners: Optional[int] = None, + irunners_scored: Optional[int] = None, + rest_ip: Optional[int] = None, + rest_required: Optional[int] = None, + token: str = Depends(oauth2_scheme), +): if not valid_token(token): - logger.warning(f'patch_decision - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"patch_decision - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_dec = Decision.get_or_none(Decision.id == decision_id) if this_dec is None: db.close() - raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found') + raise HTTPException( + status_code=404, detail=f"Decision ID {decision_id} not found" + ) if win is not None: this_dec.win = win @@ -163,22 +187,28 @@ async def patch_decision( return d_result else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch decision {decision_id}') + raise HTTPException( + status_code=500, detail=f"Unable to patch decision {decision_id}" + ) -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_decisions - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"post_decisions - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") new_dec = [] for x in dec_list.decisions: if StratGame.get_or_none(StratGame.id == x.game_id) is None: - raise HTTPException(status_code=404, detail=f'Game ID {x.game_id} not found') + raise HTTPException( + status_code=404, detail=f"Game ID {x.game_id} not found" + ) if Player.get_or_none(Player.id == x.pitcher_id) is None: - raise HTTPException(status_code=404, detail=f'Player ID {x.pitcher_id} not found') + raise HTTPException( + status_code=404, detail=f"Player ID {x.pitcher_id} not found" + ) new_dec.append(x.dict()) @@ -187,49 +217,53 @@ async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_sch Decision.insert_many(batch).on_conflict_ignore().execute() db.close() - return f'Inserted {len(new_dec)} decisions' + return f"Inserted {len(new_dec)} decisions" -@router.delete('/{decision_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{decision_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_decision - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"delete_decision - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_dec = Decision.get_or_none(Decision.id == decision_id) if this_dec is None: db.close() - raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found') + raise HTTPException( + status_code=404, detail=f"Decision ID {decision_id} not found" + ) count = this_dec.delete_instance() db.close() if count == 1: - return f'Decision {decision_id} has been deleted' + return f"Decision {decision_id} has been deleted" else: - raise HTTPException(status_code=500, detail=f'Decision {decision_id} could not be deleted') + raise HTTPException( + status_code=500, detail=f"Decision {decision_id} could not be deleted" + ) -@router.delete('/game/{game_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/game/{game_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_decisions_game - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"delete_decisions_game - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_game = StratGame.get_or_none(StratGame.id == game_id) if not this_game: db.close() - raise HTTPException(status_code=404, detail=f'Game ID {game_id} not found') + raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found") count = Decision.delete().where(Decision.game == this_game).execute() db.close() if count > 0: - return f'Deleted {count} decisions matching Game ID {game_id}' + return f"Deleted {count} decisions matching Game ID {game_id}" else: - raise HTTPException(status_code=500, detail=f'No decisions matching Game ID {game_id} were deleted') - - - + raise HTTPException( + status_code=500, + detail=f"No decisions matching Game ID {game_id} were deleted", + )