From 7ac8b752ec3daf4e590d46c1fc6dda1703bfe20f Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Tue, 21 Mar 2023 16:09:46 -0500 Subject: [PATCH 01/24] Initial commit File structure in place /players and /current built --- Dockerfile | 4 +- app/__init__.py | 0 app/database/__init__.py | 1817 ++++++++++++++++++++++++++++++++++++ app/db_engine.py | 1817 ++++++++++++++++++++++++++++++++++++ app/dependencies.py | 20 + app/main.py | 16 + app/routers_v3/__init__.py | 0 app/routers_v3/current.py | 118 +++ app/routers_v3/players.py | 204 ++++ requirements.txt | 4 +- 10 files changed, 3996 insertions(+), 4 deletions(-) create mode 100644 app/__init__.py create mode 100644 app/database/__init__.py create mode 100644 app/db_engine.py create mode 100644 app/dependencies.py create mode 100644 app/main.py create mode 100644 app/routers_v3/__init__.py create mode 100644 app/routers_v3/current.py create mode 100644 app/routers_v3/players.py diff --git a/Dockerfile b/Dockerfile index e84d3d1..9ae2380 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8 +FROM tiangolo/uvicorn-gunicorn-fastapi:latest WORKDIR /usr/src/app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt -COPY . . \ No newline at end of file +COPY ./app /app \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/database/__init__.py b/app/database/__init__.py new file mode 100644 index 0000000..ae483e2 --- /dev/null +++ b/app/database/__init__.py @@ -0,0 +1,1817 @@ +import copy +import math + +from peewee import * + +from playhouse.shortcuts import model_to_dict + +db = SqliteDatabase( + 'storage/sba_master.db', + pragmas={ + 'journal_mode': 'wal', + 'cache_size': -1 * 64000, + 'synchronous': 0 + } +) + + +""" +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 +""" + + +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) + + +def games_back(leader, chaser): + return ((leader.wins - chaser.wins) + (chaser.losses - leader.losses)) / 2 + + +def e_number(leader, chaser): + e_num = 89 - leader.wins - chaser.losses + return e_num if e_num > 0 else 0 + + +class BaseModel(Model): + class Meta: + database = db + + +class Current(BaseModel): + week = IntegerField(default=0) + freeze = BooleanField(default=True) + season = IntegerField() + transcount = IntegerField(default=0) + bstatcount = IntegerField(default=0) + pstatcount = IntegerField(default=0) + bet_week = IntegerField(default=0) + trade_deadline = IntegerField() + pick_trade_start = IntegerField() + pick_trade_end = IntegerField() + playoffs_begin = IntegerField() + injury_count = IntegerField() + + @staticmethod + def latest(): + latest_current = Current.select().order_by(-Current.id).get() + return latest_current + + +class Division(BaseModel): + division_name = CharField() + division_abbrev = CharField() + league_name = CharField(null=True) + league_abbrev = CharField(null=True) + season = IntegerField(default=0) + + def abbrev(self): + 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}' + + def full_name(self): + 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_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 = -games_back(div_teams[0], div_teams[1]) + div_teams[0].div_e_num = None + div_teams[0].wc_gb = None + div_teams[0].wc_e_num = None + else: + div_teams[x].div_gb = games_back(div_teams[0], div_teams[x]) + div_teams[x].div_e_num = e_number(div_teams[0], div_teams[x]) + + div_teams[x].save() + + @staticmethod + def sort_wildcard(season, league_abbrev): + divisions = Division.select().where(Division.league_abbrev == league_abbrev) + teams_query = Standings.select_season(season).where( + Standings.wc_gb & (Standings.team.division << divisions) + ) + league_teams = [team_stan for team_stan in teams_query] + league_teams.sort(key=lambda team: win_pct(team), reverse=True) + + for x in range(len(league_teams)): + # Special calculations for two wildcard teams + if x == 0: + league_teams[0].wc_gb = -games_back(league_teams[0], league_teams[2]) + league_teams[0].wc_e_num = None + elif x == 1: + league_teams[1].wc_gb = 0 + league_teams[1].wc_e_num = None + else: + league_teams[x].wc_gb = games_back(league_teams[1], league_teams[x]) + league_teams[x].wc_e_num = e_number(league_teams[1], league_teams[x]) + + league_teams[x].save() + + +class Manager(BaseModel): + name = CharField(unique=True) + image = CharField(null=True) + headline = CharField(null=True) + bio = CharField(null=True) + + +class Team(BaseModel): + abbrev = CharField() + sname = CharField() + lname = CharField() + manager_legacy = CharField(null=True) + division_legacy = CharField(null=True) + gmid = IntegerField() + gmid2 = IntegerField(null=True) + manager1 = ForeignKeyField(Manager, null=True) + manager2 = ForeignKeyField(Manager, null=True) + division = ForeignKeyField(Division, null=True) + mascot = CharField(null=True) + stadium = CharField(null=True) + gsheet = CharField(null=True) + thumbnail = CharField(null=True) + color = CharField(null=True) + dice_color = CharField(null=True) + season = IntegerField() + auto_draft = BooleanField() + + @staticmethod + def select_season(num): + return Team.select().where(Team.season == num) + + @staticmethod + def get_by_owner(gmid, season): + team = Team.get_or_none(Team.gmid == gmid, Team.season == season) + if not team: + team = Team.get_or_none(Team.gmid2 == gmid, Team.season == season) + if not team: + return None + return team + + @staticmethod + def get_season(name_or_abbrev, 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) + if not team: + 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) + ) + 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) + ) + 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} + + def get_gms(self): + if self.gmid2: + return [self.gmid, self.gmid2] + else: + return [self.gmid] + + def get_this_week(self): + active_team = Player.select_season(self.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': []} + + combo_pitchers = 0 + for guy in active_team: + 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: + combo_pitchers += 1 + else: + try: + for pos in guy_pos: + active_roster[pos] += 1 + except KeyError: + # This happens for season 1 without player positions listed + pass + + if combo_pitchers > 0: + 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 + combo_pitchers -= delta + + if combo_pitchers > 0: + active_roster['RP'] += combo_pitchers + + 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': []} + + combo_pitchers = 0 + for guy in short_il: + 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: + 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'] + else: + delta = combo_pitchers + short_roster['SP'] += delta + combo_pitchers -= delta + + if combo_pitchers > 0: + short_roster['RP'] += combo_pitchers + + 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': []} + + combo_pitchers = 0 + for guy in long_il: + 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: + 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'] + else: + delta = combo_pitchers + long_roster['SP'] += delta + combo_pitchers -= delta + + if combo_pitchers > 0: + long_roster['RP'] += combo_pitchers + + 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': []} + + combo_pitchers = 0 + for guy in active_team: + 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: + 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) + ) + all_adds = Transaction.select_season(Current.latest().season).where( + (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: + 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 + try: + active_roster['players'].remove(move.player) + except: + 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: + 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) + + if combo_pitchers > 0: + 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 + combo_pitchers -= delta + + if combo_pitchers > 0: + active_roster['RP'] += combo_pitchers + + 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': []} + + combo_pitchers = 0 + for guy in short_il: + 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: + combo_pitchers += 1 + else: + for pos in guy_pos: + short_roster[pos] += 1 + + 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) + ) + all_adds = Transaction.select_season(Current.latest().season).where( + (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: + combo_pitchers -= 1 + else: + for pos in guy_pos: + short_roster[pos] -= 1 + 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) + except: + 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: + 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) + + if combo_pitchers > 0: + 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 + combo_pitchers -= delta + + if combo_pitchers > 0: + short_roster['RP'] += combo_pitchers + + 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': []} + + combo_pitchers = 0 + for guy in long_il: + 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: + combo_pitchers += 1 + else: + for pos in guy_pos: + long_roster[pos] += 1 + + 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) + ) + all_adds = Transaction.select_season(Current.latest().season).where( + (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: + combo_pitchers -= 1 + else: + for pos in guy_pos: + long_roster[pos] -= 1 + 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) + except: + 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: + 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) + + if combo_pitchers > 0: + 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 + combo_pitchers -= delta + + if combo_pitchers > 0: + long_roster['RP'] += combo_pitchers + + 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_home = Result.select(fn.SUM(Result.homescore).alias('runs')).where( + Result.hometeam == self + )[0].runs + runs_scored_away = Result.select(fn.SUM(Result.awayscore).alias('runs')).where( + Result.awayteam == self + )[0].runs + runs_allowed_home = Result.select(fn.SUM(Result.homescore).alias('runs')).where( + Result.awayteam == self + )[0].runs + runs_allowed_away = Result.select(fn.SUM(Result.awayscore).alias('runs')).where( + Result.hometeam == self + )[0].runs + + if not runs_scored_home: + runs_scored_home = 0 + if not runs_scored_away: + runs_scored_away = 0 + if not runs_allowed_home: + runs_allowed_home = 0 + if not runs_allowed_away: + runs_allowed_away = 0 + + runs_scored = runs_scored_home + runs_scored_away + runs_allowed = runs_allowed_home + runs_allowed_away + if runs_allowed == 0: + pythag_win_pct = 0 + else: + 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 = Result.select_season(self.season).where( + (Result.hometeam == self) | (Result.awayteam == self) + ).order_by(-Result.id).limit(8) + + for game in last_games: + if game.homescore > game.awayscore: + if game.hometeam == self: + team_stan.last8_wins += 1 + else: + team_stan.last8_losses += 1 + else: + if game.hometeam == self: + team_stan.last8_losses += 1 + else: + team_stan.last8_wins += 1 + + return team_stan.save() + + +class Result(BaseModel): + week = IntegerField() + game = IntegerField() + awayteam = ForeignKeyField(Team) + hometeam = ForeignKeyField(Team) + awayscore = IntegerField() + homescore = IntegerField() + season = IntegerField() + scorecard_url = CharField(null=True) + + @staticmethod + def regular_season(num): + if num == 1: + return Result.select().where((Result.season == 1) & (Result.week < 21)) + elif num == 2: + return Result.select().where((Result.season == 2) & (Result.week < 19)) + elif num == 3 or num == 4: + return Result.select().where((Result.season == num) & (Result.week < 23)) + else: + return None + + @staticmethod + def post_season(num): + if num == 1: + return Result.select().where((Result.season == 1) & (Result.week >= 21)) + elif num == 2: + return Result.select().where((Result.season == 2) & (Result.week >= 19)) + elif num == 3 or num == 4: + return Result.select().where((Result.season == num) & (Result.week >= 23)) + else: + return None + + @staticmethod + def select_season(num): + return Result.select().where(Result.season == num) + + def update_standings(self): + away_stan = Standings.get_season(self.awayteam) + home_stan = Standings.get_season(self.hometeam) + away_div = Division.get_by_id(self.awayteam.division.id) + home_div = Division.get_by_id(self.hometeam.division.id) + + if self.homescore > self.awayscore: + # - generic w/l & home/away w/l + home_stan.wins += 1 + home_stan.home_wins += 1 + away_stan.losses += 1 + away_stan.away_losses += 1 + + # - update streak wl and num + if home_stan.streak_wl == 'w': + home_stan.streak_num += 1 + else: + home_stan.streak_wl = 'w' + home_stan.streak_num = 1 + + if away_stan.streak_wl == 'l': + away_stan.streak_num += 1 + else: + away_stan.streak_wl = 'l' + away_stan.streak_num = 1 + + # - if 1-run, tally accordingly + if self.homescore == self.awayscore + 1: + home_stan.one_run_wins += 1 + away_stan.one_run_losses += 1 + + # Used for one league with 3 divisions + # - update record v division + # if away_div.division_abbrev == 'BE': + # home_stan.div1_wins += 1 + # elif away_div.division_abbrev == 'DO': + # home_stan.div2_wins += 1 + # else: + # home_stan.div3_wins += 1 + # + # if home_div.division_abbrev == 'BE': + # away_stan.div1_losses += 1 + # elif home_div.division_abbrev == 'DO': + # away_stan.div2_losses += 1 + # else: + # away_stan.div3_losses += 1 + + # Used for two league plus divisions + if away_div.league_abbrev == 'AL': + if away_div.division_abbrev == 'E': + home_stan.div1_wins += 1 + else: + home_stan.div2_wins += 1 + else: + if away_div.division_abbrev == 'E': + home_stan.div3_wins += 1 + else: + home_stan.div4_wins += 1 + + if home_div.league_abbrev == 'AL': + if home_div.division_abbrev == 'E': + away_stan.div1_losses += 1 + else: + away_stan.div2_losses += 1 + else: + if home_div.division_abbrev == 'E': + away_stan.div3_losses += 1 + else: + away_stan.div4_losses += 1 + + # - adjust run_diff + home_stan.run_diff += self.homescore - self.awayscore + away_stan.run_diff -= self.homescore - self.awayscore + else: + # - generic w/l & home/away w/l + home_stan.losses += 1 + home_stan.home_losses += 1 + away_stan.wins += 1 + away_stan.away_wins += 1 + + # - update streak wl and num + if home_stan.streak_wl == 'l': + home_stan.streak_num += 1 + else: + home_stan.streak_wl = 'l' + home_stan.streak_num = 1 + + if away_stan.streak_wl == 'w': + away_stan.streak_num += 1 + else: + away_stan.streak_wl = 'w' + away_stan.streak_num = 1 + + # - if 1-run, tally accordingly + if self.awayscore == self.homescore + 1: + home_stan.one_run_losses += 1 + away_stan.one_run_wins += 1 + + # Used for one league with 3 divisions + # - update record v division + # if away_div.division_abbrev == 'BE': + # home_stan.div1_losses += 1 + # elif away_div.division_abbrev == 'DO': + # home_stan.div2_losses += 1 + # else: + # home_stan.div3_losses += 1 + # + # if home_div.division_abbrev == 'BE': + # away_stan.div1_wins += 1 + # elif home_div.division_abbrev == 'DO': + # away_stan.div2_wins += 1 + # else: + # away_stan.div3_wins += 1 + + # Used for two league plus divisions + if away_div.league_abbrev == 'AL': + if away_div.division_abbrev == 'E': + home_stan.div1_losses += 1 + else: + home_stan.div2_losses += 1 + else: + if away_div.division_abbrev == 'E': + home_stan.div3_losses += 1 + else: + home_stan.div4_losses += 1 + + if home_div.league_abbrev == 'AL': + if home_div.division_abbrev == 'E': + away_stan.div1_wins += 1 + else: + away_stan.div2_wins += 1 + else: + if home_div.division_abbrev == 'E': + away_stan.div3_wins += 1 + else: + away_stan.div4_wins += 1 + + # - adjust run_diff + home_stan.run_diff -= self.awayscore - self.homescore + away_stan.run_diff += self.awayscore - self.homescore + + home_stan.save() + away_stan.save() + + +class Player(BaseModel): + name = CharField() + wara = FloatField() + image = CharField() + image2 = CharField(null=True) + team = ForeignKeyField(Team) + season = IntegerField() + pitcher_injury = IntegerField(null=True) + pos_1 = CharField() + pos_2 = CharField(null=True) + pos_3 = CharField(null=True) + pos_4 = CharField(null=True) + pos_5 = CharField(null=True) + pos_6 = CharField(null=True) + pos_7 = CharField(null=True) + pos_8 = CharField(null=True) + last_game = CharField(null=True) + last_game2 = CharField(null=True) + il_return = CharField(null=True) + demotion_week = IntegerField(null=True) + headshot = CharField(null=True) + vanity_card = CharField(null=True) + strat_code = CharField(null=True) + bbref_id = CharField(null=True) + injury_rating = CharField(null=True) + + @staticmethod + def select_season(num): + return Player.select().where(Player.season == num) + + @staticmethod + def get_season(name, num): + player = None + try: + player = Player.get(fn.Lower(Player.name) == name.lower(), Player.season == num) + except Exception as e: + print(f'**Error** (db_engine player): {e}') + finally: + return player + + def get_positions(self): + """ + Params: None + Return: List of positions (ex ['1b', '3b']) + """ + pos_list = [] + if self.pos_1: + pos_list.append(self.pos_1) + if self.pos_2: + pos_list.append(self.pos_2) + if self.pos_3: + pos_list.append(self.pos_3) + if self.pos_4: + pos_list.append(self.pos_4) + if self.pos_5: + pos_list.append(self.pos_5) + if self.pos_6: + pos_list.append(self.pos_6) + if self.pos_7: + pos_list.append(self.pos_7) + if self.pos_8: + pos_list.append(self.pos_8) + + return pos_list + + +class Schedule(BaseModel): + week = IntegerField() + awayteam = ForeignKeyField(Team) + hometeam = ForeignKeyField(Team) + gamecount = IntegerField() + season = IntegerField() + + @staticmethod + def select_season(season): + return Schedule.select().where(Schedule.season == season) + + +class Transaction(BaseModel): + week = IntegerField() + player = ForeignKeyField(Player) + oldteam = ForeignKeyField(Team) + newteam = ForeignKeyField(Team) + season = IntegerField() + moveid = IntegerField() + cancelled = BooleanField(default=False) + frozen = BooleanField(default=False) + + @staticmethod + def select_season(num): + return Transaction.select().where(Transaction.season == num) + + +class BattingStat(BaseModel): + player = ForeignKeyField(Player) + team = ForeignKeyField(Team) + pos = CharField() + pa = IntegerField() + ab = IntegerField() + run = IntegerField() + hit = IntegerField() + rbi = IntegerField() + double = IntegerField() + triple = IntegerField() + hr = IntegerField() + bb = IntegerField() + so = IntegerField() + hbp = IntegerField() + sac = IntegerField() + ibb = IntegerField() + gidp = IntegerField() + sb = IntegerField() + cs = IntegerField() + bphr = IntegerField() + bpfo = IntegerField() + bp1b = IntegerField() + bplo = IntegerField() + xba = IntegerField() + xbt = IntegerField() + xch = IntegerField() + xhit = IntegerField() + error = IntegerField() + pb = IntegerField() + sbc = IntegerField() + csc = IntegerField() + roba = IntegerField() + robs = IntegerField() + raa = IntegerField() + rto = IntegerField() + week = IntegerField() + game = IntegerField() + season = IntegerField() + + @staticmethod + def combined_season(season): + """ + Params: season, integer (season number), optional + Return: ModelSelect object for season + """ + return BattingStat.select().where(BattingStat.season == season) + + @staticmethod + def regular_season(season): + """ + Params: num, integer (season number) + Return: ModelSelect object for season's regular season + """ + if season == 1: + 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))\ + .order_by(BattingStat.week) + elif season > 2: + return BattingStat.select().where((BattingStat.season == season) & (BattingStat.week < 23))\ + .order_by(BattingStat.week) + else: + return None + + @staticmethod + def post_season(season): + """ + Params: num, integer (season number) + Return: ModelSelect object for season's post season + """ + if season == 1: + return BattingStat.select().where((BattingStat.season == 1) & (BattingStat.week >= 21)) + elif season == 2: + return BattingStat.select().where((BattingStat.season == 2) & (BattingStat.week >= 19)) + elif season > 2: + 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) + + 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, + # '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['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 * .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 + + 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) + + 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, + # '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, + } + + 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 + + return total + + +class PitchingStat(BaseModel): + player = ForeignKeyField(Player) + team = ForeignKeyField(Team) + ip = FloatField() + hit = FloatField() + run = FloatField() + erun = FloatField() + so = FloatField() + bb = FloatField() + hbp = FloatField() + wp = FloatField() + balk = FloatField() + hr = FloatField() + ir = FloatField() + irs = FloatField() + gs = FloatField() + win = FloatField() + loss = FloatField() + hold = FloatField() + sv = FloatField() + bsv = FloatField() + week = IntegerField() + game = IntegerField() + season = IntegerField() + + @staticmethod + def select_season(season): + return PitchingStat.select().where(PitchingStat.season == season) + + @staticmethod + def regular_season(season): + if season == 1: + 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))\ + .order_by(PitchingStat.week) + elif season > 2: + 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))\ + .order_by(PitchingStat.week) + elif season == 2: + 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))\ + .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) + + 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, + } + + if total['ip']: + total['era'] = (total['erun'] * 9) / total['ip'] + + total['whip'] = (total['bb'] + total['hit']) / total['ip'] + + if total['win'] + total['loss'] > 0: + total['wl%'] = total['win'] / (total['win'] + total['loss']) + + return total + + +class Standings(BaseModel): + team = ForeignKeyField(Team) + wins = IntegerField(default=0) + losses = IntegerField(default=0) + run_diff = IntegerField(default=0) + div_gb = FloatField(default=0.0, null=True) + div_e_num = IntegerField(default=0, null=True) + wc_gb = FloatField(default=99.0, null=True) + wc_e_num = IntegerField(default=99, null=True) + home_wins = IntegerField(default=0) + home_losses = IntegerField(default=0) + away_wins = IntegerField(default=0) + away_losses = IntegerField(default=0) + last8_wins = IntegerField(default=0) + last8_losses = IntegerField(default=0) + streak_wl = CharField(default='w') + streak_num = IntegerField(default=0) + one_run_wins = IntegerField(default=0) + one_run_losses = IntegerField(default=0) + pythag_wins = IntegerField(default=0) + pythag_losses = IntegerField(default=0) + div1_wins = IntegerField(default=0) + div1_losses = IntegerField(default=0) + div2_wins = IntegerField(default=0) + div2_losses = IntegerField(default=0) + div3_wins = IntegerField(default=0) + div3_losses = IntegerField(default=0) + div4_wins = IntegerField(default=0) + div4_losses = IntegerField(default=0) + + @staticmethod + def select_season(season): + return Standings.select().join(Team).where(Standings.team.season == season) + + @staticmethod + def get_season(team): + return Standings.get_or_none(Standings.team == team) + + @staticmethod + def recalculate(season, full_wipe=True): + all_teams = Team.select_season(season).where(Team.division) + if full_wipe: + # Wipe existing data + delete_lines = Standings.select_season(season) + for line in delete_lines: + line.delete_instance() + + # Recreate current season Standings objects + create_teams = [Standings(team=team) for team in all_teams] + with db.atomic(): + Standings.bulk_create(create_teams) + + # Iterate through each individual result + for game in Result.select_season(season).where(Result.week <= 22): + # tally win and loss for each standings object + game.update_standings() + + # Set pythag record and iterate through last 8 games for last8 record + for team in all_teams: + team.run_pythag_last8() + + # Pull each division at a time and sort by win pct + for division in Division.select().where(Division.season == season): + division.sort_division(season) + + # Pull each league (filter by not null wc_gb) and sort by win pct + + # # For one league: + # Division.sort_wildcard(season, 'SBa') + + # For two leagues + Division.sort_wildcard(season, 'AL') + Division.sort_wildcard(season, 'NL') + + +class BattingCareer(BaseModel): + name = CharField() + pa = FloatField(default=0) + ab = FloatField(default=0) + run = FloatField(default=0) + hit = FloatField(default=0) + rbi = FloatField(default=0) + double = FloatField(default=0) + triple = FloatField(default=0) + hr = FloatField(default=0) + bb = FloatField(default=0) + so = FloatField(default=0) + hbp = FloatField(default=0) + sac = FloatField(default=0) + ibb = FloatField(default=0) + gidp = FloatField(default=0) + sb = FloatField(default=0) + cs = FloatField(default=0) + bphr = FloatField(default=0) + bpfo = FloatField(default=0) + bp1b = FloatField(default=0) + bplo = FloatField(default=0) + xba = FloatField(default=0) + xbt = FloatField(default=0) + game = FloatField(default=0) + + @staticmethod + def recalculate(): + # Wipe existing data + delete_lines = BattingCareer.select() + for line in delete_lines: + 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) + if not this_career: + this_career = BattingCareer(name=this_season.player.name) + this_career.save() + + this_career.pa += this_season.pa + this_career.ab += this_season.ab + this_career.run += this_season.run + this_career.hit += this_season.hit + this_career.rbi += this_season.rbi + this_career.double += this_season.double + this_career.triple += this_season.triple + this_career.hr += this_season.hr + this_career.bb += this_season.bb + this_career.so += this_season.so + this_career.hbp += this_season.hbp + this_career.sac += this_season.sac + this_career.ibb += this_season.ibb + this_career.gidp += this_season.gidp + this_career.sb += this_season.sb + this_career.cs += this_season.cs + this_career.bphr += this_season.bphr + this_career.bpfo += this_season.bpfo + this_career.bp1b += this_season.bp1b + this_career.bplo += this_season.bplo + this_career.xba += this_season.xba + this_career.xbt += this_season.xbt + this_career.save() + + +class PitchingCareer(BaseModel): + name = CharField() + ip = FloatField(default=0) + hit = FloatField(default=0) + run = FloatField(default=0) + erun = FloatField(default=0) + so = FloatField(default=0) + bb = FloatField(default=0) + hbp = FloatField(default=0) + wp = FloatField(default=0) + balk = FloatField(default=0) + hr = FloatField(default=0) + ir = FloatField(default=0) + irs = FloatField(default=0) + gs = FloatField(default=0) + win = FloatField(default=0) + loss = FloatField(default=0) + hold = FloatField(default=0) + sv = FloatField(default=0) + bsv = FloatField(default=0) + game = FloatField(default=0) + + @staticmethod + def recalculate(): + # Wipe existing data + delete_lines = PitchingCareer.select() + for line in delete_lines: + 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) + if not this_career: + this_career = PitchingCareer(name=this_season.player.name) + this_career.save() + + this_career.ip += this_season.ip + this_career.hit += this_season.hit + this_career.run += this_season.run + this_career.erun += this_season.erun + this_career.so += this_season.so + this_career.bb += this_season.bb + this_career.hbp += this_season.hbp + this_career.wp += this_season.wp + this_career.balk += this_season.balk + this_career.hr += this_season.hr + this_career.ir += this_season.ir + this_career.irs += this_season.irs + this_career.gs += this_season.gs + this_career.win += this_season.win + this_career.loss += this_season.loss + this_career.hold += this_season.hold + this_career.sv += this_season.sv + this_career.bsv += this_season.bsv + this_career.save() + + +class FieldingCareer(BaseModel): + name = CharField() + pos = CharField() + xch = IntegerField(default=0) + xhit = IntegerField(default=0) + error = IntegerField(default=0) + pb = IntegerField(default=0) + sbc = IntegerField(default=0) + csc = IntegerField(default=0) + roba = IntegerField(default=0) + robs = IntegerField(default=0) + raa = IntegerField(default=0) + rto = IntegerField(default=0) + game = IntegerField(default=0) + + @staticmethod + def recalculate(): + # Wipe existing data + delete_lines = FieldingCareer.select() + for line in delete_lines: + line.delete_instance() + + # For each seasonstat, find career or create new and increment + 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 + ) + if not this_career: + this_career = FieldingCareer(name=this_season.player.name, pos=this_season.pos) + this_career.save() + + this_career.xch += this_season.xch + this_career.xhit += this_season.xhit + this_career.error += this_season.error + this_career.pb += this_season.pb + this_career.sbc += this_season.sbc + this_career.csc += this_season.csc + this_career.roba += this_season.roba + this_career.robs += this_season.robs + this_career.raa += this_season.raa + this_career.rto += this_season.rto + this_career.save() + + +class BattingSeason(BaseModel): + player = ForeignKeyField(Player) + season = IntegerField() + season_type = CharField(default='Regular') + career = ForeignKeyField(BattingCareer, null=True) + pa = FloatField(default=0) + ab = FloatField(default=0) + run = FloatField(default=0) + hit = FloatField(default=0) + rbi = FloatField(default=0) + double = FloatField(default=0) + triple = FloatField(default=0) + hr = FloatField(default=0) + bb = FloatField(default=0) + so = FloatField(default=0) + hbp = FloatField(default=0) + sac = FloatField(default=0) + ibb = FloatField(default=0) + gidp = FloatField(default=0) + sb = FloatField(default=0) + cs = FloatField(default=0) + bphr = FloatField(default=0) + bpfo = FloatField(default=0) + bp1b = FloatField(default=0) + bplo = FloatField(default=0) + xba = FloatField(default=0) + xbt = FloatField(default=0) + game = FloatField(default=0) + + @staticmethod + def select_season(season): + return BattingSeason.select().where(BattingSeason.season == season) + + # @staticmethod + # def recalculate(season, manager_id): + # # Wipe existing data + # delete_lines = BattingSeason.select_season(season) + # for line in delete_lines: + # line.delete_instance() + # + # # For each battingstat, find season or create new and increment + # for line in BattingStat.select().where( + # (BattingStat.season == season) & (BattingStat.player.team.manager1 == manager_id) + # ): + # if line.season == 1: + # s_type = 'Regular' if line.week < 21 else 'Post' + # elif line.season == 2: + # s_type = 'Regular' if line.week < 19 else 'Post' + # else: + # s_type = 'Regular' if line.week < 23 else 'Post' + # + # this_season = BattingSeason.get_or_none(player=line.player, season_type=s_type) + # if not this_season: + # this_season = BattingSeason(player=line.player, season_type=s_type, season=line.season) + # this_season.save() + # + # this_season.pa += line.pa + # this_season.ab += line.ab + # this_season.run += line.run + # this_season.hit += line.hit + # this_season.rbi += line.rbi + # this_season.double += line.double + # this_season.triple += line.triple + # this_season.hr += line.hr + # this_season.bb += line.bb + # this_season.so += line.so + # this_season.hbp += line.hbp + # this_season.sac += line.sac + # this_season.ibb += line.ibb + # this_season.gidp += line.gidp + # this_season.sb += line.sb + # this_season.cs += line.cs + # this_season.save() + + def recalculate(self): + self.pa = 0 + self.ab = 0 + self.run = 0 + self.hit = 0 + self.rbi = 0 + self.double = 0 + self.triple = 0 + self.hr = 0 + self.bb = 0 + self.so = 0 + self.hbp = 0 + self.sac = 0 + self.ibb = 0 + self.gidp = 0 + self.sb = 0 + self.cs = 0 + self.bphr = 0 + self.bpfo = 0 + self.bp1b = 0 + self.bplo = 0 + self.xba = 0 + self.xbt = 0 + self.game = 0 + + 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) + for line in all_stats: + self.pa += line.pa + self.ab += line.ab + self.run += line.run + self.hit += line.hit + self.rbi += line.rbi + self.double += line.double + self.triple += line.triple + self.hr += line.hr + self.bb += line.bb + self.so += line.so + self.hbp += line.hbp + self.sac += line.sac + self.ibb += line.ibb + self.gidp += line.gidp + self.sb += line.sb + self.cs += line.cs + self.bphr += line.bphr + self.bpfo += line.bpfo + self.bp1b += line.bp1b + self.bplo += line.bplo + self.xba += line.xba + self.xbt += line.xbt + self.game += 1 + + self.save() + return all_stats.count() + + +class PitchingSeason(BaseModel): + player = ForeignKeyField(Player) + season = IntegerField() + season_type = CharField(default='Regular') + career = ForeignKeyField(PitchingCareer, null=True) + ip = FloatField(default=0) + hit = FloatField(default=0) + run = FloatField(default=0) + erun = FloatField(default=0) + so = FloatField(default=0) + bb = FloatField(default=0) + hbp = FloatField(default=0) + wp = FloatField(default=0) + balk = FloatField(default=0) + hr = FloatField(default=0) + ir = FloatField(default=0) + irs = FloatField(default=0) + gs = FloatField(default=0) + win = FloatField(default=0) + loss = FloatField(default=0) + hold = FloatField(default=0) + sv = FloatField(default=0) + bsv = FloatField(default=0) + game = FloatField(default=0) + + @staticmethod + def select_season(season): + return PitchingSeason.select().where(PitchingSeason.season == season) + + # @staticmethod + # def recalculate(season, manager_id): + # # Wipe existing data + # delete_lines = PitchingSeason.select_season(season) + # for line in delete_lines: + # line.delete_instance() + # + # # For each pitchingstat, find season or create new and increment + # for line in PitchingStat.select().where( + # (PitchingStat.season == season) & (PitchingStat.player.team.manager1 == manager_id) + # ): + # if line.season == 1: + # s_type = 'Regular' if line.week < 21 else 'Post' + # elif line.season == 2: + # s_type = 'Regular' if line.week < 19 else 'Post' + # else: + # s_type = 'Regular' if line.week < 23 else 'Post' + # + # this_season = PitchingSeason.get_or_none(player=line.player, season_type=s_type) + # if not this_season: + # this_season = PitchingSeason(player=line.player, season_type=s_type, season=line.season) + # this_season.save() + # + # this_season.ip += line.ip + # this_season.hit += line.hit + # this_season.run += line.run + # this_season.erun += line.erun + # this_season.so += line.so + # this_season.bb += line.bb + # this_season.hbp += line.hbp + # this_season.wp += line.wp + # this_season.balk += line.balk + # this_season.hr += line.hr + # this_season.gs += line.gs + # this_season.win += line.win + # this_season.loss += line.loss + # this_season.hold += line.hold + # this_season.sv += line.sv + # this_season.bsv += line.bsv + # this_season.game += 1 + # this_season.save() + + def recalculate(self): + self.ip = 0 + self.hit = 0 + self.run = 0 + self.erun = 0 + self.so = 0 + self.bb = 0 + self.hbp = 0 + self.wp = 0 + self.balk = 0 + self.hr = 0 + self.ir = 0 + self.irs = 0 + self.gs = 0 + self.win = 0 + self.loss = 0 + self.hold = 0 + self.sv = 0 + self.bsv = 0 + self.game = 0 + + 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) + for line in all_stats: + self.ip += line.ip + self.hit += line.hit + self.run += line.run + self.erun += line.erun + self.so += line.so + self.bb += line.bb + self.hbp += line.hbp + self.wp += line.wp + self.balk += line.balk + self.hr += line.hr + self.ir += line.ir + self.irs += line.irs + self.gs += line.gs + self.win += line.win + self.loss += line.loss + self.hold += line.hold + self.sv += line.sv + self.bsv += line.bsv + self.game += 1 + + self.save() + return all_stats.count() + + +class FieldingSeason(BaseModel): + player = ForeignKeyField(Player) + season = IntegerField() + season_type = CharField(default='Regular') + pos = CharField() + career = ForeignKeyField(FieldingCareer, null=True) + xch = IntegerField(default=0) + xhit = IntegerField(default=0) + error = IntegerField(default=0) + pb = IntegerField(default=0) + sbc = IntegerField(default=0) + csc = IntegerField(default=0) + roba = IntegerField(default=0) + robs = IntegerField(default=0) + raa = IntegerField(default=0) + rto = IntegerField(default=0) + game = IntegerField(default=0) + + @staticmethod + def select_season(season): + return FieldingSeason.select().where(FieldingSeason.season == season) + + # @staticmethod + # def recalculate(season, manager_id): + # # Wipe existing data + # delete_lines = FieldingSeason.select() + # for line in delete_lines: + # line.delete_instance() + # + # # players = Player.select_season(season).where(Player.team) + # + # # For each battingstat, find season or create new and increment + # for line in BattingStat.select().join(Player).join(Team).where( + # (BattingStat.season == season) & (BattingStat.player.team.manager1 == manager_id) + # ): + # if line.season == 1: + # s_type = 'Regular' if line.week < 21 else 'Post' + # elif line.season == 2: + # s_type = 'Regular' if line.week < 19 else 'Post' + # else: + # s_type = 'Regular' if line.week < 23 else 'Post' + # + # this_season = BattingSeason.get_or_none(player=line.player, season_type=s_type, pos=line.pos) + # if not this_season: + # this_season = BattingSeason(player=line.player, season_type=s_type, pos=line.pos, season=line.season) + # this_season.save() + # + # this_season.xch += line.xch + # this_season.xhit += line.xhit + # this_season.error += line.error + # this_season.pb += line.pb + # this_season.sbc += line.sbc + # this_season.csc += line.csc + # this_season.game += 1 + # this_season.save() + + def recalculate(self): + self.xch = 0 + self.xhit = 0 + self.error = 0 + self.pb = 0 + self.sbc = 0 + self.csc = 0 + self.roba = 0 + self.robs = 0 + self.raa = 0 + self.rto = 0 + self.game = 0 + + if self.season_type == 'Regular': + all_stats = BattingStat.regular_season(self.season).where( + (BattingStat.player == self.player) & (BattingStat.pos == self.pos) + ) + else: + all_stats = BattingStat.post_season(self.season).where( + (BattingStat.player == self.player) & (BattingStat.pos == self.pos) + ) + + for line in all_stats: + self.xch += line.xch + self.xhit += line.xhit + self.error += line.error + self.pb += line.pb + self.sbc += line.sbc + self.csc += line.csc + self.roba += line.roba + self.robs += line.robs + self.raa += line.raa + self.rto += line.rto + self.game += 1 + + self.save() + return all_stats.count() + + +class DraftPick(BaseModel): + overall = IntegerField(null=True) + round = IntegerField() + origowner = ForeignKeyField(Team) + owner = ForeignKeyField(Team) + season = IntegerField() + player = ForeignKeyField(Player, null=True) + + @staticmethod + def select_season(num): + return DraftPick.select().where(DraftPick.season == num) + + @staticmethod + def get_season(team, rd, num): + return DraftPick.get(DraftPick.season == num, DraftPick.origowner == team, DraftPick.round == rd) + + +class DraftData(BaseModel): + currentpick = IntegerField() + timer = BooleanField() + pick_deadline = DateTimeField(null=True) + result_channel = IntegerField(null=True) + ping_channel = IntegerField(null=True) + pick_minutes = IntegerField(null=True) + + +class Award(BaseModel): + name = CharField() + season = IntegerField() + timing = CharField(default="In-Season") + manager1 = ForeignKeyField(Manager, null=True) + manager2 = ForeignKeyField(Manager, null=True) + player = ForeignKeyField(Player, null=True) + team = ForeignKeyField(Team, null=True) + image = CharField(null=True) + + +class DiceRoll(BaseModel): + season = IntegerField(default=Current.latest().season) + week = IntegerField(default=Current.latest().week) + team = ForeignKeyField(Team, null=True) + roller = IntegerField() + dsix = IntegerField(null=True) + twodsix = IntegerField(null=True) + threedsix = IntegerField(null=True) + dtwenty = IntegerField(null=True) + + +class DraftList(BaseModel): + season = IntegerField() + team = ForeignKeyField(Team) + rank = IntegerField() + player = ForeignKeyField(Player) + + +# class Streak(BaseModel): +# player = ForeignKeyField(Player) +# streak_type = CharField() +# start_season = IntegerField() +# start_week = IntegerField() +# start_game = IntegerField() +# end_season = IntegerField() +# end_week = IntegerField() +# end_game = IntegerField() +# game_length = IntegerField() +# active = BooleanField() +# +# def recalculate(self): +# # Pitcher streaks +# if self.streak_type in ['win', 'loss', 'save', 'scoreless']: +# all_stats = PitchingStat.select_season(self.start_season).where( +# (PitchingStat.player == self.player) & (PitchingStat.week >= self.start_week) +# ) +# sorted_stats = sorted(all_stats, key=lambda x: f'{x.season:0>2}-{x.week:0>2}-{x.game:}') +# +# for line in sorted_stats: + +db.create_tables([ + Current, Division, Manager, Team, Result, Player, Schedule, Transaction, BattingStat, PitchingStat, Standings, + BattingCareer, PitchingCareer, FieldingCareer, Manager, Award, DiceRoll, DraftList +]) +db.close() diff --git a/app/db_engine.py b/app/db_engine.py new file mode 100644 index 0000000..ae483e2 --- /dev/null +++ b/app/db_engine.py @@ -0,0 +1,1817 @@ +import copy +import math + +from peewee import * + +from playhouse.shortcuts import model_to_dict + +db = SqliteDatabase( + 'storage/sba_master.db', + pragmas={ + 'journal_mode': 'wal', + 'cache_size': -1 * 64000, + 'synchronous': 0 + } +) + + +""" +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 +""" + + +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) + + +def games_back(leader, chaser): + return ((leader.wins - chaser.wins) + (chaser.losses - leader.losses)) / 2 + + +def e_number(leader, chaser): + e_num = 89 - leader.wins - chaser.losses + return e_num if e_num > 0 else 0 + + +class BaseModel(Model): + class Meta: + database = db + + +class Current(BaseModel): + week = IntegerField(default=0) + freeze = BooleanField(default=True) + season = IntegerField() + transcount = IntegerField(default=0) + bstatcount = IntegerField(default=0) + pstatcount = IntegerField(default=0) + bet_week = IntegerField(default=0) + trade_deadline = IntegerField() + pick_trade_start = IntegerField() + pick_trade_end = IntegerField() + playoffs_begin = IntegerField() + injury_count = IntegerField() + + @staticmethod + def latest(): + latest_current = Current.select().order_by(-Current.id).get() + return latest_current + + +class Division(BaseModel): + division_name = CharField() + division_abbrev = CharField() + league_name = CharField(null=True) + league_abbrev = CharField(null=True) + season = IntegerField(default=0) + + def abbrev(self): + 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}' + + def full_name(self): + 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_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 = -games_back(div_teams[0], div_teams[1]) + div_teams[0].div_e_num = None + div_teams[0].wc_gb = None + div_teams[0].wc_e_num = None + else: + div_teams[x].div_gb = games_back(div_teams[0], div_teams[x]) + div_teams[x].div_e_num = e_number(div_teams[0], div_teams[x]) + + div_teams[x].save() + + @staticmethod + def sort_wildcard(season, league_abbrev): + divisions = Division.select().where(Division.league_abbrev == league_abbrev) + teams_query = Standings.select_season(season).where( + Standings.wc_gb & (Standings.team.division << divisions) + ) + league_teams = [team_stan for team_stan in teams_query] + league_teams.sort(key=lambda team: win_pct(team), reverse=True) + + for x in range(len(league_teams)): + # Special calculations for two wildcard teams + if x == 0: + league_teams[0].wc_gb = -games_back(league_teams[0], league_teams[2]) + league_teams[0].wc_e_num = None + elif x == 1: + league_teams[1].wc_gb = 0 + league_teams[1].wc_e_num = None + else: + league_teams[x].wc_gb = games_back(league_teams[1], league_teams[x]) + league_teams[x].wc_e_num = e_number(league_teams[1], league_teams[x]) + + league_teams[x].save() + + +class Manager(BaseModel): + name = CharField(unique=True) + image = CharField(null=True) + headline = CharField(null=True) + bio = CharField(null=True) + + +class Team(BaseModel): + abbrev = CharField() + sname = CharField() + lname = CharField() + manager_legacy = CharField(null=True) + division_legacy = CharField(null=True) + gmid = IntegerField() + gmid2 = IntegerField(null=True) + manager1 = ForeignKeyField(Manager, null=True) + manager2 = ForeignKeyField(Manager, null=True) + division = ForeignKeyField(Division, null=True) + mascot = CharField(null=True) + stadium = CharField(null=True) + gsheet = CharField(null=True) + thumbnail = CharField(null=True) + color = CharField(null=True) + dice_color = CharField(null=True) + season = IntegerField() + auto_draft = BooleanField() + + @staticmethod + def select_season(num): + return Team.select().where(Team.season == num) + + @staticmethod + def get_by_owner(gmid, season): + team = Team.get_or_none(Team.gmid == gmid, Team.season == season) + if not team: + team = Team.get_or_none(Team.gmid2 == gmid, Team.season == season) + if not team: + return None + return team + + @staticmethod + def get_season(name_or_abbrev, 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) + if not team: + 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) + ) + 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) + ) + 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} + + def get_gms(self): + if self.gmid2: + return [self.gmid, self.gmid2] + else: + return [self.gmid] + + def get_this_week(self): + active_team = Player.select_season(self.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': []} + + combo_pitchers = 0 + for guy in active_team: + 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: + combo_pitchers += 1 + else: + try: + for pos in guy_pos: + active_roster[pos] += 1 + except KeyError: + # This happens for season 1 without player positions listed + pass + + if combo_pitchers > 0: + 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 + combo_pitchers -= delta + + if combo_pitchers > 0: + active_roster['RP'] += combo_pitchers + + 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': []} + + combo_pitchers = 0 + for guy in short_il: + 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: + 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'] + else: + delta = combo_pitchers + short_roster['SP'] += delta + combo_pitchers -= delta + + if combo_pitchers > 0: + short_roster['RP'] += combo_pitchers + + 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': []} + + combo_pitchers = 0 + for guy in long_il: + 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: + 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'] + else: + delta = combo_pitchers + long_roster['SP'] += delta + combo_pitchers -= delta + + if combo_pitchers > 0: + long_roster['RP'] += combo_pitchers + + 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': []} + + combo_pitchers = 0 + for guy in active_team: + 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: + 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) + ) + all_adds = Transaction.select_season(Current.latest().season).where( + (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: + 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 + try: + active_roster['players'].remove(move.player) + except: + 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: + 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) + + if combo_pitchers > 0: + 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 + combo_pitchers -= delta + + if combo_pitchers > 0: + active_roster['RP'] += combo_pitchers + + 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': []} + + combo_pitchers = 0 + for guy in short_il: + 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: + combo_pitchers += 1 + else: + for pos in guy_pos: + short_roster[pos] += 1 + + 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) + ) + all_adds = Transaction.select_season(Current.latest().season).where( + (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: + combo_pitchers -= 1 + else: + for pos in guy_pos: + short_roster[pos] -= 1 + 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) + except: + 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: + 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) + + if combo_pitchers > 0: + 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 + combo_pitchers -= delta + + if combo_pitchers > 0: + short_roster['RP'] += combo_pitchers + + 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': []} + + combo_pitchers = 0 + for guy in long_il: + 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: + combo_pitchers += 1 + else: + for pos in guy_pos: + long_roster[pos] += 1 + + 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) + ) + all_adds = Transaction.select_season(Current.latest().season).where( + (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: + combo_pitchers -= 1 + else: + for pos in guy_pos: + long_roster[pos] -= 1 + 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) + except: + 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: + 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) + + if combo_pitchers > 0: + 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 + combo_pitchers -= delta + + if combo_pitchers > 0: + long_roster['RP'] += combo_pitchers + + 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_home = Result.select(fn.SUM(Result.homescore).alias('runs')).where( + Result.hometeam == self + )[0].runs + runs_scored_away = Result.select(fn.SUM(Result.awayscore).alias('runs')).where( + Result.awayteam == self + )[0].runs + runs_allowed_home = Result.select(fn.SUM(Result.homescore).alias('runs')).where( + Result.awayteam == self + )[0].runs + runs_allowed_away = Result.select(fn.SUM(Result.awayscore).alias('runs')).where( + Result.hometeam == self + )[0].runs + + if not runs_scored_home: + runs_scored_home = 0 + if not runs_scored_away: + runs_scored_away = 0 + if not runs_allowed_home: + runs_allowed_home = 0 + if not runs_allowed_away: + runs_allowed_away = 0 + + runs_scored = runs_scored_home + runs_scored_away + runs_allowed = runs_allowed_home + runs_allowed_away + if runs_allowed == 0: + pythag_win_pct = 0 + else: + 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 = Result.select_season(self.season).where( + (Result.hometeam == self) | (Result.awayteam == self) + ).order_by(-Result.id).limit(8) + + for game in last_games: + if game.homescore > game.awayscore: + if game.hometeam == self: + team_stan.last8_wins += 1 + else: + team_stan.last8_losses += 1 + else: + if game.hometeam == self: + team_stan.last8_losses += 1 + else: + team_stan.last8_wins += 1 + + return team_stan.save() + + +class Result(BaseModel): + week = IntegerField() + game = IntegerField() + awayteam = ForeignKeyField(Team) + hometeam = ForeignKeyField(Team) + awayscore = IntegerField() + homescore = IntegerField() + season = IntegerField() + scorecard_url = CharField(null=True) + + @staticmethod + def regular_season(num): + if num == 1: + return Result.select().where((Result.season == 1) & (Result.week < 21)) + elif num == 2: + return Result.select().where((Result.season == 2) & (Result.week < 19)) + elif num == 3 or num == 4: + return Result.select().where((Result.season == num) & (Result.week < 23)) + else: + return None + + @staticmethod + def post_season(num): + if num == 1: + return Result.select().where((Result.season == 1) & (Result.week >= 21)) + elif num == 2: + return Result.select().where((Result.season == 2) & (Result.week >= 19)) + elif num == 3 or num == 4: + return Result.select().where((Result.season == num) & (Result.week >= 23)) + else: + return None + + @staticmethod + def select_season(num): + return Result.select().where(Result.season == num) + + def update_standings(self): + away_stan = Standings.get_season(self.awayteam) + home_stan = Standings.get_season(self.hometeam) + away_div = Division.get_by_id(self.awayteam.division.id) + home_div = Division.get_by_id(self.hometeam.division.id) + + if self.homescore > self.awayscore: + # - generic w/l & home/away w/l + home_stan.wins += 1 + home_stan.home_wins += 1 + away_stan.losses += 1 + away_stan.away_losses += 1 + + # - update streak wl and num + if home_stan.streak_wl == 'w': + home_stan.streak_num += 1 + else: + home_stan.streak_wl = 'w' + home_stan.streak_num = 1 + + if away_stan.streak_wl == 'l': + away_stan.streak_num += 1 + else: + away_stan.streak_wl = 'l' + away_stan.streak_num = 1 + + # - if 1-run, tally accordingly + if self.homescore == self.awayscore + 1: + home_stan.one_run_wins += 1 + away_stan.one_run_losses += 1 + + # Used for one league with 3 divisions + # - update record v division + # if away_div.division_abbrev == 'BE': + # home_stan.div1_wins += 1 + # elif away_div.division_abbrev == 'DO': + # home_stan.div2_wins += 1 + # else: + # home_stan.div3_wins += 1 + # + # if home_div.division_abbrev == 'BE': + # away_stan.div1_losses += 1 + # elif home_div.division_abbrev == 'DO': + # away_stan.div2_losses += 1 + # else: + # away_stan.div3_losses += 1 + + # Used for two league plus divisions + if away_div.league_abbrev == 'AL': + if away_div.division_abbrev == 'E': + home_stan.div1_wins += 1 + else: + home_stan.div2_wins += 1 + else: + if away_div.division_abbrev == 'E': + home_stan.div3_wins += 1 + else: + home_stan.div4_wins += 1 + + if home_div.league_abbrev == 'AL': + if home_div.division_abbrev == 'E': + away_stan.div1_losses += 1 + else: + away_stan.div2_losses += 1 + else: + if home_div.division_abbrev == 'E': + away_stan.div3_losses += 1 + else: + away_stan.div4_losses += 1 + + # - adjust run_diff + home_stan.run_diff += self.homescore - self.awayscore + away_stan.run_diff -= self.homescore - self.awayscore + else: + # - generic w/l & home/away w/l + home_stan.losses += 1 + home_stan.home_losses += 1 + away_stan.wins += 1 + away_stan.away_wins += 1 + + # - update streak wl and num + if home_stan.streak_wl == 'l': + home_stan.streak_num += 1 + else: + home_stan.streak_wl = 'l' + home_stan.streak_num = 1 + + if away_stan.streak_wl == 'w': + away_stan.streak_num += 1 + else: + away_stan.streak_wl = 'w' + away_stan.streak_num = 1 + + # - if 1-run, tally accordingly + if self.awayscore == self.homescore + 1: + home_stan.one_run_losses += 1 + away_stan.one_run_wins += 1 + + # Used for one league with 3 divisions + # - update record v division + # if away_div.division_abbrev == 'BE': + # home_stan.div1_losses += 1 + # elif away_div.division_abbrev == 'DO': + # home_stan.div2_losses += 1 + # else: + # home_stan.div3_losses += 1 + # + # if home_div.division_abbrev == 'BE': + # away_stan.div1_wins += 1 + # elif home_div.division_abbrev == 'DO': + # away_stan.div2_wins += 1 + # else: + # away_stan.div3_wins += 1 + + # Used for two league plus divisions + if away_div.league_abbrev == 'AL': + if away_div.division_abbrev == 'E': + home_stan.div1_losses += 1 + else: + home_stan.div2_losses += 1 + else: + if away_div.division_abbrev == 'E': + home_stan.div3_losses += 1 + else: + home_stan.div4_losses += 1 + + if home_div.league_abbrev == 'AL': + if home_div.division_abbrev == 'E': + away_stan.div1_wins += 1 + else: + away_stan.div2_wins += 1 + else: + if home_div.division_abbrev == 'E': + away_stan.div3_wins += 1 + else: + away_stan.div4_wins += 1 + + # - adjust run_diff + home_stan.run_diff -= self.awayscore - self.homescore + away_stan.run_diff += self.awayscore - self.homescore + + home_stan.save() + away_stan.save() + + +class Player(BaseModel): + name = CharField() + wara = FloatField() + image = CharField() + image2 = CharField(null=True) + team = ForeignKeyField(Team) + season = IntegerField() + pitcher_injury = IntegerField(null=True) + pos_1 = CharField() + pos_2 = CharField(null=True) + pos_3 = CharField(null=True) + pos_4 = CharField(null=True) + pos_5 = CharField(null=True) + pos_6 = CharField(null=True) + pos_7 = CharField(null=True) + pos_8 = CharField(null=True) + last_game = CharField(null=True) + last_game2 = CharField(null=True) + il_return = CharField(null=True) + demotion_week = IntegerField(null=True) + headshot = CharField(null=True) + vanity_card = CharField(null=True) + strat_code = CharField(null=True) + bbref_id = CharField(null=True) + injury_rating = CharField(null=True) + + @staticmethod + def select_season(num): + return Player.select().where(Player.season == num) + + @staticmethod + def get_season(name, num): + player = None + try: + player = Player.get(fn.Lower(Player.name) == name.lower(), Player.season == num) + except Exception as e: + print(f'**Error** (db_engine player): {e}') + finally: + return player + + def get_positions(self): + """ + Params: None + Return: List of positions (ex ['1b', '3b']) + """ + pos_list = [] + if self.pos_1: + pos_list.append(self.pos_1) + if self.pos_2: + pos_list.append(self.pos_2) + if self.pos_3: + pos_list.append(self.pos_3) + if self.pos_4: + pos_list.append(self.pos_4) + if self.pos_5: + pos_list.append(self.pos_5) + if self.pos_6: + pos_list.append(self.pos_6) + if self.pos_7: + pos_list.append(self.pos_7) + if self.pos_8: + pos_list.append(self.pos_8) + + return pos_list + + +class Schedule(BaseModel): + week = IntegerField() + awayteam = ForeignKeyField(Team) + hometeam = ForeignKeyField(Team) + gamecount = IntegerField() + season = IntegerField() + + @staticmethod + def select_season(season): + return Schedule.select().where(Schedule.season == season) + + +class Transaction(BaseModel): + week = IntegerField() + player = ForeignKeyField(Player) + oldteam = ForeignKeyField(Team) + newteam = ForeignKeyField(Team) + season = IntegerField() + moveid = IntegerField() + cancelled = BooleanField(default=False) + frozen = BooleanField(default=False) + + @staticmethod + def select_season(num): + return Transaction.select().where(Transaction.season == num) + + +class BattingStat(BaseModel): + player = ForeignKeyField(Player) + team = ForeignKeyField(Team) + pos = CharField() + pa = IntegerField() + ab = IntegerField() + run = IntegerField() + hit = IntegerField() + rbi = IntegerField() + double = IntegerField() + triple = IntegerField() + hr = IntegerField() + bb = IntegerField() + so = IntegerField() + hbp = IntegerField() + sac = IntegerField() + ibb = IntegerField() + gidp = IntegerField() + sb = IntegerField() + cs = IntegerField() + bphr = IntegerField() + bpfo = IntegerField() + bp1b = IntegerField() + bplo = IntegerField() + xba = IntegerField() + xbt = IntegerField() + xch = IntegerField() + xhit = IntegerField() + error = IntegerField() + pb = IntegerField() + sbc = IntegerField() + csc = IntegerField() + roba = IntegerField() + robs = IntegerField() + raa = IntegerField() + rto = IntegerField() + week = IntegerField() + game = IntegerField() + season = IntegerField() + + @staticmethod + def combined_season(season): + """ + Params: season, integer (season number), optional + Return: ModelSelect object for season + """ + return BattingStat.select().where(BattingStat.season == season) + + @staticmethod + def regular_season(season): + """ + Params: num, integer (season number) + Return: ModelSelect object for season's regular season + """ + if season == 1: + 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))\ + .order_by(BattingStat.week) + elif season > 2: + return BattingStat.select().where((BattingStat.season == season) & (BattingStat.week < 23))\ + .order_by(BattingStat.week) + else: + return None + + @staticmethod + def post_season(season): + """ + Params: num, integer (season number) + Return: ModelSelect object for season's post season + """ + if season == 1: + return BattingStat.select().where((BattingStat.season == 1) & (BattingStat.week >= 21)) + elif season == 2: + return BattingStat.select().where((BattingStat.season == 2) & (BattingStat.week >= 19)) + elif season > 2: + 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) + + 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, + # '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['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 * .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 + + 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) + + 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, + # '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, + } + + 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 + + return total + + +class PitchingStat(BaseModel): + player = ForeignKeyField(Player) + team = ForeignKeyField(Team) + ip = FloatField() + hit = FloatField() + run = FloatField() + erun = FloatField() + so = FloatField() + bb = FloatField() + hbp = FloatField() + wp = FloatField() + balk = FloatField() + hr = FloatField() + ir = FloatField() + irs = FloatField() + gs = FloatField() + win = FloatField() + loss = FloatField() + hold = FloatField() + sv = FloatField() + bsv = FloatField() + week = IntegerField() + game = IntegerField() + season = IntegerField() + + @staticmethod + def select_season(season): + return PitchingStat.select().where(PitchingStat.season == season) + + @staticmethod + def regular_season(season): + if season == 1: + 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))\ + .order_by(PitchingStat.week) + elif season > 2: + 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))\ + .order_by(PitchingStat.week) + elif season == 2: + 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))\ + .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) + + 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, + } + + if total['ip']: + total['era'] = (total['erun'] * 9) / total['ip'] + + total['whip'] = (total['bb'] + total['hit']) / total['ip'] + + if total['win'] + total['loss'] > 0: + total['wl%'] = total['win'] / (total['win'] + total['loss']) + + return total + + +class Standings(BaseModel): + team = ForeignKeyField(Team) + wins = IntegerField(default=0) + losses = IntegerField(default=0) + run_diff = IntegerField(default=0) + div_gb = FloatField(default=0.0, null=True) + div_e_num = IntegerField(default=0, null=True) + wc_gb = FloatField(default=99.0, null=True) + wc_e_num = IntegerField(default=99, null=True) + home_wins = IntegerField(default=0) + home_losses = IntegerField(default=0) + away_wins = IntegerField(default=0) + away_losses = IntegerField(default=0) + last8_wins = IntegerField(default=0) + last8_losses = IntegerField(default=0) + streak_wl = CharField(default='w') + streak_num = IntegerField(default=0) + one_run_wins = IntegerField(default=0) + one_run_losses = IntegerField(default=0) + pythag_wins = IntegerField(default=0) + pythag_losses = IntegerField(default=0) + div1_wins = IntegerField(default=0) + div1_losses = IntegerField(default=0) + div2_wins = IntegerField(default=0) + div2_losses = IntegerField(default=0) + div3_wins = IntegerField(default=0) + div3_losses = IntegerField(default=0) + div4_wins = IntegerField(default=0) + div4_losses = IntegerField(default=0) + + @staticmethod + def select_season(season): + return Standings.select().join(Team).where(Standings.team.season == season) + + @staticmethod + def get_season(team): + return Standings.get_or_none(Standings.team == team) + + @staticmethod + def recalculate(season, full_wipe=True): + all_teams = Team.select_season(season).where(Team.division) + if full_wipe: + # Wipe existing data + delete_lines = Standings.select_season(season) + for line in delete_lines: + line.delete_instance() + + # Recreate current season Standings objects + create_teams = [Standings(team=team) for team in all_teams] + with db.atomic(): + Standings.bulk_create(create_teams) + + # Iterate through each individual result + for game in Result.select_season(season).where(Result.week <= 22): + # tally win and loss for each standings object + game.update_standings() + + # Set pythag record and iterate through last 8 games for last8 record + for team in all_teams: + team.run_pythag_last8() + + # Pull each division at a time and sort by win pct + for division in Division.select().where(Division.season == season): + division.sort_division(season) + + # Pull each league (filter by not null wc_gb) and sort by win pct + + # # For one league: + # Division.sort_wildcard(season, 'SBa') + + # For two leagues + Division.sort_wildcard(season, 'AL') + Division.sort_wildcard(season, 'NL') + + +class BattingCareer(BaseModel): + name = CharField() + pa = FloatField(default=0) + ab = FloatField(default=0) + run = FloatField(default=0) + hit = FloatField(default=0) + rbi = FloatField(default=0) + double = FloatField(default=0) + triple = FloatField(default=0) + hr = FloatField(default=0) + bb = FloatField(default=0) + so = FloatField(default=0) + hbp = FloatField(default=0) + sac = FloatField(default=0) + ibb = FloatField(default=0) + gidp = FloatField(default=0) + sb = FloatField(default=0) + cs = FloatField(default=0) + bphr = FloatField(default=0) + bpfo = FloatField(default=0) + bp1b = FloatField(default=0) + bplo = FloatField(default=0) + xba = FloatField(default=0) + xbt = FloatField(default=0) + game = FloatField(default=0) + + @staticmethod + def recalculate(): + # Wipe existing data + delete_lines = BattingCareer.select() + for line in delete_lines: + 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) + if not this_career: + this_career = BattingCareer(name=this_season.player.name) + this_career.save() + + this_career.pa += this_season.pa + this_career.ab += this_season.ab + this_career.run += this_season.run + this_career.hit += this_season.hit + this_career.rbi += this_season.rbi + this_career.double += this_season.double + this_career.triple += this_season.triple + this_career.hr += this_season.hr + this_career.bb += this_season.bb + this_career.so += this_season.so + this_career.hbp += this_season.hbp + this_career.sac += this_season.sac + this_career.ibb += this_season.ibb + this_career.gidp += this_season.gidp + this_career.sb += this_season.sb + this_career.cs += this_season.cs + this_career.bphr += this_season.bphr + this_career.bpfo += this_season.bpfo + this_career.bp1b += this_season.bp1b + this_career.bplo += this_season.bplo + this_career.xba += this_season.xba + this_career.xbt += this_season.xbt + this_career.save() + + +class PitchingCareer(BaseModel): + name = CharField() + ip = FloatField(default=0) + hit = FloatField(default=0) + run = FloatField(default=0) + erun = FloatField(default=0) + so = FloatField(default=0) + bb = FloatField(default=0) + hbp = FloatField(default=0) + wp = FloatField(default=0) + balk = FloatField(default=0) + hr = FloatField(default=0) + ir = FloatField(default=0) + irs = FloatField(default=0) + gs = FloatField(default=0) + win = FloatField(default=0) + loss = FloatField(default=0) + hold = FloatField(default=0) + sv = FloatField(default=0) + bsv = FloatField(default=0) + game = FloatField(default=0) + + @staticmethod + def recalculate(): + # Wipe existing data + delete_lines = PitchingCareer.select() + for line in delete_lines: + 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) + if not this_career: + this_career = PitchingCareer(name=this_season.player.name) + this_career.save() + + this_career.ip += this_season.ip + this_career.hit += this_season.hit + this_career.run += this_season.run + this_career.erun += this_season.erun + this_career.so += this_season.so + this_career.bb += this_season.bb + this_career.hbp += this_season.hbp + this_career.wp += this_season.wp + this_career.balk += this_season.balk + this_career.hr += this_season.hr + this_career.ir += this_season.ir + this_career.irs += this_season.irs + this_career.gs += this_season.gs + this_career.win += this_season.win + this_career.loss += this_season.loss + this_career.hold += this_season.hold + this_career.sv += this_season.sv + this_career.bsv += this_season.bsv + this_career.save() + + +class FieldingCareer(BaseModel): + name = CharField() + pos = CharField() + xch = IntegerField(default=0) + xhit = IntegerField(default=0) + error = IntegerField(default=0) + pb = IntegerField(default=0) + sbc = IntegerField(default=0) + csc = IntegerField(default=0) + roba = IntegerField(default=0) + robs = IntegerField(default=0) + raa = IntegerField(default=0) + rto = IntegerField(default=0) + game = IntegerField(default=0) + + @staticmethod + def recalculate(): + # Wipe existing data + delete_lines = FieldingCareer.select() + for line in delete_lines: + line.delete_instance() + + # For each seasonstat, find career or create new and increment + 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 + ) + if not this_career: + this_career = FieldingCareer(name=this_season.player.name, pos=this_season.pos) + this_career.save() + + this_career.xch += this_season.xch + this_career.xhit += this_season.xhit + this_career.error += this_season.error + this_career.pb += this_season.pb + this_career.sbc += this_season.sbc + this_career.csc += this_season.csc + this_career.roba += this_season.roba + this_career.robs += this_season.robs + this_career.raa += this_season.raa + this_career.rto += this_season.rto + this_career.save() + + +class BattingSeason(BaseModel): + player = ForeignKeyField(Player) + season = IntegerField() + season_type = CharField(default='Regular') + career = ForeignKeyField(BattingCareer, null=True) + pa = FloatField(default=0) + ab = FloatField(default=0) + run = FloatField(default=0) + hit = FloatField(default=0) + rbi = FloatField(default=0) + double = FloatField(default=0) + triple = FloatField(default=0) + hr = FloatField(default=0) + bb = FloatField(default=0) + so = FloatField(default=0) + hbp = FloatField(default=0) + sac = FloatField(default=0) + ibb = FloatField(default=0) + gidp = FloatField(default=0) + sb = FloatField(default=0) + cs = FloatField(default=0) + bphr = FloatField(default=0) + bpfo = FloatField(default=0) + bp1b = FloatField(default=0) + bplo = FloatField(default=0) + xba = FloatField(default=0) + xbt = FloatField(default=0) + game = FloatField(default=0) + + @staticmethod + def select_season(season): + return BattingSeason.select().where(BattingSeason.season == season) + + # @staticmethod + # def recalculate(season, manager_id): + # # Wipe existing data + # delete_lines = BattingSeason.select_season(season) + # for line in delete_lines: + # line.delete_instance() + # + # # For each battingstat, find season or create new and increment + # for line in BattingStat.select().where( + # (BattingStat.season == season) & (BattingStat.player.team.manager1 == manager_id) + # ): + # if line.season == 1: + # s_type = 'Regular' if line.week < 21 else 'Post' + # elif line.season == 2: + # s_type = 'Regular' if line.week < 19 else 'Post' + # else: + # s_type = 'Regular' if line.week < 23 else 'Post' + # + # this_season = BattingSeason.get_or_none(player=line.player, season_type=s_type) + # if not this_season: + # this_season = BattingSeason(player=line.player, season_type=s_type, season=line.season) + # this_season.save() + # + # this_season.pa += line.pa + # this_season.ab += line.ab + # this_season.run += line.run + # this_season.hit += line.hit + # this_season.rbi += line.rbi + # this_season.double += line.double + # this_season.triple += line.triple + # this_season.hr += line.hr + # this_season.bb += line.bb + # this_season.so += line.so + # this_season.hbp += line.hbp + # this_season.sac += line.sac + # this_season.ibb += line.ibb + # this_season.gidp += line.gidp + # this_season.sb += line.sb + # this_season.cs += line.cs + # this_season.save() + + def recalculate(self): + self.pa = 0 + self.ab = 0 + self.run = 0 + self.hit = 0 + self.rbi = 0 + self.double = 0 + self.triple = 0 + self.hr = 0 + self.bb = 0 + self.so = 0 + self.hbp = 0 + self.sac = 0 + self.ibb = 0 + self.gidp = 0 + self.sb = 0 + self.cs = 0 + self.bphr = 0 + self.bpfo = 0 + self.bp1b = 0 + self.bplo = 0 + self.xba = 0 + self.xbt = 0 + self.game = 0 + + 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) + for line in all_stats: + self.pa += line.pa + self.ab += line.ab + self.run += line.run + self.hit += line.hit + self.rbi += line.rbi + self.double += line.double + self.triple += line.triple + self.hr += line.hr + self.bb += line.bb + self.so += line.so + self.hbp += line.hbp + self.sac += line.sac + self.ibb += line.ibb + self.gidp += line.gidp + self.sb += line.sb + self.cs += line.cs + self.bphr += line.bphr + self.bpfo += line.bpfo + self.bp1b += line.bp1b + self.bplo += line.bplo + self.xba += line.xba + self.xbt += line.xbt + self.game += 1 + + self.save() + return all_stats.count() + + +class PitchingSeason(BaseModel): + player = ForeignKeyField(Player) + season = IntegerField() + season_type = CharField(default='Regular') + career = ForeignKeyField(PitchingCareer, null=True) + ip = FloatField(default=0) + hit = FloatField(default=0) + run = FloatField(default=0) + erun = FloatField(default=0) + so = FloatField(default=0) + bb = FloatField(default=0) + hbp = FloatField(default=0) + wp = FloatField(default=0) + balk = FloatField(default=0) + hr = FloatField(default=0) + ir = FloatField(default=0) + irs = FloatField(default=0) + gs = FloatField(default=0) + win = FloatField(default=0) + loss = FloatField(default=0) + hold = FloatField(default=0) + sv = FloatField(default=0) + bsv = FloatField(default=0) + game = FloatField(default=0) + + @staticmethod + def select_season(season): + return PitchingSeason.select().where(PitchingSeason.season == season) + + # @staticmethod + # def recalculate(season, manager_id): + # # Wipe existing data + # delete_lines = PitchingSeason.select_season(season) + # for line in delete_lines: + # line.delete_instance() + # + # # For each pitchingstat, find season or create new and increment + # for line in PitchingStat.select().where( + # (PitchingStat.season == season) & (PitchingStat.player.team.manager1 == manager_id) + # ): + # if line.season == 1: + # s_type = 'Regular' if line.week < 21 else 'Post' + # elif line.season == 2: + # s_type = 'Regular' if line.week < 19 else 'Post' + # else: + # s_type = 'Regular' if line.week < 23 else 'Post' + # + # this_season = PitchingSeason.get_or_none(player=line.player, season_type=s_type) + # if not this_season: + # this_season = PitchingSeason(player=line.player, season_type=s_type, season=line.season) + # this_season.save() + # + # this_season.ip += line.ip + # this_season.hit += line.hit + # this_season.run += line.run + # this_season.erun += line.erun + # this_season.so += line.so + # this_season.bb += line.bb + # this_season.hbp += line.hbp + # this_season.wp += line.wp + # this_season.balk += line.balk + # this_season.hr += line.hr + # this_season.gs += line.gs + # this_season.win += line.win + # this_season.loss += line.loss + # this_season.hold += line.hold + # this_season.sv += line.sv + # this_season.bsv += line.bsv + # this_season.game += 1 + # this_season.save() + + def recalculate(self): + self.ip = 0 + self.hit = 0 + self.run = 0 + self.erun = 0 + self.so = 0 + self.bb = 0 + self.hbp = 0 + self.wp = 0 + self.balk = 0 + self.hr = 0 + self.ir = 0 + self.irs = 0 + self.gs = 0 + self.win = 0 + self.loss = 0 + self.hold = 0 + self.sv = 0 + self.bsv = 0 + self.game = 0 + + 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) + for line in all_stats: + self.ip += line.ip + self.hit += line.hit + self.run += line.run + self.erun += line.erun + self.so += line.so + self.bb += line.bb + self.hbp += line.hbp + self.wp += line.wp + self.balk += line.balk + self.hr += line.hr + self.ir += line.ir + self.irs += line.irs + self.gs += line.gs + self.win += line.win + self.loss += line.loss + self.hold += line.hold + self.sv += line.sv + self.bsv += line.bsv + self.game += 1 + + self.save() + return all_stats.count() + + +class FieldingSeason(BaseModel): + player = ForeignKeyField(Player) + season = IntegerField() + season_type = CharField(default='Regular') + pos = CharField() + career = ForeignKeyField(FieldingCareer, null=True) + xch = IntegerField(default=0) + xhit = IntegerField(default=0) + error = IntegerField(default=0) + pb = IntegerField(default=0) + sbc = IntegerField(default=0) + csc = IntegerField(default=0) + roba = IntegerField(default=0) + robs = IntegerField(default=0) + raa = IntegerField(default=0) + rto = IntegerField(default=0) + game = IntegerField(default=0) + + @staticmethod + def select_season(season): + return FieldingSeason.select().where(FieldingSeason.season == season) + + # @staticmethod + # def recalculate(season, manager_id): + # # Wipe existing data + # delete_lines = FieldingSeason.select() + # for line in delete_lines: + # line.delete_instance() + # + # # players = Player.select_season(season).where(Player.team) + # + # # For each battingstat, find season or create new and increment + # for line in BattingStat.select().join(Player).join(Team).where( + # (BattingStat.season == season) & (BattingStat.player.team.manager1 == manager_id) + # ): + # if line.season == 1: + # s_type = 'Regular' if line.week < 21 else 'Post' + # elif line.season == 2: + # s_type = 'Regular' if line.week < 19 else 'Post' + # else: + # s_type = 'Regular' if line.week < 23 else 'Post' + # + # this_season = BattingSeason.get_or_none(player=line.player, season_type=s_type, pos=line.pos) + # if not this_season: + # this_season = BattingSeason(player=line.player, season_type=s_type, pos=line.pos, season=line.season) + # this_season.save() + # + # this_season.xch += line.xch + # this_season.xhit += line.xhit + # this_season.error += line.error + # this_season.pb += line.pb + # this_season.sbc += line.sbc + # this_season.csc += line.csc + # this_season.game += 1 + # this_season.save() + + def recalculate(self): + self.xch = 0 + self.xhit = 0 + self.error = 0 + self.pb = 0 + self.sbc = 0 + self.csc = 0 + self.roba = 0 + self.robs = 0 + self.raa = 0 + self.rto = 0 + self.game = 0 + + if self.season_type == 'Regular': + all_stats = BattingStat.regular_season(self.season).where( + (BattingStat.player == self.player) & (BattingStat.pos == self.pos) + ) + else: + all_stats = BattingStat.post_season(self.season).where( + (BattingStat.player == self.player) & (BattingStat.pos == self.pos) + ) + + for line in all_stats: + self.xch += line.xch + self.xhit += line.xhit + self.error += line.error + self.pb += line.pb + self.sbc += line.sbc + self.csc += line.csc + self.roba += line.roba + self.robs += line.robs + self.raa += line.raa + self.rto += line.rto + self.game += 1 + + self.save() + return all_stats.count() + + +class DraftPick(BaseModel): + overall = IntegerField(null=True) + round = IntegerField() + origowner = ForeignKeyField(Team) + owner = ForeignKeyField(Team) + season = IntegerField() + player = ForeignKeyField(Player, null=True) + + @staticmethod + def select_season(num): + return DraftPick.select().where(DraftPick.season == num) + + @staticmethod + def get_season(team, rd, num): + return DraftPick.get(DraftPick.season == num, DraftPick.origowner == team, DraftPick.round == rd) + + +class DraftData(BaseModel): + currentpick = IntegerField() + timer = BooleanField() + pick_deadline = DateTimeField(null=True) + result_channel = IntegerField(null=True) + ping_channel = IntegerField(null=True) + pick_minutes = IntegerField(null=True) + + +class Award(BaseModel): + name = CharField() + season = IntegerField() + timing = CharField(default="In-Season") + manager1 = ForeignKeyField(Manager, null=True) + manager2 = ForeignKeyField(Manager, null=True) + player = ForeignKeyField(Player, null=True) + team = ForeignKeyField(Team, null=True) + image = CharField(null=True) + + +class DiceRoll(BaseModel): + season = IntegerField(default=Current.latest().season) + week = IntegerField(default=Current.latest().week) + team = ForeignKeyField(Team, null=True) + roller = IntegerField() + dsix = IntegerField(null=True) + twodsix = IntegerField(null=True) + threedsix = IntegerField(null=True) + dtwenty = IntegerField(null=True) + + +class DraftList(BaseModel): + season = IntegerField() + team = ForeignKeyField(Team) + rank = IntegerField() + player = ForeignKeyField(Player) + + +# class Streak(BaseModel): +# player = ForeignKeyField(Player) +# streak_type = CharField() +# start_season = IntegerField() +# start_week = IntegerField() +# start_game = IntegerField() +# end_season = IntegerField() +# end_week = IntegerField() +# end_game = IntegerField() +# game_length = IntegerField() +# active = BooleanField() +# +# def recalculate(self): +# # Pitcher streaks +# if self.streak_type in ['win', 'loss', 'save', 'scoreless']: +# all_stats = PitchingStat.select_season(self.start_season).where( +# (PitchingStat.player == self.player) & (PitchingStat.week >= self.start_week) +# ) +# sorted_stats = sorted(all_stats, key=lambda x: f'{x.season:0>2}-{x.week:0>2}-{x.game:}') +# +# for line in sorted_stats: + +db.create_tables([ + Current, Division, Manager, Team, Result, Player, Schedule, Transaction, BattingStat, PitchingStat, Standings, + BattingCareer, PitchingCareer, FieldingCareer, Manager, Award, DiceRoll, DraftList +]) +db.close() diff --git a/app/dependencies.py b/app/dependencies.py new file mode 100644 index 0000000..cba247d --- /dev/null +++ b/app/dependencies.py @@ -0,0 +1,20 @@ +import datetime +import logging +import os + +from fastapi.security import OAuth2PasswordBearer + +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 'WARN' +logging.basicConfig( + filename=f'logs/database/{date}.log', + format='%(asctime)s - database - %(levelname)s - %(message)s', + level=log_level +) + + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + + +def valid_token(token): + return token == os.environ.get('API_TOKEN') diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..75c4971 --- /dev/null +++ b/app/main.py @@ -0,0 +1,16 @@ +from fastapi import Depends, FastAPI + +from routers_v3 import current, players + +app = FastAPI( + responses={404: {'description': 'Not found'}} +) + + +app.include_router(current.router) +app.include_router(players.router) + + +# @app.get("/api") +# async def root(): +# return {"message": "Hello Bigger Applications!"} diff --git a/app/routers_v3/__init__.py b/app/routers_v3/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/routers_v3/current.py b/app/routers_v3/current.py new file mode 100644 index 0000000..05b7b75 --- /dev/null +++ b/app/routers_v3/current.py @@ -0,0 +1,118 @@ +from fastapi import APIRouter, Depends, HTTPException +from typing import Optional +import logging +import pydantic + +from db_engine import db, Current, model_to_dict, DatabaseError +from dependencies import oauth2_scheme, valid_token, logging + +router = APIRouter( + prefix='/api/v3/current', + tags=['current'] +) + + +class CurrentModel(pydantic.BaseModel): + week: Optional[int] = 0 + freeze: Optional[bool] = False + season: int + transcount: Optional[int] = 0 + bstatcount: Optional[int] = 0 + pstatcount: Optional[int] = 0 + bet_week: Optional[int] = 0 + trade_deadline: int + pick_trade_start: int = 69 + pick_trade_end: int = 420 + playoffs_begin: int + injury_count: Optional[int] = 0 + + +@router.get('') +async def get_current(season: Optional[int] = None): + if season is not None: + current = Current.get_or_none(season=season) + else: + current = Current.latest() + + if current is not None: + r_curr = model_to_dict(current) + db.close() + return r_curr + else: + return None + + +@router.patch('/{current_id}') +async def patch_current( + current_id: int, season: Optional[int] = None, week: Optional[int] = None, freeze: Optional[bool] = None, + transcount: Optional[int] = None, bstatcount: Optional[int] = None, pstatcount: Optional[int] = None, + bet_week: Optional[int] = None, trade_deadline: Optional[int] = None, pick_trade_start: Optional[int] = None, + pick_trade_end: Optional[int] = None, injury_count: Optional[int] = None, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_current - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + try: + current = Current.get_by_id(current_id) + except Exception as e: + raise HTTPException(status_code=404, detail=f'Current id {current_id} not found') + + if week is not None: + current.week = week + if season is not None: + current.season = season + if freeze is not None: + current.freeze = freeze + if transcount is not None: + current.transcount = transcount + if bstatcount is not None: + current.bstatcount = bstatcount + if pstatcount is not None: + current.pstatcount = pstatcount + if bet_week is not None: + current.bet_week = bet_week + if trade_deadline is not None: + current.trade_deadline = trade_deadline + if pick_trade_start is not None: + current.pick_trade_start = pick_trade_start + if pick_trade_end is not None: + current.pick_trade_end = pick_trade_end + if injury_count is not None: + current.injury_count = injury_count + + if current.save(): + r_curr = model_to_dict(current) + db.close() + return r_curr + else: + db.close() + raise HTTPException(status_code=500, detail=f'Unable to patch current {current_id}') + + +@router.post('') +async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_current - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_current = Current(**new_current.dict()) + + if this_current.save(): + r_curr = model_to_dict(this_current) + db.close() + return r_curr + else: + db.close() + raise HTTPException(status_code=500, detail=f'Unable to post season {new_current.season} current') + + +@router.delete('/{current_id}') +def delete_current(current_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_current - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + if Current.delete_by_id(current_id) == 1: + return f'Deleted current ID {current_id}' + + raise HTTPException(status_code=500, detail=f'Unable to delete current {current_id}') diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py new file mode 100644 index 0000000..c08363d --- /dev/null +++ b/app/routers_v3/players.py @@ -0,0 +1,204 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from typing import List, Optional +import logging +import pydantic + +from db_engine import db, Player, model_to_dict, DatabaseError, chunked +from dependencies import oauth2_scheme, valid_token, logging + +router = APIRouter( + prefix='/api/v3/players', + tags=['players'] +) + + +class PlayerModel(pydantic.BaseModel): + name: str + wara: float + image: str + image2: Optional[str] = None + team_id: int + season: int + pitcher_injury: Optional[int] = None + pos_1: str + pos_2: Optional[str] = None + pos_3: Optional[str] = None + pos_4: Optional[str] = None + pos_5: Optional[str] = None + pos_6: Optional[str] = None + pos_7: Optional[str] = None + pos_8: Optional[str] = None + last_game: Optional[str] = None + last_game2: Optional[str] = None + il_return: Optional[str] = None + demotion_week: Optional[int] = None + strat_code: Optional[str] = None + bbref_id: Optional[str] = None + injury_rating: Optional[str] = None + + +class PlayerList(pydantic.BaseModel): + players: List[PlayerModel] + + +@router.get('') +def get_players( + season: Optional[int], team_id: list = Query(default=None), pos: list = Query(default=None), + is_injured: Optional[bool] = None, sort: Optional[str] = None): + logging.info(f'team_id: {team_id}') + + all_players = Player.select_season(season) + + if team_id is not None: + all_players = all_players.where(Player.team_id << team_id) + + if pos is not None: + p_list = [x.upper() for x in pos] + all_players = all_players.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) + ) + + if is_injured is not None: + all_players = all_players.where(Player.il_return.is_null(False)) + + if sort is not None: + if sort == 'cost-asc': + all_players = all_players.order_by(Player.wara) + elif sort == 'cost-desc': + all_players = all_players.order_by(-Player.wara) + elif sort == 'name-asc': + all_players = all_players.order_by(Player.name) + elif sort == 'name-desc': + all_players = all_players.order_by(-Player.name) + + return_players = { + 'count': all_players.count(), + 'players': [model_to_dict(x) for x in all_players] + } + db.close() + return return_players + + +@router.get('/{player_id}') +def get_one_player(player_id: int): + this_player = Player.get_or_none(Player.id == player_id) + if this_player: + r_player = model_to_dict(this_player) + else: + r_player = None + db.close() + return r_player + + +@router.patch('/{player_id}') +def patch_player( + player_id: int, wara: Optional[int] = None, image: Optional[str] = None, image2: Optional[str] = None, + team_id: Optional[int] = None, season: Optional[int] = None, last_game: Optional[str] = None, + last_game2: Optional[str] = None, il_return: Optional[str] = None, demotion_week: Optional[int] = None, + strat_code: Optional[str] = None, bbref_id: Optional[str] = None, injury_rating: Optional[str] = None, + pos_1: Optional[str] = None, pos_2: Optional[str] = None, pos_3: Optional[str] = None, + pos_4: Optional[str] = None, pos_5: Optional[str] = None, pos_6: Optional[str] = None, + pos_7: Optional[str] = None, pos_8: Optional[str] = None, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_player - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_player = Player.get_or_none(Player.id == player_id) + if not this_player: + return None + + if wara is not None: + this_player.wara = wara + if image is not None: + this_player.image = image + if image2 is not None: + this_player.image2 = image2 + if team_id is not None: + this_player.team_id = team_id + if season is not None: + this_player.season = season + if last_game is not None: + this_player.last_game = last_game + if last_game2 is not None: + this_player.last_game2 = last_game2 + if il_return is not None: + this_player.il_return = il_return + if demotion_week is not None: + this_player.demotion_week = demotion_week + if strat_code is not None: + this_player.strat_code = strat_code + if bbref_id is not None: + this_player.bbref_id = bbref_id + if injury_rating is not None: + this_player.injury_rating = injury_rating + if pos_1 is not None: + this_player.pos_1 = pos_1 + if pos_2 is not None: + this_player.pos_2 = pos_2 + if pos_3 is not None: + this_player.pos_3 = pos_3 + if pos_4 is not None: + this_player.pos_4 = pos_4 + if pos_5 is not None: + this_player.pos_5 = pos_5 + if pos_6 is not None: + this_player.pos_6 = pos_6 + if pos_7 is not None: + this_player.pos_7 = pos_7 + if pos_8 is not None: + this_player.pos_8 = pos_8 + + if this_player.save() == 1: + r_player = model_to_dict(this_player) + db.close() + return r_player + else: + db.close() + raise HTTPException(status_code=500, detail=f'Unable to patch player {player_id}') + + +@router.post('/') +def post_players(p_list: PlayerList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_players - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + new_players = [] + for player in p_list.players: + dupe = Player.get_or_none(Player.season == player.season, Player.name == player.name) + if dupe: + db.close() + raise HTTPException( + status_code=500, + detail=f'Player name {player.name} already in use in Season {player.season}' + ) + + new_players.append(player.dict()) + + with db.atomic(): + for batch in chunked(new_players, 15): + Player.insert_many(batch).on_conflict_replace().execute() + db.close() + + return f'Inserted {len(new_players)} players' + + +@router.delete('/{player_id}') +def delete_player(player_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'delete_player - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_player = Player.get_or_none(Player.id == player_id) + if not this_player: + db.close() + raise HTTPException(status_code=404, detail=f'Player ID {player_id} not found') + + count = this_player.delete_instance() + db.close() + + if count == 1: + return f'Player {player_id} has been deleted' + else: + raise HTTPException(status_code=500, detail=f'Player {player_id} could not be deleted') diff --git a/requirements.txt b/requirements.txt index 1e90bfe..6b47181 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -fastapi==0.61.1 -uvicorn==0.12.2 +fastapi +uvicorn peewee==3.13.3 python-multipart pandas From 0c7712cd787c4044bbea91eb26c38f393ad85698 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 22 Mar 2023 10:42:55 -0500 Subject: [PATCH 02/24] Added /schedules and /results --- app/database/__init__.py | 1817 ----------------------------------- app/main.py | 4 +- app/routers_v3/current.py | 2 +- app/routers_v3/players.py | 4 +- app/routers_v3/results.py | 161 ++++ app/routers_v3/schedules.py | 143 +++ 6 files changed, 310 insertions(+), 1821 deletions(-) delete mode 100644 app/database/__init__.py create mode 100644 app/routers_v3/results.py create mode 100644 app/routers_v3/schedules.py diff --git a/app/database/__init__.py b/app/database/__init__.py deleted file mode 100644 index ae483e2..0000000 --- a/app/database/__init__.py +++ /dev/null @@ -1,1817 +0,0 @@ -import copy -import math - -from peewee import * - -from playhouse.shortcuts import model_to_dict - -db = SqliteDatabase( - 'storage/sba_master.db', - pragmas={ - 'journal_mode': 'wal', - 'cache_size': -1 * 64000, - 'synchronous': 0 - } -) - - -""" -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 -""" - - -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) - - -def games_back(leader, chaser): - return ((leader.wins - chaser.wins) + (chaser.losses - leader.losses)) / 2 - - -def e_number(leader, chaser): - e_num = 89 - leader.wins - chaser.losses - return e_num if e_num > 0 else 0 - - -class BaseModel(Model): - class Meta: - database = db - - -class Current(BaseModel): - week = IntegerField(default=0) - freeze = BooleanField(default=True) - season = IntegerField() - transcount = IntegerField(default=0) - bstatcount = IntegerField(default=0) - pstatcount = IntegerField(default=0) - bet_week = IntegerField(default=0) - trade_deadline = IntegerField() - pick_trade_start = IntegerField() - pick_trade_end = IntegerField() - playoffs_begin = IntegerField() - injury_count = IntegerField() - - @staticmethod - def latest(): - latest_current = Current.select().order_by(-Current.id).get() - return latest_current - - -class Division(BaseModel): - division_name = CharField() - division_abbrev = CharField() - league_name = CharField(null=True) - league_abbrev = CharField(null=True) - season = IntegerField(default=0) - - def abbrev(self): - 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}' - - def full_name(self): - 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_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 = -games_back(div_teams[0], div_teams[1]) - div_teams[0].div_e_num = None - div_teams[0].wc_gb = None - div_teams[0].wc_e_num = None - else: - div_teams[x].div_gb = games_back(div_teams[0], div_teams[x]) - div_teams[x].div_e_num = e_number(div_teams[0], div_teams[x]) - - div_teams[x].save() - - @staticmethod - def sort_wildcard(season, league_abbrev): - divisions = Division.select().where(Division.league_abbrev == league_abbrev) - teams_query = Standings.select_season(season).where( - Standings.wc_gb & (Standings.team.division << divisions) - ) - league_teams = [team_stan for team_stan in teams_query] - league_teams.sort(key=lambda team: win_pct(team), reverse=True) - - for x in range(len(league_teams)): - # Special calculations for two wildcard teams - if x == 0: - league_teams[0].wc_gb = -games_back(league_teams[0], league_teams[2]) - league_teams[0].wc_e_num = None - elif x == 1: - league_teams[1].wc_gb = 0 - league_teams[1].wc_e_num = None - else: - league_teams[x].wc_gb = games_back(league_teams[1], league_teams[x]) - league_teams[x].wc_e_num = e_number(league_teams[1], league_teams[x]) - - league_teams[x].save() - - -class Manager(BaseModel): - name = CharField(unique=True) - image = CharField(null=True) - headline = CharField(null=True) - bio = CharField(null=True) - - -class Team(BaseModel): - abbrev = CharField() - sname = CharField() - lname = CharField() - manager_legacy = CharField(null=True) - division_legacy = CharField(null=True) - gmid = IntegerField() - gmid2 = IntegerField(null=True) - manager1 = ForeignKeyField(Manager, null=True) - manager2 = ForeignKeyField(Manager, null=True) - division = ForeignKeyField(Division, null=True) - mascot = CharField(null=True) - stadium = CharField(null=True) - gsheet = CharField(null=True) - thumbnail = CharField(null=True) - color = CharField(null=True) - dice_color = CharField(null=True) - season = IntegerField() - auto_draft = BooleanField() - - @staticmethod - def select_season(num): - return Team.select().where(Team.season == num) - - @staticmethod - def get_by_owner(gmid, season): - team = Team.get_or_none(Team.gmid == gmid, Team.season == season) - if not team: - team = Team.get_or_none(Team.gmid2 == gmid, Team.season == season) - if not team: - return None - return team - - @staticmethod - def get_season(name_or_abbrev, 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) - if not team: - 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) - ) - 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) - ) - 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} - - def get_gms(self): - if self.gmid2: - return [self.gmid, self.gmid2] - else: - return [self.gmid] - - def get_this_week(self): - active_team = Player.select_season(self.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': []} - - combo_pitchers = 0 - for guy in active_team: - 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: - combo_pitchers += 1 - else: - try: - for pos in guy_pos: - active_roster[pos] += 1 - except KeyError: - # This happens for season 1 without player positions listed - pass - - if combo_pitchers > 0: - 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 - combo_pitchers -= delta - - if combo_pitchers > 0: - active_roster['RP'] += combo_pitchers - - 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': []} - - combo_pitchers = 0 - for guy in short_il: - 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: - 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'] - else: - delta = combo_pitchers - short_roster['SP'] += delta - combo_pitchers -= delta - - if combo_pitchers > 0: - short_roster['RP'] += combo_pitchers - - 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': []} - - combo_pitchers = 0 - for guy in long_il: - 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: - 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'] - else: - delta = combo_pitchers - long_roster['SP'] += delta - combo_pitchers -= delta - - if combo_pitchers > 0: - long_roster['RP'] += combo_pitchers - - 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': []} - - combo_pitchers = 0 - for guy in active_team: - 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: - 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) - ) - all_adds = Transaction.select_season(Current.latest().season).where( - (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: - 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 - try: - active_roster['players'].remove(move.player) - except: - 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: - 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) - - if combo_pitchers > 0: - 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 - combo_pitchers -= delta - - if combo_pitchers > 0: - active_roster['RP'] += combo_pitchers - - 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': []} - - combo_pitchers = 0 - for guy in short_il: - 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: - combo_pitchers += 1 - else: - for pos in guy_pos: - short_roster[pos] += 1 - - 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) - ) - all_adds = Transaction.select_season(Current.latest().season).where( - (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: - combo_pitchers -= 1 - else: - for pos in guy_pos: - short_roster[pos] -= 1 - 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) - except: - 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: - 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) - - if combo_pitchers > 0: - 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 - combo_pitchers -= delta - - if combo_pitchers > 0: - short_roster['RP'] += combo_pitchers - - 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': []} - - combo_pitchers = 0 - for guy in long_il: - 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: - combo_pitchers += 1 - else: - for pos in guy_pos: - long_roster[pos] += 1 - - 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) - ) - all_adds = Transaction.select_season(Current.latest().season).where( - (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: - combo_pitchers -= 1 - else: - for pos in guy_pos: - long_roster[pos] -= 1 - 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) - except: - 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: - 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) - - if combo_pitchers > 0: - 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 - combo_pitchers -= delta - - if combo_pitchers > 0: - long_roster['RP'] += combo_pitchers - - 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_home = Result.select(fn.SUM(Result.homescore).alias('runs')).where( - Result.hometeam == self - )[0].runs - runs_scored_away = Result.select(fn.SUM(Result.awayscore).alias('runs')).where( - Result.awayteam == self - )[0].runs - runs_allowed_home = Result.select(fn.SUM(Result.homescore).alias('runs')).where( - Result.awayteam == self - )[0].runs - runs_allowed_away = Result.select(fn.SUM(Result.awayscore).alias('runs')).where( - Result.hometeam == self - )[0].runs - - if not runs_scored_home: - runs_scored_home = 0 - if not runs_scored_away: - runs_scored_away = 0 - if not runs_allowed_home: - runs_allowed_home = 0 - if not runs_allowed_away: - runs_allowed_away = 0 - - runs_scored = runs_scored_home + runs_scored_away - runs_allowed = runs_allowed_home + runs_allowed_away - if runs_allowed == 0: - pythag_win_pct = 0 - else: - 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 = Result.select_season(self.season).where( - (Result.hometeam == self) | (Result.awayteam == self) - ).order_by(-Result.id).limit(8) - - for game in last_games: - if game.homescore > game.awayscore: - if game.hometeam == self: - team_stan.last8_wins += 1 - else: - team_stan.last8_losses += 1 - else: - if game.hometeam == self: - team_stan.last8_losses += 1 - else: - team_stan.last8_wins += 1 - - return team_stan.save() - - -class Result(BaseModel): - week = IntegerField() - game = IntegerField() - awayteam = ForeignKeyField(Team) - hometeam = ForeignKeyField(Team) - awayscore = IntegerField() - homescore = IntegerField() - season = IntegerField() - scorecard_url = CharField(null=True) - - @staticmethod - def regular_season(num): - if num == 1: - return Result.select().where((Result.season == 1) & (Result.week < 21)) - elif num == 2: - return Result.select().where((Result.season == 2) & (Result.week < 19)) - elif num == 3 or num == 4: - return Result.select().where((Result.season == num) & (Result.week < 23)) - else: - return None - - @staticmethod - def post_season(num): - if num == 1: - return Result.select().where((Result.season == 1) & (Result.week >= 21)) - elif num == 2: - return Result.select().where((Result.season == 2) & (Result.week >= 19)) - elif num == 3 or num == 4: - return Result.select().where((Result.season == num) & (Result.week >= 23)) - else: - return None - - @staticmethod - def select_season(num): - return Result.select().where(Result.season == num) - - def update_standings(self): - away_stan = Standings.get_season(self.awayteam) - home_stan = Standings.get_season(self.hometeam) - away_div = Division.get_by_id(self.awayteam.division.id) - home_div = Division.get_by_id(self.hometeam.division.id) - - if self.homescore > self.awayscore: - # - generic w/l & home/away w/l - home_stan.wins += 1 - home_stan.home_wins += 1 - away_stan.losses += 1 - away_stan.away_losses += 1 - - # - update streak wl and num - if home_stan.streak_wl == 'w': - home_stan.streak_num += 1 - else: - home_stan.streak_wl = 'w' - home_stan.streak_num = 1 - - if away_stan.streak_wl == 'l': - away_stan.streak_num += 1 - else: - away_stan.streak_wl = 'l' - away_stan.streak_num = 1 - - # - if 1-run, tally accordingly - if self.homescore == self.awayscore + 1: - home_stan.one_run_wins += 1 - away_stan.one_run_losses += 1 - - # Used for one league with 3 divisions - # - update record v division - # if away_div.division_abbrev == 'BE': - # home_stan.div1_wins += 1 - # elif away_div.division_abbrev == 'DO': - # home_stan.div2_wins += 1 - # else: - # home_stan.div3_wins += 1 - # - # if home_div.division_abbrev == 'BE': - # away_stan.div1_losses += 1 - # elif home_div.division_abbrev == 'DO': - # away_stan.div2_losses += 1 - # else: - # away_stan.div3_losses += 1 - - # Used for two league plus divisions - if away_div.league_abbrev == 'AL': - if away_div.division_abbrev == 'E': - home_stan.div1_wins += 1 - else: - home_stan.div2_wins += 1 - else: - if away_div.division_abbrev == 'E': - home_stan.div3_wins += 1 - else: - home_stan.div4_wins += 1 - - if home_div.league_abbrev == 'AL': - if home_div.division_abbrev == 'E': - away_stan.div1_losses += 1 - else: - away_stan.div2_losses += 1 - else: - if home_div.division_abbrev == 'E': - away_stan.div3_losses += 1 - else: - away_stan.div4_losses += 1 - - # - adjust run_diff - home_stan.run_diff += self.homescore - self.awayscore - away_stan.run_diff -= self.homescore - self.awayscore - else: - # - generic w/l & home/away w/l - home_stan.losses += 1 - home_stan.home_losses += 1 - away_stan.wins += 1 - away_stan.away_wins += 1 - - # - update streak wl and num - if home_stan.streak_wl == 'l': - home_stan.streak_num += 1 - else: - home_stan.streak_wl = 'l' - home_stan.streak_num = 1 - - if away_stan.streak_wl == 'w': - away_stan.streak_num += 1 - else: - away_stan.streak_wl = 'w' - away_stan.streak_num = 1 - - # - if 1-run, tally accordingly - if self.awayscore == self.homescore + 1: - home_stan.one_run_losses += 1 - away_stan.one_run_wins += 1 - - # Used for one league with 3 divisions - # - update record v division - # if away_div.division_abbrev == 'BE': - # home_stan.div1_losses += 1 - # elif away_div.division_abbrev == 'DO': - # home_stan.div2_losses += 1 - # else: - # home_stan.div3_losses += 1 - # - # if home_div.division_abbrev == 'BE': - # away_stan.div1_wins += 1 - # elif home_div.division_abbrev == 'DO': - # away_stan.div2_wins += 1 - # else: - # away_stan.div3_wins += 1 - - # Used for two league plus divisions - if away_div.league_abbrev == 'AL': - if away_div.division_abbrev == 'E': - home_stan.div1_losses += 1 - else: - home_stan.div2_losses += 1 - else: - if away_div.division_abbrev == 'E': - home_stan.div3_losses += 1 - else: - home_stan.div4_losses += 1 - - if home_div.league_abbrev == 'AL': - if home_div.division_abbrev == 'E': - away_stan.div1_wins += 1 - else: - away_stan.div2_wins += 1 - else: - if home_div.division_abbrev == 'E': - away_stan.div3_wins += 1 - else: - away_stan.div4_wins += 1 - - # - adjust run_diff - home_stan.run_diff -= self.awayscore - self.homescore - away_stan.run_diff += self.awayscore - self.homescore - - home_stan.save() - away_stan.save() - - -class Player(BaseModel): - name = CharField() - wara = FloatField() - image = CharField() - image2 = CharField(null=True) - team = ForeignKeyField(Team) - season = IntegerField() - pitcher_injury = IntegerField(null=True) - pos_1 = CharField() - pos_2 = CharField(null=True) - pos_3 = CharField(null=True) - pos_4 = CharField(null=True) - pos_5 = CharField(null=True) - pos_6 = CharField(null=True) - pos_7 = CharField(null=True) - pos_8 = CharField(null=True) - last_game = CharField(null=True) - last_game2 = CharField(null=True) - il_return = CharField(null=True) - demotion_week = IntegerField(null=True) - headshot = CharField(null=True) - vanity_card = CharField(null=True) - strat_code = CharField(null=True) - bbref_id = CharField(null=True) - injury_rating = CharField(null=True) - - @staticmethod - def select_season(num): - return Player.select().where(Player.season == num) - - @staticmethod - def get_season(name, num): - player = None - try: - player = Player.get(fn.Lower(Player.name) == name.lower(), Player.season == num) - except Exception as e: - print(f'**Error** (db_engine player): {e}') - finally: - return player - - def get_positions(self): - """ - Params: None - Return: List of positions (ex ['1b', '3b']) - """ - pos_list = [] - if self.pos_1: - pos_list.append(self.pos_1) - if self.pos_2: - pos_list.append(self.pos_2) - if self.pos_3: - pos_list.append(self.pos_3) - if self.pos_4: - pos_list.append(self.pos_4) - if self.pos_5: - pos_list.append(self.pos_5) - if self.pos_6: - pos_list.append(self.pos_6) - if self.pos_7: - pos_list.append(self.pos_7) - if self.pos_8: - pos_list.append(self.pos_8) - - return pos_list - - -class Schedule(BaseModel): - week = IntegerField() - awayteam = ForeignKeyField(Team) - hometeam = ForeignKeyField(Team) - gamecount = IntegerField() - season = IntegerField() - - @staticmethod - def select_season(season): - return Schedule.select().where(Schedule.season == season) - - -class Transaction(BaseModel): - week = IntegerField() - player = ForeignKeyField(Player) - oldteam = ForeignKeyField(Team) - newteam = ForeignKeyField(Team) - season = IntegerField() - moveid = IntegerField() - cancelled = BooleanField(default=False) - frozen = BooleanField(default=False) - - @staticmethod - def select_season(num): - return Transaction.select().where(Transaction.season == num) - - -class BattingStat(BaseModel): - player = ForeignKeyField(Player) - team = ForeignKeyField(Team) - pos = CharField() - pa = IntegerField() - ab = IntegerField() - run = IntegerField() - hit = IntegerField() - rbi = IntegerField() - double = IntegerField() - triple = IntegerField() - hr = IntegerField() - bb = IntegerField() - so = IntegerField() - hbp = IntegerField() - sac = IntegerField() - ibb = IntegerField() - gidp = IntegerField() - sb = IntegerField() - cs = IntegerField() - bphr = IntegerField() - bpfo = IntegerField() - bp1b = IntegerField() - bplo = IntegerField() - xba = IntegerField() - xbt = IntegerField() - xch = IntegerField() - xhit = IntegerField() - error = IntegerField() - pb = IntegerField() - sbc = IntegerField() - csc = IntegerField() - roba = IntegerField() - robs = IntegerField() - raa = IntegerField() - rto = IntegerField() - week = IntegerField() - game = IntegerField() - season = IntegerField() - - @staticmethod - def combined_season(season): - """ - Params: season, integer (season number), optional - Return: ModelSelect object for season - """ - return BattingStat.select().where(BattingStat.season == season) - - @staticmethod - def regular_season(season): - """ - Params: num, integer (season number) - Return: ModelSelect object for season's regular season - """ - if season == 1: - 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))\ - .order_by(BattingStat.week) - elif season > 2: - return BattingStat.select().where((BattingStat.season == season) & (BattingStat.week < 23))\ - .order_by(BattingStat.week) - else: - return None - - @staticmethod - def post_season(season): - """ - Params: num, integer (season number) - Return: ModelSelect object for season's post season - """ - if season == 1: - return BattingStat.select().where((BattingStat.season == 1) & (BattingStat.week >= 21)) - elif season == 2: - return BattingStat.select().where((BattingStat.season == 2) & (BattingStat.week >= 19)) - elif season > 2: - 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) - - 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, - # '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['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 * .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 - - 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) - - 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, - # '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, - } - - 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 - - return total - - -class PitchingStat(BaseModel): - player = ForeignKeyField(Player) - team = ForeignKeyField(Team) - ip = FloatField() - hit = FloatField() - run = FloatField() - erun = FloatField() - so = FloatField() - bb = FloatField() - hbp = FloatField() - wp = FloatField() - balk = FloatField() - hr = FloatField() - ir = FloatField() - irs = FloatField() - gs = FloatField() - win = FloatField() - loss = FloatField() - hold = FloatField() - sv = FloatField() - bsv = FloatField() - week = IntegerField() - game = IntegerField() - season = IntegerField() - - @staticmethod - def select_season(season): - return PitchingStat.select().where(PitchingStat.season == season) - - @staticmethod - def regular_season(season): - if season == 1: - 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))\ - .order_by(PitchingStat.week) - elif season > 2: - 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))\ - .order_by(PitchingStat.week) - elif season == 2: - 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))\ - .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) - - 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, - } - - if total['ip']: - total['era'] = (total['erun'] * 9) / total['ip'] - - total['whip'] = (total['bb'] + total['hit']) / total['ip'] - - if total['win'] + total['loss'] > 0: - total['wl%'] = total['win'] / (total['win'] + total['loss']) - - return total - - -class Standings(BaseModel): - team = ForeignKeyField(Team) - wins = IntegerField(default=0) - losses = IntegerField(default=0) - run_diff = IntegerField(default=0) - div_gb = FloatField(default=0.0, null=True) - div_e_num = IntegerField(default=0, null=True) - wc_gb = FloatField(default=99.0, null=True) - wc_e_num = IntegerField(default=99, null=True) - home_wins = IntegerField(default=0) - home_losses = IntegerField(default=0) - away_wins = IntegerField(default=0) - away_losses = IntegerField(default=0) - last8_wins = IntegerField(default=0) - last8_losses = IntegerField(default=0) - streak_wl = CharField(default='w') - streak_num = IntegerField(default=0) - one_run_wins = IntegerField(default=0) - one_run_losses = IntegerField(default=0) - pythag_wins = IntegerField(default=0) - pythag_losses = IntegerField(default=0) - div1_wins = IntegerField(default=0) - div1_losses = IntegerField(default=0) - div2_wins = IntegerField(default=0) - div2_losses = IntegerField(default=0) - div3_wins = IntegerField(default=0) - div3_losses = IntegerField(default=0) - div4_wins = IntegerField(default=0) - div4_losses = IntegerField(default=0) - - @staticmethod - def select_season(season): - return Standings.select().join(Team).where(Standings.team.season == season) - - @staticmethod - def get_season(team): - return Standings.get_or_none(Standings.team == team) - - @staticmethod - def recalculate(season, full_wipe=True): - all_teams = Team.select_season(season).where(Team.division) - if full_wipe: - # Wipe existing data - delete_lines = Standings.select_season(season) - for line in delete_lines: - line.delete_instance() - - # Recreate current season Standings objects - create_teams = [Standings(team=team) for team in all_teams] - with db.atomic(): - Standings.bulk_create(create_teams) - - # Iterate through each individual result - for game in Result.select_season(season).where(Result.week <= 22): - # tally win and loss for each standings object - game.update_standings() - - # Set pythag record and iterate through last 8 games for last8 record - for team in all_teams: - team.run_pythag_last8() - - # Pull each division at a time and sort by win pct - for division in Division.select().where(Division.season == season): - division.sort_division(season) - - # Pull each league (filter by not null wc_gb) and sort by win pct - - # # For one league: - # Division.sort_wildcard(season, 'SBa') - - # For two leagues - Division.sort_wildcard(season, 'AL') - Division.sort_wildcard(season, 'NL') - - -class BattingCareer(BaseModel): - name = CharField() - pa = FloatField(default=0) - ab = FloatField(default=0) - run = FloatField(default=0) - hit = FloatField(default=0) - rbi = FloatField(default=0) - double = FloatField(default=0) - triple = FloatField(default=0) - hr = FloatField(default=0) - bb = FloatField(default=0) - so = FloatField(default=0) - hbp = FloatField(default=0) - sac = FloatField(default=0) - ibb = FloatField(default=0) - gidp = FloatField(default=0) - sb = FloatField(default=0) - cs = FloatField(default=0) - bphr = FloatField(default=0) - bpfo = FloatField(default=0) - bp1b = FloatField(default=0) - bplo = FloatField(default=0) - xba = FloatField(default=0) - xbt = FloatField(default=0) - game = FloatField(default=0) - - @staticmethod - def recalculate(): - # Wipe existing data - delete_lines = BattingCareer.select() - for line in delete_lines: - 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) - if not this_career: - this_career = BattingCareer(name=this_season.player.name) - this_career.save() - - this_career.pa += this_season.pa - this_career.ab += this_season.ab - this_career.run += this_season.run - this_career.hit += this_season.hit - this_career.rbi += this_season.rbi - this_career.double += this_season.double - this_career.triple += this_season.triple - this_career.hr += this_season.hr - this_career.bb += this_season.bb - this_career.so += this_season.so - this_career.hbp += this_season.hbp - this_career.sac += this_season.sac - this_career.ibb += this_season.ibb - this_career.gidp += this_season.gidp - this_career.sb += this_season.sb - this_career.cs += this_season.cs - this_career.bphr += this_season.bphr - this_career.bpfo += this_season.bpfo - this_career.bp1b += this_season.bp1b - this_career.bplo += this_season.bplo - this_career.xba += this_season.xba - this_career.xbt += this_season.xbt - this_career.save() - - -class PitchingCareer(BaseModel): - name = CharField() - ip = FloatField(default=0) - hit = FloatField(default=0) - run = FloatField(default=0) - erun = FloatField(default=0) - so = FloatField(default=0) - bb = FloatField(default=0) - hbp = FloatField(default=0) - wp = FloatField(default=0) - balk = FloatField(default=0) - hr = FloatField(default=0) - ir = FloatField(default=0) - irs = FloatField(default=0) - gs = FloatField(default=0) - win = FloatField(default=0) - loss = FloatField(default=0) - hold = FloatField(default=0) - sv = FloatField(default=0) - bsv = FloatField(default=0) - game = FloatField(default=0) - - @staticmethod - def recalculate(): - # Wipe existing data - delete_lines = PitchingCareer.select() - for line in delete_lines: - 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) - if not this_career: - this_career = PitchingCareer(name=this_season.player.name) - this_career.save() - - this_career.ip += this_season.ip - this_career.hit += this_season.hit - this_career.run += this_season.run - this_career.erun += this_season.erun - this_career.so += this_season.so - this_career.bb += this_season.bb - this_career.hbp += this_season.hbp - this_career.wp += this_season.wp - this_career.balk += this_season.balk - this_career.hr += this_season.hr - this_career.ir += this_season.ir - this_career.irs += this_season.irs - this_career.gs += this_season.gs - this_career.win += this_season.win - this_career.loss += this_season.loss - this_career.hold += this_season.hold - this_career.sv += this_season.sv - this_career.bsv += this_season.bsv - this_career.save() - - -class FieldingCareer(BaseModel): - name = CharField() - pos = CharField() - xch = IntegerField(default=0) - xhit = IntegerField(default=0) - error = IntegerField(default=0) - pb = IntegerField(default=0) - sbc = IntegerField(default=0) - csc = IntegerField(default=0) - roba = IntegerField(default=0) - robs = IntegerField(default=0) - raa = IntegerField(default=0) - rto = IntegerField(default=0) - game = IntegerField(default=0) - - @staticmethod - def recalculate(): - # Wipe existing data - delete_lines = FieldingCareer.select() - for line in delete_lines: - line.delete_instance() - - # For each seasonstat, find career or create new and increment - 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 - ) - if not this_career: - this_career = FieldingCareer(name=this_season.player.name, pos=this_season.pos) - this_career.save() - - this_career.xch += this_season.xch - this_career.xhit += this_season.xhit - this_career.error += this_season.error - this_career.pb += this_season.pb - this_career.sbc += this_season.sbc - this_career.csc += this_season.csc - this_career.roba += this_season.roba - this_career.robs += this_season.robs - this_career.raa += this_season.raa - this_career.rto += this_season.rto - this_career.save() - - -class BattingSeason(BaseModel): - player = ForeignKeyField(Player) - season = IntegerField() - season_type = CharField(default='Regular') - career = ForeignKeyField(BattingCareer, null=True) - pa = FloatField(default=0) - ab = FloatField(default=0) - run = FloatField(default=0) - hit = FloatField(default=0) - rbi = FloatField(default=0) - double = FloatField(default=0) - triple = FloatField(default=0) - hr = FloatField(default=0) - bb = FloatField(default=0) - so = FloatField(default=0) - hbp = FloatField(default=0) - sac = FloatField(default=0) - ibb = FloatField(default=0) - gidp = FloatField(default=0) - sb = FloatField(default=0) - cs = FloatField(default=0) - bphr = FloatField(default=0) - bpfo = FloatField(default=0) - bp1b = FloatField(default=0) - bplo = FloatField(default=0) - xba = FloatField(default=0) - xbt = FloatField(default=0) - game = FloatField(default=0) - - @staticmethod - def select_season(season): - return BattingSeason.select().where(BattingSeason.season == season) - - # @staticmethod - # def recalculate(season, manager_id): - # # Wipe existing data - # delete_lines = BattingSeason.select_season(season) - # for line in delete_lines: - # line.delete_instance() - # - # # For each battingstat, find season or create new and increment - # for line in BattingStat.select().where( - # (BattingStat.season == season) & (BattingStat.player.team.manager1 == manager_id) - # ): - # if line.season == 1: - # s_type = 'Regular' if line.week < 21 else 'Post' - # elif line.season == 2: - # s_type = 'Regular' if line.week < 19 else 'Post' - # else: - # s_type = 'Regular' if line.week < 23 else 'Post' - # - # this_season = BattingSeason.get_or_none(player=line.player, season_type=s_type) - # if not this_season: - # this_season = BattingSeason(player=line.player, season_type=s_type, season=line.season) - # this_season.save() - # - # this_season.pa += line.pa - # this_season.ab += line.ab - # this_season.run += line.run - # this_season.hit += line.hit - # this_season.rbi += line.rbi - # this_season.double += line.double - # this_season.triple += line.triple - # this_season.hr += line.hr - # this_season.bb += line.bb - # this_season.so += line.so - # this_season.hbp += line.hbp - # this_season.sac += line.sac - # this_season.ibb += line.ibb - # this_season.gidp += line.gidp - # this_season.sb += line.sb - # this_season.cs += line.cs - # this_season.save() - - def recalculate(self): - self.pa = 0 - self.ab = 0 - self.run = 0 - self.hit = 0 - self.rbi = 0 - self.double = 0 - self.triple = 0 - self.hr = 0 - self.bb = 0 - self.so = 0 - self.hbp = 0 - self.sac = 0 - self.ibb = 0 - self.gidp = 0 - self.sb = 0 - self.cs = 0 - self.bphr = 0 - self.bpfo = 0 - self.bp1b = 0 - self.bplo = 0 - self.xba = 0 - self.xbt = 0 - self.game = 0 - - 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) - for line in all_stats: - self.pa += line.pa - self.ab += line.ab - self.run += line.run - self.hit += line.hit - self.rbi += line.rbi - self.double += line.double - self.triple += line.triple - self.hr += line.hr - self.bb += line.bb - self.so += line.so - self.hbp += line.hbp - self.sac += line.sac - self.ibb += line.ibb - self.gidp += line.gidp - self.sb += line.sb - self.cs += line.cs - self.bphr += line.bphr - self.bpfo += line.bpfo - self.bp1b += line.bp1b - self.bplo += line.bplo - self.xba += line.xba - self.xbt += line.xbt - self.game += 1 - - self.save() - return all_stats.count() - - -class PitchingSeason(BaseModel): - player = ForeignKeyField(Player) - season = IntegerField() - season_type = CharField(default='Regular') - career = ForeignKeyField(PitchingCareer, null=True) - ip = FloatField(default=0) - hit = FloatField(default=0) - run = FloatField(default=0) - erun = FloatField(default=0) - so = FloatField(default=0) - bb = FloatField(default=0) - hbp = FloatField(default=0) - wp = FloatField(default=0) - balk = FloatField(default=0) - hr = FloatField(default=0) - ir = FloatField(default=0) - irs = FloatField(default=0) - gs = FloatField(default=0) - win = FloatField(default=0) - loss = FloatField(default=0) - hold = FloatField(default=0) - sv = FloatField(default=0) - bsv = FloatField(default=0) - game = FloatField(default=0) - - @staticmethod - def select_season(season): - return PitchingSeason.select().where(PitchingSeason.season == season) - - # @staticmethod - # def recalculate(season, manager_id): - # # Wipe existing data - # delete_lines = PitchingSeason.select_season(season) - # for line in delete_lines: - # line.delete_instance() - # - # # For each pitchingstat, find season or create new and increment - # for line in PitchingStat.select().where( - # (PitchingStat.season == season) & (PitchingStat.player.team.manager1 == manager_id) - # ): - # if line.season == 1: - # s_type = 'Regular' if line.week < 21 else 'Post' - # elif line.season == 2: - # s_type = 'Regular' if line.week < 19 else 'Post' - # else: - # s_type = 'Regular' if line.week < 23 else 'Post' - # - # this_season = PitchingSeason.get_or_none(player=line.player, season_type=s_type) - # if not this_season: - # this_season = PitchingSeason(player=line.player, season_type=s_type, season=line.season) - # this_season.save() - # - # this_season.ip += line.ip - # this_season.hit += line.hit - # this_season.run += line.run - # this_season.erun += line.erun - # this_season.so += line.so - # this_season.bb += line.bb - # this_season.hbp += line.hbp - # this_season.wp += line.wp - # this_season.balk += line.balk - # this_season.hr += line.hr - # this_season.gs += line.gs - # this_season.win += line.win - # this_season.loss += line.loss - # this_season.hold += line.hold - # this_season.sv += line.sv - # this_season.bsv += line.bsv - # this_season.game += 1 - # this_season.save() - - def recalculate(self): - self.ip = 0 - self.hit = 0 - self.run = 0 - self.erun = 0 - self.so = 0 - self.bb = 0 - self.hbp = 0 - self.wp = 0 - self.balk = 0 - self.hr = 0 - self.ir = 0 - self.irs = 0 - self.gs = 0 - self.win = 0 - self.loss = 0 - self.hold = 0 - self.sv = 0 - self.bsv = 0 - self.game = 0 - - 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) - for line in all_stats: - self.ip += line.ip - self.hit += line.hit - self.run += line.run - self.erun += line.erun - self.so += line.so - self.bb += line.bb - self.hbp += line.hbp - self.wp += line.wp - self.balk += line.balk - self.hr += line.hr - self.ir += line.ir - self.irs += line.irs - self.gs += line.gs - self.win += line.win - self.loss += line.loss - self.hold += line.hold - self.sv += line.sv - self.bsv += line.bsv - self.game += 1 - - self.save() - return all_stats.count() - - -class FieldingSeason(BaseModel): - player = ForeignKeyField(Player) - season = IntegerField() - season_type = CharField(default='Regular') - pos = CharField() - career = ForeignKeyField(FieldingCareer, null=True) - xch = IntegerField(default=0) - xhit = IntegerField(default=0) - error = IntegerField(default=0) - pb = IntegerField(default=0) - sbc = IntegerField(default=0) - csc = IntegerField(default=0) - roba = IntegerField(default=0) - robs = IntegerField(default=0) - raa = IntegerField(default=0) - rto = IntegerField(default=0) - game = IntegerField(default=0) - - @staticmethod - def select_season(season): - return FieldingSeason.select().where(FieldingSeason.season == season) - - # @staticmethod - # def recalculate(season, manager_id): - # # Wipe existing data - # delete_lines = FieldingSeason.select() - # for line in delete_lines: - # line.delete_instance() - # - # # players = Player.select_season(season).where(Player.team) - # - # # For each battingstat, find season or create new and increment - # for line in BattingStat.select().join(Player).join(Team).where( - # (BattingStat.season == season) & (BattingStat.player.team.manager1 == manager_id) - # ): - # if line.season == 1: - # s_type = 'Regular' if line.week < 21 else 'Post' - # elif line.season == 2: - # s_type = 'Regular' if line.week < 19 else 'Post' - # else: - # s_type = 'Regular' if line.week < 23 else 'Post' - # - # this_season = BattingSeason.get_or_none(player=line.player, season_type=s_type, pos=line.pos) - # if not this_season: - # this_season = BattingSeason(player=line.player, season_type=s_type, pos=line.pos, season=line.season) - # this_season.save() - # - # this_season.xch += line.xch - # this_season.xhit += line.xhit - # this_season.error += line.error - # this_season.pb += line.pb - # this_season.sbc += line.sbc - # this_season.csc += line.csc - # this_season.game += 1 - # this_season.save() - - def recalculate(self): - self.xch = 0 - self.xhit = 0 - self.error = 0 - self.pb = 0 - self.sbc = 0 - self.csc = 0 - self.roba = 0 - self.robs = 0 - self.raa = 0 - self.rto = 0 - self.game = 0 - - if self.season_type == 'Regular': - all_stats = BattingStat.regular_season(self.season).where( - (BattingStat.player == self.player) & (BattingStat.pos == self.pos) - ) - else: - all_stats = BattingStat.post_season(self.season).where( - (BattingStat.player == self.player) & (BattingStat.pos == self.pos) - ) - - for line in all_stats: - self.xch += line.xch - self.xhit += line.xhit - self.error += line.error - self.pb += line.pb - self.sbc += line.sbc - self.csc += line.csc - self.roba += line.roba - self.robs += line.robs - self.raa += line.raa - self.rto += line.rto - self.game += 1 - - self.save() - return all_stats.count() - - -class DraftPick(BaseModel): - overall = IntegerField(null=True) - round = IntegerField() - origowner = ForeignKeyField(Team) - owner = ForeignKeyField(Team) - season = IntegerField() - player = ForeignKeyField(Player, null=True) - - @staticmethod - def select_season(num): - return DraftPick.select().where(DraftPick.season == num) - - @staticmethod - def get_season(team, rd, num): - return DraftPick.get(DraftPick.season == num, DraftPick.origowner == team, DraftPick.round == rd) - - -class DraftData(BaseModel): - currentpick = IntegerField() - timer = BooleanField() - pick_deadline = DateTimeField(null=True) - result_channel = IntegerField(null=True) - ping_channel = IntegerField(null=True) - pick_minutes = IntegerField(null=True) - - -class Award(BaseModel): - name = CharField() - season = IntegerField() - timing = CharField(default="In-Season") - manager1 = ForeignKeyField(Manager, null=True) - manager2 = ForeignKeyField(Manager, null=True) - player = ForeignKeyField(Player, null=True) - team = ForeignKeyField(Team, null=True) - image = CharField(null=True) - - -class DiceRoll(BaseModel): - season = IntegerField(default=Current.latest().season) - week = IntegerField(default=Current.latest().week) - team = ForeignKeyField(Team, null=True) - roller = IntegerField() - dsix = IntegerField(null=True) - twodsix = IntegerField(null=True) - threedsix = IntegerField(null=True) - dtwenty = IntegerField(null=True) - - -class DraftList(BaseModel): - season = IntegerField() - team = ForeignKeyField(Team) - rank = IntegerField() - player = ForeignKeyField(Player) - - -# class Streak(BaseModel): -# player = ForeignKeyField(Player) -# streak_type = CharField() -# start_season = IntegerField() -# start_week = IntegerField() -# start_game = IntegerField() -# end_season = IntegerField() -# end_week = IntegerField() -# end_game = IntegerField() -# game_length = IntegerField() -# active = BooleanField() -# -# def recalculate(self): -# # Pitcher streaks -# if self.streak_type in ['win', 'loss', 'save', 'scoreless']: -# all_stats = PitchingStat.select_season(self.start_season).where( -# (PitchingStat.player == self.player) & (PitchingStat.week >= self.start_week) -# ) -# sorted_stats = sorted(all_stats, key=lambda x: f'{x.season:0>2}-{x.week:0>2}-{x.game:}') -# -# for line in sorted_stats: - -db.create_tables([ - Current, Division, Manager, Team, Result, Player, Schedule, Transaction, BattingStat, PitchingStat, Standings, - BattingCareer, PitchingCareer, FieldingCareer, Manager, Award, DiceRoll, DraftList -]) -db.close() diff --git a/app/main.py b/app/main.py index 75c4971..1bcea59 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,6 @@ from fastapi import Depends, FastAPI -from routers_v3 import current, players +from routers_v3 import current, players, results, schedules app = FastAPI( responses={404: {'description': 'Not found'}} @@ -9,6 +9,8 @@ app = FastAPI( app.include_router(current.router) app.include_router(players.router) +app.include_router(results.router) +app.include_router(schedules.router) # @app.get("/api") diff --git a/app/routers_v3/current.py b/app/routers_v3/current.py index 05b7b75..1c4d525 100644 --- a/app/routers_v3/current.py +++ b/app/routers_v3/current.py @@ -3,7 +3,7 @@ from typing import Optional import logging import pydantic -from db_engine import db, Current, model_to_dict, DatabaseError +from db_engine import db, Current, model_to_dict from dependencies import oauth2_scheme, valid_token, logging router = APIRouter( diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py index c08363d..433a968 100644 --- a/app/routers_v3/players.py +++ b/app/routers_v3/players.py @@ -3,7 +3,7 @@ from typing import List, Optional import logging import pydantic -from db_engine import db, Player, model_to_dict, DatabaseError, chunked +from db_engine import db, Player, model_to_dict, chunked from dependencies import oauth2_scheme, valid_token, logging router = APIRouter( @@ -158,7 +158,7 @@ def patch_player( raise HTTPException(status_code=500, detail=f'Unable to patch player {player_id}') -@router.post('/') +@router.post('') def post_players(p_list: PlayerList, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning(f'post_players - Bad Token: {token}') diff --git a/app/routers_v3/results.py b/app/routers_v3/results.py new file mode 100644 index 0000000..818d4bc --- /dev/null +++ b/app/routers_v3/results.py @@ -0,0 +1,161 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from typing import List, Optional +import logging +import pydantic + +from db_engine import db, Result, Team, model_to_dict, DatabaseError, chunked +from dependencies import oauth2_scheme, valid_token, logging + +router = APIRouter( + prefix='/api/v3/results', + tags=['results'] +) + + +class ResultModel(pydantic.BaseModel): + week: int + game: int + awayteam_id: int + hometeam_id: int + awayscore: int + homescore: int + season: int + scorecard_url: Optional[str] = None + + +class ResultList(pydantic.BaseModel): + results: List[ResultModel] + + +@router.get('') +def get_results( + season: int, team_abbrev: list = Query(default=None), week_start: list = Query(default=None), + week_end: list = Query(default=None), game_num: list = Query(default=None), + away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None)): + all_results = Result.select_season(season) + + if team_abbrev is not None: + team_list = [] + for x in team_abbrev: + team_list.append(Team.get_season(x, season)) + all_results = all_results.where( + (Result.awayteam << team_list) | (Result.hometeam << team_list) + ) + + if away_abbrev is not None: + team_list = [] + for x in away_abbrev: + team_list.append(Team.get_season(x, season)) + all_results = all_results.where(Result.awayteam << team_list) + + if home_abbrev is not None: + team_list = [] + for x in home_abbrev: + team_list.append(Team.get_season(x, season)) + all_results = all_results.where(Result.hometeam << team_list) + + if game_num is not None: + all_results = all_results.where(Result.game << game_num) + + if week_start is not None: + all_results = all_results.where(Result.week >= week_start) + + if week_end is not None: + all_results = all_results.where(Result.week <= week_end) + + return_results = { + 'count': all_results.count(), + 'results': [model_to_dict(x) for x in all_results] + } + db.close() + return return_results + + +@router.get('/{result_id}') +def get_one_result(result_id: int): + this_result = Result.get_or_none(Result.id == result_id) + if this_result is not None: + r_result = model_to_dict(this_result) + else: + r_result = None + db.close() + return r_result + + +@router.patch('/{result_id}') +def patch_result( + result_id: int, week_num: Optional[int] = None, game_num: Optional[int] = None, + away_team_id: Optional[int] = None, home_team_id: Optional[int] = None, away_score: Optional[int] = None, + home_score: Optional[int] = None, season: Optional[int] = None, scorecard_url: Optional[str] = None): + this_result = Result.get_or_none(Result.id == result_id) + if this_result is None: + raise KeyError(f'Result ID {result_id} not found') + + if week_num is not None: + this_result.week = week_num + + if game_num is not None: + this_result.game = game_num + + if away_team_id is not None: + this_result.awayteam_id = away_team_id + + if home_team_id is not None: + this_result.hometeam_id = home_team_id + + if away_score is not None: + this_result.awayscore = away_score + + if home_score is not None: + this_result.homescore = home_score + + if season is not None: + this_result.season = season + + if scorecard_url is not None: + this_result.scorecard_url = scorecard_url + + if this_result.save() == 1: + r_result = model_to_dict(this_result) + db.close() + return r_result + else: + db.close() + raise DatabaseError(f'Unable to patch result {result_id}') + + +@router.post('') +def post_results(result_list: ResultList): + new_results = [] + for x in result_list.results: + if Team.get_or_none(Team.id == x.awayteam_id) is None: + raise KeyError(f'Team ID {x.awayteam_id} not found') + if Team.get_or_none(Team.id == x.hometeam_id) is None: + raise KeyError(f'Team ID {x.hometeam_id} not found') + + new_results.append(x.dict()) + + with db.atomic(): + for batch in chunked(new_results, 15): + Result.insert_many(batch).on_conflict_replace().execute() + db.close() + + return f'Inserted {len(new_results)} results' + + +@router.delete('/{result_id}') +def delete_result(result_id: int): + this_result = Result.get_or_none(Result.id == result_id) + if not this_result: + db.close() + raise KeyError(f'Result ID {result_id} not found') + + count = this_result.delete_instance() + db.close() + + if count == 1: + return f'Result {result_id} has been deleted' + else: + raise DatabaseError(f'Result {result_id} could not be deleted') + + diff --git a/app/routers_v3/schedules.py b/app/routers_v3/schedules.py new file mode 100644 index 0000000..44d7b0f --- /dev/null +++ b/app/routers_v3/schedules.py @@ -0,0 +1,143 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from typing import List, Optional +import logging +import pydantic + +from db_engine import db, Schedule, Team, model_to_dict, DatabaseError, chunked +from dependencies import oauth2_scheme, valid_token, logging + +router = APIRouter( + prefix='/api/v3/schedules', + tags=['schedules'] +) + + +class ScheduleModel(pydantic.BaseModel): + week: int + awayteam_id: int + hometeam_id: int + gamecount: int + season: int + + +class ScheduleList(pydantic.BaseModel): + schedules: List[ScheduleModel] + + +@router.get('') +def get_schedules( + season: int, team_abbrev: list = Query(default=None), away_abbrev: list = Query(default=None), + home_abbrev: list = Query(default=None), week_start: Optional[int] = None, week_end: Optional[int] = None): + all_sched = Schedule.select_season(season) + + if team_abbrev is not None: + team_list = [] + for x in team_abbrev: + team_list.append(Team.get_season(x, season)) + all_sched = all_sched.where( + (Schedule.awayteam << team_list) | (Schedule.hometeam << team_list) + ) + + if away_abbrev is not None: + team_list = [] + for x in away_abbrev: + team_list.append(Team.get_season(x, season)) + all_sched = all_sched.where(Schedule.awayteam << team_list) + + if home_abbrev is not None: + team_list = [] + for x in home_abbrev: + team_list.append(Team.get_season(x, season)) + all_sched = all_sched.where(Schedule.hometeam << team_list) + + if week_start is not None: + all_sched = all_sched.where(Schedule.week >= week_start) + + if week_end is not None: + all_sched = all_sched.where(Schedule.week <= week_end) + + all_sched = all_sched.order_by(Schedule.id) + + return_sched = { + 'count': all_sched.count(), + 'schedules': [model_to_dict(x) for x in all_sched] + } + db.close() + return return_sched + + +@router.get('/{schedule_id}') +def get_one_schedule(schedule_id: int): + this_sched = Schedule.get_or_none(Schedule.id == schedule_id) + if this_sched is not None: + r_sched = model_to_dict(this_sched) + else: + r_sched = None + db.close() + return r_sched + + +@router.patch('/{schedule_id}') +def patch_schedule( + schedule_id: int, week: list = Query(default=None), awayteam_id: Optional[int] = None, + hometeam_id: Optional[int] = None, gamecount: Optional[int] = None, season: Optional[int] = None): + this_sched = Schedule.get_or_none(Schedule.id == schedule_id) + if this_sched is None: + raise KeyError(f'Schedule ID {schedule_id} not found') + + if week is not None: + this_sched.week = week + + if awayteam_id is not None: + this_sched.awayteam_id = awayteam_id + + if hometeam_id is not None: + this_sched.hometeam_id = hometeam_id + + if gamecount is not None: + this_sched.gamecount = gamecount + + if season is not None: + this_sched.season = season + + if this_sched.save() == 1: + r_sched = model_to_dict(this_sched) + db.close() + return r_sched + else: + db.close() + raise DatabaseError(f'Unable to patch schedule {schedule_id}') + + +@router.post('') +def post_schedules(sched_list: ScheduleList): + new_sched = [] + for x in sched_list.schedules: + if Team.get_or_none(Team.id == x.awayteam_id) is None: + raise KeyError(f'Team ID {x.awayteam_id} not found') + if Team.get_or_none(Team.id == x.hometeam_id) is None: + raise KeyError(f'Team ID {x.hometeam_id} not found') + + new_sched.append(x.dict()) + + with db.atomic(): + for batch in chunked(new_sched, 15): + Schedule.insert_many(batch).on_conflict_replace().execute() + db.close() + + return f'Inserted {len(new_sched)} schedules' + + +@router.delete('/{schedule_id}') +def delete_schedule(schedule_id: int): + this_sched = Schedule.get_or_none(Schedule.id == schedule_id) + if this_sched is None: + raise KeyError(f'Schedule ID {schedule_id} not found') + + count = this_sched.delete_instance() + db.close() + + if count == 1: + return f'Schedule {this_sched} has been deleted' + else: + raise DatabaseError(f'Schedule {this_sched} could not be deleted') From 54b04855994b732d74086c31818b8d18d8515bad Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 22 Mar 2023 15:56:10 -0500 Subject: [PATCH 03/24] /standings, /teams, /transactions added --- app/main.py | 5 +- app/routers_v3/players.py | 8 +- app/routers_v3/results.py | 40 ++++--- app/routers_v3/schedules.py | 21 ++-- app/routers_v3/standings.py | 102 +++++++++++++++++ app/routers_v3/teams.py | 200 +++++++++++++++++++++++++++++++++ app/routers_v3/transactions.py | 176 +++++++++++++++++++++++++++++ 7 files changed, 524 insertions(+), 28 deletions(-) create mode 100644 app/routers_v3/standings.py create mode 100644 app/routers_v3/teams.py create mode 100644 app/routers_v3/transactions.py diff --git a/app/main.py b/app/main.py index 1bcea59..3e1af89 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,6 @@ from fastapi import Depends, FastAPI -from routers_v3 import current, players, results, schedules +from routers_v3 import current, players, results, schedules, standings, teams, transactions app = FastAPI( responses={404: {'description': 'Not found'}} @@ -11,6 +11,9 @@ app.include_router(current.router) app.include_router(players.router) app.include_router(results.router) app.include_router(schedules.router) +app.include_router(teams.router) +app.include_router(transactions.router) +app.include_router(standings.router) # @app.get("/api") diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py index 433a968..ed77a82 100644 --- a/app/routers_v3/players.py +++ b/app/routers_v3/players.py @@ -44,7 +44,7 @@ class PlayerList(pydantic.BaseModel): @router.get('') def get_players( season: Optional[int], team_id: list = Query(default=None), pos: list = Query(default=None), - is_injured: Optional[bool] = None, sort: Optional[str] = None): + is_injured: Optional[bool] = None, short_output: Optional[bool] = False, sort: Optional[str] = None): logging.info(f'team_id: {team_id}') all_players = Player.select_season(season) @@ -74,17 +74,17 @@ def get_players( return_players = { 'count': all_players.count(), - 'players': [model_to_dict(x) for x in all_players] + 'players': [model_to_dict(x, recurse=not short_output) for x in all_players] } db.close() return return_players @router.get('/{player_id}') -def get_one_player(player_id: int): +def get_one_player(player_id: int, short_output: Optional[bool] = False): this_player = Player.get_or_none(Player.id == player_id) if this_player: - r_player = model_to_dict(this_player) + r_player = model_to_dict(this_player, recurse=not short_output) else: r_player = None db.close() diff --git a/app/routers_v3/results.py b/app/routers_v3/results.py index 818d4bc..fcae40e 100644 --- a/app/routers_v3/results.py +++ b/app/routers_v3/results.py @@ -31,7 +31,8 @@ class ResultList(pydantic.BaseModel): def get_results( season: int, team_abbrev: list = Query(default=None), week_start: list = Query(default=None), week_end: list = Query(default=None), game_num: list = Query(default=None), - away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None)): + away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None), + short_output: Optional[bool] = False): all_results = Result.select_season(season) if team_abbrev is not None: @@ -65,17 +66,17 @@ def get_results( return_results = { 'count': all_results.count(), - 'results': [model_to_dict(x) for x in all_results] + 'results': [model_to_dict(x, recurse=not short_output) for x in all_results] } db.close() return return_results @router.get('/{result_id}') -def get_one_result(result_id: int): +def get_one_result(result_id: int, short_output: Optional[bool] = False): this_result = Result.get_or_none(Result.id == result_id) if this_result is not None: - r_result = model_to_dict(this_result) + r_result = model_to_dict(this_result, recurse=not short_output) else: r_result = None db.close() @@ -86,10 +87,15 @@ def get_one_result(result_id: int): def patch_result( result_id: int, week_num: Optional[int] = None, game_num: Optional[int] = None, away_team_id: Optional[int] = None, home_team_id: Optional[int] = None, away_score: Optional[int] = None, - home_score: Optional[int] = None, season: Optional[int] = None, scorecard_url: Optional[str] = None): + home_score: Optional[int] = None, season: Optional[int] = None, scorecard_url: Optional[str] = None, + token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_player - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + this_result = Result.get_or_none(Result.id == result_id) if this_result is None: - raise KeyError(f'Result ID {result_id} not found') + raise HTTPException(status_code=404, detail=f'Result ID {result_id} not found') if week_num is not None: this_result.week = week_num @@ -121,17 +127,21 @@ def patch_result( return r_result else: db.close() - raise DatabaseError(f'Unable to patch result {result_id}') + raise HTTPException(status_code=500, detail=f'Unable to patch result {result_id}') @router.post('') -def post_results(result_list: ResultList): +def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_player - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + new_results = [] for x in result_list.results: if Team.get_or_none(Team.id == x.awayteam_id) is None: - raise KeyError(f'Team ID {x.awayteam_id} not found') + raise HTTPException(status_code=404, detail=f'Team ID {x.awayteam_id} not found') if Team.get_or_none(Team.id == x.hometeam_id) is None: - raise KeyError(f'Team ID {x.hometeam_id} not found') + raise HTTPException(status_code=404, detail=f'Team ID {x.hometeam_id} not found') new_results.append(x.dict()) @@ -144,11 +154,15 @@ def post_results(result_list: ResultList): @router.delete('/{result_id}') -def delete_result(result_id: int): +def delete_result(result_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'delete_result - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + this_result = Result.get_or_none(Result.id == result_id) if not this_result: db.close() - raise KeyError(f'Result ID {result_id} not found') + raise HTTPException(status_code=404, detail=f'Result ID {result_id} not found') count = this_result.delete_instance() db.close() @@ -156,6 +170,6 @@ def delete_result(result_id: int): if count == 1: return f'Result {result_id} has been deleted' else: - raise DatabaseError(f'Result {result_id} could not be deleted') + raise HTTPException(status_code=500, detail=f'Result {result_id} could not be deleted') diff --git a/app/routers_v3/schedules.py b/app/routers_v3/schedules.py index 44d7b0f..db3cc8a 100644 --- a/app/routers_v3/schedules.py +++ b/app/routers_v3/schedules.py @@ -27,7 +27,8 @@ class ScheduleList(pydantic.BaseModel): @router.get('') def get_schedules( season: int, team_abbrev: list = Query(default=None), away_abbrev: list = Query(default=None), - home_abbrev: list = Query(default=None), week_start: Optional[int] = None, week_end: Optional[int] = None): + home_abbrev: list = Query(default=None), week_start: Optional[int] = None, week_end: Optional[int] = None, + short_output: Optional[bool] = False): all_sched = Schedule.select_season(season) if team_abbrev is not None: @@ -60,17 +61,17 @@ def get_schedules( return_sched = { 'count': all_sched.count(), - 'schedules': [model_to_dict(x) for x in all_sched] + 'schedules': [model_to_dict(x, recurse=not short_output) for x in all_sched] } db.close() return return_sched @router.get('/{schedule_id}') -def get_one_schedule(schedule_id: int): +def get_one_schedule(schedule_id: int, short_output: Optional[bool] = False): this_sched = Schedule.get_or_none(Schedule.id == schedule_id) if this_sched is not None: - r_sched = model_to_dict(this_sched) + r_sched = model_to_dict(this_sched, recurse=not short_output) else: r_sched = None db.close() @@ -83,7 +84,7 @@ def patch_schedule( hometeam_id: Optional[int] = None, gamecount: Optional[int] = None, season: Optional[int] = None): this_sched = Schedule.get_or_none(Schedule.id == schedule_id) if this_sched is None: - raise KeyError(f'Schedule ID {schedule_id} not found') + raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found') if week is not None: this_sched.week = week @@ -106,7 +107,7 @@ def patch_schedule( return r_sched else: db.close() - raise DatabaseError(f'Unable to patch schedule {schedule_id}') + raise HTTPException(status_code=500, detail=f'Unable to patch schedule {schedule_id}') @router.post('') @@ -114,9 +115,9 @@ def post_schedules(sched_list: ScheduleList): new_sched = [] for x in sched_list.schedules: if Team.get_or_none(Team.id == x.awayteam_id) is None: - raise KeyError(f'Team ID {x.awayteam_id} not found') + raise HTTPException(status_code=404, detail=f'Team ID {x.awayteam_id} not found') if Team.get_or_none(Team.id == x.hometeam_id) is None: - raise KeyError(f'Team ID {x.hometeam_id} not found') + raise HTTPException(status_code=404, detail=f'Team ID {x.hometeam_id} not found') new_sched.append(x.dict()) @@ -132,7 +133,7 @@ def post_schedules(sched_list: ScheduleList): def delete_schedule(schedule_id: int): this_sched = Schedule.get_or_none(Schedule.id == schedule_id) if this_sched is None: - raise KeyError(f'Schedule ID {schedule_id} not found') + raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found') count = this_sched.delete_instance() db.close() @@ -140,4 +141,4 @@ def delete_schedule(schedule_id: int): if count == 1: return f'Schedule {this_sched} has been deleted' else: - raise DatabaseError(f'Schedule {this_sched} could not be deleted') + raise HTTPException(status_code=500, detail=f'Schedule {this_sched} could not be deleted') diff --git a/app/routers_v3/standings.py b/app/routers_v3/standings.py new file mode 100644 index 0000000..87a265f --- /dev/null +++ b/app/routers_v3/standings.py @@ -0,0 +1,102 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from typing import List, Optional +import logging +import pydantic + +from db_engine import db, Standings, Team, Division, model_to_dict, chunked, fn +from dependencies import oauth2_scheme, valid_token, logging + +router = APIRouter( + prefix='/api/v3/standings', + tags=['standings'] +) + + +@router.get('') +async def v1_standings( + season: int, team_abbrev: Optional[str] = None, league_abbrev: Optional[str] = None, + division_abbrev: Optional[str] = None, short_output: Optional[bool] = False): + standings = Standings.select_season(season) + + if standings.count() == 0: + db.close() + raise HTTPException(status_code=404, detail=f'No output for season {season}') + + if team_abbrev: + this_team = Team.get_season(team_abbrev, season) + if not this_team: + db.close() + raise HTTPException(status_code=404, detail=f'Team {team_abbrev} not found') + + standings = standings.where(Standings.team == this_team) + + if league_abbrev: + these_divisions = Division.select().where(fn.Lower(Division.league_abbrev) == league_abbrev.lower()) + if these_divisions.count() == 0: + db.close() + raise HTTPException(status_code=404, detail=f'No output for league {league_abbrev}') + + standings = standings.where(Standings.team.division << these_divisions) + + if division_abbrev: + this_division = Division.select().where(fn.Lower(Division.division_abbrev) == division_abbrev.lower()) + if not this_division: + db.close() + raise HTTPException(status_code=404, detail=f'No output for division {division_abbrev}') + + standings = standings.where(Standings.team.division << this_division) + + 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) + + div_teams = [team_stan for team_stan in standings] + div_teams.sort(key=lambda team: win_pct(team), reverse=True) + + return_standings = { + 'count': len(div_teams), + 'standings': [model_to_dict(x, recurse=not short_output) for x in div_teams] + } + + db.close() + return return_standings + + +@router.patch('/api/v1/standings/{stan_id}') +async def patch_standings( + stan_id, wins: Optional[int] = None, losses: Optional[int] = None, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_player - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + try: + this_stan = Standings.get_by_id(stan_id) + except Exception as e: + db.close() + raise HTTPException(status_code=404, detail=f'No team found with id {stan_id}') + + if wins: + this_stan.wins = wins + if losses: + this_stan.losses = losses + + this_stan.save() + db.close() + + return model_to_dict(this_stan) + + +@router.post('/api/v1/standings/s{season}/recalculate') +async def recalculate_standings(season: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'recalculate_standings - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + code = Standings.recalculate(season) + db.close() + if code == 69: + HTTPException(status_code=500, detail=f'Error recreating Standings rows') + raise HTTPException(status_code=200, detail=f'Just recalculated standings for season {season}') diff --git a/app/routers_v3/teams.py b/app/routers_v3/teams.py new file mode 100644 index 0000000..6bce3f6 --- /dev/null +++ b/app/routers_v3/teams.py @@ -0,0 +1,200 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from typing import List, Optional +import logging +import pydantic + +from db_engine import db, Team, Manager, Division, model_to_dict, chunked +from dependencies import oauth2_scheme, valid_token, logging + +router = APIRouter( + prefix='/api/v3/teams', + tags=['teams'] +) + + +class TeamModel(pydantic.BaseModel): + abbrev: str + sname: str + lname: str + gmid: Optional[int] = None + gmid2: Optional[int] = None + manager1_id: Optional[int] = None + manager2_id: Optional[int] = None + division_id: Optional[int] = None + stadium: Optional[str] = None + thumbnail: Optional[str] = None + color: Optional[str] = None + dice_color: Optional[str] = None + season: int + + +class TeamList(pydantic.BaseModel): + teams: List[TeamModel] + + +@router.get('') +def get_teams( + season: Optional[int] = None, owner_id: Optional[int] = None, manager_id: Optional[int] = None, + abbrev: Optional[str] = None, active_only: Optional[bool] = False, short_output: Optional[bool] = False): + if season is not None: + all_teams = Team.select_season(season) + else: + all_teams = Team.select() + + if manager_id is not None: + all_teams = all_teams.where( + (Team.manager1_id == manager_id) | (Team.manager2_id == manager_id) + ) + + if owner_id: + all_teams = all_teams.where((Team.gmid == owner_id) | (Team.gmid2 == owner_id)) + + if active_only: + all_teams = all_teams.where( + ~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL')) + ) + + if abbrev is not None: + all_teams = all_teams.where(Team.abbrev == abbrev) + + return_teams = { + 'count': all_teams.count(), + 'teams': [model_to_dict(x, recurse=not short_output) for x in all_teams] + } + db.close() + return return_teams + + +@router.get('/{team_id}') +def get_one_team(team_id: int, short_output: Optional[bool] = False): + this_team = Team.get_or_none(Team.id == team_id) + if this_team: + r_team = model_to_dict(this_team, recurse=not short_output) + else: + r_team = None + db.close() + return r_team + + +@router.get('/{team_id}') +def patch_team( + team_id: int, manager1_id: Optional[int] = None, manager2_id: Optional[int] = None, gmid: Optional[int] = None, + gmid2: Optional[int] = None, mascot: Optional[str] = None, stadium: Optional[str] = None, + thumbnail: Optional[str] = None, color: Optional[str] = None, abbrev: Optional[str] = None, + sname: Optional[str] = None, lname: Optional[str] = None, dice_color: Optional[str] = None, + division_id: Optional[int] = None): + this_team = Team.get_or_none(Team.id == team_id) + if not this_team: + return None + + if abbrev is not None: + this_team.abbrev = abbrev + if manager1_id is not None: + if manager1_id == 0: + this_team.manager1 = None + else: + this_manager = Manager.get_or_none(Manager.id == manager1_id) + if not this_manager: + db.close() + raise HTTPException(status_code=404, detail=f'Manager ID {manager1_id} not found') + this_team.manager1 = this_manager + if manager2_id is not None: + if manager2_id == 0: + this_team.manager2 = None + else: + this_manager = Manager.get_or_none(Manager.id == manager2_id) + if not this_manager: + db.close() + raise HTTPException(status_code=404, detail=f'Manager ID {manager2_id} not found') + this_team.manager2 = this_manager + if gmid is not None: + this_team.gmid = gmid + if gmid2 is not None: + if gmid2 == 0: + this_team.gmid2 = None + else: + this_team.gmid2 = gmid2 + if mascot is not None: + if mascot == 'False': + this_team.mascot = None + else: + this_team.mascot = mascot + if stadium is not None: + this_team.stadium = stadium + if thumbnail is not None: + this_team.thumbnail = thumbnail + if color is not None: + this_team.color = color + if dice_color is not None: + this_team.dice_color = dice_color + if sname is not None: + this_team.sname = sname + if lname is not None: + this_team.lname = lname + if division_id is not None: + if division_id == 0: + this_team.division = None + else: + this_division = Division.get_or_none(Division.id == division_id) + if not this_division: + db.close() + raise HTTPException(status_code=404, detail=f'Division ID {division_id} not found') + this_team.division = this_division + + if this_team.save(): + r_team = model_to_dict(this_team) + db.close() + return r_team + else: + db.close() + raise HTTPException(status_code=500, detail=f'Unable to patch team {team_id}') + + +@router.get('') +def post_team(team_list: TeamList): + new_teams = [] + for team in team_list.teams: + dupe_team = Team.get_or_none(Team.season == team.season, Team.abbrev == team.abbrev) + if dupe_team: + db.close() + raise HTTPException( + status_code=500, detail=f'Team Abbrev {team.abbrev} already in use in Season {team.season}' + ) + + if team.manager1_id and not Manager.get_or_none(Manager.id == team.manager1_id): + db.close() + raise HTTPException(status_code=404, detail=f'Manager ID {team.manager1_id} not found') + + if team.manager2_id and not Manager.get_or_none(Manager.id == team.manager2_id): + db.close() + raise HTTPException(status_code=404, detail=f'Manager ID {team.manager2_id} not found') + + if team.division_id and not Division.get_or_none(Division.id == team.division_id): + db.close() + raise HTTPException(status_code=404, detail=f'Division ID {team.division_id} not found') + + new_teams.append(team.dict()) + + with db.atomic(): + for batch in chunked(new_teams, 15): + Team.insert_many(batch).on_conflict_replace().execute() + db.close() + + return f'Inserted {len(new_teams)} teams' + + +@router.get('/{team_id}') +def delete_team(team_id: int): + this_team = Team.get_or_none(Team.id == team_id) + if not this_team: + db.close() + raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found') + + count = this_team.delete_instance() + db.close() + + if count == 1: + return f'Team {team_id} has been deleted' + else: + raise HTTPException(status_code=500, detail=f'Team {team_id} could not be deleted') + diff --git a/app/routers_v3/transactions.py b/app/routers_v3/transactions.py new file mode 100644 index 0000000..9ce6584 --- /dev/null +++ b/app/routers_v3/transactions.py @@ -0,0 +1,176 @@ +from fastapi import APIRouter, Depends, HTTPException, Query, Response +from typing import List, Optional +from pandas import DataFrame +import logging +import pydantic + +from db_engine import db, Transaction, Team, Player, model_to_dict, chunked, fn +from dependencies import oauth2_scheme, valid_token, logging + +router = APIRouter( + prefix='/api/v3/transactions', + tags=['transactions'] +) + + +class TransactionModel(pydantic.BaseModel): + week: int + player_id: int + oldteam_id: int + newteam_id: int + season: int + moveid: str + cancelled: Optional[bool] = False + frozen: Optional[bool] = False + + +class TransactionList(pydantic.BaseModel): + count: int + moves: List[TransactionModel] + + +@router.get('') +async def get_transactions( + season, team_abbrev: Optional[str] = None, week_start: Optional[int] = 0, + week_end: Optional[int] = None, cancelled: Optional[bool] = None, frozen: Optional[bool] = None, + player_name: Optional[str] = None, player_id: Optional[int] = None, move_id: Optional[str] = None, + is_trade: Optional[bool] = None, short_output: Optional[bool] = False): + if season: + transactions = Transaction.select_season(season) + else: + transactions = Transaction.select() + + # if transactions.count() == 0: + # db.close() + # raise HTTPException(status_code=404, detail=f'Season {season} not found') + + if team_abbrev: + these_teams = Team.select().where( + (Team.abbrev == team_abbrev.upper()) | + (Team.abbrev == f'{team_abbrev.upper()}MiL') | (Team.abbrev == f'{team_abbrev.upper()}IL') + ) + if these_teams.count() == 0: + db.close() + raise HTTPException(status_code=404, detail=f'Team {team_abbrev} not found') + + transactions = transactions.where( + (Transaction.newteam << these_teams) | (Transaction.oldteam << these_teams) + ) + + if week_start is not None: + transactions = transactions.where(Transaction.week >= week_start) + + if week_end is not None: + transactions = transactions.where(Transaction.week <= week_end) + + if move_id: + transactions = transactions.where(Transaction.moveid == move_id) + + if player_id or player_name: + if player_id: + try: + this_player = Player.get_by_id(player_id) + except Exception as e: + db.close() + raise HTTPException(status_code=404, detail=f'Player id {player_id} not found') + + transactions = transactions.where(Transaction.player == this_player) + else: + these_players = Player.select().where(fn.Lower(Player.name) == player_name.lower()) + print(f'these_players: {these_players}\nCount: {these_players.count()}') + if these_players.count() == 0: + db.close() + raise HTTPException(status_code=404, detail=f'Player {player_name} not found') + + transactions = transactions.where(Transaction.player << these_players) + + if cancelled: + transactions = transactions.where(Transaction.cancelled == 1) + else: + transactions = transactions.where(Transaction.cancelled == 0) + + if frozen: + transactions = transactions.where(Transaction.frozen == 1) + else: + transactions = transactions.where(Transaction.frozen == 0) + + if is_trade is not None: + raise HTTPException(status_code=501, detail='The is_trade parameter is not implemented, yet') + + transactions = transactions.order_by(-Transaction.week, Transaction.moveid) + + return_trans = { + 'count': transactions.count(), + 'transactions': [model_to_dict(x, recurse=not short_output) for x in transactions] + } + + db.close() + return return_trans + + +@router.patch('/{move_id}') +async def patch_transactions( + move_id, token: str = Depends(oauth2_scheme), frozen: Optional[bool] = None, cancelled: Optional[bool] = None): + if not valid_token(token): + logging.warning(f'patch_transactions - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + these_moves = Transaction.select().where(Transaction.moveid == move_id) + if these_moves.count() == 0: + db.close() + raise HTTPException(status_code=404, detail=f'Move ID {move_id} not found') + + if frozen is not None: + for x in these_moves: + x.frozen = frozen + x.save() + if cancelled is not None: + for x in these_moves: + x.cancelled = cancelled + x.save() + + db.close() + raise HTTPException(status_code=200, detail=f'Updated {these_moves.count()} transactions') + + +@router.post('') +async def post_transactions(moves: TransactionList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_transactions - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + all_moves = [] + + for x in moves.moves: + if Team.get_or_none(Team.id == x.oldteam_id) is None: + raise HTTPException(status_code=404, detail=f'Team ID {x.oldteam_id} not found') + if Team.get_or_none(Team.id == x.newteam_id) is None: + raise HTTPException(status_code=404, detail=f'Team ID {x.newteam_id} not found') + if Player.get_or_none(Player.id == x.player_id): + raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found') + + all_moves.append(x.dict()) + + with db.atomic(): + for batch in chunked(all_moves, 15): + Transaction.insert_many(batch).on_conflict_replace().execute() + + db.close() + raise HTTPException(status_code=200, detail=f'{len(all_moves)} transactions have been added') + + +@router.delete('/{move_id}') +async def delete_transactions(move_id, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'delete_transactions - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + delete_query = Transaction.delete().where(Transaction.moveid == move_id) + + count = delete_query.execute() + db.close() + if count > 0: + raise HTTPException(status_code=200, detail=f'Removed {count} transactions') + else: + raise HTTPException(status_code=418, detail=f'Well slap my ass and call me a teapot; ' + f'I did not delete any records') From 782804424d1c3ca476ed54b05dd684dceeeb4f94 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Fri, 5 May 2023 11:06:11 -0500 Subject: [PATCH 04/24] Fix relative package issues --- Dockerfile | 2 +- app/db_engine.py | 34 ++++- app/main.py | 3 +- app/routers_v3/battingstats.py | 264 +++++++++++++++++++++++++++++++++ app/routers_v3/current.py | 6 +- app/routers_v3/players.py | 85 +++-------- app/routers_v3/results.py | 19 ++- app/routers_v3/schedules.py | 33 +++-- app/routers_v3/standings.py | 40 ++--- app/routers_v3/teams.py | 30 ++-- app/routers_v3/transactions.py | 8 +- 11 files changed, 389 insertions(+), 135 deletions(-) create mode 100644 app/routers_v3/battingstats.py diff --git a/Dockerfile b/Dockerfile index 9ae2380..582ab12 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,4 @@ WORKDIR /usr/src/app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt -COPY ./app /app \ No newline at end of file +COPY ./app /app/app \ No newline at end of file diff --git a/app/db_engine.py b/app/db_engine.py index ae483e2..fb60341 100644 --- a/app/db_engine.py +++ b/app/db_engine.py @@ -1,8 +1,8 @@ import copy import math +from typing import Literal from peewee import * - from playhouse.shortcuts import model_to_dict db = SqliteDatabase( @@ -24,6 +24,38 @@ Per season updates: """ +WEEK_NUMS = { + 'regular': { + + } +} + + +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} + else: + 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} + else: + 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} + else: + return {'start': 1, 'end': 21} + + def win_pct(this_team_stan): if this_team_stan.wins + this_team_stan.losses == 0: return 0 diff --git a/app/main.py b/app/main.py index 3e1af89..744545a 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,6 @@ from fastapi import Depends, FastAPI -from routers_v3 import current, players, results, schedules, standings, teams, transactions +from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats app = FastAPI( responses={404: {'description': 'Not found'}} @@ -14,6 +14,7 @@ app.include_router(schedules.router) app.include_router(teams.router) app.include_router(transactions.router) app.include_router(standings.router) +app.include_router(battingstats.router) # @app.get("/api") diff --git a/app/routers_v3/battingstats.py b/app/routers_v3/battingstats.py new file mode 100644 index 0000000..fb4c763 --- /dev/null +++ b/app/routers_v3/battingstats.py @@ -0,0 +1,264 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +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 + +router = APIRouter( + prefix='/api/v3/battingstats', + tags=['battingstats'] +) + + +class BatStatModel(pydantic.BaseModel): + player_id: int + team_id: int + pos: str + pa: int + ab: int + run: int + hit: int + rbi: int + double: int + triple: int + hr: int + bb: int + so: int + hbp: int + sac: int + ibb: int + gidp: int + sb: int + cs: int + bphr: int + bpfo: int + bp1b: int + bplo: int + xba: int + xbt: int + xch: int + xhit: int + error: int + pb: int + sbc: int + csc: int + roba: int + robs: int + raa: int + rto: int + week: int + game: int + season: int + + +class BatStatList(pydantic.BaseModel): + count: int + stats: List[BatStatModel] + + +@router.get('') +async def get_batstats( + season: int, s_type: Optional[str] = None, 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']: + all_stats = BattingStat.combined_season(season) + if all_stats.count() == 0: + db.close() + return {'count': 0, 'stats': []} + else: + all_stats = BattingStat.regular_season(season) + if all_stats.count() == 0: + db.close() + return {'count': 0, 'stats': []} + if position is not None: + all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position]) + if team_abbrev is not None: + t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev]) + all_stats = all_stats.where(BattingStat.team << t_query) + if player_name is not None or player_id is not None: + if player_id: + all_stats = all_stats.where(BattingStat.player_id << player_id) + else: + p_query = Player.select_season(season).where(Player.name << player_name) + all_stats = all_stats.where(BattingStat.player << p_query) + if game_num: + all_stats = all_stats.where(BattingStat.game == game_num) + + start = 1 + end = Current.get(Current.season == season).week + if week_start is not None: + start = week_start + if week_end is not None: + end = min(week_end, end) + if start > end: + db.close() + raise HTTPException( + status_code=404, + detail=f'Start week {start} is after end week {end} - cannot pull stats' + ) + all_stats = all_stats.where( + (BattingStat.week >= start) & (BattingStat.week <= end) + ) + + if limit: + all_stats = all_stats.limit(limit) + if sort: + 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] + } + + db.close() + return return_stats + + +@router.get('/season/{season}') +async def get_seasonstats( + season: int, s_type: Literal['regular', 'post', 'total'] = 'regular', team_abbrev: list = Query(default=None), + team_id: list = Query(default=None), player_name: list = Query(default=None), + player_id: list = Query(default=None), full_player: Optional[bool] = False): + weeks = per_season_weeks(season, s_type) + all_stats = ( + BattingStat + .select(BattingStat.player, 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.week >= weeks['start']) & (BattingStat.week <= weeks['end']) & + (BattingStat.season == season)) + .order_by(BattingStat.player) + .group_by(BattingStat.player) + ) + + if team_abbrev is None and team_id is None and player_name is None and player_id is None: + raise HTTPException( + status_code=400, + detail=f'Must include team_id/team_abbrev and/or player_name/player_id' + ) + + if team_id is not None: + 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_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_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': [{ + 'player': model_to_dict(x.player, recurse=False) if full_player else x.player_id, + '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, + 'xba': x.sum_xba, + 'xbt': x.sum_xbt, + 'xch': x.sum_xch, + 'xhit': x.sum_xhit, + 'error': x.sum_error, + 'pb': x.sum_pb, + 'sbc': x.sum_sbc, + 'csc': x.sum_csc, + 'roba': x.sum_roba, + 'robs': x.sum_robs, + 'raa': x.sum_raa, + 'rto': x.sum_rto, + } for x in all_stats] + } + 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}') +async def patch_batstats(stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.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') + + BattingStat.update(**new_stats.dict()).where(BattingStat.id == stat_id).execute() + r_stat = model_to_dict(BattingStat.get_by_id(stat_id)) + db.close() + return r_stat + + +@router.post('') +async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_batstats - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + all_stats = [] + + for x in s_list.stats: + 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') + if this_player is None: + raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found') + + all_stats.append(BattingStat(**x.dict())) + + with db.atomic(): + BattingStat.bulk_create(all_stats, batch_size=15) + + # Update career stats + + return f'Added {len(all_stats)} batting lines' diff --git a/app/routers_v3/current.py b/app/routers_v3/current.py index 1c4d525..8522b2c 100644 --- a/app/routers_v3/current.py +++ b/app/routers_v3/current.py @@ -3,8 +3,8 @@ from typing import Optional import logging import pydantic -from db_engine import db, Current, model_to_dict -from dependencies import oauth2_scheme, valid_token, logging +from ..db_engine import db, Current, model_to_dict +from ..dependencies import oauth2_scheme, valid_token router = APIRouter( prefix='/api/v3/current', @@ -107,7 +107,7 @@ async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_sc @router.delete('/{current_id}') -def delete_current(current_id: int, token: str = Depends(oauth2_scheme)): +async def delete_current(current_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning(f'patch_current - Bad Token: {token}') raise HTTPException(status_code=401, detail='Unauthorized') diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py index ed77a82..18b0217 100644 --- a/app/routers_v3/players.py +++ b/app/routers_v3/players.py @@ -3,8 +3,8 @@ from typing import List, Optional import logging import pydantic -from db_engine import db, Player, model_to_dict, chunked -from dependencies import oauth2_scheme, valid_token, logging +from ..db_engine import db, Player, model_to_dict, chunked +from ..dependencies import oauth2_scheme, valid_token router = APIRouter( prefix='/api/v3/players', @@ -42,9 +42,9 @@ class PlayerList(pydantic.BaseModel): @router.get('') -def get_players( +async def get_players( season: Optional[int], team_id: list = Query(default=None), pos: list = Query(default=None), - is_injured: Optional[bool] = None, short_output: Optional[bool] = False, sort: Optional[str] = None): + is_injured: Optional[bool] = None, sort: Optional[str] = None): logging.info(f'team_id: {team_id}') all_players = Player.select_season(season) @@ -74,17 +74,17 @@ def get_players( return_players = { 'count': all_players.count(), - 'players': [model_to_dict(x, recurse=not short_output) for x in all_players] + 'players': [model_to_dict(x, recurse=False) for x in all_players] } db.close() return return_players @router.get('/{player_id}') -def get_one_player(player_id: int, short_output: Optional[bool] = False): +async def get_one_player(player_id: int, short_output: Optional[bool] = False): this_player = Player.get_or_none(Player.id == player_id) if this_player: - r_player = model_to_dict(this_player, recurse=not short_output) + r_player = model_to_dict(this_player) else: r_player = None db.close() @@ -92,74 +92,23 @@ def get_one_player(player_id: int, short_output: Optional[bool] = False): @router.patch('/{player_id}') -def patch_player( - player_id: int, wara: Optional[int] = None, image: Optional[str] = None, image2: Optional[str] = None, - team_id: Optional[int] = None, season: Optional[int] = None, last_game: Optional[str] = None, - last_game2: Optional[str] = None, il_return: Optional[str] = None, demotion_week: Optional[int] = None, - strat_code: Optional[str] = None, bbref_id: Optional[str] = None, injury_rating: Optional[str] = None, - pos_1: Optional[str] = None, pos_2: Optional[str] = None, pos_3: Optional[str] = None, - pos_4: Optional[str] = None, pos_5: Optional[str] = None, pos_6: Optional[str] = None, - pos_7: Optional[str] = None, pos_8: Optional[str] = None, token: str = Depends(oauth2_scheme)): +async def patch_player( + player_id: int, new_player: PlayerModel, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning(f'patch_player - Bad Token: {token}') raise HTTPException(status_code=401, detail='Unauthorized') - this_player = Player.get_or_none(Player.id == player_id) - if not this_player: - return None + if Player.get_or_none(Player.id == player_id) is None: + raise HTTPException(status_code=404, detail=f'Player ID {player_id} not found') - if wara is not None: - this_player.wara = wara - if image is not None: - this_player.image = image - if image2 is not None: - this_player.image2 = image2 - if team_id is not None: - this_player.team_id = team_id - if season is not None: - this_player.season = season - if last_game is not None: - this_player.last_game = last_game - if last_game2 is not None: - this_player.last_game2 = last_game2 - if il_return is not None: - this_player.il_return = il_return - if demotion_week is not None: - this_player.demotion_week = demotion_week - if strat_code is not None: - this_player.strat_code = strat_code - if bbref_id is not None: - this_player.bbref_id = bbref_id - if injury_rating is not None: - this_player.injury_rating = injury_rating - if pos_1 is not None: - this_player.pos_1 = pos_1 - if pos_2 is not None: - this_player.pos_2 = pos_2 - if pos_3 is not None: - this_player.pos_3 = pos_3 - if pos_4 is not None: - this_player.pos_4 = pos_4 - if pos_5 is not None: - this_player.pos_5 = pos_5 - if pos_6 is not None: - this_player.pos_6 = pos_6 - if pos_7 is not None: - this_player.pos_7 = pos_7 - if pos_8 is not None: - this_player.pos_8 = pos_8 - - if this_player.save() == 1: - r_player = model_to_dict(this_player) - db.close() - return r_player - else: - db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch player {player_id}') + Player.update(**new_player.dict()).where(Player.id == player_id).execute() + r_player = model_to_dict(Player.get_by_id(player_id)) + db.close() + return r_player @router.post('') -def post_players(p_list: PlayerList, token: str = Depends(oauth2_scheme)): +async def post_players(p_list: PlayerList, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning(f'post_players - Bad Token: {token}') raise HTTPException(status_code=401, detail='Unauthorized') @@ -185,7 +134,7 @@ def post_players(p_list: PlayerList, token: str = Depends(oauth2_scheme)): @router.delete('/{player_id}') -def delete_player(player_id: int, token: str = Depends(oauth2_scheme)): +async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning(f'delete_player - Bad Token: {token}') raise HTTPException(status_code=401, detail='Unauthorized') diff --git a/app/routers_v3/results.py b/app/routers_v3/results.py index fcae40e..175ebbc 100644 --- a/app/routers_v3/results.py +++ b/app/routers_v3/results.py @@ -3,8 +3,8 @@ from typing import List, Optional import logging import pydantic -from db_engine import db, Result, Team, model_to_dict, DatabaseError, chunked -from dependencies import oauth2_scheme, valid_token, logging +from ..db_engine import db, Result, Team, model_to_dict, chunked +from ..dependencies import oauth2_scheme, valid_token router = APIRouter( prefix='/api/v3/results', @@ -28,11 +28,10 @@ class ResultList(pydantic.BaseModel): @router.get('') -def get_results( +async def get_results( season: int, team_abbrev: list = Query(default=None), week_start: list = Query(default=None), week_end: list = Query(default=None), game_num: list = Query(default=None), - away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None), - short_output: Optional[bool] = False): + away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None)): all_results = Result.select_season(season) if team_abbrev is not None: @@ -66,14 +65,14 @@ def get_results( return_results = { 'count': all_results.count(), - 'results': [model_to_dict(x, recurse=not short_output) for x in all_results] + 'results': [model_to_dict(x, recurse=False) for x in all_results] } db.close() return return_results @router.get('/{result_id}') -def get_one_result(result_id: int, short_output: Optional[bool] = False): +async def get_one_result(result_id: int, short_output: Optional[bool] = False): this_result = Result.get_or_none(Result.id == result_id) if this_result is not None: r_result = model_to_dict(this_result, recurse=not short_output) @@ -84,7 +83,7 @@ def get_one_result(result_id: int, short_output: Optional[bool] = False): @router.patch('/{result_id}') -def patch_result( +async def patch_result( result_id: int, week_num: Optional[int] = None, game_num: Optional[int] = None, away_team_id: Optional[int] = None, home_team_id: Optional[int] = None, away_score: Optional[int] = None, home_score: Optional[int] = None, season: Optional[int] = None, scorecard_url: Optional[str] = None, @@ -131,7 +130,7 @@ def patch_result( @router.post('') -def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)): +async def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning(f'patch_player - Bad Token: {token}') raise HTTPException(status_code=401, detail='Unauthorized') @@ -154,7 +153,7 @@ def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)): @router.delete('/{result_id}') -def delete_result(result_id: int, token: str = Depends(oauth2_scheme)): +async def delete_result(result_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning(f'delete_result - Bad Token: {token}') raise HTTPException(status_code=401, detail='Unauthorized') diff --git a/app/routers_v3/schedules.py b/app/routers_v3/schedules.py index db3cc8a..a748761 100644 --- a/app/routers_v3/schedules.py +++ b/app/routers_v3/schedules.py @@ -3,8 +3,8 @@ from typing import List, Optional import logging import pydantic -from db_engine import db, Schedule, Team, model_to_dict, DatabaseError, chunked -from dependencies import oauth2_scheme, valid_token, logging +from ..db_engine import db, Schedule, Team, model_to_dict, chunked +from ..dependencies import oauth2_scheme, valid_token router = APIRouter( prefix='/api/v3/schedules', @@ -25,10 +25,10 @@ class ScheduleList(pydantic.BaseModel): @router.get('') -def get_schedules( +async def get_schedules( season: int, team_abbrev: list = Query(default=None), away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None), week_start: Optional[int] = None, week_end: Optional[int] = None, - short_output: Optional[bool] = False): + short_output: Optional[bool] = True): all_sched = Schedule.select_season(season) if team_abbrev is not None: @@ -68,10 +68,10 @@ def get_schedules( @router.get('/{schedule_id}') -def get_one_schedule(schedule_id: int, short_output: Optional[bool] = False): +async def get_one_schedule(schedule_id: int): this_sched = Schedule.get_or_none(Schedule.id == schedule_id) if this_sched is not None: - r_sched = model_to_dict(this_sched, recurse=not short_output) + r_sched = model_to_dict(this_sched) else: r_sched = None db.close() @@ -79,9 +79,14 @@ def get_one_schedule(schedule_id: int, short_output: Optional[bool] = False): @router.patch('/{schedule_id}') -def patch_schedule( +async def patch_schedule( schedule_id: int, week: list = Query(default=None), awayteam_id: Optional[int] = None, - hometeam_id: Optional[int] = None, gamecount: Optional[int] = None, season: Optional[int] = None): + hometeam_id: Optional[int] = None, gamecount: Optional[int] = None, season: Optional[int] = None, + token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_schedule - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + this_sched = Schedule.get_or_none(Schedule.id == schedule_id) if this_sched is None: raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found') @@ -111,7 +116,11 @@ def patch_schedule( @router.post('') -def post_schedules(sched_list: ScheduleList): +async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_schedules - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + new_sched = [] for x in sched_list.schedules: if Team.get_or_none(Team.id == x.awayteam_id) is None: @@ -130,7 +139,11 @@ def post_schedules(sched_list: ScheduleList): @router.delete('/{schedule_id}') -def delete_schedule(schedule_id: int): +async def delete_schedule(schedule_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'delete_schedule - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + this_sched = Schedule.get_or_none(Schedule.id == schedule_id) if this_sched is None: raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found') diff --git a/app/routers_v3/standings.py b/app/routers_v3/standings.py index 87a265f..470bf31 100644 --- a/app/routers_v3/standings.py +++ b/app/routers_v3/standings.py @@ -3,8 +3,8 @@ from typing import List, Optional import logging import pydantic -from db_engine import db, Standings, Team, Division, model_to_dict, chunked, fn -from dependencies import oauth2_scheme, valid_token, logging +from ..db_engine import db, Standings, Team, Division, model_to_dict, chunked, fn +from ..dependencies import oauth2_scheme, valid_token router = APIRouter( prefix='/api/v3/standings', @@ -13,8 +13,8 @@ router = APIRouter( @router.get('') -async def v1_standings( - season: int, team_abbrev: Optional[str] = None, league_abbrev: Optional[str] = None, +async def get_standings( + season: int, team_abbrev: list = Query(default=None), league_abbrev: Optional[str] = None, division_abbrev: Optional[str] = None, short_output: Optional[bool] = False): standings = Standings.select_season(season) @@ -22,29 +22,17 @@ async def v1_standings( db.close() raise HTTPException(status_code=404, detail=f'No output for season {season}') - if team_abbrev: - this_team = Team.get_season(team_abbrev, season) - if not this_team: - db.close() - raise HTTPException(status_code=404, detail=f'Team {team_abbrev} not found') - - standings = standings.where(Standings.team == this_team) + if team_abbrev is not None: + t_query = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev]) + standings = standings.where(Standings.team << t_query) if league_abbrev: - these_divisions = Division.select().where(fn.Lower(Division.league_abbrev) == league_abbrev.lower()) - if these_divisions.count() == 0: - db.close() - raise HTTPException(status_code=404, detail=f'No output for league {league_abbrev}') - - standings = standings.where(Standings.team.division << these_divisions) + l_query = Division.select().where(fn.Lower(Division.league_abbrev) << league_abbrev.lower()) + standings = standings.where(Standings.team.division << l_query) if division_abbrev: - this_division = Division.select().where(fn.Lower(Division.division_abbrev) == division_abbrev.lower()) - if not this_division: - db.close() - raise HTTPException(status_code=404, detail=f'No output for division {division_abbrev}') - - standings = standings.where(Standings.team.division << this_division) + d_query = Division.select().where(fn.Lower(Division.division_abbrev) << division_abbrev.lower()) + standings = standings.where(Standings.team.division << d_query) def win_pct(this_team_stan): if this_team_stan.wins + this_team_stan.losses == 0: @@ -65,11 +53,11 @@ async def v1_standings( return return_standings -@router.patch('/api/v1/standings/{stan_id}') +@router.patch('/{stan_id}') async def patch_standings( stan_id, wins: Optional[int] = None, losses: Optional[int] = None, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logging.warning(f'patch_player - Bad Token: {token}') + logging.warning(f'patch_standings - Bad Token: {token}') raise HTTPException(status_code=401, detail='Unauthorized') try: @@ -89,7 +77,7 @@ async def patch_standings( return model_to_dict(this_stan) -@router.post('/api/v1/standings/s{season}/recalculate') +@router.post('/s{season}/recalculate') async def recalculate_standings(season: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning(f'recalculate_standings - Bad Token: {token}') diff --git a/app/routers_v3/teams.py b/app/routers_v3/teams.py index 6bce3f6..25362f0 100644 --- a/app/routers_v3/teams.py +++ b/app/routers_v3/teams.py @@ -3,8 +3,8 @@ from typing import List, Optional import logging import pydantic -from db_engine import db, Team, Manager, Division, model_to_dict, chunked -from dependencies import oauth2_scheme, valid_token, logging +from ..db_engine import db, Team, Manager, Division, model_to_dict, chunked +from ..dependencies import oauth2_scheme, valid_token router = APIRouter( prefix='/api/v3/teams', @@ -33,7 +33,7 @@ class TeamList(pydantic.BaseModel): @router.get('') -def get_teams( +async def get_teams( season: Optional[int] = None, owner_id: Optional[int] = None, manager_id: Optional[int] = None, abbrev: Optional[str] = None, active_only: Optional[bool] = False, short_output: Optional[bool] = False): if season is not None: @@ -66,10 +66,10 @@ def get_teams( @router.get('/{team_id}') -def get_one_team(team_id: int, short_output: Optional[bool] = False): +async def get_one_team(team_id: int): this_team = Team.get_or_none(Team.id == team_id) if this_team: - r_team = model_to_dict(this_team, recurse=not short_output) + r_team = model_to_dict(this_team) else: r_team = None db.close() @@ -77,12 +77,16 @@ def get_one_team(team_id: int, short_output: Optional[bool] = False): @router.get('/{team_id}') -def patch_team( +async def patch_team( team_id: int, manager1_id: Optional[int] = None, manager2_id: Optional[int] = None, gmid: Optional[int] = None, gmid2: Optional[int] = None, mascot: Optional[str] = None, stadium: Optional[str] = None, thumbnail: Optional[str] = None, color: Optional[str] = None, abbrev: Optional[str] = None, sname: Optional[str] = None, lname: Optional[str] = None, dice_color: Optional[str] = None, - division_id: Optional[int] = None): + division_id: Optional[int] = None, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_team - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + this_team = Team.get_or_none(Team.id == team_id) if not this_team: return None @@ -151,7 +155,11 @@ def patch_team( @router.get('') -def post_team(team_list: TeamList): +async def post_team(team_list: TeamList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_team - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + new_teams = [] for team in team_list.teams: dupe_team = Team.get_or_none(Team.season == team.season, Team.abbrev == team.abbrev) @@ -184,7 +192,11 @@ def post_team(team_list: TeamList): @router.get('/{team_id}') -def delete_team(team_id: int): +async def delete_team(team_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'delete_team - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + this_team = Team.get_or_none(Team.id == team_id) if not this_team: db.close() diff --git a/app/routers_v3/transactions.py b/app/routers_v3/transactions.py index 9ce6584..8ad792d 100644 --- a/app/routers_v3/transactions.py +++ b/app/routers_v3/transactions.py @@ -4,8 +4,8 @@ from pandas import DataFrame import logging import pydantic -from db_engine import db, Transaction, Team, Player, model_to_dict, chunked, fn -from dependencies import oauth2_scheme, valid_token, logging +from ..db_engine import db, Transaction, Team, Player, model_to_dict, chunked, fn +from ..dependencies import oauth2_scheme, valid_token router = APIRouter( prefix='/api/v3/transactions', @@ -40,10 +40,6 @@ async def get_transactions( else: transactions = Transaction.select() - # if transactions.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'Season {season} not found') - if team_abbrev: these_teams = Team.select().where( (Team.abbrev == team_abbrev.upper()) | From 3bd4150da5717b16d6a4c183ae664391d69da89a Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Mon, 8 May 2023 09:02:41 -0500 Subject: [PATCH 05/24] Batstats and pitchstats --- app/main.py | 4 +- app/routers_v3/battingstats.py | 80 +++++++++++------------ app/routers_v3/pitchingstats.py | 108 ++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 40 deletions(-) create mode 100644 app/routers_v3/pitchingstats.py diff --git a/app/main.py b/app/main.py index 744545a..1292c13 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,7 @@ from fastapi import Depends, FastAPI -from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats +from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, \ + pitchingstats app = FastAPI( responses={404: {'description': 'Not found'}} @@ -15,6 +16,7 @@ app.include_router(teams.router) app.include_router(transactions.router) app.include_router(standings.router) app.include_router(battingstats.router) +app.include_router(pitchingstats.router) # @app.get("/api") diff --git a/app/routers_v3/battingstats.py b/app/routers_v3/battingstats.py index fb4c763..12c20ee 100644 --- a/app/routers_v3/battingstats.py +++ b/app/routers_v3/battingstats.py @@ -16,38 +16,38 @@ class BatStatModel(pydantic.BaseModel): player_id: int team_id: int pos: str - pa: int - ab: int - run: int - hit: int - rbi: int - double: int - triple: int - hr: int - bb: int - so: int - hbp: int - sac: int - ibb: int - gidp: int - sb: int - cs: int - bphr: int - bpfo: int - bp1b: int - bplo: int - xba: int - xbt: int - xch: int - xhit: int - error: int - pb: int - sbc: int - csc: int - roba: int - robs: int - raa: int - rto: int + pa: Optional[int] = 0 + ab: Optional[int] = 0 + run: Optional[int] = 0 + hit: Optional[int] = 0 + rbi: Optional[int] = 0 + double: Optional[int] = 0 + triple: Optional[int] = 0 + hr: Optional[int] = 0 + bb: Optional[int] = 0 + so: Optional[int] = 0 + hbp: Optional[int] = 0 + sac: Optional[int] = 0 + ibb: Optional[int] = 0 + gidp: Optional[int] = 0 + sb: Optional[int] = 0 + cs: Optional[int] = 0 + bphr: Optional[int] = 0 + bpfo: Optional[int] = 0 + bp1b: Optional[int] = 0 + bplo: Optional[int] = 0 + xba: Optional[int] = 0 + xbt: Optional[int] = 0 + xch: Optional[int] = 0 + xhit: Optional[int] = 0 + error: Optional[int] = 0 + pb: Optional[int] = 0 + sbc: Optional[int] = 0 + csc: Optional[int] = 0 + roba: Optional[int] = 0 + robs: Optional[int] = 0 + raa: Optional[int] = 0 + rto: Optional[int] = 0 week: int game: int season: int @@ -60,7 +60,7 @@ class BatStatList(pydantic.BaseModel): @router.get('') async def get_batstats( - season: int, s_type: Optional[str] = None, team_abbrev: list = Query(default=None), + 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, @@ -80,6 +80,7 @@ async def get_batstats( if all_stats.count() == 0: db.close() return {'count': 0, 'stats': []} + if position is not None: all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position]) if team_abbrev is not None: @@ -89,7 +90,7 @@ 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(Player.name << 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) @@ -217,10 +218,10 @@ async def get_seasonstats( 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.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}') @@ -257,7 +258,8 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme) all_stats.append(BattingStat(**x.dict())) with db.atomic(): - BattingStat.bulk_create(all_stats, batch_size=15) + for batch in chunked(all_stats, 15): + BattingStat.insert_many(batch).on_conflict_replace().execute() # Update career stats diff --git a/app/routers_v3/pitchingstats.py b/app/routers_v3/pitchingstats.py new file mode 100644 index 0000000..41f7316 --- /dev/null +++ b/app/routers_v3/pitchingstats.py @@ -0,0 +1,108 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from typing import List, Optional, Literal +import logging +import pydantic + +from ..db_engine import db, PitchingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks +from ..dependencies import oauth2_scheme, valid_token + +router = APIRouter( + prefix='/api/v3/pitchingstats', + tags=['pitchingstats'] +) + + +class PitStatModel(pydantic.BaseModel): + player_id: int + team_id: int + ip: Optional[float] = 0.0 + hit: Optional[int] = 0 + run: Optional[int] = 0 + erun: Optional[int] = 0 + so: Optional[int] = 0 + bb: Optional[int] = 0 + hbp: Optional[int] = 0 + wp: Optional[int] = 0 + balk: Optional[int] = 0 + hr: Optional[int] = 0 + gs: Optional[int] = 0 + win: Optional[int] = 0 + loss: Optional[int] = 0 + hold: Optional[int] = 0 + sv: Optional[int] = 0 + bsv: Optional[int] = 0 + ir: Optional[int] = 0 + irs: Optional[int] = 0 + week: int + game: int + season: int + + +class PitStatList(pydantic.BaseModel): + count: int + stats: List[PitStatModel] + + +@router.get('') +async def get_pitstats( + 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), + limit: Optional[int] = None, sort: Optional[str] = None, short_output: Optional[bool] = True): + if 'post' in s_type.lower(): + all_stats = PitchingStat.post_season(season) + if all_stats.count() == 0: + db.close() + return {'count': 0, 'stats': []} + elif s_type.lower() in ['combined', 'total', 'all']: + all_stats = PitchingStat.combined_season(season) + if all_stats.count() == 0: + db.close() + return {'count': 0, 'stats': []} + else: + all_stats = PitchingStat.regular_season(season) + if all_stats.count() == 0: + db.close() + return {'count': 0, 'stats': []} + + if team_abbrev is not None: + t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev]) + all_stats = all_stats.where(PitchingStat.team << t_query) + if player_name is not None or player_id is not None: + if player_id: + all_stats = all_stats.where(PitchingStat.player_id << player_id) + else: + p_query = Player.select_season(season).where(fn.Lower(Player.name) << [x.lower() for x in player_name]) + all_stats = all_stats.where(PitchingStat.player << p_query) + if game_num: + all_stats = all_stats.where(PitchingStat.game == game_num) + + start = 1 + end = Current.get(Current.season == season).week + if week_start is not None: + start = week_start + if week_end is not None: + end = min(week_end, end) + if start > end: + db.close() + raise HTTPException( + status_code=404, + detail=f'Start week {start} is after end week {end} - cannot pull stats' + ) + all_stats = all_stats.where( + (PitchingStat.week >= start) & (PitchingStat.week <= end) + ) + + if limit: + all_stats = all_stats.limit(limit) + if sort: + if sort == 'newest': + all_stats = all_stats.order_by(-PitchingStat.week, -PitchingStat.game) + + return_stats = { + 'count': all_stats.count(), + 'stats': [model_to_dict(x, recurse=not short_output) for x in all_stats] + } + + db.close() + return return_stats From 28e02136ddbd38ed80f21c68555f74e1bdc886b9 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 10 May 2023 13:26:16 -0500 Subject: [PATCH 06/24] Update main.py Bug fix: patching player to 0 wara --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index b8f13d4..24dd95c 100644 --- a/main.py +++ b/main.py @@ -1539,7 +1539,7 @@ async def v1_players_patch( raise HTTPException(status_code=404, detail=f'Team id {team_id} not found') if name: this_player.name = name - if wara: + if wara is not None: this_player.wara = wara if image: this_player.image = image From d5cfaaa475aa49dabe03f76f91c5d24abc483009 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 10 May 2023 16:31:52 -0500 Subject: [PATCH 07/24] Batting, Pitching, and Fielding stats complete --- app/main.py | 3 +- app/routers_v3/battingstats.py | 9 +- app/routers_v3/fieldingstats.py | 152 ++++++++++++++++++++++++++++++++ app/routers_v3/pitchingstats.py | 113 ++++++++++++++++++++++++ 4 files changed, 272 insertions(+), 5 deletions(-) create mode 100644 app/routers_v3/fieldingstats.py diff --git a/app/main.py b/app/main.py index 1292c13..fc8edd1 100644 --- a/app/main.py +++ b/app/main.py @@ -1,7 +1,7 @@ from fastapi import Depends, FastAPI from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, \ - pitchingstats + pitchingstats, fieldingstats app = FastAPI( responses={404: {'description': 'Not found'}} @@ -17,6 +17,7 @@ app.include_router(transactions.router) app.include_router(standings.router) app.include_router(battingstats.router) app.include_router(pitchingstats.router) +app.include_router(fieldingstats.router) # @app.get("/api") diff --git a/app/routers_v3/battingstats.py b/app/routers_v3/battingstats.py index 12c20ee..54c6979 100644 --- a/app/routers_v3/battingstats.py +++ b/app/routers_v3/battingstats.py @@ -130,7 +130,7 @@ async def get_batstats( async def get_seasonstats( season: int, s_type: Literal['regular', 'post', 'total'] = 'regular', team_abbrev: list = Query(default=None), team_id: list = Query(default=None), player_name: list = Query(default=None), - player_id: list = Query(default=None), full_player: Optional[bool] = False): + player_id: list = Query(default=None), short_output: Optional[bool] = False): weeks = per_season_weeks(season, s_type) all_stats = ( BattingStat @@ -177,9 +177,9 @@ async def get_seasonstats( all_stats = all_stats.where(BattingStat.player << all_players) return_stats = { - 'count': all_stats.count(), + 'count': sum(1 for i in all_stats if i.sum_pa > 0), 'stats': [{ - 'player': model_to_dict(x.player, recurse=False) if full_player else x.player_id, + 'player': x.player_id if short_output else model_to_dict(x.player, recurse=False), 'pa': x.sum_pa, 'ab': x.sum_ab, 'run': x.sum_run, @@ -212,7 +212,7 @@ async def get_seasonstats( 'robs': x.sum_robs, 'raa': x.sum_raa, 'rto': x.sum_rto, - } for x in all_stats] + } for x in all_stats if x.sum_pa > 0] } db.close() return return_stats @@ -263,4 +263,5 @@ 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' diff --git a/app/routers_v3/fieldingstats.py b/app/routers_v3/fieldingstats.py new file mode 100644 index 0000000..16eef00 --- /dev/null +++ b/app/routers_v3/fieldingstats.py @@ -0,0 +1,152 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +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 + +router = APIRouter( + prefix='/api/v3/fieldingstats', + tags=['fieldingstats'] +) + + +@router.get('') +async def get_fieldingstats( + 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']: + all_stats = BattingStat.combined_season(season) + if all_stats.count() == 0: + db.close() + return {'count': 0, 'stats': []} + else: + all_stats = BattingStat.regular_season(season) + if all_stats.count() == 0: + db.close() + return {'count': 0, 'stats': []} + + all_stats = all_stats.where( + (BattingStat.xch > 0) | (BattingStat.pb > 0) | (BattingStat.sbc > 0) + ) + + if position is not None: + all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position]) + if team_abbrev is not None: + t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev]) + all_stats = all_stats.where(BattingStat.team << t_query) + if player_name is not None or player_id is not None: + 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]) + all_stats = all_stats.where(BattingStat.player << p_query) + if game_num: + all_stats = all_stats.where(BattingStat.game == game_num) + + start = 1 + end = Current.get(Current.season == season).week + if week_start is not None: + start = week_start + if week_end is not None: + end = min(week_end, end) + if start > end: + db.close() + raise HTTPException( + status_code=404, + detail=f'Start week {start} is after end week {end} - cannot pull stats' + ) + all_stats = all_stats.where( + (BattingStat.week >= start) & (BattingStat.week <= end) + ) + + if limit: + all_stats = all_stats.limit(limit) + if sort: + if sort == 'newest': + all_stats = all_stats.order_by(-BattingStat.week, -BattingStat.game) + + return_stats = { + 'count': all_stats.count(), + 'stats': [{ + 'player': x.player_id if short_output else model_to_dict(x.player, recurse=False), + 'team': x.team_id if short_output else model_to_dict(x.team, recurse=False), + 'pos': x.pos, + 'xch': x.xch, + 'xhit': x.xhit, + 'error': x.error, + 'pb': x.pb, + 'sbc': x.sbc, + 'csc': x.csc, + 'week': x.week, + 'game': x.game, + 'season': x.season + } for x in all_stats] + } + + db.close() + return return_stats + + +@router.get('/season/{season}') +async def get_stasonstats( + season: int, s_type: Literal['regular', 'post', 'total'] = 'regular', team_abbrev: list = Query(default=None), + team_id: list = Query(default=None), player_name: list = Query(default=None), + player_id: list = Query(default=None), short_output: Optional[bool] = True): + weeks = per_season_weeks(season, s_type) + all_stats = ( + BattingStat + .select(BattingStat.player, BattingStat.pos, 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')) + .where((BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) & + (BattingStat.season == season)) + .order_by(BattingStat.player) + .group_by(BattingStat.player, BattingStat.pos) + ) + + if team_abbrev is None and team_id is None and player_name is None and player_id is None: + raise HTTPException( + status_code=400, + detail=f'Must include team_id/team_abbrev and/or player_name/player_id' + ) + + if team_id is not None: + 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_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_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': sum(1 for i in all_stats if i.sum_xch + i.sum_sbc > 0), + 'stats': [{ + 'player': x.player_id if short_output else model_to_dict(x.player, recurse=False), + 'pos': x.pos, + 'xch': x.sum_xch, + 'xhit': x.sum_xhit, + 'error': x.sum_error, + 'pb': x.sum_pb, + 'sbc': x.sum_sbc, + 'csc': x.sum_csc + } for x in all_stats if x.sum_xch + x.sum_sbc > 0] + } + db.close() + return return_stats diff --git a/app/routers_v3/pitchingstats.py b/app/routers_v3/pitchingstats.py index 41f7316..d9cf992 100644 --- a/app/routers_v3/pitchingstats.py +++ b/app/routers_v3/pitchingstats.py @@ -106,3 +106,116 @@ async def get_pitstats( db.close() return return_stats + + +@router.get('/season/{season}') +async def get_seasonstats( + season: int, s_type: Literal['regular', 'post', 'total'] = 'regular', team_abbrev: list = Query(default=None), + team_id: list = Query(default=None), player_name: list = Query(default=None), + player_id: list = Query(default=None), full_player: Optional[bool] = False): + weeks = per_season_weeks(season, s_type) + all_stats = ( + PitchingStat + .select(PitchingStat.player, fn.SUM(PitchingStat.ip).alias('sum_ip'), + fn.SUM(PitchingStat.hit).alias('sum_hit'), fn.SUM(PitchingStat.run).alias('sum_run'), + fn.SUM(PitchingStat.erun).alias('sum_erun'), fn.SUM(PitchingStat.so).alias('sum_so'), + fn.SUM(PitchingStat.bb).alias('sum_bb'), fn.SUM(PitchingStat.hbp).alias('sum_hbp'), + fn.SUM(PitchingStat.wp).alias('sum_wp'), fn.SUM(PitchingStat.balk).alias('sum_balk'), + fn.SUM(PitchingStat.hr).alias('sum_hr'), fn.SUM(PitchingStat.ir).alias('sum_ir'), + fn.SUM(PitchingStat.win).alias('sum_win'), fn.SUM(PitchingStat.loss).alias('sum_loss'), + fn.SUM(PitchingStat.hold).alias('sum_hold'), fn.SUM(PitchingStat.sv).alias('sum_sv'), + fn.SUM(PitchingStat.bsv).alias('sum_bsv'), fn.SUM(PitchingStat.irs).alias('sum_irs'), + fn.SUM(PitchingStat.gs).alias('sum_gs')) + .where((PitchingStat.week >= weeks['start']) & (PitchingStat.week <= weeks['end']) & + (PitchingStat.season == season)) + .order_by(PitchingStat.player) + .group_by(PitchingStat.player) + ) + + if team_abbrev is None and team_id is None and player_name is None and player_id is None: + raise HTTPException( + status_code=400, + detail=f'Must include team_id/team_abbrev and/or player_name/player_id' + ) + + if team_id is not None: + all_teams = Team.select().where(Team.id << team_id) + all_stats = all_stats.where(PitchingStat.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_stats = all_stats.where(PitchingStat.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_stats = all_stats.where(PitchingStat.player << all_players) + elif player_id is not None: + all_players = Player.select().where(Player.id << player_id) + all_stats = all_stats.where(PitchingStat.player << all_players) + + return_stats = { + 'count': all_stats.count(), + 'stats': [{ + 'player': model_to_dict(x.player, recurse=False) if full_player else x.player_id, + 'ip': x.sum_ip, + 'hit': x.sum_hit, + 'run': x.sum_run, + 'erun': x.sum_erun, + 'so': x.sum_so, + 'bb': x.sum_bb, + 'hbp': x.sum_hbp, + 'wp': x.sum_wp, + 'balk': x.sum_balk, + 'hr': x.sum_hr, + 'ir': x.sum_ir, + 'irs': x.sum_irs, + 'gs': x.sum_gs, + 'win': x.sum_win, + 'loss': x.sum_loss, + 'hold': x.sum_hold, + 'sv': x.sum_sv, + 'bsv': x.sum_bsv + } for x in all_stats] + } + db.close() + return return_stats + + +@router.patch('/{stat_id}') +async def patch_pitstats(stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_pitstats - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + if PitchingStat.get_or_none(PitchingStat.id == stat_id) is None: + raise HTTPException(status_code=404, detail=f'Stat ID {stat_id} not found') + + PitchingStat.update(**new_stats.dict()).where(PitchingStat.id == stat_id).execute() + r_stat = model_to_dict(PitchingStat.get_by_id(stat_id)) + db.close() + return r_stat + + +@router.post('') +async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_pitstats - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + all_stats = [] + + for x in s_list.stats: + 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') + if this_player is None: + raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found') + + all_stats.append(PitchingStat(**x.dict())) + + with db.atomic(): + for batch in chunked(all_stats, 15): + PitchingStat.insert_many(batch).on_conflict_replace().execute() + + db.close() + return f'Added {len(all_stats)} batting lines' From 63491a7b627eb27d0c10927849c8b647c0cf2dea Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Fri, 12 May 2023 15:28:34 -0500 Subject: [PATCH 08/24] Added /draftpicks --- app/main.py | 18 +++- app/routers_v3/draftpicks.py | 156 +++++++++++++++++++++++++++++++++++ app/routers_v3/players.py | 4 +- 3 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 app/routers_v3/draftpicks.py diff --git a/app/main.py b/app/main.py index fc8edd1..c9c1f31 100644 --- a/app/main.py +++ b/app/main.py @@ -1,7 +1,9 @@ -from fastapi import Depends, FastAPI +from fastapi import Depends, FastAPI, Request +# 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 + pitchingstats, fieldingstats, draftpicks app = FastAPI( responses={404: {'description': 'Not found'}} @@ -18,6 +20,18 @@ app.include_router(standings.router) app.include_router(battingstats.router) app.include_router(pitchingstats.router) app.include_router(fieldingstats.router) +app.include_router(draftpicks.router) + + +# @app.get("/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') +# +# +# @app.get("/openapi.json", include_in_schema=False) +# async def openapi(): +# return get_openapi(title='SBa Dev API', version=f'0.1.1', routes=app.routes) # @app.get("/api") diff --git a/app/routers_v3/draftpicks.py b/app/routers_v3/draftpicks.py new file mode 100644 index 0000000..78fe623 --- /dev/null +++ b/app/routers_v3/draftpicks.py @@ -0,0 +1,156 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from typing import List, Optional +import logging +import pydantic + +from ..db_engine import db, DraftPick, Team, model_to_dict, chunked +from ..dependencies import oauth2_scheme, valid_token + +router = APIRouter( + prefix='/api/v3/draftpicks', + tags=['draftpicks'] +) + + +class DraftPickModel(pydantic.BaseModel): + overall: Optional[int] = None + round: int + origowner_id: int + owner_id: Optional[int] = None + season: int + player_id: Optional[int] = None + + +class DraftPickList(pydantic.BaseModel): + picks: List[DraftPickModel] + + +@router.get('') +async def get_picks( + season: int, owner_team_abbrev: list = Query(default=None), orig_team_abbrev: list = Query(default=None), + owner_team_id: list = Query(default=None), orig_team_id: list = Query(default=None), + pick_round_start: Optional[int] = None, pick_round_end: Optional[int] = None, traded: Optional[bool] = None, + overall: Optional[int] = None, overall_start: Optional[int] = None, overall_end: Optional[int] = None, + short_output: Optional[bool] = True): + all_picks = DraftPick.select().where(DraftPick.season == season) + + if owner_team_abbrev is not None: + team_list = [] + for x in owner_team_abbrev: + team_list.append(Team.get_season(x, season)) + all_picks = all_picks.where( + (DraftPick.owner << team_list) | (DraftPick.owner << team_list) + ) + + if orig_team_abbrev is not None: + team_list = [] + for x in orig_team_abbrev: + team_list.append(Team.get_season(x, season)) + all_picks = all_picks.where( + (DraftPick.origowner << team_list) | (DraftPick.origowner << team_list) + ) + + if owner_team_id is not None: + all_picks = all_picks.where( + (DraftPick.owner_id << owner_team_id) | (DraftPick.owner_id << owner_team_id) + ) + + if orig_team_id is not None: + all_picks = all_picks.where( + (DraftPick.origowner_id << orig_team_id) | (DraftPick.origowner_id << orig_team_id) + ) + + if pick_round_start is not None and pick_round_end is not None and pick_round_end < pick_round_start: + raise HTTPException(status_code=400, detail=f'pick_round_end must be greater than or equal to pick_round_start') + + if pick_round_start is not None: + all_picks = all_picks.where(DraftPick.round >= pick_round_start) + if pick_round_end is not None: + all_picks = all_picks.where(DraftPick.round <= pick_round_end) + if traded is not None: + all_picks = all_picks.where(DraftPick.origowner != DraftPick.owner) + if overall is not None: + all_picks = all_picks.where(DraftPick.overall == overall) + if overall_start is not None: + all_picks = all_picks.where(DraftPick.overall >= overall_start) + if overall_end is not None: + all_picks = all_picks.where(DraftPick.overall <= overall_end) + + return_picks = {'count': all_picks.count(), 'picks': []} + for line in all_picks: + return_picks['picks'].append(model_to_dict(line, recurse=not short_output)) + + db.close() + return return_picks + + +@router.get('/{pick_id}') +async def get_one_pick(pick_id: int, short_output: Optional[bool] = False): + this_pick = DraftPick.get_or_none(DraftPick.id == pick_id) + if this_pick is not None: + r_pick = model_to_dict(this_pick, recurse=not short_output) + else: + raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found') + db.close() + return r_pick + + +@router.patch('/{pick_id}') +async def patch_pick(pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_pick - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + if DraftPick.get_or_none(DraftPick.id == pick_id) is None: + raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found') + + DraftPick.update(**new_pick.dict()).where(DraftPick.id == pick_id).execute() + r_pick = model_to_dict(DraftPick.get_by_id(pick_id)) + db.close() + return r_pick + + +@router.post('') +async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_picks - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + new_picks = [] + for pick in p_list.picks: + dupe = DraftPick.get_or_none(DraftPick.season == pick.season, DraftPick.overall == pick.overall) + if dupe: + db.close() + raise HTTPException( + status_code=500, + detail=f'Pick # {pick.overall} already exists for season {pick.season}' + ) + + new_picks.append(pick.dict()) + + with db.atomic(): + for batch in chunked(new_picks, 15): + DraftPick.insert_many(batch).on_conflict_replace().execute() + db.close() + + return f'Inserted {len(new_picks)} picks' + + +@router.delete('/{pick_id}') +async def delete_pick(pick_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'delete_pick - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_pick = DraftPick.get_or_none(DraftPick.id == pick_id) + if this_pick is None: + raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found') + + count = this_pick.delete_instance() + db.close() + + if count == 1: + return f'Draft pick {pick_id} has been deleted' + else: + raise HTTPException(status_code=500, detail=f'Draft pick {pick_id} could not be deleted') + diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py index 18b0217..99c5354 100644 --- a/app/routers_v3/players.py +++ b/app/routers_v3/players.py @@ -45,8 +45,6 @@ class PlayerList(pydantic.BaseModel): async def get_players( season: Optional[int], team_id: list = Query(default=None), pos: list = Query(default=None), is_injured: Optional[bool] = None, sort: Optional[str] = None): - logging.info(f'team_id: {team_id}') - all_players = Player.select_season(season) if team_id is not None: @@ -84,7 +82,7 @@ async def get_players( async def get_one_player(player_id: int, short_output: Optional[bool] = False): this_player = Player.get_or_none(Player.id == player_id) if this_player: - r_player = model_to_dict(this_player) + r_player = model_to_dict(this_player, recurse=not short_output) else: r_player = None db.close() From 9bfa9c86dcf7d99de7a7419520ec149cff0249f2 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Sat, 13 May 2023 00:16:09 -0500 Subject: [PATCH 09/24] Added /draftlist --- app/main.py | 3 +- app/routers_v3/draftlist.py | 82 +++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 app/routers_v3/draftlist.py diff --git a/app/main.py b/app/main.py index c9c1f31..b6c90b0 100644 --- a/app/main.py +++ b/app/main.py @@ -3,7 +3,7 @@ from fastapi import Depends, FastAPI, Request # from fastapi.openapi.utils import get_openapi from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, \ - pitchingstats, fieldingstats, draftpicks + pitchingstats, fieldingstats, draftpicks, draftlist app = FastAPI( responses={404: {'description': 'Not found'}} @@ -21,6 +21,7 @@ app.include_router(battingstats.router) app.include_router(pitchingstats.router) app.include_router(fieldingstats.router) app.include_router(draftpicks.router) +app.include_router(draftlist.router) # @app.get("/docs", include_in_schema=False) diff --git a/app/routers_v3/draftlist.py b/app/routers_v3/draftlist.py new file mode 100644 index 0000000..a66f963 --- /dev/null +++ b/app/routers_v3/draftlist.py @@ -0,0 +1,82 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from typing import List, Optional +import logging +import pydantic + +from ..db_engine import db, DraftList, Team, model_to_dict, chunked +from ..dependencies import oauth2_scheme, valid_token + +router = APIRouter( + prefix='/api/v3/draftlist', + tags=['draftlist'] +) + + +class DraftListModel(pydantic.BaseModel): + season: int + team_id: int + rank: int + player_id: int + + +class DraftListList(pydantic.BaseModel): + count: int + draft_list: List[DraftListModel] + + +@router.get('') +async def get_draftlist(season: Optional[int], team_id: list = Query(default=None)): + all_list = DraftList.select() + + if season is not None: + all_list = all_list.where(DraftList.season == season) + if team_id is not None: + all_list = all_list.where(DraftList.team_id << team_id) + + r_list = { + 'count': all_list.count(), + 'picks': [model_to_dict(x) for x in all_list] + } + + db.close() + return r_list + + +@router.get('/team/{team_id}') +async def get_team_draftlist(team_id: int): + this_team = Team.get_or_none(Team.id == team_id) + if this_team is None: + raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found') + + this_list = DraftList.select().where(DraftList.team == this_team) + r_list = { + 'count': this_list.count(), + 'picks': [model_to_dict(x) for x in this_list] + } + + db.close() + return r_list + + +@router.post('') +async def post_draftlist(draft_list: DraftListList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_draftlist - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + new_list = [] + this_team = Team.get_or_none(Team.id == draft_list.draft_list[0].team_id) + if this_team is None: + raise HTTPException(status_code=404, detail=f'Team ID {draft_list.draft_list[0].team_id} not found') + + DraftList.delete().where(DraftList.team == this_team).execute() + + for x in draft_list.draft_list: + new_list.append(x.dict()) + + with db.atomic(): + for batch in chunked(new_list, 15): + DraftList.insert_many(batch).on_conflict_replace().execute() + + db.close() + return f'Inserted {len(new_list)} list values' From fb29f8bff321a20c1a7379b4770e08b3a15bcc85 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Fri, 26 May 2023 12:48:40 -0500 Subject: [PATCH 10/24] Update with Sheets testing Changed to /battingstats/totals Updated some parameters to queries --- app/main.py | 12 +++++++ app/routers_v3/battingstats.py | 66 +++++++++++++++++++++++++++------- app/routers_v3/draftlist.py | 13 +++++-- app/routers_v3/results.py | 4 +-- app/routers_v3/teams.py | 17 +++++---- app/routers_v3/transactions.py | 37 +++++-------------- 6 files changed, 95 insertions(+), 54 deletions(-) diff --git a/app/main.py b/app/main.py index b6c90b0..4b6edec 100644 --- a/app/main.py +++ b/app/main.py @@ -1,3 +1,7 @@ +import datetime +import logging +import os + from fastapi import Depends, FastAPI, Request # from fastapi.openapi.docs import get_swagger_ui_html # from fastapi.openapi.utils import get_openapi @@ -5,6 +9,14 @@ from fastapi import Depends, FastAPI, Request from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, \ pitchingstats, fieldingstats, draftpicks, draftlist +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 'WARN' +logging.basicConfig( + filename=f'logs/database/{date}.log', + format='%(asctime)s - sba-database - %(levelname)s - %(message)s', + level=log_level +) + app = FastAPI( responses={404: {'description': 'Not found'}} ) diff --git a/app/routers_v3/battingstats.py b/app/routers_v3/battingstats.py index 54c6979..fc2c870 100644 --- a/app/routers_v3/battingstats.py +++ b/app/routers_v3/battingstats.py @@ -119,19 +119,34 @@ async def get_batstats( return_stats = { 'count': all_stats.count(), - 'stats': [model_to_dict(x, recurse=not short_output) for x in all_stats] + 'stats': [model_to_dict(x, recurse=not short_output) for x in all_stats], + # 'stats': [{'id': x.id} for x in all_stats] } db.close() return return_stats -@router.get('/season/{season}') -async def get_seasonstats( - season: int, s_type: Literal['regular', 'post', 'total'] = 'regular', team_abbrev: list = Query(default=None), +@router.get('/totals') +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), - player_id: list = Query(default=None), short_output: Optional[bool] = False): - weeks = per_season_weeks(season, s_type) + 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): + if s_type is not None: + weeks = per_season_weeks(season, s_type) + else: + weeks = {'start': 1, 'end': 99} + if week_start is not None: + weeks['start'] = week_start + if week_end is not None: + 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 + all_stats = ( BattingStat .select(BattingStat.player, fn.SUM(BattingStat.pa).alias('sum_pa'), fn.SUM(BattingStat.ab).alias('sum_ab'), @@ -149,18 +164,42 @@ async def get_seasonstats( 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')) + fn.SUM(BattingStat.raa).alias('sum_raa'), fn.SUM(BattingStat.rto).alias('sum_rto'), + BattingStat.team) .where((BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) & (BattingStat.season == season)) - .order_by(BattingStat.player) - .group_by(BattingStat.player) + .having(fn.SUM(BattingStat.pa) > min_pa) # TESTING THIS ) - if team_abbrev is None and team_id is None and player_name is None and player_id is None: - raise HTTPException( - status_code=400, - detail=f'Must include team_id/team_abbrev and/or player_name/player_id' + # if min_pa is not None: + # all_stats = all_stats.having(fn.SUM(BattingStat.pa) >= min_pa) + if game_num is not None: + all_stats = all_stats.where(BattingStat.game << game_num) + 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) ) + all_stats = all_stats.where(BattingStat.player << all_players) + if sort is not None: + if sort == 'player': + all_stats = all_stats.order_by(BattingStat.player) + elif sort == 'team': + all_stats = all_stats.order_by(BattingStat.team) + if group_by is not None: + if group_by == 'team': + all_stats = all_stats.group_by(BattingStat.team) + elif group_by == 'player': + all_stats = all_stats.group_by(BattingStat.player) + elif group_by == 'playerteam': + all_stats = all_stats.group_by(BattingStat.team, BattingStat.player) + + # if team_abbrev is None and team_id is None and player_name is None and player_id is None: + # raise HTTPException( + # status_code=400, + # detail=f'Must include team_id/team_abbrev and/or player_name/player_id' + # ) if team_id is not None: all_teams = Team.select().where(Team.id << team_id) @@ -180,6 +219,7 @@ async def get_seasonstats( 'count': sum(1 for i in all_stats if i.sum_pa > 0), 'stats': [{ 'player': x.player_id if short_output else model_to_dict(x.player, recurse=False), + 'team': x.team_id if short_output else model_to_dict(x.team, recurse=False), 'pa': x.sum_pa, 'ab': x.sum_ab, 'run': x.sum_run, diff --git a/app/routers_v3/draftlist.py b/app/routers_v3/draftlist.py index a66f963..4832c86 100644 --- a/app/routers_v3/draftlist.py +++ b/app/routers_v3/draftlist.py @@ -25,7 +25,12 @@ class DraftListList(pydantic.BaseModel): @router.get('') -async def get_draftlist(season: Optional[int], team_id: list = Query(default=None)): +async def get_draftlist( + season: Optional[int], team_id: list = Query(default=None), token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_draftlist - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + all_list = DraftList.select() if season is not None: @@ -43,7 +48,11 @@ async def get_draftlist(season: Optional[int], team_id: list = Query(default=Non @router.get('/team/{team_id}') -async def get_team_draftlist(team_id: int): +async def get_team_draftlist(team_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_draftlist - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + this_team = Team.get_or_none(Team.id == team_id) if this_team is None: raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found') diff --git a/app/routers_v3/results.py b/app/routers_v3/results.py index 175ebbc..dc563ee 100644 --- a/app/routers_v3/results.py +++ b/app/routers_v3/results.py @@ -29,8 +29,8 @@ class ResultList(pydantic.BaseModel): @router.get('') async def get_results( - season: int, team_abbrev: list = Query(default=None), week_start: list = Query(default=None), - week_end: list = Query(default=None), game_num: list = Query(default=None), + season: int, team_abbrev: list = Query(default=None), week_start: Optional[int] = None, + week_end: Optional[int] = None, game_num: list = Query(default=None), away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None)): all_results = Result.select_season(season) diff --git a/app/routers_v3/teams.py b/app/routers_v3/teams.py index 25362f0..8f8ca3b 100644 --- a/app/routers_v3/teams.py +++ b/app/routers_v3/teams.py @@ -34,29 +34,28 @@ class TeamList(pydantic.BaseModel): @router.get('') async def get_teams( - season: Optional[int] = None, owner_id: Optional[int] = None, manager_id: Optional[int] = None, - abbrev: Optional[str] = None, active_only: Optional[bool] = False, short_output: Optional[bool] = False): + season: Optional[int] = None, owner_id: list = Query(default=None), manager_id: list = Query(default=None), + team_abbrev: list = Query(default=None), active_only: Optional[bool] = False, + short_output: Optional[bool] = False): if season is not None: all_teams = Team.select_season(season) else: all_teams = Team.select() if manager_id is not None: + managers = Manager.select().where(Manager.id << manager_id) all_teams = all_teams.where( - (Team.manager1_id == manager_id) | (Team.manager2_id == manager_id) + (Team.manager1_id << managers) | (Team.manager2_id << managers) ) - if owner_id: - all_teams = all_teams.where((Team.gmid == owner_id) | (Team.gmid2 == owner_id)) - + all_teams = all_teams.where((Team.gmid << owner_id) | (Team.gmid2 << owner_id)) + if team_abbrev is not None: + all_teams = all_teams.where(Team.abbrev << team_abbrev) if active_only: all_teams = all_teams.where( ~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL')) ) - if abbrev is not None: - all_teams = all_teams.where(Team.abbrev == abbrev) - return_teams = { 'count': all_teams.count(), 'teams': [model_to_dict(x, recurse=not short_output) for x in all_teams] diff --git a/app/routers_v3/transactions.py b/app/routers_v3/transactions.py index 8ad792d..17f159f 100644 --- a/app/routers_v3/transactions.py +++ b/app/routers_v3/transactions.py @@ -31,53 +31,34 @@ class TransactionList(pydantic.BaseModel): @router.get('') async def get_transactions( - season, team_abbrev: Optional[str] = None, week_start: Optional[int] = 0, + season, team_abbrev: list = Query(default=None), week_start: Optional[int] = 0, week_end: Optional[int] = None, cancelled: Optional[bool] = None, frozen: Optional[bool] = None, - player_name: Optional[str] = None, player_id: Optional[int] = None, move_id: Optional[str] = None, + player_name: list = Query(default=None), player_id: list = Query(default=None), move_id: Optional[str] = None, is_trade: Optional[bool] = None, short_output: Optional[bool] = False): if season: transactions = Transaction.select_season(season) else: transactions = Transaction.select() - if team_abbrev: - these_teams = Team.select().where( - (Team.abbrev == team_abbrev.upper()) | - (Team.abbrev == f'{team_abbrev.upper()}MiL') | (Team.abbrev == f'{team_abbrev.upper()}IL') - ) - if these_teams.count() == 0: - db.close() - raise HTTPException(status_code=404, detail=f'Team {team_abbrev} not found') - + if team_abbrev is not None: + t_list = [x.upper() for x in team_abbrev] + these_teams = Team.select().where((Team.abbrev << t_list)) transactions = transactions.where( (Transaction.newteam << these_teams) | (Transaction.oldteam << these_teams) ) - if week_start is not None: transactions = transactions.where(Transaction.week >= week_start) - if week_end is not None: transactions = transactions.where(Transaction.week <= week_end) - if move_id: transactions = transactions.where(Transaction.moveid == move_id) - if player_id or player_name: if player_id: - try: - this_player = Player.get_by_id(player_id) - except Exception as e: - db.close() - raise HTTPException(status_code=404, detail=f'Player id {player_id} not found') - - transactions = transactions.where(Transaction.player == this_player) + p_list = Player.select().where(Player.id << player_id) + transactions = transactions.where(Transaction.player << p_list) else: - these_players = Player.select().where(fn.Lower(Player.name) == player_name.lower()) - print(f'these_players: {these_players}\nCount: {these_players.count()}') - if these_players.count() == 0: - db.close() - raise HTTPException(status_code=404, detail=f'Player {player_name} not found') - + p_list = [x.lower() for x in player_name] + these_players = Player.select().where(fn.Lower(Player.name) << p_list) transactions = transactions.where(Transaction.player << these_players) if cancelled: From c8aad86d3d4f180204efeba340afbb1576c68225 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Fri, 26 May 2023 13:42:10 -0500 Subject: [PATCH 11/24] Finished /fieldingstats/totals --- app/routers_v3/battingstats.py | 2 +- app/routers_v3/fieldingstats.py | 55 +++++++++++++++++++++++++-------- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/app/routers_v3/battingstats.py b/app/routers_v3/battingstats.py index fc2c870..d1452ea 100644 --- a/app/routers_v3/battingstats.py +++ b/app/routers_v3/battingstats.py @@ -168,7 +168,7 @@ async def get_totalstats( BattingStat.team) .where((BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) & (BattingStat.season == season)) - .having(fn.SUM(BattingStat.pa) > min_pa) # TESTING THIS + .having(fn.SUM(BattingStat.pa) >= min_pa) ) # if min_pa is not None: diff --git a/app/routers_v3/fieldingstats.py b/app/routers_v3/fieldingstats.py index 16eef00..490edcd 100644 --- a/app/routers_v3/fieldingstats.py +++ b/app/routers_v3/fieldingstats.py @@ -97,30 +97,58 @@ async def get_fieldingstats( return return_stats -@router.get('/season/{season}') -async def get_stasonstats( - season: int, s_type: Literal['regular', 'post', 'total'] = 'regular', team_abbrev: list = Query(default=None), +@router.get('/totals') +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), - player_id: list = Query(default=None), short_output: Optional[bool] = True): - weeks = per_season_weeks(season, s_type) + 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_ch: Optional[int] = 1): + if s_type is not None: + weeks = per_season_weeks(season, s_type) + else: + weeks = {'start': 1, 'end': 99} + if week_start is not None: + weeks['start'] = week_start + if week_end is not None: + 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 + all_stats = ( BattingStat .select(BattingStat.player, BattingStat.pos, 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.csc).alias('sum_csc'), BattingStat.team) .where((BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) & (BattingStat.season == season)) - .order_by(BattingStat.player) - .group_by(BattingStat.player, BattingStat.pos) + .having(fn.SUM(BattingStat.xch) >= min_ch) ) - if team_abbrev is None and team_id is None and player_name is None and player_id is None: - raise HTTPException( - status_code=400, - detail=f'Must include team_id/team_abbrev and/or player_name/player_id' + if game_num is not None: + all_stats = all_stats.where(BattingStat.game << game_num) + 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) ) - + all_stats = all_stats.where(BattingStat.player << all_players) + if sort is not None: + if sort == 'player': + all_stats = all_stats.order_by(BattingStat.player) + elif sort == 'team': + all_stats = all_stats.order_by(BattingStat.team) + if group_by is not None: + if group_by == 'team': + all_stats = all_stats.group_by(BattingStat.pos, BattingStat.team) + elif group_by == 'player': + all_stats = all_stats.group_by(BattingStat.pos, BattingStat.player) + elif group_by == 'playerteam': + all_stats = all_stats.group_by(BattingStat.pos, BattingStat.team, BattingStat.player) if team_id is not None: all_teams = Team.select().where(Team.id << team_id) all_stats = all_stats.where(BattingStat.team << all_teams) @@ -139,6 +167,7 @@ async def get_stasonstats( 'count': sum(1 for i in all_stats if i.sum_xch + i.sum_sbc > 0), 'stats': [{ 'player': x.player_id if short_output else model_to_dict(x.player, recurse=False), + 'team': x.team_id if short_output else model_to_dict(x.team, recurse=False), 'pos': x.pos, 'xch': x.sum_xch, 'xhit': x.sum_xhit, From 8f53d83d9e8c7d58317fefe838e178c0ae2a7697 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Sat, 27 May 2023 11:16:52 -0500 Subject: [PATCH 12/24] Added logging to dependencies, added week parameter to stats/totals --- app/dependencies.py | 22 ++++++++-- app/routers_v3/battingstats.py | 63 +++++++++++++-------------- app/routers_v3/fieldingstats.py | 42 +++++++++++------- app/routers_v3/pitchingstats.py | 77 +++++++++++++++++++++++++-------- 4 files changed, 136 insertions(+), 68 deletions(-) diff --git a/app/dependencies.py b/app/dependencies.py index cba247d..615288f 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -5,13 +5,27 @@ import os from fastapi.security import OAuth2PasswordBearer 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 'WARN' +LOG_DATA = { + 'filename': f'logs/database/{date}.log', + 'format': '%(asctime)s - database - %(levelname)s - %(message)s', + 'log_level': logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else 'WARN' +} + + logging.basicConfig( - filename=f'logs/database/{date}.log', - format='%(asctime)s - database - %(levelname)s - %(message)s', - level=log_level + filename=LOG_DATA['filename'], + format=LOG_DATA['format'], + level=LOG_DATA['log_level'] ) +# 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 'WARN' +# logging.basicConfig( +# filename=f'logs/database/{date}.log', +# format='%(asctime)s - sba-database - %(levelname)s - %(message)s', +# level=log_level +# ) + oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") diff --git a/app/routers_v3/battingstats.py b/app/routers_v3/battingstats.py index d1452ea..9c88c96 100644 --- a/app/routers_v3/battingstats.py +++ b/app/routers_v3/battingstats.py @@ -4,7 +4,9 @@ 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 +from ..dependencies import oauth2_scheme, valid_token, LOG_DATA + +logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level']) router = APIRouter( prefix='/api/v3/battingstats', @@ -134,18 +136,9 @@ async def get_totalstats( 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): - if s_type is not None: - weeks = per_season_weeks(season, s_type) - else: - weeks = {'start': 1, 'end': 99} - if week_start is not None: - weeks['start'] = week_start - if week_end is not None: - 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 + 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.') all_stats = ( BattingStat @@ -166,13 +159,31 @@ async def get_totalstats( 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'), BattingStat.team) - .where((BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) & - (BattingStat.season == season)) + .where(BattingStat.season == season) .having(fn.SUM(BattingStat.pa) >= min_pa) ) - # if min_pa is not None: - # all_stats = all_stats.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]: + weeks = {} + if s_type is not None: + weeks = per_season_weeks(season, s_type) + 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.' + ) + 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 + + all_stats = all_stats.where( + (BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) + ) + elif week is not None: + all_stats = all_stats.where(BattingStat.week << week) + if game_num is not None: all_stats = all_stats.where(BattingStat.game << game_num) if position is not None: @@ -216,7 +227,7 @@ async def get_totalstats( all_stats = all_stats.where(BattingStat.player << all_players) return_stats = { - 'count': sum(1 for i in all_stats if i.sum_pa > 0), + 'count': all_stats.count(), 'stats': [{ 'player': x.player_id if short_output else model_to_dict(x.player, recurse=False), 'team': x.team_id if short_output else model_to_dict(x.team, recurse=False), @@ -239,20 +250,8 @@ async def get_totalstats( 'bphr': x.sum_bphr, 'bpfo': x.sum_bpfo, 'bp1b': x.sum_bp1b, - 'bplo': x.sum_bplo, - 'xba': x.sum_xba, - 'xbt': x.sum_xbt, - 'xch': x.sum_xch, - 'xhit': x.sum_xhit, - 'error': x.sum_error, - 'pb': x.sum_pb, - 'sbc': x.sum_sbc, - 'csc': x.sum_csc, - 'roba': x.sum_roba, - 'robs': x.sum_robs, - 'raa': x.sum_raa, - 'rto': x.sum_rto, - } for x in all_stats if x.sum_pa > 0] + 'bplo': x.sum_bplo + } for x in all_stats] } db.close() return return_stats diff --git a/app/routers_v3/fieldingstats.py b/app/routers_v3/fieldingstats.py index 490edcd..2712acd 100644 --- a/app/routers_v3/fieldingstats.py +++ b/app/routers_v3/fieldingstats.py @@ -4,7 +4,9 @@ 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 +from ..dependencies import oauth2_scheme, valid_token, LOG_DATA + +logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level']) router = APIRouter( prefix='/api/v3/fieldingstats', @@ -104,18 +106,7 @@ async def get_totalstats( 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_ch: Optional[int] = 1): - if s_type is not None: - weeks = per_season_weeks(season, s_type) - else: - weeks = {'start': 1, 'end': 99} - if week_start is not None: - weeks['start'] = week_start - if week_end is not None: - 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 + min_ch: Optional[int] = 1, week: list = Query(default=None)): all_stats = ( BattingStat @@ -123,11 +114,32 @@ async def get_totalstats( 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'), BattingStat.team) - .where((BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) & - (BattingStat.season == season)) + .where(BattingStat.season == season) .having(fn.SUM(BattingStat.xch) >= min_ch) ) + if True in [s_type is not None, week_start is not None, week_end is not None]: + weeks = {} + if s_type is not None: + weeks = per_season_weeks(season, s_type) + 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.' + ) + 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 + + all_stats = all_stats.where( + (BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) + ) + + elif week is not None: + all_stats = all_stats.where(BattingStat.week << week) + if game_num is not None: all_stats = all_stats.where(BattingStat.game << game_num) if position is not None: diff --git a/app/routers_v3/pitchingstats.py b/app/routers_v3/pitchingstats.py index d9cf992..c1ebc97 100644 --- a/app/routers_v3/pitchingstats.py +++ b/app/routers_v3/pitchingstats.py @@ -1,10 +1,15 @@ +import datetime +import os + from fastapi import APIRouter, Depends, HTTPException, Query from typing import List, Optional, Literal import logging import pydantic from ..db_engine import db, PitchingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks -from ..dependencies import oauth2_scheme, valid_token +from ..dependencies import oauth2_scheme, valid_token, LOG_DATA + +logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level']) router = APIRouter( prefix='/api/v3/pitchingstats', @@ -108,12 +113,17 @@ async def get_pitstats( return return_stats -@router.get('/season/{season}') -async def get_seasonstats( - season: int, s_type: Literal['regular', 'post', 'total'] = 'regular', team_abbrev: list = Query(default=None), +@router.get('/totals') +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), - player_id: list = Query(default=None), full_player: Optional[bool] = False): - weeks = per_season_weeks(season, s_type) + week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None), + is_sp: Optional[bool] = None, min_ip: Optional[float] = 0.25, sort: Optional[str] = None, + player_id: list = Query(default=None), short_output: Optional[bool] = False, + group_by: Literal['team', 'player', 'playerteam'] = 'player', 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.') + all_stats = ( PitchingStat .select(PitchingStat.player, fn.SUM(PitchingStat.ip).alias('sum_ip'), @@ -125,26 +135,58 @@ async def get_seasonstats( fn.SUM(PitchingStat.win).alias('sum_win'), fn.SUM(PitchingStat.loss).alias('sum_loss'), fn.SUM(PitchingStat.hold).alias('sum_hold'), fn.SUM(PitchingStat.sv).alias('sum_sv'), fn.SUM(PitchingStat.bsv).alias('sum_bsv'), fn.SUM(PitchingStat.irs).alias('sum_irs'), - fn.SUM(PitchingStat.gs).alias('sum_gs')) - .where((PitchingStat.week >= weeks['start']) & (PitchingStat.week <= weeks['end']) & - (PitchingStat.season == season)) - .order_by(PitchingStat.player) - .group_by(PitchingStat.player) + fn.SUM(PitchingStat.gs).alias('sum_gs'), PitchingStat.team) + .where(PitchingStat.season == season) + .having(fn.SUM(PitchingStat.ip) >= min_ip) ) - if team_abbrev is None and team_id is None and player_name is None and player_id is None: - raise HTTPException( - status_code=400, - detail=f'Must include team_id/team_abbrev and/or player_name/player_id' + if True in [s_type is not None, week_start is not None, week_end is not None]: + weeks = {} + if s_type is not None: + weeks = per_season_weeks(season, s_type) + 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.' + ) + 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 + + all_stats = all_stats.where( + (PitchingStat.week >= weeks['start']) & (PitchingStat.week <= weeks['end']) ) + elif week is not None: + all_stats = all_stats.where(PitchingStat.week << week) + + if game_num is not None: + all_stats = all_stats.where(PitchingStat.game << game_num) + if is_sp is not None: + if is_sp: + all_stats = all_stats.where(PitchingStat.gs == 1) + if not is_sp: + all_stats = all_stats.where(PitchingStat.gs == 0) + if sort is not None: + if sort == 'player': + all_stats = all_stats.order_by(PitchingStat.player) + elif sort == 'team': + all_stats = all_stats.order_by(PitchingStat.team) + if group_by is not None: + if group_by == 'team': + all_stats = all_stats.group_by(PitchingStat.team) + elif group_by == 'player': + all_stats = all_stats.group_by(PitchingStat.player) + elif group_by == 'playerteam': + all_stats = all_stats.group_by(PitchingStat.team, PitchingStat.player) if team_id is not None: all_teams = Team.select().where(Team.id << team_id) all_stats = all_stats.where(PitchingStat.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_stats = all_stats.where(PitchingStat.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_stats = all_stats.where(PitchingStat.player << all_players) @@ -155,7 +197,8 @@ async def get_seasonstats( return_stats = { 'count': all_stats.count(), 'stats': [{ - 'player': model_to_dict(x.player, recurse=False) if full_player else x.player_id, + 'player': x.player_id if short_output else model_to_dict(x.player, recurse=False), + 'team': x.team_id if short_output else model_to_dict(x.team, recurse=False), 'ip': x.sum_ip, 'hit': x.sum_hit, 'run': x.sum_run, From 1759932643525c18710ca3e5c35a4fd2d7394b3c Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 21 Jun 2023 10:04:51 -0500 Subject: [PATCH 13/24] Add csv support for /players --- app/routers_v3/players.py | 44 ++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py index 99c5354..d92a2bd 100644 --- a/app/routers_v3/players.py +++ b/app/routers_v3/players.py @@ -1,10 +1,13 @@ -from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi import APIRouter, Depends, HTTPException, Query, Response from typing import List, Optional import logging import pydantic +from pandas import DataFrame from ..db_engine import db, Player, model_to_dict, chunked -from ..dependencies import oauth2_scheme, valid_token +from ..dependencies import oauth2_scheme, valid_token, LOG_DATA + +logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level']) router = APIRouter( prefix='/api/v3/players', @@ -44,7 +47,8 @@ class PlayerList(pydantic.BaseModel): @router.get('') async def get_players( season: Optional[int], team_id: list = Query(default=None), pos: list = Query(default=None), - is_injured: Optional[bool] = None, sort: Optional[str] = None): + is_injured: Optional[bool] = None, sort: Optional[str] = None, short_output: Optional[bool] = False, + csv: Optional[bool] = False): all_players = Player.select_season(season) if team_id is not None: @@ -70,10 +74,36 @@ async def get_players( elif sort == 'name-desc': all_players = all_players.order_by(-Player.name) - return_players = { - 'count': all_players.count(), - 'players': [model_to_dict(x, recurse=False) for x in all_players] - } + print(f'csv: {csv}') + if csv: + player_list = [ + ['name', 'wara', 'image', 'image2', 'team', 'season', 'pitcher_injury', 'pos_1', 'pos_2', 'pos_3', + 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'last_game', 'last_game2', 'il_return', 'demotion_week', + 'headshot', 'vanity_card', 'strat_code', 'bbref_id', 'injury_rating'] + ] + for line in all_players: + player_list.append( + [ + line.name, line.wara, line.image, line.image2, line.team.abbrev, line.season, line.pitcher_injury, + line.pos_1, line.pos_2, line.pos_3, line.pos_4, line.pos_5, line.pos_6, line.pos_7, line.pos_8, + line.last_game, line.last_game2, line.il_return, line.demotion_week, line.headshot, + line.vanity_card, line.strat_code.replace(",", "-_-"), line.bbref_id, line.injury_rating + ] + ) + return_players = { + 'count': all_players.count(), + 'players': DataFrame(player_list).to_csv(header=False, index=False), + 'csv': True + } + + db.close() + return Response(content=return_players['players'], media_type='text/csv') + + else: + return_players = { + 'count': all_players.count(), + 'players': [model_to_dict(x, recurse=not short_output) for x in all_players] + } db.close() return return_players From 0f436cf74b2ae233fca456d7c33f272b38ebce58 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 21 Jun 2023 10:05:01 -0500 Subject: [PATCH 14/24] Added /managers --- app/main.py | 3 +- app/routers_v3/managers.py | 143 +++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 app/routers_v3/managers.py diff --git a/app/main.py b/app/main.py index 4b6edec..bdfd755 100644 --- a/app/main.py +++ b/app/main.py @@ -7,7 +7,7 @@ from fastapi import Depends, FastAPI, Request # from fastapi.openapi.utils import get_openapi from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, \ - pitchingstats, fieldingstats, draftpicks, draftlist + pitchingstats, fieldingstats, draftpicks, draftlist, managers 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 'WARN' @@ -34,6 +34,7 @@ app.include_router(pitchingstats.router) app.include_router(fieldingstats.router) app.include_router(draftpicks.router) app.include_router(draftlist.router) +app.include_router(managers.router) # @app.get("/docs", include_in_schema=False) diff --git a/app/routers_v3/managers.py b/app/routers_v3/managers.py new file mode 100644 index 0000000..84586bc --- /dev/null +++ b/app/routers_v3/managers.py @@ -0,0 +1,143 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from typing import List, Optional +import logging +import pydantic + +from ..db_engine import db, Manager, Team, Current, model_to_dict, chunked, fn +from ..dependencies import oauth2_scheme, valid_token + +router = APIRouter( + prefix='/api/v3/managers', + tags=['managers'] +) + + +class ManagerModel(pydantic.BaseModel): + name: str + image: Optional[str] = None + headline: Optional[str] = None + bio: Optional[str] = None + + +@router.get('') +async def get_managers( + name: list = Query(default=None), active: Optional[bool] = None, short_output: Optional[bool] = False): + if active is not None: + current = Current.latest() + t_query = Team.select().where( + Team.season == current.season & ~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL')) + ) + a_mgr = [] + i_mgr = [] + + for x in t_query: + if x.manager1 is not None: + a_mgr.append(x.manager1) + if x.manager2 is not None: + a_mgr.append(x.manager2) + + if active: + final_mgrs = [model_to_dict(y, recurse=not short_output) for y in a_mgr] + else: + for z in Manager.select(): + if z not in a_mgr: + i_mgr.append(z) + final_mgrs = [model_to_dict(y, recurse=not short_output) for y in i_mgr] + + return_managers = { + 'count': len(final_mgrs), + 'managers': final_mgrs + } + + else: + all_managers = Manager.select() + if name is not None: + name_list = [x.lower() for x in name] + all_managers = all_managers.where(fn.Lower(Manager.name) << name_list) + + return_managers = { + 'count': all_managers.count(), + 'managers': [model_to_dict(x, recurse=not short_output) for x in all_managers] + } + + db.close() + return return_managers + + +@router.get('/{manager_id}') +async def get_one_manager(manager_id: int, short_output: Optional[bool] = False): + this_manager = Manager.get_or_none(Manager.id == manager_id) + if this_manager is not None: + r_manager = model_to_dict(this_manager, recurse=not short_output) + db.close() + return r_manager + else: + raise HTTPException(status_code=404, detail=f'Manager {manager_id} not found') + + +@router.patch('/{manager_id}') +async def patch_manager( + manager_id: int, name: Optional[str] = None, image: Optional[str] = None, headline: Optional[str] = None, + bio: Optional[str] = None, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_manager - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_manager = Manager.get_or_none(Manager.id == manager_id) + if this_manager is None: + db.close() + raise HTTPException(status_code=404, detail=f'Manager ID {manager_id} not found') + + if name is not None: + this_manager.name = name + if image is not None: + this_manager.image = image + if headline is not None: + this_manager.headline = headline + if bio is not None: + this_manager.bio = bio + + if this_manager.save() == 1: + r_manager = model_to_dict(this_manager) + db.close() + return r_manager + else: + db.close() + raise HTTPException(status_code=500, detail=f'Unable to patch manager {this_manager}') + + +@router.post('') +async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_manager - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_manager = Manager(**new_manager.dict()) + + if this_manager.save(): + r_manager = model_to_dict(this_manager) + db.close() + return r_manager + else: + db.close() + raise HTTPException(status_code=500, detail=f'Unable to post manager {this_manager.name}') + + +@router.delete('/{manager_id}') +async def delete_manager(manager_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'delete_manager - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_manager = Manager.get_or_none(Manager.id == manager_id) + if this_manager is None: + db.close() + raise HTTPException(status_code=404, detail=f'Manager ID {manager_id} not found') + + count = this_manager.delete_instance() + db.close() + + if count == 1: + return f'Manager {manager_id} has been deleted' + else: + raise HTTPException(status_code=500, detail=f'Manager {manager_id} could not be deleted') From 5d7780a920ef7bb27f5593efc55b7a8e14c86bdc Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 21 Jun 2023 10:05:16 -0500 Subject: [PATCH 15/24] Updated ip_min in /pitchingstats --- app/routers_v3/pitchingstats.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/routers_v3/pitchingstats.py b/app/routers_v3/pitchingstats.py index c1ebc97..093476b 100644 --- a/app/routers_v3/pitchingstats.py +++ b/app/routers_v3/pitchingstats.py @@ -53,7 +53,8 @@ async def get_pitstats( 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), - limit: Optional[int] = None, sort: Optional[str] = None, short_output: Optional[bool] = True): + limit: Optional[int] = None, ip_min: Optional[float] = None, sort: Optional[str] = None, + short_output: Optional[bool] = True): if 'post' in s_type.lower(): all_stats = PitchingStat.post_season(season) if all_stats.count() == 0: @@ -81,6 +82,8 @@ async def get_pitstats( all_stats = all_stats.where(PitchingStat.player << p_query) if game_num: all_stats = all_stats.where(PitchingStat.game == game_num) + if ip_min is not None: + all_stats = all_stats.where(PitchingStat.ip >= ip_min) start = 1 end = Current.get(Current.season == season).week @@ -118,7 +121,7 @@ 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), - is_sp: Optional[bool] = None, min_ip: Optional[float] = 0.25, sort: Optional[str] = None, + is_sp: Optional[bool] = None, ip_min: Optional[float] = 0.25, sort: Optional[str] = None, player_id: list = Query(default=None), short_output: Optional[bool] = False, group_by: Literal['team', 'player', 'playerteam'] = 'player', 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: @@ -137,7 +140,7 @@ async def get_totalstats( fn.SUM(PitchingStat.bsv).alias('sum_bsv'), fn.SUM(PitchingStat.irs).alias('sum_irs'), fn.SUM(PitchingStat.gs).alias('sum_gs'), PitchingStat.team) .where(PitchingStat.season == season) - .having(fn.SUM(PitchingStat.ip) >= min_ip) + .having(fn.SUM(PitchingStat.ip) >= ip_min) ) if True in [s_type is not None, week_start is not None, week_end is not None]: From 449dafcfaefb5dfa86cd513a0fddc8f5fac45768 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 21 Jun 2023 10:05:33 -0500 Subject: [PATCH 16/24] Add short_output in /results --- app/routers_v3/results.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/routers_v3/results.py b/app/routers_v3/results.py index dc563ee..fd3ac6e 100644 --- a/app/routers_v3/results.py +++ b/app/routers_v3/results.py @@ -31,7 +31,8 @@ class ResultList(pydantic.BaseModel): async def get_results( season: int, team_abbrev: list = Query(default=None), week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None), - away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None)): + away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None), + short_output: Optional[bool] = False): all_results = Result.select_season(season) if team_abbrev is not None: @@ -65,7 +66,7 @@ async def get_results( return_results = { 'count': all_results.count(), - 'results': [model_to_dict(x, recurse=False) for x in all_results] + 'results': [model_to_dict(x, recurse=not short_output) for x in all_results] } db.close() return return_results From 48e3089e50b9efc80b7cc8104c2237bdba7aa93a Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 21 Jun 2023 14:39:06 -0500 Subject: [PATCH 17/24] Added /awards --- app/main.py | 3 +- app/routers_v3/awards.py | 162 +++++++++++++++++++++++++++++++++++++ app/routers_v3/managers.py | 15 +++- 3 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 app/routers_v3/awards.py diff --git a/app/main.py b/app/main.py index bdfd755..91c8322 100644 --- a/app/main.py +++ b/app/main.py @@ -7,7 +7,7 @@ from fastapi import Depends, FastAPI, Request # from fastapi.openapi.utils import get_openapi from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, \ - pitchingstats, fieldingstats, draftpicks, draftlist, managers + pitchingstats, fieldingstats, draftpicks, draftlist, managers, awards 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 'WARN' @@ -35,6 +35,7 @@ app.include_router(fieldingstats.router) app.include_router(draftpicks.router) app.include_router(draftlist.router) app.include_router(managers.router) +app.include_router(awards.router) # @app.get("/docs", include_in_schema=False) diff --git a/app/routers_v3/awards.py b/app/routers_v3/awards.py new file mode 100644 index 0000000..243bd33 --- /dev/null +++ b/app/routers_v3/awards.py @@ -0,0 +1,162 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from typing import List, Optional +import logging +import pydantic + +from ..db_engine import db, Award, Team, Player, Manager, model_to_dict, chunked, fn +from ..dependencies import oauth2_scheme, valid_token + +router = APIRouter( + prefix='/api/v3/awards', + tags=['awards'] +) + + +class AwardModel(pydantic.BaseModel): + name: str + season: int + timing: Optional[str] = "In-Season" + manager1_id: Optional[int] = None + manager2_id: Optional[int] = None + player_id: Optional[int] = None + team_id: Optional[int] = None + image: Optional[str] = None + + +class AwardList(pydantic.BaseModel): + count: int + awards: List[AwardModel] + + +@router.get('') +async def get_awards( + name: list = Query(default=None), season: Optional[int] = None, timing: Optional[str] = None, + manager_id: list = Query(default=None), player_id: list = Query(default=None), + team_id: list = Query(default=None), short_output: Optional[bool] = False): + all_awards = Award.select() + + if name is not None: + name_list = [x.lower() for x in name] + all_awards = all_awards.where(fn.Lower(Award.name) == name_list) + if season is not None: + all_awards = all_awards.where(Award.season == season) + if timing is not None: + all_awards = all_awards.where(fn.Lower(Award.timing) == timing.lower()) + if manager_id is not None: + managers = Manager.select().where(Manager.id << manager_id) + all_awards = all_awards.where( + (Award.manager1 << managers) | (Award.manager2 << managers) + ) + if player_id is not None: + all_awards = all_awards.where(Award.player_id << player_id) + if team_id is not None: + all_awards = all_awards.where(Award.team_id << team_id) + + return_awards = { + 'count': all_awards.count(), + 'awards': [model_to_dict(x, recurse=not short_output) for x in all_awards] + } + db.close() + return return_awards + + +@router.get('/{award_id}') +async def get_one_award(award_id: int, short_output: Optional[bool] = False): + this_award = Award.get_or_none(Award.id == award_id) + if this_award is None: + db.close() + raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found') + + db.close() + return model_to_dict(this_award, recurse=not short_output) + + +@router.patch('/{award_id}') +async def patch_award( + award_id: int, name: Optional[str] = None, season: Optional[int] = None, timing: Optional[str] = None, + image: Optional[str] = None, manager1_id: Optional[int] = None, manager2_id: Optional[int] = None, + player_id: Optional[int] = None, team_id: Optional[int] = None, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_player - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_award = Award.get_or_none(Award.id == award_id) + if this_award is None: + db.close() + raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found') + + if name is not None: + this_award.name = name + if season is not None: + this_award.season = season + if timing is not None: + this_award.timing = timing + if image is not None: + this_award.image = image + if manager1_id is not None: + this_award.manager1_id = manager1_id + if manager2_id is not None: + this_award.manager2_id = manager2_id + if player_id is not None: + this_award.player_id = player_id + if team_id is not None: + this_award.team_id = team_id + + if this_award.save() == 1: + r_award = model_to_dict(this_award) + db.close() + return r_award + else: + db.close() + raise HTTPException(status_code=500, detail=f'Unable to patch award {award_id}') + + +@router.post('') +async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_player - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + new_awards = [] + for x in award_list.awards: + if x.manager1_id is not None and Manager.get_or_none(Manager.id == x.manager1_id) is None: + raise HTTPException(status_code=404, detail=f'Manager ID {x.manager1_id} not found') + if x.manager2_id is not None and Manager.get_or_none(Manager.id == x.manager2_id) is None: + raise HTTPException(status_code=404, detail=f'Manager ID {x.manager2_id} not found') + if x.player_id is not None and Player.get_or_none(Player.id == x.player_id) is None: + raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found') + if x.team_id is not None and Team.get_or_none(Team.id == x.team_id) is None: + raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found') + + new_awards.append(x.dict()) + + with db.atomic(): + for batch in chunked(new_awards, 15): + Award.insert_many(batch).on_conflict_replace().execute() + db.close() + + return f'Inserted {len(new_awards)} awards' + + +@router.delete('/{award_id}') +async def delete_award(award_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_player - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_award = Award.get_or_none(Award.id == award_id) + if this_award is None: + db.close() + raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found') + + count = this_award.delete_instance() + db.close() + + if count == 1: + return f'Award {award_id} has been deleted' + else: + raise HTTPException(status_code=500, detail=f'Award {award_id} could not be deleted') + + + + diff --git a/app/routers_v3/managers.py b/app/routers_v3/managers.py index 84586bc..abe8bea 100644 --- a/app/routers_v3/managers.py +++ b/app/routers_v3/managers.py @@ -3,7 +3,7 @@ from typing import List, Optional import logging import pydantic -from ..db_engine import db, Manager, Team, Current, model_to_dict, chunked, fn +from ..db_engine import db, Manager, Team, Current, model_to_dict, fn from ..dependencies import oauth2_scheme, valid_token router = APIRouter( @@ -24,23 +24,32 @@ async def get_managers( name: list = Query(default=None), active: Optional[bool] = None, short_output: Optional[bool] = False): if active is not None: current = Current.latest() - t_query = Team.select().where( - Team.season == current.season & ~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL')) + t_query = Team.select_season(current.season) + t_query = t_query.where( + ~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL')) ) + logging.info(f'tquery: {t_query}') a_mgr = [] i_mgr = [] for x in t_query: + logging.info(f'Team: {x.abbrev} / mgr1: {x.manager1} / mgr2: {x.manager2}') if x.manager1 is not None: a_mgr.append(x.manager1) + logging.info(f'appending {x.manager1.name}') if x.manager2 is not None: a_mgr.append(x.manager2) + logging.info(f'appending {x.manager2.name}') + logging.info(f'a_mgr: {a_mgr}') if active: final_mgrs = [model_to_dict(y, recurse=not short_output) for y in a_mgr] else: + logging.info(f'checking inactive') for z in Manager.select(): + logging.info(f'checking: {z.name}') if z not in a_mgr: + logging.info(f'+inactive: {z.name}') i_mgr.append(z) final_mgrs = [model_to_dict(y, recurse=not short_output) for y in i_mgr] From 635baa91ad4cbacc385817fd073b861139fa08da Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Tue, 27 Jun 2023 10:18:20 -0500 Subject: [PATCH 18/24] Bug fixes --- app/routers_v3/players.py | 11 +++++++---- app/routers_v3/standings.py | 10 +++++----- app/routers_v3/teams.py | 5 +++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py index d92a2bd..48cc431 100644 --- a/app/routers_v3/players.py +++ b/app/routers_v3/players.py @@ -4,7 +4,7 @@ import logging import pydantic from pandas import DataFrame -from ..db_engine import db, Player, model_to_dict, chunked +from ..db_engine import db, Player, model_to_dict, chunked, fn from ..dependencies import oauth2_scheme, valid_token, LOG_DATA logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level']) @@ -46,14 +46,17 @@ class PlayerList(pydantic.BaseModel): @router.get('') async def get_players( - season: Optional[int], team_id: list = Query(default=None), pos: list = Query(default=None), - is_injured: Optional[bool] = None, sort: Optional[str] = None, short_output: Optional[bool] = False, - csv: Optional[bool] = False): + season: Optional[int], name: Optional[str] = None, team_id: list = Query(default=None), + pos: list = Query(default=None), is_injured: Optional[bool] = None, sort: Optional[str] = None, + short_output: Optional[bool] = False, csv: Optional[bool] = False): all_players = Player.select_season(season) if team_id is not None: all_players = all_players.where(Player.team_id << team_id) + if name is not None: + all_players = all_players.where(fn.lower(Player.name) == name.lower()) + if pos is not None: p_list = [x.upper() for x in pos] all_players = all_players.where( diff --git a/app/routers_v3/standings.py b/app/routers_v3/standings.py index 470bf31..f1ff85f 100644 --- a/app/routers_v3/standings.py +++ b/app/routers_v3/standings.py @@ -26,12 +26,12 @@ async def get_standings( t_query = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev]) standings = standings.where(Standings.team << t_query) - if league_abbrev: - l_query = Division.select().where(fn.Lower(Division.league_abbrev) << league_abbrev.lower()) + if league_abbrev is not None: + l_query = Division.select().where(fn.Lower(Division.league_abbrev) == league_abbrev.lower()) standings = standings.where(Standings.team.division << l_query) - if division_abbrev: - d_query = Division.select().where(fn.Lower(Division.division_abbrev) << division_abbrev.lower()) + if division_abbrev is not None: + d_query = Division.select().where(fn.Lower(Division.division_abbrev) == division_abbrev.lower()) standings = standings.where(Standings.team.division << d_query) def win_pct(this_team_stan): @@ -41,7 +41,7 @@ async def get_standings( return (this_team_stan.wins / (this_team_stan.wins + this_team_stan.losses)) + \ (this_team_stan.run_diff * .000001) - div_teams = [team_stan for team_stan in standings] + div_teams = [x for x in standings] div_teams.sort(key=lambda team: win_pct(team), reverse=True) return_standings = { diff --git a/app/routers_v3/teams.py b/app/routers_v3/teams.py index 8f8ca3b..b861c0a 100644 --- a/app/routers_v3/teams.py +++ b/app/routers_v3/teams.py @@ -3,7 +3,7 @@ from typing import List, Optional import logging import pydantic -from ..db_engine import db, Team, Manager, Division, model_to_dict, chunked +from ..db_engine import db, Team, Manager, Division, model_to_dict, chunked, fn from ..dependencies import oauth2_scheme, valid_token router = APIRouter( @@ -50,7 +50,8 @@ async def get_teams( if owner_id: all_teams = all_teams.where((Team.gmid << owner_id) | (Team.gmid2 << owner_id)) if team_abbrev is not None: - all_teams = all_teams.where(Team.abbrev << team_abbrev) + team_list = [x.lower() for x in team_abbrev] + all_teams = all_teams.where(fn.lower(Team.abbrev) << team_list) if active_only: all_teams = all_teams.where( ~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL')) From e687629485b6066c794252deaa3fab09645eb88b Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Sun, 9 Jul 2023 10:18:20 -0500 Subject: [PATCH 19/24] Added /teams roster pull --- app/routers_v3/teams.py | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/app/routers_v3/teams.py b/app/routers_v3/teams.py index b861c0a..0c40f57 100644 --- a/app/routers_v3/teams.py +++ b/app/routers_v3/teams.py @@ -1,5 +1,6 @@ from fastapi import APIRouter, Depends, HTTPException, Query -from typing import List, Optional +from typing import List, Optional, Literal +import copy import logging import pydantic @@ -76,7 +77,43 @@ async def get_one_team(team_id: int): return r_team -@router.get('/{team_id}') +@router.get('/{team_id}/roster/{which}') +async def get_team_roster(team_id: int, which: Literal['current', 'next'], sort: Optional[str] = None): + try: + this_team = Team.get_by_id(team_id) + except Exception as e: + raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found') + + if which == 'current': + full_roster = this_team.get_this_week() + else: + full_roster = this_team.get_next_week() + + active_players = copy.deepcopy(full_roster['active']['players']) + sil_players = copy.deepcopy(full_roster['shortil']['players']) + lil_players = copy.deepcopy(full_roster['longil']['players']) + full_roster['active']['players'] = [] + full_roster['shortil']['players'] = [] + full_roster['longil']['players'] = [] + + for player in active_players: + full_roster['active']['players'].append(model_to_dict(player)) + for player in sil_players: + full_roster['shortil']['players'].append(model_to_dict(player)) + for player in lil_players: + full_roster['longil']['players'].append(model_to_dict(player)) + + if sort: + if sort == 'wara-desc': + full_roster['active']['players'].sort(key=lambda p: p["wara"], reverse=True) + full_roster['active']['players'].sort(key=lambda p: p["wara"], reverse=True) + full_roster['active']['players'].sort(key=lambda p: p["wara"], reverse=True) + + db.close() + return full_roster + + +@router.patch('/{team_id}') async def patch_team( team_id: int, manager1_id: Optional[int] = None, manager2_id: Optional[int] = None, gmid: Optional[int] = None, gmid2: Optional[int] = None, mascot: Optional[str] = None, stadium: Optional[str] = None, From 8b202fad1afbb990c4c729f64c1aabef8dc9fba2 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Mon, 17 Jul 2023 23:08:24 -0500 Subject: [PATCH 20/24] Added draftdata and keepers --- app/db_engine.py | 93 ++++++++++++++++++++++++++++- app/main.py | 4 +- app/routers_v3/draftdata.py | 73 +++++++++++++++++++++++ app/routers_v3/keepers.py | 113 ++++++++++++++++++++++++++++++++++++ 4 files changed, 281 insertions(+), 2 deletions(-) create mode 100644 app/routers_v3/draftdata.py create mode 100644 app/routers_v3/keepers.py diff --git a/app/db_engine.py b/app/db_engine.py index fb60341..a331277 100644 --- a/app/db_engine.py +++ b/app/db_engine.py @@ -1820,6 +1820,97 @@ class DraftList(BaseModel): player = ForeignKeyField(Player) +class Keeper(BaseModel): + season = IntegerField() + team = ForeignKeyField(Team) + player = ForeignKeyField(Player) + + +class Injury(BaseModel): + season = IntegerField() + player = ForeignKeyField(Player) + total_games = IntegerField() + start_week = IntegerField() + start_game = IntegerField() + end_week = IntegerField() + end_game = IntegerField() + is_active = BooleanField(default=True) + + +class StratGame(BaseModel): + season = IntegerField() + week = IntegerField() + game_num = IntegerField() + season_type = CharField(default='regular') + away_team = ForeignKeyField(Team) + home_team = ForeignKeyField(Team) + away_score = IntegerField(null=True) + home_score = IntegerField(null=True) + + +class StratPlay(BaseModel): + game = ForeignKeyField(StratGame) + play_num = IntegerField() + batter = ForeignKeyField(Player) + pitcher = ForeignKeyField(Player) + on_base_code = IntegerField() + inning_half = CharField() + inning_num = IntegerField() + batting_order = IntegerField() + starting_outs = IntegerField() + away_score = IntegerField() + home_score = IntegerField() + batter_pos = CharField() + + # These _final fields track the base this runner advances to post-play (None) if out + on_first = ForeignKeyField(Player, null=True) + on_first_final = IntegerField(null=True) + on_second = ForeignKeyField(Player, null=True) + on_second_final = IntegerField(null=True) + on_third = ForeignKeyField(Player, null=True) + on_third_final = IntegerField(null=True) + batter_final = IntegerField(null=True) + + pa = IntegerField(default=0) + ab = IntegerField(default=0) + run = IntegerField(default=0) + hit = IntegerField(default=0) + rbi = IntegerField(default=0) + double = IntegerField(default=0) + triple = IntegerField(default=0) + homerun = IntegerField(default=0) + bb = IntegerField(default=0) + so = IntegerField(default=0) + hbp = IntegerField(default=0) + sac = IntegerField(default=0) + ibb = IntegerField(default=0) + gidp = IntegerField(default=0) + bphr = IntegerField(default=0) + bpfo = IntegerField(default=0) + bp1b = IntegerField(default=0) + bplo = IntegerField(default=0) + sb = IntegerField(default=0) + cs = IntegerField(default=0) + outs = IntegerField(default=0) + wpa = FloatField(default=0) + + # These fields are only required if the play is an x-check or baserunning play + catcher = ForeignKeyField(Player, null=True) + defender = ForeignKeyField(Player, null=True) + runner = ForeignKeyField(Player, null=True) + + check_pos = CharField(null=True) + error = IntegerField(default=0) + wild_pitch = IntegerField(default=0) + passed_ball = IntegerField(default=0) + pick_off = IntegerField(default=0) + balk = IntegerField(default=0) + is_go_ahead = BooleanField(default=False) + is_tied = BooleanField(default=False) + is_new_inning = BooleanField(default=False) + + + # class Streak(BaseModel): # player = ForeignKeyField(Player) # streak_type = CharField() @@ -1844,6 +1935,6 @@ class DraftList(BaseModel): db.create_tables([ Current, Division, Manager, Team, Result, Player, Schedule, Transaction, BattingStat, PitchingStat, Standings, - BattingCareer, PitchingCareer, FieldingCareer, Manager, Award, DiceRoll, DraftList + BattingCareer, PitchingCareer, FieldingCareer, Manager, Award, DiceRoll, DraftList, Keeper ]) db.close() diff --git a/app/main.py b/app/main.py index 91c8322..3376bd6 100644 --- a/app/main.py +++ b/app/main.py @@ -7,7 +7,7 @@ from fastapi import Depends, FastAPI, Request # 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 + pitchingstats, fieldingstats, draftpicks, draftlist, managers, awards, draftdata, keepers 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 'WARN' @@ -36,6 +36,8 @@ app.include_router(draftpicks.router) app.include_router(draftlist.router) app.include_router(managers.router) app.include_router(awards.router) +app.include_router(draftdata.router) +app.include_router(keepers.router) # @app.get("/docs", include_in_schema=False) diff --git a/app/routers_v3/draftdata.py b/app/routers_v3/draftdata.py new file mode 100644 index 0000000..28d98dd --- /dev/null +++ b/app/routers_v3/draftdata.py @@ -0,0 +1,73 @@ +import datetime + +from fastapi import APIRouter, Depends, HTTPException +from typing import Optional +import logging +import pydantic + +from ..db_engine import db, DraftData, model_to_dict +from ..dependencies import oauth2_scheme, valid_token + +router = APIRouter( + prefix='/api/v3/draftdata', + tags=['draftdata'] +) + + +class DraftDataModel(pydantic.BaseModel): + currentpick: int + timer: bool + pick_deadline: datetime.datetime + result_channel_id = int + ping_channel_id = int + pick_minutes = int + + +@router.get('') +async def get_draftdata(): + draft_data = DraftData.get_or_none() + + if draft_data is not None: + r_data = model_to_dict(draft_data) + db.close() + return r_data + + raise HTTPException(status_code=404, detail=f'No draft data found') + + +@router.patch('/{data_id}') +async def patch_draftdata( + data_id: int, currentpick: Optional[int] = None, timer: Optional[bool] = None, + pick_deadline: Optional[datetime.datetime] = None, result_channel: Optional[int] = None, + ping_channel: Optional[int] = None, pick_minutes: Optional[int] = None, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_draftdata - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + draft_data = DraftData.get_or_none(DraftData.id == data_id) + if draft_data is None: + db.close() + raise HTTPException(status_code=404, detail=f'No draft data found') + + if currentpick is not None: + draft_data.currentpick = currentpick + if timer is not None: + draft_data.timer = timer + if pick_deadline is not None: + draft_data.pick_deadline = pick_deadline + if result_channel is not None: + draft_data.result_channel = result_channel + if ping_channel is not None: + draft_data.ping_channel = ping_channel + if pick_minutes is not None: + draft_data.pick_minutes = pick_minutes + + saved = draft_data.save() + r_data = model_to_dict(draft_data) + db.close() + + if saved == 1: + return r_data + else: + raise HTTPException(status_code=500, detail='Updating draft data failed') + diff --git a/app/routers_v3/keepers.py b/app/routers_v3/keepers.py new file mode 100644 index 0000000..cdc75c2 --- /dev/null +++ b/app/routers_v3/keepers.py @@ -0,0 +1,113 @@ +from fastapi import APIRouter, Depends, HTTPException, Query, Response +from typing import List, Optional +import logging +import pydantic + +from ..db_engine import db, Keeper, Player, model_to_dict, chunked, fn +from ..dependencies import oauth2_scheme, valid_token, LOG_DATA + +logging.basicConfig(filename=LOG_DATA['filename'], format=LOG_DATA['format'], level=LOG_DATA['log_level']) + +router = APIRouter( + prefix='/api/v3/keepers', + tags=['keepers'] +) + + +class KeeperModel(pydantic.BaseModel): + season: int + team_id: int + player_id: int + + +class KeeperList(pydantic.BaseModel): + count: Optional[int] = None + keepers: List[KeeperModel] + + +@router.get('') +async def get_keepers( + season: list = Query(default=None), team_id: list = Query(default=None), player_id: list = Query(default=None), + short_output: bool = False): + all_keepers = Keeper.select() + + if season is not None: + all_keepers = all_keepers.where(Keeper.season << season) + if team_id is not None: + all_keepers = all_keepers.where(Keeper.team_id << team_id) + if player_id is not None: + all_keepers = all_keepers.where(Keeper.player_id << player_id) + + return_keepers = { + 'count': all_keepers.count(), + 'keepers': [model_to_dict(x, recurse=not short_output) for x in all_keepers] + } + db.close() + return return_keepers + + +@router.patch('/{keeper_id}') +async def patch_keeper( + keeper_id: int, season: Optional[int] = None, team_id: Optional[int] = None, player_id: Optional[int] = None, + token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'patch_keeper - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_keeper = Keeper.get_or_none(Keeper.id == keeper_id) + if not this_keeper: + raise HTTPException(status_code=404, detail=f'Keeper ID {keeper_id} not found') + + if season is not None: + this_keeper.season = season + if player_id is not None: + this_keeper.player_id = player_id + if team_id is not None: + this_keeper.team_id = team_id + + if this_keeper.save(): + r_keeper = model_to_dict(this_keeper) + db.close() + return r_keeper + else: + db.close() + raise HTTPException(status_code=500, detail=f'Unable to patch keeper {keeper_id}') + + +@router.post('') +async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'post_keepers - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + new_keepers = [] + for keeper in k_list.keepers: + new_keepers.append(keeper.dict()) + + with db.atomic(): + for batch in chunked(new_keepers, 14): + Keeper.insert_many(batch).on_conflict_replace().execute() + db.close() + + return f'Inserted {len(new_keepers)} keepers' + + +@router.delete('/{keeper_id}') +async def delete_keeper(keeper_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'delete_keeper - Bad Token: {token}') + raise HTTPException(status_code=401, detail='Unauthorized') + + this_keeper = Keeper.get_or_none(Keeper.id == keeper_id) + if not this_keeper: + raise HTTPException(status_code=404, detail=f'Keeper ID {keeper_id} not found') + + count = this_keeper.delete_instance() + db.close() + + if count == 1: + return f'Keeper ID {keeper_id} has been deleted' + else: + raise HTTPException(status_code=500, detail=f'Keeper ID {keeper_id} could not be deleted') + + From 1f007c654cf04003031c7ec8a025d04d3f0e5ee4 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Mon, 17 Jul 2023 23:08:37 -0500 Subject: [PATCH 21/24] Add sorting to draftpicks --- app/routers_v3/draftpicks.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/routers_v3/draftpicks.py b/app/routers_v3/draftpicks.py index 78fe623..3d8d93e 100644 --- a/app/routers_v3/draftpicks.py +++ b/app/routers_v3/draftpicks.py @@ -31,7 +31,7 @@ async def get_picks( owner_team_id: list = Query(default=None), orig_team_id: list = Query(default=None), pick_round_start: Optional[int] = None, pick_round_end: Optional[int] = None, traded: Optional[bool] = None, overall: Optional[int] = None, overall_start: Optional[int] = None, overall_end: Optional[int] = None, - short_output: Optional[bool] = True): + short_output: Optional[bool] = True, sort: Optional[str] = None, limit: Optional[int] = None): all_picks = DraftPick.select().where(DraftPick.season == season) if owner_team_abbrev is not None: @@ -75,6 +75,14 @@ async def get_picks( all_picks = all_picks.where(DraftPick.overall >= overall_start) if overall_end is not None: all_picks = all_picks.where(DraftPick.overall <= overall_end) + if limit is not None: + all_picks = all_picks.limit(limit) + + if sort is not None: + if sort == 'order-asc': + all_picks = all_picks.order_by(DraftPick.overall) + elif sort == 'order-desc': + all_picks = all_picks.order_by(-DraftPick.overall) return_picks = {'count': all_picks.count(), 'picks': []} for line in all_picks: From 115a50c50ae9ac3c11f619fc9828a7bef4ab48dd Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Mon, 17 Jul 2023 23:08:53 -0500 Subject: [PATCH 22/24] Typo fixes --- app/routers_v3/draftlist.py | 2 +- app/routers_v3/teams.py | 2 +- app/routers_v3/transactions.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/routers_v3/draftlist.py b/app/routers_v3/draftlist.py index 4832c86..01ca060 100644 --- a/app/routers_v3/draftlist.py +++ b/app/routers_v3/draftlist.py @@ -28,7 +28,7 @@ class DraftListList(pydantic.BaseModel): async def get_draftlist( season: Optional[int], team_id: list = Query(default=None), token: str = Depends(oauth2_scheme)): if not valid_token(token): - logging.warning(f'post_draftlist - Bad Token: {token}') + logging.warning(f'get_draftlist - Bad Token: {token}') raise HTTPException(status_code=401, detail='Unauthorized') all_list = DraftList.select() diff --git a/app/routers_v3/teams.py b/app/routers_v3/teams.py index 0c40f57..77e8205 100644 --- a/app/routers_v3/teams.py +++ b/app/routers_v3/teams.py @@ -191,7 +191,7 @@ async def patch_team( raise HTTPException(status_code=500, detail=f'Unable to patch team {team_id}') -@router.get('') +@router.post('') async def post_team(team_list: TeamList, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning(f'post_team - Bad Token: {token}') diff --git a/app/routers_v3/transactions.py b/app/routers_v3/transactions.py index 17f159f..c8edd0f 100644 --- a/app/routers_v3/transactions.py +++ b/app/routers_v3/transactions.py @@ -123,7 +123,7 @@ async def post_transactions(moves: TransactionList, token: str = Depends(oauth2_ raise HTTPException(status_code=404, detail=f'Team ID {x.oldteam_id} not found') if Team.get_or_none(Team.id == x.newteam_id) is None: raise HTTPException(status_code=404, detail=f'Team ID {x.newteam_id} not found') - if Player.get_or_none(Player.id == x.player_id): + if Player.get_or_none(Player.id == x.player_id) is None: raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found') all_moves.append(x.dict()) From 8f316fb406bec65698afd522328982086e77f235 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Mon, 17 Jul 2023 23:09:04 -0500 Subject: [PATCH 23/24] Season 8 updates to players --- app/routers_v3/players.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py index 48cc431..9480f89 100644 --- a/app/routers_v3/players.py +++ b/app/routers_v3/players.py @@ -31,6 +31,8 @@ class PlayerModel(pydantic.BaseModel): pos_6: Optional[str] = None pos_7: Optional[str] = None pos_8: Optional[str] = None + vanity_card: Optional[str] = None + headshot: Optional[str] = None last_game: Optional[str] = None last_game2: Optional[str] = None il_return: Optional[str] = None @@ -47,13 +49,17 @@ class PlayerList(pydantic.BaseModel): @router.get('') async def get_players( season: Optional[int], name: Optional[str] = None, team_id: list = Query(default=None), - pos: list = Query(default=None), is_injured: Optional[bool] = None, sort: Optional[str] = None, - short_output: Optional[bool] = False, csv: Optional[bool] = False): + pos: list = Query(default=None), strat_code: list = Query(default=None), is_injured: Optional[bool] = None, + sort: Optional[str] = None, short_output: Optional[bool] = False, csv: Optional[bool] = False): all_players = Player.select_season(season) if team_id is not None: all_players = all_players.where(Player.team_id << team_id) + if strat_code is not None: + code_list = [x.lower() for x in strat_code] + all_players = all_players.where(fn.Lower(Player.strat_code) << code_list) + if name is not None: all_players = all_players.where(fn.lower(Player.name) == name.lower()) @@ -82,7 +88,7 @@ async def get_players( player_list = [ ['name', 'wara', 'image', 'image2', 'team', 'season', 'pitcher_injury', 'pos_1', 'pos_2', 'pos_3', 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'last_game', 'last_game2', 'il_return', 'demotion_week', - 'headshot', 'vanity_card', 'strat_code', 'bbref_id', 'injury_rating'] + 'headshot', 'vanity_card', 'strat_code', 'bbref_id', 'injury_rating', 'player_id'] ] for line in all_players: player_list.append( @@ -90,7 +96,7 @@ async def get_players( line.name, line.wara, line.image, line.image2, line.team.abbrev, line.season, line.pitcher_injury, line.pos_1, line.pos_2, line.pos_3, line.pos_4, line.pos_5, line.pos_6, line.pos_7, line.pos_8, line.last_game, line.last_game2, line.il_return, line.demotion_week, line.headshot, - line.vanity_card, line.strat_code.replace(",", "-_-"), line.bbref_id, line.injury_rating + line.vanity_card, line.strat_code.replace(",", "-_-"), line.bbref_id, line.injury_rating, line.id ] ) return_players = { From 6c03eabfe30e3dd8c64b9983252109611e2fefea Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Mon, 24 Jul 2023 23:04:26 -0500 Subject: [PATCH 24/24] Season 8 Draft Fixes --- app/db_engine.py | 2 +- app/routers_v3/draftpicks.py | 7 ++++++- app/routers_v3/standings.py | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/db_engine.py b/app/db_engine.py index a331277..c7d50de 100644 --- a/app/db_engine.py +++ b/app/db_engine.py @@ -246,7 +246,7 @@ class Team(BaseModel): return [self.gmid] def get_this_week(self): - active_team = Player.select_season(self.season).where(Player.team == self) + 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': []} diff --git a/app/routers_v3/draftpicks.py b/app/routers_v3/draftpicks.py index 3d8d93e..8cb4a10 100644 --- a/app/routers_v3/draftpicks.py +++ b/app/routers_v3/draftpicks.py @@ -31,7 +31,8 @@ async def get_picks( owner_team_id: list = Query(default=None), orig_team_id: list = Query(default=None), pick_round_start: Optional[int] = None, pick_round_end: Optional[int] = None, traded: Optional[bool] = None, overall: Optional[int] = None, overall_start: Optional[int] = None, overall_end: Optional[int] = None, - short_output: Optional[bool] = True, sort: Optional[str] = None, limit: Optional[int] = None): + short_output: Optional[bool] = False, sort: Optional[str] = None, limit: Optional[int] = None, + player_id: list = Query(default=None), player_taken: Optional[bool] = None): all_picks = DraftPick.select().where(DraftPick.season == season) if owner_team_abbrev is not None: @@ -63,6 +64,8 @@ async def get_picks( if pick_round_start is not None and pick_round_end is not None and pick_round_end < pick_round_start: raise HTTPException(status_code=400, detail=f'pick_round_end must be greater than or equal to pick_round_start') + if player_id is not None: + all_picks = all_picks.where(DraftPick.player_id << player_id) if pick_round_start is not None: all_picks = all_picks.where(DraftPick.round >= pick_round_start) if pick_round_end is not None: @@ -75,6 +78,8 @@ async def get_picks( all_picks = all_picks.where(DraftPick.overall >= overall_start) if overall_end is not None: all_picks = all_picks.where(DraftPick.overall <= overall_end) + if player_taken is not None: + all_picks = all_picks.where(DraftPick.player.is_null(not player_taken)) if limit is not None: all_picks = all_picks.limit(limit) diff --git a/app/routers_v3/standings.py b/app/routers_v3/standings.py index f1ff85f..c671f6d 100644 --- a/app/routers_v3/standings.py +++ b/app/routers_v3/standings.py @@ -18,9 +18,9 @@ async def get_standings( division_abbrev: Optional[str] = None, short_output: Optional[bool] = False): standings = Standings.select_season(season) - if standings.count() == 0: - db.close() - raise HTTPException(status_code=404, detail=f'No output for season {season}') + # if standings.count() == 0: + # db.close() + # raise HTTPException(status_code=404, detail=f'No output for season {season}') if team_abbrev is not None: t_query = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev])