From 8c11ae81bc908be27c1608e3e4e28416c59bcf37 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Thu, 5 Mar 2026 07:03:17 -0600 Subject: [PATCH] fix: remove debug print() statements from gameplay_models.py (#32) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deleted select_speed_testing() and select_all_testing() — dead code that only printed to stdout. Both functions were already commented out in main(). Co-Authored-By: Claude Sonnet 4.6 --- in_game/gameplay_models.py | 1113 +++++++++++++++++++++++------------- 1 file changed, 703 insertions(+), 410 deletions(-) diff --git a/in_game/gameplay_models.py b/in_game/gameplay_models.py index 77b76cd..c4df7d6 100644 --- a/in_game/gameplay_models.py +++ b/in_game/gameplay_models.py @@ -8,26 +8,45 @@ import discord import pydantic from pydantic import field_validator -from sqlmodel import Session, SQLModel, UniqueConstraint, create_engine, select, or_, Field, Relationship, text, BigInteger +from sqlmodel import ( + Session, + SQLModel, + UniqueConstraint, + create_engine, + select, + or_, + Field, + Relationship, + text, + BigInteger, +) from sqlalchemy import Column, func, desc from exceptions import * -from in_game.managerai_responses import DefenseResponse, JumpResponse, RunResponse, TagResponse, ThrowResponse, UncappedRunResponse +from in_game.managerai_responses import ( + DefenseResponse, + JumpResponse, + RunResponse, + TagResponse, + ThrowResponse, + UncappedRunResponse, +) - -logger = logging.getLogger('discord_app') +logger = logging.getLogger("discord_app") # sqlite_url = 'sqlite:///storage/gameplay.db' # connect_args = {"check_same_thread": False} # engine = create_engine(sqlite_url, echo=False, connect_args=connect_args) -postgres_url = f'postgresql://{os.getenv('DB_USERNAME')}:{os.getenv('DB_PASSWORD')}@{os.getenv('DB_URL')}/{os.getenv('DB_NAME')}' +postgres_url = f"postgresql://{os.getenv('DB_USERNAME')}:{os.getenv('DB_PASSWORD')}@{os.getenv('DB_URL')}/{os.getenv('DB_NAME')}" engine = create_engine(postgres_url, pool_size=10, max_overflow=30) -CACHE_LIMIT = 259200 # 1209600 # in seconds -SBA_COLOR = 'a6ce39' -SBA_LOGO = 'https://paper-dynasty.s3.us-east-1.amazonaws.com/static-images/sba-logo.png' +CACHE_LIMIT = 259200 # 1209600 # in seconds +SBA_COLOR = "a6ce39" +SBA_LOGO = "https://paper-dynasty.s3.us-east-1.amazonaws.com/static-images/sba-logo.png" class ManagerAiBase(SQLModel): - id: int | None = Field(sa_column=Column(BigInteger(), primary_key=True, autoincrement=True)) + id: int | None = Field( + sa_column=Column(BigInteger(), primary_key=True, autoincrement=True) + ) name: str = Field(index=True) steal: int | None = Field(default=5) running: int | None = Field(default=5) @@ -43,26 +62,32 @@ class ManagerAiBase(SQLModel): class GameCardsetLink(SQLModel, table=True): - game_id: int | None = Field(default=None, foreign_key='game.id', primary_key=True) - cardset_id: int | None = Field(default=None, foreign_key='cardset.id', primary_key=True) + game_id: int | None = Field(default=None, foreign_key="game.id", primary_key=True) + cardset_id: int | None = Field( + default=None, foreign_key="cardset.id", primary_key=True + ) priority: int | None = Field(default=1, index=True) - game: 'Game' = Relationship(back_populates='cardset_links') - cardset: 'Cardset' = Relationship(back_populates='game_links') + game: "Game" = Relationship(back_populates="cardset_links") + cardset: "Cardset" = Relationship(back_populates="game_links") class RosterLink(SQLModel, table=True): - game_id: int | None = Field(default=None, foreign_key='game.id', primary_key=True) - card_id: int | None = Field(default=None, foreign_key='card.id', primary_key=True) - team_id: int = Field(index=True, foreign_key='team.id') + game_id: int | None = Field(default=None, foreign_key="game.id", primary_key=True) + card_id: int | None = Field(default=None, foreign_key="card.id", primary_key=True) + team_id: int = Field(index=True, foreign_key="team.id") - game: 'Game' = Relationship(back_populates='roster_links') - card: 'Card' = Relationship() - team: 'Team' = Relationship() + game: "Game" = Relationship(back_populates="roster_links") + card: "Card" = Relationship() + team: "Team" = Relationship() class TeamBase(SQLModel): - id: int = Field(sa_column=Column(BigInteger(), primary_key=True, autoincrement=False, unique=True)) + id: int = Field( + sa_column=Column( + BigInteger(), primary_key=True, autoincrement=False, unique=True + ) + ) abbrev: str = Field(index=True) sname: str lname: str @@ -79,7 +104,9 @@ class TeamBase(SQLModel): ranking: int has_guide: bool is_ai: bool - created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True) + created: datetime.datetime = Field( + default_factory=datetime.datetime.now, nullable=True + ) @property def description(self) -> str: @@ -87,26 +114,29 @@ class TeamBase(SQLModel): class Team(TeamBase, table=True): - cards: list['Card'] = Relationship(back_populates='team', cascade_delete=True) - lineups: list['Lineup'] = Relationship(back_populates='team', cascade_delete=True) + cards: list["Card"] = Relationship(back_populates="team", cascade_delete=True) + lineups: list["Lineup"] = Relationship(back_populates="team", cascade_delete=True) # away_games: list['Game'] = Relationship(back_populates='away_team') # home_games: list['Game'] = Relationship(back_populates='home_team') @property def embed(self) -> discord.Embed: embed = discord.Embed( - title=f'{self.lname}', - color=int(self.color, 16) if self.color else int(SBA_COLOR, 16) + title=f"{self.lname}", + color=int(self.color, 16) if self.color else int(SBA_COLOR, 16), ) - embed.set_footer(text=f'Paper Dynasty Season {self.season}', icon_url=SBA_LOGO) + embed.set_footer(text=f"Paper Dynasty Season {self.season}", icon_url=SBA_LOGO) embed.set_thumbnail(url=self.logo if self.logo else SBA_LOGO) return embed - + class Game(SQLModel, table=True): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=True)) - away_team_id: int = Field(foreign_key='team.id') - home_team_id: int = Field(foreign_key='team.id') + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=True), + ) + away_team_id: int = Field(foreign_key="team.id") + home_team_id: int = Field(foreign_key="team.id") channel_id: int = Field(sa_column=(Column(BigInteger(), index=True))) season: int active: bool | None = Field(default=True) @@ -123,8 +153,12 @@ class Game(SQLModel, table=True): roll_buttons: bool | None = Field(default=True) auto_roll: bool | None = Field(default=False) - cardset_links: list[GameCardsetLink] = Relationship(back_populates='game', cascade_delete=True) - roster_links: list[RosterLink] = Relationship(back_populates='game', cascade_delete=True) + cardset_links: list[GameCardsetLink] = Relationship( + back_populates="game", cascade_delete=True + ) + roster_links: list[RosterLink] = Relationship( + back_populates="game", cascade_delete=True + ) away_team: Team = Relationship( # back_populates='away_games', # sa_relationship_kwargs={ @@ -143,26 +177,31 @@ class Game(SQLModel, table=True): # } sa_relationship_kwargs=dict(foreign_keys="[Game.home_team_id]") ) - lineups: list['Lineup'] = Relationship(back_populates='game', cascade_delete=True) - plays: list['Play'] = Relationship(back_populates='game', cascade_delete=True) + lineups: list["Lineup"] = Relationship(back_populates="game", cascade_delete=True) + plays: list["Play"] = Relationship(back_populates="game", cascade_delete=True) - @field_validator('ai_team', 'game_type') + @field_validator("ai_team", "game_type") def lowercase_strings(cls, value: str) -> str: return value.lower() - + @property def cardset_param_string(self) -> str: - pri_cardsets = '' - back_cardsets = '' + pri_cardsets = "" + back_cardsets = "" for link in self.cardset_links: if link.priority == 1: - pri_cardsets += f'&cardset_id={link.cardset_id}' + pri_cardsets += f"&cardset_id={link.cardset_id}" else: - back_cardsets += f'&backup_cardset_id={link.cardset_id}' - return f'{pri_cardsets}{back_cardsets}' + back_cardsets += f"&backup_cardset_id={link.cardset_id}" + return f"{pri_cardsets}{back_cardsets}" def current_play_or_none(self, session: Session): - this_play = session.exec(select(Play).where(Play.game == self, Play.complete == False).order_by(Play.play_num.desc()).limit(1)).all() + this_play = session.exec( + select(Play) + .where(Play.game == self, Play.complete == False) + .order_by(Play.play_num.desc()) + .limit(1) + ).all() if len(this_play) == 1: return this_play[0] else: @@ -175,11 +214,15 @@ class Game(SQLModel, table=True): existing_play = self.current_play_or_none(session) if existing_play is not None: return existing_play - - all_plays = session.exec(select(func.count(Play.id)).where(Play.game == self)).one() + + all_plays = session.exec( + select(func.count(Play.id)).where(Play.game == self) + ).one() if all_plays > 0: - raise PlayInitException(f'{all_plays} plays for game {self.id} already exist, but all are complete.') - + raise PlayInitException( + f"{all_plays} plays for game {self.id} already exist, but all are complete." + ) + leadoff_batter, home_pitcher, home_catcher = None, None, None home_positions, away_positions = [], [] for line in [x for x in self.lineups if x.active]: @@ -191,25 +234,31 @@ class Game(SQLModel, table=True): else: if line.position not in home_positions: home_positions.append(line.position) - if line.position == 'P': + if line.position == "P": home_pitcher = line - elif line.position == 'C': + elif line.position == "C": home_catcher = line - + if len(home_positions) != 10: - e_msg = f'Only {len(home_positions)} players found on home team' + e_msg = f"Only {len(home_positions)} players found on home team" log_exception(LineupsMissingException, e_msg) if len(away_positions) != 10: - e_msg = f'Only {len(away_positions)} players found on away team' + e_msg = f"Only {len(away_positions)} players found on away team" log_exception(LineupsMissingException, e_msg) if None in [leadoff_batter, home_pitcher, home_catcher]: - e_msg = f'Could not set the initial pitcher, catcher, and batter' + e_msg = f"Could not set the initial pitcher, catcher, and batter" log_exception(LineupsMissingException, e_msg) - manager_ai_id = ((datetime.datetime.now().day * (self.away_team_id if self.ai_team == 'away' else self.home_team_id)) % 3) + 1 + manager_ai_id = ( + ( + datetime.datetime.now().day + * (self.away_team_id if self.ai_team == "away" else self.home_team_id) + ) + % 3 + ) + 1 if manager_ai_id > 3 or manager_ai_id < 1: manager_ai_id = 1 - + new_play = Play( game=self, play_num=1, @@ -219,7 +268,7 @@ class Game(SQLModel, table=True): catcher=home_catcher, is_tied=True, is_new_inning=True, - managerai_id=manager_ai_id + managerai_id=manager_ai_id, ) session.add(new_play) session.commit() @@ -230,30 +279,36 @@ class Game(SQLModel, table=True): return new_play def team_lineup(self, session: Session, team: Team, with_links: bool = True) -> str: - all_lineups = session.exec(select(Lineup).where(Lineup.team == team, Lineup.game == self, Lineup.active).order_by(Lineup.batting_order)).all() + all_lineups = session.exec( + select(Lineup) + .where(Lineup.team == team, Lineup.game == self, Lineup.active) + .order_by(Lineup.batting_order) + ).all() - logger.info(f'all_lineups: {all_lineups}') - lineup_val = '' + logger.info(f"all_lineups: {all_lineups}") + lineup_val = "" for line in all_lineups: - logger.info(f'line in all_lineups: {line}') + logger.info(f"line in all_lineups: {line}") if with_links: - name_string = line.player.name_card_link("batting" if line.position != "P" else "pitching") + name_string = line.player.name_card_link( + "batting" if line.position != "P" else "pitching" + ) else: - name_string = f'{line.player.name_with_desc}' + name_string = f"{line.player.name_with_desc}" - if line.position == 'P': + if line.position == "P": if line.card.pitcherscouting: this_hand = line.card.pitcherscouting.pitchingcard.hand elif line.card.batterscouting: # Fallback to batting hand if pitcherscouting is missing this_hand = line.card.batterscouting.battingcard.hand else: - this_hand = '?' + this_hand = "?" else: this_hand = line.card.batterscouting.battingcard.hand - lineup_val += f'{line.batting_order}. {this_hand.upper()} | {name_string}, {line.position}\n' - + lineup_val += f"{line.batting_order}. {this_hand.upper()} | {name_string}, {line.position}\n" + return lineup_val @property @@ -266,29 +321,28 @@ class Game(SQLModel, table=True): raise NoHumanTeamsException else: raise MultipleHumanTeamsException - + @property def league_name(self): - if 'gauntlet' in self.game_type: - parts = self.game_type.split('-') - return f'{parts[0]}-{parts[1]}' + if "gauntlet" in self.game_type: + parts = self.game_type.split("-") + return f"{parts[0]}-{parts[1]}" else: return self.game_type class ManagerAi(ManagerAiBase, table=True): - plays: list['Play'] = Relationship(back_populates='managerai') + plays: list["Play"] = Relationship(back_populates="managerai") + def create_ai(session: Session = None): def get_new_ai(this_session: Session): all_ai = this_session.exec(select(ManagerAi.id)).all() if len(all_ai) == 0: - logger.info(f'Creating ManagerAI records') + logger.info(f"Creating ManagerAI records") new_ai = [ + ManagerAi(name="Balanced"), ManagerAi( - name='Balanced' - ), - ManagerAi( - name='Yolo', + name="Yolo", steal=10, running=10, hold=5, @@ -299,10 +353,10 @@ class ManagerAi(ManagerAiBase, table=True): bullpen_matchup=3, behind_aggression=10, ahead_aggression=10, - decide_throw=10 + decide_throw=10, ), ManagerAi( - name='Safe', + name="Safe", steal=3, running=3, hold=8, @@ -313,44 +367,59 @@ class ManagerAi(ManagerAiBase, table=True): bullpen_matchup=8, behind_aggression=5, ahead_aggression=1, - decide_throw=1 - ) + decide_throw=1, + ), ] for x in new_ai: session.add(x) session.commit() - + if session is None: with Session(engine) as session: get_new_ai(session) else: get_new_ai(session) - + return True - def check_jump(self, session: Session, this_game: Game, to_base: Literal[2, 3, 4]) -> JumpResponse: - logger.info(f'Checking jump to {to_base} in Game {this_game.id}') + def check_jump( + self, session: Session, this_game: Game, to_base: Literal[2, 3, 4] + ) -> JumpResponse: + logger.info(f"Checking jump to {to_base} in Game {this_game.id}") this_resp = JumpResponse(min_safe=20) this_play = this_game.current_play_or_none(session) if this_play is None: - raise GameException(f'No game found while checking for jump') - + raise GameException(f"No game found while checking for jump") + num_outs = this_play.starting_outs run_diff = this_play.away_score - this_play.home_score - if this_game.ai_team == 'home': + if this_game.ai_team == "home": run_diff = run_diff * -1 - + pitcher_hold = this_play.pitcher.card.pitcherscouting.pitchingcard.hold - catcher_defense = session.exec(select(PositionRating).where(PositionRating.player_id == this_play.catcher.player_id, PositionRating.position == 'C', PositionRating.variant == this_play.catcher.card.variant)).one() + catcher_defense = session.exec( + select(PositionRating).where( + PositionRating.player_id == this_play.catcher.player_id, + PositionRating.position == "C", + PositionRating.variant == this_play.catcher.card.variant, + ) + ).one() catcher_hold = catcher_defense.arm - battery_hold = pitcher_hold + catcher_hold - logger.info(f'game state: {num_outs} outs, {run_diff} run diff, battery_hold: {battery_hold}') - + battery_hold = pitcher_hold + catcher_hold + logger.info( + f"game state: {num_outs} outs, {run_diff} run diff, battery_hold: {battery_hold}" + ) + if to_base == 2: runner = this_play.on_first if runner is None: - log_exception(CardNotFoundException, f'Attempted to check a jump to 2nd base, but no runner found on first.') - logger.info(f'Checking steal numbers for {runner.player.name} in Game {this_game.id}') + log_exception( + CardNotFoundException, + f"Attempted to check a jump to 2nd base, but no runner found on first.", + ) + logger.info( + f"Checking steal numbers for {runner.player.name} in Game {this_game.id}" + ) match self.steal: case 10: @@ -365,34 +434,39 @@ class ManagerAi(ManagerAiBase, table=True): this_resp.min_safe = 16 + num_outs case _: this_resp.min_safe = 17 + num_outs - + if self.steal > 7 and num_outs < 2 and run_diff <= 5: this_resp.run_if_auto_jump = True elif self.steal < 5: this_resp.must_auto_jump = True - + runner_card = runner.card.batterscouting.battingcard if this_resp.run_if_auto_jump and runner_card.steal_auto: - this_resp.ai_note = f'- WILL SEND **{runner.player.name}** to second!' + this_resp.ai_note = f"- WILL SEND **{runner.player.name}** to second!" elif this_resp.must_auto_jump and not runner_card.steal_auto: - logger.info(f'No jump ai note') + logger.info(f"No jump ai note") else: jump_safe_range = runner_card.steal_high + battery_hold nojump_safe_range = runner_card.steal_low + battery_hold - logger.info(f'jump_safe_range: {jump_safe_range} / nojump_safe_range: {nojump_safe_range} / min_safe: {this_resp.min_safe}') + logger.info( + f"jump_safe_range: {jump_safe_range} / nojump_safe_range: {nojump_safe_range} / min_safe: {this_resp.min_safe}" + ) if this_resp.min_safe <= nojump_safe_range: - this_resp.ai_note = f'- SEND **{runner.player.name}** to second!' + this_resp.ai_note = f"- SEND **{runner.player.name}** to second!" elif this_resp.min_safe <= jump_safe_range: - this_resp.ai_note = f'- SEND **{runner.player.name}** to second if they get the jump' + this_resp.ai_note = f"- SEND **{runner.player.name}** to second if they get the jump" elif to_base == 3: runner = this_play.on_second if runner is None: - log_exception(CardNotFoundException, f'Attempted to check a jump to 3rd base, but no runner found on second.') + log_exception( + CardNotFoundException, + f"Attempted to check a jump to 3rd base, but no runner found on second.", + ) match self.steal: case 10: @@ -405,55 +479,66 @@ class ManagerAi(ManagerAiBase, table=True): if self.steal == 10 and num_outs < 2 and run_diff <= 5: this_resp.run_if_auto_jump = True elif self.steal <= 5: - this_resp.must_auto_jump = True - + this_resp.must_auto_jump = True + runner_card = runner.card.batterscouting.battingcard if this_resp.run_if_auto_jump and runner_card.steal_auto: - this_resp.ai_note = f'- SEND **{runner.player.name}** to third!' + this_resp.ai_note = f"- SEND **{runner.player.name}** to third!" - elif this_resp.must_auto_jump and not runner_card.steal_auto or this_resp.min_safe is None: - logger.info(f'No jump ai note') + elif ( + this_resp.must_auto_jump + and not runner_card.steal_auto + or this_resp.min_safe is None + ): + logger.info(f"No jump ai note") else: jump_safe_range = runner_card.steal_low + battery_hold - logger.info(f'jump_safe_range: {jump_safe_range} / min_safe: {this_resp.min_safe}') + logger.info( + f"jump_safe_range: {jump_safe_range} / min_safe: {this_resp.min_safe}" + ) if this_resp.min_safe <= jump_safe_range: - this_resp.ai_note = f'- SEND **{runner.player.name}** to third!' - + this_resp.ai_note = f"- SEND **{runner.player.name}** to third!" + elif run_diff in [-1, 0]: runner = this_play.on_third if runner is None: - log_exception(CardNotFoundException, f'Attempted to check a jump to home, but no runner found on third.') + log_exception( + CardNotFoundException, + f"Attempted to check a jump to home, but no runner found on third.", + ) if self.steal == 10: this_resp.min_safe = 5 elif this_play.inning_num > 7 and self.steal >= 5: - this_resp.min_safe = 6 + this_resp.min_safe = 6 elif self.steal > 5: - this_resp.min_safe = 7 + this_resp.min_safe = 7 elif self.steal > 2: - this_resp.min_safe = 8 + this_resp.min_safe = 8 else: - this_resp.min_safe = 10 - + this_resp.min_safe = 10 + runner_card = runner.card.batterscouting.battingcard jump_safe_range = runner_card.steal_low - 9 if this_resp.min_safe <= jump_safe_range: - this_resp.ai_note = f'- SEND **{runner.player.name}** to third!' - - logger.info(f'Returning jump resp to game {this_game.id}: {this_resp}') + this_resp.ai_note = f"- SEND **{runner.player.name}** to third!" + + logger.info(f"Returning jump resp to game {this_game.id}: {this_resp}") return this_resp - + def tag_from_second(self, session: Session, this_game: Game) -> TagResponse: this_resp = TagResponse() this_play = this_game.current_play_or_none(session) if this_play is None: - raise GameException(f'No game found while checking tag_from_second') - + raise GameException(f"No game found while checking tag_from_second") + ai_rd = this_play.ai_run_diff - aggression_mod = abs(self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5) + aggression_mod = abs( + self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5 + ) adjusted_running = self.running + aggression_mod if adjusted_running >= 8: @@ -462,23 +547,25 @@ class ManagerAi(ManagerAiBase, table=True): this_resp.min_safe = 7 else: this_resp.min_safe = 10 - + if this_play.starting_outs == 1: this_resp.min_safe -= 2 else: this_resp.min_safe += 2 - - logger.info(f'tag_from_second response: {this_resp}') + + logger.info(f"tag_from_second response: {this_resp}") return this_resp def tag_from_third(self, session: Session, this_game: Game) -> TagResponse: this_resp = TagResponse() this_play = this_game.current_play_or_none(session) if this_play is None: - raise GameException(f'No game found while checking tag_from_third') - + raise GameException(f"No game found while checking tag_from_third") + ai_rd = this_play.ai_run_diff - aggression_mod = abs(self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5) + aggression_mod = abs( + self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5 + ) adjusted_running = self.running + aggression_mod if adjusted_running >= 8: @@ -487,22 +574,22 @@ class ManagerAi(ManagerAiBase, table=True): this_resp.min_safe = 10 else: this_resp.min_safe = 12 - + if ai_rd in [-1, 0]: this_resp.min_safe -= 2 - + if this_play.starting_outs == 1: this_resp.min_safe -= 2 - - logger.info(f'tag_from_third response: {this_resp}') + + logger.info(f"tag_from_third response: {this_resp}") return this_resp def throw_at_uncapped(self, session: Session, this_game: Game) -> ThrowResponse: this_resp = ThrowResponse() this_play = this_game.current_play_or_none(session) if this_play is None: - raise GameException(f'No game found while checking throw_at_uncapped') - + raise GameException(f"No game found while checking throw_at_uncapped") + ai_rd = this_play.ai_run_diff aggression = self.ahead_aggression if ai_rd > 0 else self.behind_aggression current_outs = this_play.starting_outs + this_play.outs @@ -533,24 +620,30 @@ class ManagerAi(ManagerAiBase, table=True): if self.behind_aggression < 5: this_resp.at_trail_runner = True this_resp.trail_max_safe_delta = -4 - - logger.info(f'throw_at_uncapped response: {this_resp}') + + logger.info(f"throw_at_uncapped response: {this_resp}") return this_resp - def uncapped_advance(self, session: Session, this_game: Game, lead_base: int, trail_base: int) -> UncappedRunResponse: + def uncapped_advance( + self, session: Session, this_game: Game, lead_base: int, trail_base: int + ) -> UncappedRunResponse: this_resp = UncappedRunResponse() this_play = this_game.current_play_or_none(session) if this_play is None: - raise GameException(f'No game found while checking uncapped_advance_lead') - + raise GameException(f"No game found while checking uncapped_advance_lead") + ai_rd = this_play.ai_run_diff - aggression = self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5 + aggression = ( + self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5 + ) if ai_rd > 4: if lead_base == 4: this_resp.min_safe = 16 - this_play.starting_outs - aggression this_resp.send_trail = True - this_resp.trail_min_safe = 10 - aggression - this_play.starting_outs - this_play.outs + this_resp.trail_min_safe = ( + 10 - aggression - this_play.starting_outs - this_play.outs + ) elif lead_base == 3: this_resp.min_safe = 14 + (this_play.starting_outs * 2) - aggression if this_play.starting_outs + this_play.outs >= 2: @@ -559,9 +652,13 @@ class ManagerAi(ManagerAiBase, table=True): if lead_base == 4: this_resp.min_safe = 12 - this_play.starting_outs - aggression this_resp.send_trail = True - this_resp.trail_min_safe = 10 - aggression - this_play.starting_outs - this_play.outs + this_resp.trail_min_safe = ( + 10 - aggression - this_play.starting_outs - this_play.outs + ) elif lead_base == 3: - this_resp.min_safe = 12 + (this_play.starting_outs * 2) - (aggression * 2) + this_resp.min_safe = ( + 12 + (this_play.starting_outs * 2) - (aggression * 2) + ) if this_play.starting_outs + this_play.outs >= 2: this_resp.send_trail = False else: @@ -582,222 +679,317 @@ class ManagerAi(ManagerAiBase, table=True): this_resp.min_safe = 20 if this_resp.trail_min_safe < 1: this_resp.min_safe = 1 - - logger.info(f'Uncapped advance response: {this_resp}') + + logger.info(f"Uncapped advance response: {this_resp}") return this_resp def defense_alignment(self, session: Session, this_game: Game) -> DefenseResponse: - logger.info(f'checking defensive alignment in game {this_game.id}') + logger.info(f"checking defensive alignment in game {this_game.id}") this_resp = DefenseResponse() this_play = this_game.current_play_or_none(session) if this_play is None: - raise GameException(f'No game found while checking defense_alignment') - - logger.info(f'defense_alignment - this_play: {this_play}') + raise GameException(f"No game found while checking defense_alignment") + + logger.info(f"defense_alignment - this_play: {this_play}") ai_rd = this_play.ai_run_diff - aggression = self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5 + aggression = ( + self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5 + ) pitcher_hold = this_play.pitcher.card.pitcherscouting.pitchingcard.hold - - catcher_defense = session.exec(select(PositionRating).where(PositionRating.player_id == this_play.catcher.player_id, PositionRating.position == 'C', PositionRating.variant == this_play.catcher.card.variant)).one() + + catcher_defense = session.exec( + select(PositionRating).where( + PositionRating.player_id == this_play.catcher.player_id, + PositionRating.position == "C", + PositionRating.variant == this_play.catcher.card.variant, + ) + ).one() catcher_hold = catcher_defense.arm - battery_hold = pitcher_hold + catcher_hold + battery_hold = pitcher_hold + catcher_hold if this_play.starting_outs == 2 and this_play.on_base_code > 0: - logger.info(f'Checking for holds with 2 outs') + logger.info(f"Checking for holds with 2 outs") if this_play.on_base_code == 1: this_resp.hold_first = True - this_resp.ai_note += f'- hold {this_play.on_first.player.name} on 1st\n' + this_resp.ai_note += f"- hold {this_play.on_first.player.name} on 1st\n" elif this_play.on_base_code == 2: this_resp.hold_second = True - this_resp.ai_note += f'- hold {this_play.on_second.player.name} on 2nd\n' + this_resp.ai_note += ( + f"- hold {this_play.on_second.player.name} on 2nd\n" + ) elif this_play.on_base_code in [4, 7]: this_resp.hold_first = True this_resp.hold_second = True - this_resp.ai_note += f'- hold {this_play.on_first.player.name} on 1st\n- hold {this_play.on_second.player.name} on 2nd\n' + this_resp.ai_note += f"- hold {this_play.on_first.player.name} on 1st\n- hold {this_play.on_second.player.name} on 2nd\n" elif this_play.on_base_code == 5: this_resp.hold_first = True - this_resp.ai_note += f'- hold {this_play.on_first.player.name} on first\n' + this_resp.ai_note += ( + f"- hold {this_play.on_first.player.name} on first\n" + ) elif this_play.on_base_code == 6: this_resp.hold_second = True - this_resp.ai_note += f'- hold {this_play.on_second.player.name} on 2nd\n' + this_resp.ai_note += ( + f"- hold {this_play.on_second.player.name} on 2nd\n" + ) elif this_play.on_base_code in [1, 5]: - logger.info(f'Checking for hold with runner on first') + logger.info(f"Checking for hold with runner on first") runner = this_play.on_first.player - if this_play.on_first.card.batterscouting.battingcard.steal_auto and ((this_play.on_first.card.batterscouting.battingcard.steal_high + battery_hold) >= (12 - aggression)): + if this_play.on_first.card.batterscouting.battingcard.steal_auto and ( + ( + this_play.on_first.card.batterscouting.battingcard.steal_high + + battery_hold + ) + >= (12 - aggression) + ): this_resp.hold_first = True - this_resp.ai_note += f'- hold {runner.name} on 1st\n' + this_resp.ai_note += f"- hold {runner.name} on 1st\n" elif this_play.on_base_code in [2, 4]: - logger.info(f'Checking for hold with runner on second') - if (this_play.on_second.card.batterscouting.battingcard.steal_low + max(battery_hold, 5)) >= (14 - aggression): + logger.info(f"Checking for hold with runner on second") + if ( + this_play.on_second.card.batterscouting.battingcard.steal_low + + max(battery_hold, 5) + ) >= (14 - aggression): this_resp.hold_second = True - this_resp.ai_note += f'- hold {this_play.on_second.player.name} on 2nd\n' + this_resp.ai_note += ( + f"- hold {this_play.on_second.player.name} on 2nd\n" + ) # Defensive Alignment if this_play.on_third and this_play.starting_outs < 2: if this_play.could_walkoff: this_resp.outfield_in = True this_resp.infield_in = True - this_resp.ai_note += f'- play the outfield and infield in' + this_resp.ai_note += f"- play the outfield and infield in" elif this_play.on_first and this_play.starting_outs == 1: this_resp.corners_in = True - this_resp.ai_note += f'- play the corners in\n' + this_resp.ai_note += f"- play the corners in\n" elif abs(this_play.away_score - this_play.home_score) <= 3: this_resp.infield_in = True - this_resp.ai_note += f'- play the whole infield in\n' + this_resp.ai_note += f"- play the whole infield in\n" else: this_resp.corners_in = True - this_resp.ai_note += f'- play the corners in\n' - + this_resp.ai_note += f"- play the corners in\n" + if len(this_resp.ai_note) == 0 and this_play.on_base_code > 0: - this_resp.ai_note += f'- play straight up\n' - - logger.info(f'Defense alignment response: {this_resp}') + this_resp.ai_note += f"- play straight up\n" + + logger.info(f"Defense alignment response: {this_resp}") return this_resp - + def gb_decide_run(self, session: Session, this_game: Game) -> RunResponse: this_resp = RunResponse() this_play = this_game.current_play_or_none(session) if this_play is None: - raise GameException(f'No game found while checking gb_decide_run') - + raise GameException(f"No game found while checking gb_decide_run") + ai_rd = this_play.ai_run_diff - aggression = self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5 + aggression = ( + self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5 + ) this_resp.min_safe = 15 - aggression # TODO: write this algorithm - logger.info(f'gb_decide_run response: {this_resp}') + logger.info(f"gb_decide_run response: {this_resp}") return this_resp - - def gb_decide_throw(self, session: Session, this_game: Game, runner_speed: int, defender_range: int) -> ThrowResponse: + + def gb_decide_throw( + self, session: Session, this_game: Game, runner_speed: int, defender_range: int + ) -> ThrowResponse: this_resp = ThrowResponse(at_lead_runner=True) this_play = this_game.current_play_or_none(session) if this_play is None: - raise GameException(f'No game found while checking gb_decide_throw') - + raise GameException(f"No game found while checking gb_decide_throw") + ai_rd = this_play.ai_run_diff - aggression = self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5 + aggression = ( + self.ahead_aggression - 5 if ai_rd > 0 else self.behind_aggression - 5 + ) if (runner_speed - 4 + defender_range) <= (10 + aggression): this_resp.at_lead_runner = True - - logger.info(f'gb_decide_throw response: {this_resp}') + + logger.info(f"gb_decide_throw response: {this_resp}") return this_resp def replace_pitcher(self, session: Session, this_game: Game) -> bool: - logger.info(f'Checking if fatigued pitcher should be replaced') + logger.info(f"Checking if fatigued pitcher should be replaced") this_play = this_game.current_play_or_none(session) if this_play is None: - raise GameException(f'No game found while checking replace_pitcher') - - this_pitcher = this_play.pitcher - outs = session.exec(select(func.sum(Play.outs)).where( - Play.game == this_game, Play.pitcher == this_pitcher, Play.complete == True - )).one() - logger.info(f'Pitcher: {this_pitcher.card.player.name_with_desc} / Outs: {outs}') + raise GameException(f"No game found while checking replace_pitcher") - allowed_runners = session.exec(select(func.count(Play.id)).where( - Play.game == this_game, Play.pitcher == this_pitcher, or_(Play.hit == 1, Play.bb == 1) - )).one() + this_pitcher = this_play.pitcher + outs = session.exec( + select(func.sum(Play.outs)).where( + Play.game == this_game, + Play.pitcher == this_pitcher, + Play.complete == True, + ) + ).one() + logger.info( + f"Pitcher: {this_pitcher.card.player.name_with_desc} / Outs: {outs}" + ) + + allowed_runners = session.exec( + select(func.count(Play.id)).where( + Play.game == this_game, + Play.pitcher == this_pitcher, + or_(Play.hit == 1, Play.bb == 1), + ) + ).one() run_diff = this_play.ai_run_diff - logger.info(f'run diff: {run_diff} / allowed runners: {allowed_runners} / behind aggro: {self.behind_aggression} / ahead aggro: {self.ahead_aggression}') - logger.info(f'this play: {this_play}') + logger.info( + f"run diff: {run_diff} / allowed runners: {allowed_runners} / behind aggro: {self.behind_aggression} / ahead aggro: {self.ahead_aggression}" + ) + logger.info(f"this play: {this_play}") if this_pitcher.replacing_id is None: pitcher_pow = this_pitcher.card.pitcherscouting.pitchingcard.starter_rating - logger.info(f'Starter POW: {pitcher_pow}') + logger.info(f"Starter POW: {pitcher_pow}") if outs >= pitcher_pow * 3 + 6: - logger.info(f'Starter has thrown POW + 3 - being pulled') + logger.info(f"Starter has thrown POW + 3 - being pulled") return True - + elif allowed_runners < 5: - logger.info(f'Starter is cooking with {allowed_runners} runners allowed - staying in') + logger.info( + f"Starter is cooking with {allowed_runners} runners allowed - staying in" + ) return False - + elif this_pitcher.is_fatigued and this_play.on_base_code > 1: - logger.info(f'Starter is fatigued') + logger.info(f"Starter is fatigued") return True - - elif (run_diff > 5 or (run_diff > 2 and self.ahead_aggression > 5)) and (allowed_runners < run_diff or this_play.on_base_code <= 3): - logger.info(f'AI team has big lead of {run_diff} - staying in') + + elif (run_diff > 5 or (run_diff > 2 and self.ahead_aggression > 5)) and ( + allowed_runners < run_diff or this_play.on_base_code <= 3 + ): + logger.info(f"AI team has big lead of {run_diff} - staying in") return False - - elif (run_diff > 2 or (run_diff >= 0 and self.ahead_aggression > 5)) and (allowed_runners < run_diff or this_play.on_base_code <= 1): - logger.info(f'AI team has lead of {run_diff} - staying in') + + elif (run_diff > 2 or (run_diff >= 0 and self.ahead_aggression > 5)) and ( + allowed_runners < run_diff or this_play.on_base_code <= 1 + ): + logger.info(f"AI team has lead of {run_diff} - staying in") return False - - elif (run_diff >= 0 or (run_diff >= -2 and self.behind_aggression > 5)) and (allowed_runners < 5 and this_play.on_base_code <= run_diff): - logger.info(f'AI team in close game with run diff of {run_diff} - staying in') + + elif ( + run_diff >= 0 or (run_diff >= -2 and self.behind_aggression > 5) + ) and (allowed_runners < 5 and this_play.on_base_code <= run_diff): + logger.info( + f"AI team in close game with run diff of {run_diff} - staying in" + ) return False - - elif run_diff >= -3 and self.behind_aggression > 5 and allowed_runners < 5 and this_play.on_base_code <= 1: - logger.info(f'AI team is close behind with run diff of {run_diff} - staying in') + + elif ( + run_diff >= -3 + and self.behind_aggression > 5 + and allowed_runners < 5 + and this_play.on_base_code <= 1 + ): + logger.info( + f"AI team is close behind with run diff of {run_diff} - staying in" + ) return False - + elif run_diff <= -5 and this_play.inning_num <= 3: - logger.info(f'AI team is way behind and starter is going to wear it - staying in') + logger.info( + f"AI team is way behind and starter is going to wear it - staying in" + ) return False - + else: - logger.info(f'AI team found no exceptions - pull starter') + logger.info(f"AI team found no exceptions - pull starter") return True else: pitcher_pow = this_pitcher.card.pitcherscouting.pitchingcard.relief_rating - logger.info(f'Reliever POW: {pitcher_pow}') + logger.info(f"Reliever POW: {pitcher_pow}") if outs >= pitcher_pow * 3 + 3: - logger.info(f'Only allow POW + 1 IP - pull reliever') - return True - - elif this_pitcher.is_fatigued and this_play.is_new_inning: - logger.info(f'Reliever is fatigued to start the inning - pull reliever') + logger.info(f"Only allow POW + 1 IP - pull reliever") return True - elif (run_diff > 5 or (run_diff > 2 and self.ahead_aggression > 5)) and (this_play.starting_outs == 2 or allowed_runners <= run_diff or this_play.on_base_code <= 3 or this_play.starting_outs == 2): - logger.info(f'AI team has big lead of {run_diff} - staying in') + elif this_pitcher.is_fatigued and this_play.is_new_inning: + logger.info(f"Reliever is fatigued to start the inning - pull reliever") + return True + + elif (run_diff > 5 or (run_diff > 2 and self.ahead_aggression > 5)) and ( + this_play.starting_outs == 2 + or allowed_runners <= run_diff + or this_play.on_base_code <= 3 + or this_play.starting_outs == 2 + ): + logger.info(f"AI team has big lead of {run_diff} - staying in") return False - - elif (run_diff > 2 or (run_diff >= 0 and self.ahead_aggression > 5)) and (allowed_runners < run_diff or this_play.on_base_code <= 1 or this_play.starting_outs == 2): - logger.info(f'AI team has lead of {run_diff} - staying in') + + elif (run_diff > 2 or (run_diff >= 0 and self.ahead_aggression > 5)) and ( + allowed_runners < run_diff + or this_play.on_base_code <= 1 + or this_play.starting_outs == 2 + ): + logger.info(f"AI team has lead of {run_diff} - staying in") return False - - elif (run_diff >= 0 or (run_diff >= -2 and self.behind_aggression > 5)) and (allowed_runners < 5 or this_play.on_base_code <= run_diff or this_play.starting_outs == 2): - logger.info(f'AI team in close game with run diff of {run_diff} - staying in') + + elif ( + run_diff >= 0 or (run_diff >= -2 and self.behind_aggression > 5) + ) and ( + allowed_runners < 5 + or this_play.on_base_code <= run_diff + or this_play.starting_outs == 2 + ): + logger.info( + f"AI team in close game with run diff of {run_diff} - staying in" + ) return False - - elif run_diff >= -3 and self.behind_aggression > 5 and allowed_runners < 5 and this_play.on_base_code <= 1: - logger.info(f'AI team is close behind with run diff of {run_diff} - staying in') + + elif ( + run_diff >= -3 + and self.behind_aggression > 5 + and allowed_runners < 5 + and this_play.on_base_code <= 1 + ): + logger.info( + f"AI team is close behind with run diff of {run_diff} - staying in" + ) return False - + elif run_diff <= -5 and this_play.starting_outs != 0: - logger.info(f'AI team is way behind and reliever is going to wear it - staying in') + logger.info( + f"AI team is way behind and reliever is going to wear it - staying in" + ) return False - + else: - logger.info(f'AI team found no exceptions - pull reliever') + logger.info(f"AI team found no exceptions - pull reliever") return True class CardsetBase(SQLModel): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=False)) + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=False), + ) name: str ranked_legal: bool | None = Field(default=False) class Cardset(CardsetBase, table=True): - game_links: list[GameCardsetLink] = Relationship(back_populates='cardset', cascade_delete=True) - players: list['Player'] = Relationship(back_populates='cardset') + game_links: list[GameCardsetLink] = Relationship( + back_populates="cardset", cascade_delete=True + ) + players: list["Player"] = Relationship(back_populates="cardset") class PlayerBase(SQLModel): - id: int | None = Field(sa_column=Column(BigInteger(), primary_key=True, autoincrement=False)) + id: int | None = Field( + sa_column=Column(BigInteger(), primary_key=True, autoincrement=False) + ) name: str cost: int image: str mlbclub: str franchise: str - cardset_id: int | None = Field(default=None, foreign_key='cardset.id') + cardset_id: int | None = Field(default=None, foreign_key="cardset.id") set_num: int rarity_id: int | None = Field(default=None) pos_1: str @@ -817,9 +1009,13 @@ class PlayerBase(SQLModel): bbref_id: str | None = Field(default=None) fangr_id: str | None = Field(default=None) mlbplayer_id: int | None = Field(default=None) - created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True) + created: datetime.datetime = Field( + default_factory=datetime.datetime.now, nullable=True + ) - @field_validator('pos_1', 'pos_2', 'pos_3', 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8') + @field_validator( + "pos_1", "pos_2", "pos_3", "pos_4", "pos_5", "pos_6", "pos_7", "pos_8" + ) def uppercase_strings(cls, value: str) -> str: if value is not None: return value.upper() @@ -828,73 +1024,82 @@ class PlayerBase(SQLModel): @property def batter_card_url(self): - if self.image and 'batting' in self.image: + if self.image and "batting" in self.image: return self.image - elif self.image2 and 'batting' in self.image2: - return self.image2 - else: - return None - - @property - def pitcher_card_url(self): - if self.image and 'pitching' in self.image: - return self.image - elif self.image2 and 'pitching' in self.image2: + elif self.image2 and "batting" in self.image2: return self.image2 else: return None - def name_card_link(self, which: Literal['pitching', 'batting']): - if which == 'pitching': - return f'[{self.name}]({self.pitcher_card_url})' + @property + def pitcher_card_url(self): + if self.image and "pitching" in self.image: + return self.image + elif self.image2 and "pitching" in self.image2: + return self.image2 else: - return f'[{self.name}]({self.batter_card_url})' + return None + + def name_card_link(self, which: Literal["pitching", "batting"]): + if which == "pitching": + return f"[{self.name}]({self.pitcher_card_url})" + else: + return f"[{self.name}]({self.batter_card_url})" class Player(PlayerBase, table=True): - cardset: Cardset = Relationship(back_populates='players') - cards: list['Card'] = Relationship(back_populates='player', cascade_delete=True) - lineups: list['Lineup'] = Relationship(back_populates='player', cascade_delete=True) - positions: list['PositionRating'] = Relationship(back_populates='player', cascade_delete=True) + cardset: Cardset = Relationship(back_populates="players") + cards: list["Card"] = Relationship(back_populates="player", cascade_delete=True) + lineups: list["Lineup"] = Relationship(back_populates="player", cascade_delete=True) + positions: list["PositionRating"] = Relationship( + back_populates="player", cascade_delete=True + ) @property def name_with_desc(self): - return f'{self.description} {self.name}' + return f"{self.description} {self.name}" def player_description(player: Player = None, player_dict: dict = None) -> str: if player is None and player_dict is None: - err = 'One of "player" or "player_dict" must be included to get full description' - logger.error(f'gameplay_models - player_description - {err}') + err = ( + 'One of "player" or "player_dict" must be included to get full description' + ) + logger.error(f"gameplay_models - player_description - {err}") raise TypeError(err) - + if player is not None: - return f'{player.description} {player.name}' - - r_val = f'{player_dict['description']}' - if 'name' in player_dict: + return f"{player.description} {player.name}" + + r_val = f"{player_dict['description']}" + if "name" in player_dict: r_val += f' {player_dict["name"]}' - elif 'p_name' in player_dict: + elif "p_name" in player_dict: r_val += f' {player_dict["p_name"]}' return r_val class BattingCardBase(SQLModel): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=False)) + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=False), + ) variant: int | None = Field(default=0) steal_low: int = Field(default=0, ge=0, le=20) steal_high: int = Field(default=0, ge=0, le=20) steal_auto: bool = Field(default=False) steal_jump: float = Field(default=0.0, ge=0.0, le=1.0) - bunting: str = Field(default='C') - hit_and_run: str = Field(default='C') + bunting: str = Field(default="C") + hit_and_run: str = Field(default="C") running: int = Field(default=10, ge=1, le=20) offense_col: int = Field(ge=1, le=3) hand: str - created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True) + created: datetime.datetime = Field( + default_factory=datetime.datetime.now, nullable=True + ) # created: datetime.datetime | None = Field(sa_column_kwargs={"server_default": text("CURRENT_TIMESTAMP"),}) - @field_validator('hand') + @field_validator("hand") def lowercase_hand(cls, value: str) -> str: return value.lower() @@ -904,7 +1109,10 @@ class BattingCard(BattingCardBase, table=True): class BattingRatingsBase(SQLModel): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=False)) + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=False), + ) homerun: float = Field(default=0.0, ge=0.0, le=108.0) bp_homerun: float = Field(default=0.0, ge=0.0, le=108.0) triple: float = Field(default=0.0, ge=0.0, le=108.0) @@ -933,7 +1141,9 @@ class BattingRatingsBase(SQLModel): pull_rate: float = Field(default=0.0, ge=0.0, le=1.0) center_rate: float = Field(default=0.0, ge=0.0, le=1.0) slap_rate: float = Field(default=0.0, ge=0.0, le=1.0) - created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True) + created: datetime.datetime = Field( + default_factory=datetime.datetime.now, nullable=True + ) class BattingRatings(BattingRatingsBase, table=True): @@ -941,26 +1151,48 @@ class BattingRatings(BattingRatingsBase, table=True): class BatterScoutingBase(SQLModel): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=True)) - battingcard_id: int | None = Field(default=None, foreign_key='battingcard.id', ondelete='CASCADE') - ratings_vl_id: int | None = Field(default=None, foreign_key='battingratings.id', ondelete='CASCADE') - ratings_vr_id: int | None = Field(default=None, foreign_key='battingratings.id', ondelete='CASCADE') - created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True) + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=True), + ) + battingcard_id: int | None = Field( + default=None, foreign_key="battingcard.id", ondelete="CASCADE" + ) + ratings_vl_id: int | None = Field( + default=None, foreign_key="battingratings.id", ondelete="CASCADE" + ) + ratings_vr_id: int | None = Field( + default=None, foreign_key="battingratings.id", ondelete="CASCADE" + ) + created: datetime.datetime = Field( + default_factory=datetime.datetime.now, nullable=True + ) class BatterScouting(BatterScoutingBase, table=True): - battingcard: BattingCard = Relationship() #back_populates='batterscouting') + battingcard: BattingCard = Relationship() # back_populates='batterscouting') ratings_vl: BattingRatings = Relationship( - sa_relationship_kwargs=dict(foreign_keys="[BatterScouting.ratings_vl_id]",single_parent=True), cascade_delete=True + sa_relationship_kwargs=dict( + foreign_keys="[BatterScouting.ratings_vl_id]", single_parent=True + ), + cascade_delete=True, ) ratings_vr: BattingRatings = Relationship( - sa_relationship_kwargs=dict(foreign_keys="[BatterScouting.ratings_vr_id]",single_parent=True), cascade_delete=True + sa_relationship_kwargs=dict( + foreign_keys="[BatterScouting.ratings_vr_id]", single_parent=True + ), + cascade_delete=True, + ) + cards: list["Card"] = Relationship( + back_populates="batterscouting", cascade_delete=False ) - cards: list['Card'] = Relationship(back_populates='batterscouting', cascade_delete=False) class PitchingCardBase(SQLModel): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=False)) + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=False), + ) variant: int | None = Field(default=0) balk: int = Field(default=0, ge=0, le=20) wild_pitch: int = Field(default=0, ge=0, le=20) @@ -969,11 +1201,13 @@ class PitchingCardBase(SQLModel): relief_rating: int = Field(default=1, ge=1, le=10) closer_rating: int | None = Field(default=None, ge=0, le=9) offense_col: int = Field(ge=1, le=3) - batting: str = Field(default='#1WR-C') + batting: str = Field(default="#1WR-C") hand: str - created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True) + created: datetime.datetime = Field( + default_factory=datetime.datetime.now, nullable=True + ) - @field_validator('hand') + @field_validator("hand") def lowercase_hand(cls, value: str) -> str: return value.lower() @@ -983,7 +1217,10 @@ class PitchingCard(PitchingCardBase, table=True): class PitchingRatingsBase(SQLModel): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=False)) + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=False), + ) homerun: float = Field(default=0.0, ge=0.0, le=108.0) bp_homerun: float = Field(default=0.0, ge=0.0, le=108.0) triple: float = Field(default=0.0, ge=0.0, le=108.0) @@ -1014,7 +1251,9 @@ class PitchingRatingsBase(SQLModel): avg: float = Field(default=0.0, ge=0.0, le=1.0) obp: float = Field(default=0.0, ge=0.0, le=1.0) slg: float = Field(default=0.0, ge=0.0, le=4.0) - created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True) + created: datetime.datetime = Field( + default_factory=datetime.datetime.now, nullable=True + ) class PitchingRatings(PitchingRatingsBase, table=True): @@ -1022,64 +1261,100 @@ class PitchingRatings(PitchingRatingsBase, table=True): class PitcherScoutingBase(SQLModel): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=True)) - pitchingcard_id: int | None = Field(default=None, foreign_key='pitchingcard.id', ondelete='CASCADE') - ratings_vl_id: int | None = Field(default=None, foreign_key='pitchingratings.id', ondelete='CASCADE') - ratings_vr_id: int | None = Field(default=None, foreign_key='pitchingratings.id', ondelete='CASCADE') - created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True) + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=True), + ) + pitchingcard_id: int | None = Field( + default=None, foreign_key="pitchingcard.id", ondelete="CASCADE" + ) + ratings_vl_id: int | None = Field( + default=None, foreign_key="pitchingratings.id", ondelete="CASCADE" + ) + ratings_vr_id: int | None = Field( + default=None, foreign_key="pitchingratings.id", ondelete="CASCADE" + ) + created: datetime.datetime = Field( + default_factory=datetime.datetime.now, nullable=True + ) class PitcherScouting(PitcherScoutingBase, table=True): pitchingcard: PitchingCard = Relationship() ratings_vl: PitchingRatings = Relationship( - sa_relationship_kwargs=dict(foreign_keys="[PitcherScouting.ratings_vl_id]",single_parent=True), cascade_delete=True + sa_relationship_kwargs=dict( + foreign_keys="[PitcherScouting.ratings_vl_id]", single_parent=True + ), + cascade_delete=True, ) ratings_vr: PitchingRatings = Relationship( - sa_relationship_kwargs=dict(foreign_keys="[PitcherScouting.ratings_vr_id]",single_parent=True), cascade_delete=True + sa_relationship_kwargs=dict( + foreign_keys="[PitcherScouting.ratings_vr_id]", single_parent=True + ), + cascade_delete=True, ) - cards: list['Card'] = Relationship(back_populates='pitcherscouting') + cards: list["Card"] = Relationship(back_populates="pitcherscouting") class CardBase(SQLModel): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=False)) - player_id: int = Field(foreign_key='player.id', index=True, ondelete='CASCADE') - team_id: int = Field(foreign_key='team.id', index=True, ondelete='CASCADE') - batterscouting_id: int | None = Field(default=None, foreign_key='batterscouting.id', ondelete='CASCADE') - pitcherscouting_id: int | None = Field(default=None, foreign_key='pitcherscouting.id', ondelete='CASCADE') + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=False), + ) + player_id: int = Field(foreign_key="player.id", index=True, ondelete="CASCADE") + team_id: int = Field(foreign_key="team.id", index=True, ondelete="CASCADE") + batterscouting_id: int | None = Field( + default=None, foreign_key="batterscouting.id", ondelete="CASCADE" + ) + pitcherscouting_id: int | None = Field( + default=None, foreign_key="pitcherscouting.id", ondelete="CASCADE" + ) variant: int | None = Field(default=0) - created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True) - + created: datetime.datetime = Field( + default_factory=datetime.datetime.now, nullable=True + ) + class Card(CardBase, table=True): - player: Player = Relationship(back_populates='cards') - team: Team = Relationship(back_populates='cards') - lineups: list['Lineup'] = Relationship(back_populates='card', cascade_delete=True) + player: Player = Relationship(back_populates="cards") + team: Team = Relationship(back_populates="cards") + lineups: list["Lineup"] = Relationship(back_populates="card", cascade_delete=True) variant: int = Field(default=0, index=True) - batterscouting: BatterScouting = Relationship(back_populates='cards') - pitcherscouting: PitcherScouting = Relationship(back_populates='cards') + batterscouting: BatterScouting = Relationship(back_populates="cards") + pitcherscouting: PitcherScouting = Relationship(back_populates="cards") class PositionRatingBase(SQLModel): __table_args__ = (UniqueConstraint("player_id", "variant", "position"),) - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=True)) - player_id: int = Field(foreign_key='player.id', index=True, ondelete='CASCADE') + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=True), + ) + player_id: int = Field(foreign_key="player.id", index=True, ondelete="CASCADE") variant: int = Field(default=0, index=True) - position: str = Field(index=True, include=['P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF']) + position: str = Field( + index=True, include=["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"] + ) innings: int = Field(default=0) range: int = Field(default=5) error: int = Field(default=0) arm: int | None = Field(default=None) pb: int | None = Field(default=None) overthrow: int | None = Field(default=None) - created: datetime.datetime = Field(default_factory=datetime.datetime.now, nullable=True) + created: datetime.datetime = Field( + default_factory=datetime.datetime.now, nullable=True + ) class PositionRating(PositionRatingBase, table=True): - player: Player = Relationship(back_populates='positions') + player: Player = Relationship(back_populates="positions") class Lineup(SQLModel, table=True): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=True)) + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=True), + ) position: str = Field(index=True) batting_order: int = Field(index=True) after_play: int | None = Field(default=0) @@ -1087,31 +1362,34 @@ class Lineup(SQLModel, table=True): active: bool = Field(default=True, index=True) is_fatigued: bool | None = Field(default=None) - game_id: int = Field(foreign_key='game.id', index=True, ondelete='CASCADE') - game: Game = Relationship(back_populates='lineups') - - team_id: int = Field(foreign_key='team.id', index=True, ondelete='CASCADE') - team: Team = Relationship(back_populates='lineups') - - player_id: int = Field(foreign_key='player.id', index=True, ondelete='CASCADE') - player: Player = Relationship(back_populates='lineups') + game_id: int = Field(foreign_key="game.id", index=True, ondelete="CASCADE") + game: Game = Relationship(back_populates="lineups") - card_id: int = Field(foreign_key='card.id', index=True, ondelete='CASCADE') - card: Card = Relationship(back_populates='lineups') + team_id: int = Field(foreign_key="team.id", index=True, ondelete="CASCADE") + team: Team = Relationship(back_populates="lineups") - @field_validator('position') + player_id: int = Field(foreign_key="player.id", index=True, ondelete="CASCADE") + player: Player = Relationship(back_populates="lineups") + + card_id: int = Field(foreign_key="card.id", index=True, ondelete="CASCADE") + card: Card = Relationship(back_populates="lineups") + + @field_validator("position") def uppercase_strings(cls, value: str) -> str: return value.upper() class PlayBase(SQLModel): - id: int | None = Field(default=None, sa_column=Column(BigInteger(), primary_key=True, autoincrement=True)) - game_id: int = Field(foreign_key='game.id') + id: int | None = Field( + default=None, + sa_column=Column(BigInteger(), primary_key=True, autoincrement=True), + ) + game_id: int = Field(foreign_key="game.id") play_num: int - batter_id: int = Field(foreign_key='lineup.id') - pitcher_id: int = Field(foreign_key='lineup.id') + batter_id: int = Field(foreign_key="lineup.id") + pitcher_id: int = Field(foreign_key="lineup.id") on_base_code: int = Field(default=0) - inning_half: str = Field(default='top') + inning_half: str = Field(default="top") inning_num: int = Field(default=1, ge=1) batting_order: int = Field(default=1, ge=1, le=9) starting_outs: int = Field(default=0, ge=0, le=2) @@ -1120,13 +1398,13 @@ class PlayBase(SQLModel): batter_pos: str | None = Field(default=None) in_pow: bool = Field(default=False) - on_first_id: int | None = Field(default=None, foreign_key='lineup.id') - on_first_final: int | None = Field(default=None) # None = out, 1-4 = base - on_second_id: int | None = Field(default=None, foreign_key='lineup.id') - on_second_final: int | None = Field(default=None) # None = out, 1-4 = base - on_third_id: int | None = Field(default=None, foreign_key='lineup.id') - on_third_final: int | None = Field(default=None) # None = out, 1-4 = base - batter_final: int | None = Field(default=None) # None = out, 1-4 = base + on_first_id: int | None = Field(default=None, foreign_key="lineup.id") + on_first_final: int | None = Field(default=None) # None = out, 1-4 = base + on_second_id: int | None = Field(default=None, foreign_key="lineup.id") + on_second_final: int | None = Field(default=None) # None = out, 1-4 = base + on_third_id: int | None = Field(default=None, foreign_key="lineup.id") + on_third_final: int | None = Field(default=None) # None = out, 1-4 = base + batter_final: int | None = Field(default=None) # None = out, 1-4 = base pa: int = Field(default=1, ge=0, le=1) ab: int = Field(default=1, ge=0, le=1) @@ -1154,9 +1432,9 @@ class PlayBase(SQLModel): wpa: float = Field(default=0) re24: float = Field(default=0) - catcher_id: int = Field(foreign_key='lineup.id') - defender_id: int | None = Field(default=None, foreign_key='lineup.id') - runner_id: int | None = Field(default=None, foreign_key='lineup.id') + catcher_id: int = Field(foreign_key="lineup.id") + defender_id: int | None = Field(default=None, foreign_key="lineup.id") + runner_id: int | None = Field(default=None, foreign_key="lineup.id") check_pos: str | None = Field(default=None) error: int = Field(default=0) @@ -1169,26 +1447,26 @@ class PlayBase(SQLModel): is_go_ahead: bool = Field(default=False) is_tied: bool = Field(default=False) is_new_inning: bool = Field(default=False) - managerai_id: int | None = Field(default=None, foreign_key='managerai.id') + managerai_id: int | None = Field(default=None, foreign_key="managerai.id") - @field_validator('inning_half') + @field_validator("inning_half") def lowercase_strings(cls, value: str) -> str: return value.lower() - @field_validator('check_pos', 'batter_pos') + @field_validator("check_pos", "batter_pos") def uppercase_strings(cls, value: str) -> str: return value.upper() @property def ai_run_diff(self) -> int: - if self.game.ai_team == 'away': + if self.game.ai_team == "away": return self.away_score - self.home_score else: return self.home_score - self.away_score class Play(PlayBase, table=True): - game: Game = Relationship(back_populates='plays') + game: Game = Relationship(back_populates="plays") batter: Lineup = Relationship( sa_relationship_kwargs=dict(foreign_keys="[Play.batter_id]") ) @@ -1213,7 +1491,7 @@ class Play(PlayBase, table=True): runner: Lineup = Relationship( sa_relationship_kwargs=dict(foreign_keys="[Play.runner_id]") ) - managerai: ManagerAi = Relationship(back_populates='plays') + managerai: ManagerAi = Relationship(back_populates="plays") def init_ai(self, session: Session): id = ((datetime.datetime.now().day * self.batter.team.id) % 3) + 1 @@ -1221,48 +1499,52 @@ class Play(PlayBase, table=True): self.managerai_id = 1 else: self.managerai_id = id - + session.add(self) session.commit() @property def scorebug_ascii(self): - occupied = '●' - unoccupied = '○' + occupied = "●" + unoccupied = "○" first_base = unoccupied if not self.on_first else occupied second_base = unoccupied if not self.on_second else occupied third_base = unoccupied if not self.on_third else occupied - half = '▲' if self.inning_half == 'top' else '▼' + half = "▲" if self.inning_half == "top" else "▼" if self.game.active: - inning = f'{half} {self.inning_num}' + inning = f"{half} {self.inning_num}" outs = f'{self.starting_outs} Out{"s" if self.starting_outs != 1 else ""}' else: inning = f'F/{self.inning_num if self.inning_half == "bot" else self.inning_num - 1}' - outs = '' - - game_string = f'```\n' \ - f'{self.game.away_team.abbrev.replace("Gauntlet-", ""): ^5}{self.away_score: ^3} {second_base}{inning: >10}\n' \ - f'{self.game.home_team.abbrev.replace("Gauntlet-", ""): ^5}{self.home_score: ^3} {third_base} {first_base}{outs: >8}\n```' - + outs = "" + + game_string = ( + f"```\n" + f'{self.game.away_team.abbrev.replace("Gauntlet-", ""): ^5}{self.away_score: ^3} {second_base}{inning: >10}\n' + f'{self.game.home_team.abbrev.replace("Gauntlet-", ""): ^5}{self.home_score: ^3} {third_base} {first_base}{outs: >8}\n```' + ) + return game_string @property def ai_is_batting(self) -> bool: if self.game.ai_team is None: return False - - if (self.game.ai_team == 'away' and self.inning_half == 'top') or (self.game.ai_team == 'home' and self.inning_half == 'bot'): + + if (self.game.ai_team == "away" and self.inning_half == "top") or ( + self.game.ai_team == "home" and self.inning_half == "bot" + ): return True else: return False @property def could_walkoff(self) -> bool: - if self.inning_half == 'bot' and self.on_third is not None: + if self.inning_half == "bot" and self.on_third is not None: runs_needed = self.away_score - self.home_score + 1 - + if runs_needed == 2 or (self.home_score - self.away_score == 9): return True @@ -1295,55 +1577,66 @@ def create_test_games(): season=9, ) - cardset_2024 = Cardset(name='2024 Season', ranked_legal=True) - cardset_2022 = Cardset(name='2022 Season', ranked_legal=False) + cardset_2024 = Cardset(name="2024 Season", ranked_legal=True) + cardset_2022 = Cardset(name="2022 Season", ranked_legal=False) - game_1_cardset_2024_link = GameCardsetLink(game=game_1, cardset=cardset_2024, priority=1) - game_1_cardset_2022_link = GameCardsetLink(game=game_1, cardset=cardset_2022, priority=2) - game_2_cardset_2024_link = GameCardsetLink(game=game_2, cardset=cardset_2024, priority=1) + game_1_cardset_2024_link = GameCardsetLink( + game=game_1, cardset=cardset_2024, priority=1 + ) + game_1_cardset_2022_link = GameCardsetLink( + game=game_1, cardset=cardset_2022, priority=2 + ) + game_2_cardset_2024_link = GameCardsetLink( + game=game_2, cardset=cardset_2024, priority=1 + ) for team_id in [1, 2]: - for (order, pos) in [(1, 'C'), (2, '1B'), (3, '2B'), (4, '3B'), (5, 'SS'), (6, 'LF'), (7, 'CF'), (8, 'RF'), (9, 'DH')]: - this_lineup = Lineup(team_id=team_id, card_id=order, player_id=68+order, position=pos, batting_order=order, game=game_1) - + for order, pos in [ + (1, "C"), + (2, "1B"), + (3, "2B"), + (4, "3B"), + (5, "SS"), + (6, "LF"), + (7, "CF"), + (8, "RF"), + (9, "DH"), + ]: + this_lineup = Lineup( + team_id=team_id, + card_id=order, + player_id=68 + order, + position=pos, + batting_order=order, + game=game_1, + ) + for team_id in [3, 4]: - for (order, pos) in [(1, 'C'), (2, '1B'), (3, '2B'), (4, '3B'), (5, 'SS'), (6, 'LF'), (7, 'CF'), (8, 'RF'), (9, 'DH')]: - this_lineup = Lineup(team_id=team_id, card_id=order, player_id=100+order, position=pos, batting_order=order, game=game_2) + for order, pos in [ + (1, "C"), + (2, "1B"), + (3, "2B"), + (4, "3B"), + (5, "SS"), + (6, "LF"), + (7, "CF"), + (8, "RF"), + (9, "DH"), + ]: + this_lineup = Lineup( + team_id=team_id, + card_id=order, + player_id=100 + order, + position=pos, + batting_order=order, + game=game_2, + ) session.add(game_1) session.add(game_2) session.commit() -def select_speed_testing(): - with Session(engine) as session: - game_1 = session.exec(select(Game).where(Game.id == 1)).one() - ss_search_start = datetime.datetime.now() - man_ss = [x for x in game_1.lineups if x.position == 'SS' and x.active] - ss_search_end = datetime.datetime.now() - - ss_query_start = datetime.datetime.now() - query_ss = session.exec(select(Lineup).where(Lineup.game == game_1, Lineup.position == 'SS', Lineup.active == True)).all() - ss_query_end = datetime.datetime.now() - - manual_time = ss_search_end - ss_search_start - query_time = ss_query_end - ss_query_start - - print(f'Manual Shortstops: time: {manual_time.microseconds} ms / {man_ss}') - print(f'Query Shortstops: time: {query_time.microseconds} ms / {query_ss}') - print(f'Game: {game_1}') - - games = session.exec(select(Game).where(Game.active == True)).all() - print(f'len(games): {len(games)}') - - -def select_all_testing(): - with Session(engine) as session: - game_search = session.exec(select(Team)).all() - for game in game_search: - print(f'Game: {game}') - - # def select_specic_fields(): # with Session(engine) as session: # games = session.exec(select(Game.id, Game.away_team, Game.home_team))