import datetime import os import pytest from sqlmodel import Session, SQLModel, create_engine, select, text from testcontainers.postgres import PostgresContainer from in_game.gameplay_models import ( BatterScouting, BattingCard, BattingRatings, Card, Cardset, Game, GameCardsetLink, Lineup, ManagerAi, PitcherScouting, PitchingCard, PitchingRatings, Play, PositionRating, RosterLink, Team, Player, ) @pytest.fixture(name="session") def session_fixture(): # Ensure Docker socket is set for testcontainers if not os.getenv("DOCKER_HOST"): os.environ["DOCKER_HOST"] = "unix:///home/cal/.docker/desktop/docker.sock" with PostgresContainer("postgres:13") as postgres: postgres_url = postgres.get_connection_url() engine = create_engine(postgres_url) SQLModel.metadata.create_all(engine) with Session(engine) as session: team_1 = Team( id=31, abbrev="NCB", sname="CornBelters", lname="Normal CornBelters", gmid=1234, gmname="Cal", gsheet="asdf1234", wallet=6969, team_value=69420, collection_value=169420, color="006900", season=7, event=False, career=1234, ranking=1337, has_guide=True, is_ai=True, ) team_2 = Team( id=400, abbrev="WV", sname="Black Bears", lname="West Virginia Black Bears", gmid=5678, gmname="Evil Cal", gsheet="https://i.postimg.cc/HjDc8bBF/blackbears-transparent.png", wallet=350, team_value=420, collection_value=169, color="6699FF", season=7, event=False, career=2, ranking=969, has_guide=False, is_ai=False, ) team_3 = Team( id=69, abbrev="NCB3", sname="CornBelters", lname="Normal CornBelters", gmid=1234, gmname="Cal", gsheet="asdf1234", wallet=6969, team_value=69420, collection_value=169420, color="006900", season=7, event=False, career=1234, ranking=1337, has_guide=True, is_ai=False, ) team_4 = Team( id=420, abbrev="WV4", sname="Black Bears", lname="West Virginia Black Bears", gmid=5678, gmname="Evil Cal", gsheet="https://i.postimg.cc/HjDc8bBF/blackbears-transparent.png", wallet=350, team_value=420, collection_value=169, color="6699FF", season=7, event=False, career=2, ranking=969, has_guide=False, is_ai=True, ) old_cache_team = Team( id=3, abbrev="BAL", sname="Orioles", lname="Baltimore Orioles", gmid=181818, gmname="Brandon Hyde", gsheet="asdf1234", wallet=6969, team_value=69420, collection_value=169420, color="https://i.postimg.cc/8kLZCYXh/S10CLS.png", season=7, event=False, career=0, ranking=500, has_guide=False, is_ai=False, created=datetime.datetime.today() - datetime.timedelta(days=60), ) session.add(team_1) session.add(team_2) session.add(team_3) session.add(team_4) session.add(old_cache_team) session.commit() game_1 = Game( id=1, away_team_id=31, home_team_id=400, channel_id=1234, season=9, ai_team="away", game_type="minor-league", ) game_2 = Game( id=2, away_team_id=69, home_team_id=420, channel_id=5678, season=9, active=False, is_pd=True, ranked=True, week=6, game_num=9, away_roster_id=69, home_roster_id=420, first_message=12345678, ai_team="home", game_type="minor-league", ) game_3 = Game( id=3, away_team_id=69, home_team_id=420, channel_id=5678, season=9, active=True, is_pd=True, ranked=True, week=6, game_num=10, away_roster_id=69, home_roster_id=420, first_message=34567890, ai_team="home", game_type="minor-league", ) session.add(game_1) session.add(game_2) session.add(game_3) session.commit() cardset_1 = Cardset(id=1, name="1969 Live") cardset_2 = Cardset(id=2, name="2024 Season") session.add(cardset_1) session.add(cardset_2) session.commit() link_1 = GameCardsetLink(game=game_1, cardset=cardset_1, priority=1) link_2 = GameCardsetLink(game=game_1, cardset=cardset_2, priority=1) link_3 = GameCardsetLink(game=game_2, cardset=cardset_1, priority=1) link_4 = GameCardsetLink(game=game_2, cardset=cardset_2, priority=2) session.add(link_1) session.add(link_2) session.add(link_3) session.add(link_4) session.commit() all_players = [] all_pitratings = [] all_batratings = [] all_batcards = [] all_pitcards = [] pos_list = ["C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "DH", "P"] # Create players first for x in range(40): if x < 10: mlb_team = "Baltimore Orioles" team_id = 31 elif x < 20: mlb_team = "New York Yankees" team_id = 400 elif x < 30: mlb_team = "Boston Red Sox" team_id = 69 else: mlb_team = "Tampa Bay Rays" team_id = 420 all_players.append( Player( id=x + 1, name=f"Player {x}", cost=x * 3, image=f"player_{x}_image_url", mlbclub=mlb_team, franchise=mlb_team, cardset=cardset_1 if x % 2 == 1 else cardset_2, set_num=x, rarity_id=1, pos_1=pos_list[(x % 10)], description="Live" if x % 2 == 1 else "2024", ) ) # Create cards and ratings for each player # Is Batter if (x + 1) % 10 != 0: all_batcards.append( BattingCard( id=x + 1, steal_high=10 + (x % 10), hand="R", offense_col=1 ) ) all_batratings.append(BattingRatings(id=x + 1, homerun=x % 10)) all_batratings.append(BattingRatings(id=x + 1001, homerun=x % 10)) # Is Pitcher else: all_pitcards.append( PitchingCard( id=x + 1, wild_pitch=x / 10, hand="R", starter_rating=5, offense_col=1, ) ) all_pitratings.append(PitchingRatings(id=x + 1, homerun=x % 10)) all_pitratings.append(PitchingRatings(id=x + 1001, homerun=x % 10)) all_players.append( Player( id=41, name="Player 40", cost=41 * 3, mlbclub="Junior All-Stars", franchise="Junior All-Stars", cardset=cardset_1, set_num=41, pos_1="DH", description="Live", created=datetime.datetime.today() - datetime.timedelta(days=60), image="player_41_image_URL", rarity_id=1, ) ) all_players.append( Player( id=69, name="Player 68", cost=69 * 3, mlbclub="Junior All-Stars", franchise="Junior All-Stars", cardset=cardset_1, set_num=69, pos_1="DH", description="Live", created=datetime.datetime.today() - datetime.timedelta(days=60), image="player_69_image_URL", rarity_id=1, ) ) # Add scouting data for player 41 (batter) all_batcards.append( BattingCard(id=41, steal_high=15, hand="R", offense_col=1) ) all_batratings.append(BattingRatings(id=41, homerun=5)) all_batratings.append(BattingRatings(id=41001, homerun=5)) all_players.append( Player( id=70, name="Player 69", cost=70 * 3, mlbclub="Junior All-Stars", franchise="Junior All-Stars", cardset=cardset_1, set_num=70, pos_1="SP", pos_2="DH", description="Live", created=datetime.datetime.today() - datetime.timedelta(days=60), image="player_69_pitchingcard", image2="player_69_battingcard", rarity_id=1, ) ) # Add scouting data for player 70 (two-way player - pitcher primary) all_pitcards.append( PitchingCard( id=70, wild_pitch=2.0, hand="R", starter_rating=7, offense_col=1 ) ) all_pitratings.append(PitchingRatings(id=70, homerun=3)) all_pitratings.append(PitchingRatings(id=70001, homerun=3)) # Also add batting data for player 70 with unique ID all_batcards.append( BattingCard( id=170, # Use unique ID to avoid conflicts steal_high=12, hand="R", offense_col=1, ) ) all_batratings.append(BattingRatings(id=170, homerun=4)) all_batratings.append(BattingRatings(id=170001, homerun=4)) # Add records in correct order to avoid foreign key constraint errors # 1. Add players first (no dependencies) for player in all_players: session.add(player) session.commit() # 2. Add batting/pitching cards and ratings (depend on players) for batcard in all_batcards: session.add(batcard) for pitcard in all_pitcards: session.add(pitcard) for batrating in all_batratings: session.add(batrating) for pitrating in all_pitratings: session.add(pitrating) session.commit() # 3. Create scouting records and cards in a coordinated way # Create cards for each player based on their position for x in range(40): if x < 10: team_id = 31 elif x < 20: team_id = 400 elif x < 30: team_id = 69 else: team_id = 420 # Is Batter if (x + 1) % 10 != 0: # Create batter scouting (let PostgreSQL assign ID) batscouting = BatterScouting( battingcard_id=x + 1, ratings_vr_id=x + 1, ratings_vl_id=x + 1001, ) session.add(batscouting) session.commit() session.refresh(batscouting) # Get the auto-generated ID # Create card with the actual scouting ID card = Card( id=x + 1, player_id=x + 1, team_id=team_id, batterscouting_id=batscouting.id, ) session.add(card) # Is Pitcher else: # Create pitcher scouting (let PostgreSQL assign ID) pitscouting = PitcherScouting( pitchingcard_id=x + 1, ratings_vr_id=x + 1, ratings_vl_id=x + 1001, ) session.add(pitscouting) session.commit() session.refresh(pitscouting) # Get the auto-generated ID # Create card with the actual scouting ID card = Card( id=x + 1, player_id=x + 1, team_id=team_id, pitcherscouting_id=pitscouting.id, ) session.add(card) # Handle special players 41 and 70 # Create batter scouting for player 41 batscouting_41 = BatterScouting( battingcard_id=41, ratings_vr_id=41, ratings_vl_id=41001, ) session.add(batscouting_41) session.commit() session.refresh(batscouting_41) card_41 = Card( id=41, player_id=41, team_id=420, created=datetime.datetime.now() - datetime.timedelta(days=60), batterscouting_id=batscouting_41.id, ) session.add(card_41) # Create pitcher scouting for player 70 pitscouting_70 = PitcherScouting( pitchingcard_id=70, ratings_vr_id=70, ratings_vl_id=70001 ) session.add(pitscouting_70) session.commit() session.refresh(pitscouting_70) # Create batter scouting for player 70 (two-way player) batscouting_70 = BatterScouting( battingcard_id=170, ratings_vr_id=170, ratings_vl_id=170001, ) session.add(batscouting_70) session.commit() session.refresh(batscouting_70) card_70 = Card( id=70, player_id=70, team_id=420, pitcherscouting_id=pitscouting_70.id, batterscouting_id=batscouting_70.id, ) session.add(card_70) session.commit() # Add position ratings for players (needed for manager AI decisions) all_position_ratings = [] pos_list = ["C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "DH", "P"] for x in range(40): player_pos = pos_list[(x % 10)] # Add defensive rating for each player's primary position all_position_ratings.append( PositionRating( player_id=x + 1, variant=0, position=player_pos, innings=100, range=5, error=1, arm=( 7 if player_pos == "C" else None ), # Catchers need arm rating pb=( 2 if player_pos == "C" else None ), # Catchers need passed ball rating overthrow=1 if player_pos == "C" else None, ) ) # Add position ratings for special players all_position_ratings.append( PositionRating( player_id=41, variant=0, position="DH", innings=100, range=5, error=1, ) ) all_position_ratings.append( PositionRating( player_id=69, variant=0, position="DH", innings=100, range=5, error=1, ) ) all_position_ratings.append( PositionRating( player_id=70, variant=0, position="SP", innings=100, range=5, error=1, ) ) # Also add DH rating for player 70 (two-way player) all_position_ratings.append( PositionRating( player_id=70, variant=0, position="DH", innings=50, range=4, error=2 ) ) for pos_rating in all_position_ratings: session.add(pos_rating) session.commit() all_lineups = [] player_count = 1 lineup_id = 1 for team in [team_1, team_2]: for order, pos in [ (1, "C"), (2, "1B"), (3, "2B"), (4, "3B"), (5, "SS"), (6, "LF"), (7, "CF"), (8, "RF"), (9, "DH"), (10, "P"), ]: all_lineups.append( Lineup( id=lineup_id, position=pos, batting_order=order, game=game_1, team=team, player_id=player_count, card_id=player_count, ) ) player_count += 1 lineup_id += 1 for team in [team_3, team_4]: for order, pos in [ (1, "C"), (2, "1B"), (3, "2B"), (4, "3B"), (5, "SS"), (6, "LF"), (7, "CF"), (8, "RF"), (9, "DH"), (10, "P"), ]: all_lineups.append( Lineup( id=lineup_id, position=pos, batting_order=order, game=game_3, team=team, player_id=player_count, card_id=player_count, ) ) player_count += 1 lineup_id += 1 for lineup in all_lineups: session.add(lineup) game_1_play_1 = Play( game=game_1, play_num=1, batter=all_lineups[0], batter_pos=all_lineups[0].position, pitcher=all_lineups[19], catcher=all_lineups[10], pa=1, so=1, outs=1, complete=True, ) game_1_play_2 = Play( game=game_1, play_num=2, batter=all_lineups[1], batter_pos=all_lineups[1].position, pitcher=all_lineups[19], catcher=all_lineups[10], starting_outs=1, ) session.add(game_1_play_1) session.add(game_1_play_2) session.commit() g1_t1_cards = session.exec(select(Card).where(Card.team_id == 31)).all() g1_t2_cards = session.exec(select(Card).where(Card.team_id == 400)).all() g2_t1_cards = session.exec(select(Card).where(Card.team_id == 69)).all() g2_t2_cards = session.exec(select(Card).where(Card.team_id == 420)).all() for card in [*g1_t1_cards, *g1_t2_cards]: session.add( RosterLink(game_id=1, card_id=card.id, team_id=card.team_id) ) for card in [*g2_t1_cards, *g2_t2_cards]: session.add( RosterLink(game_id=3, card_id=card.id, team_id=card.team_id) ) # session.add(RosterLink( # game_id=3, # card_id=12, # team_id=420 # )) session.commit() # Create ManagerAi objects with explicit IDs for test compatibility manager_ai_1 = ManagerAi(id=1, name="Balanced") manager_ai_2 = ManagerAi( id=2, name="Yolo", steal=10, running=10, hold=5, catcher_throw=10, uncapped_home=10, uncapped_third=10, uncapped_trail=10, bullpen_matchup=3, behind_aggression=10, ahead_aggression=10, decide_throw=10, ) manager_ai_3 = ManagerAi( id=3, name="Safe", steal=3, running=3, hold=8, catcher_throw=5, uncapped_home=5, uncapped_third=3, uncapped_trail=5, bullpen_matchup=8, behind_aggression=5, ahead_aggression=1, decide_throw=1, ) session.add(manager_ai_1) session.add(manager_ai_2) session.add(manager_ai_3) session.commit() # Reset sequences so auto-generated IDs don't conflict with factory data session.exec( text( "SELECT setval(pg_get_serial_sequence('lineup', 'id'), COALESCE((SELECT MAX(id) FROM lineup), 1))" ) ) session.exec( text( "SELECT setval(pg_get_serial_sequence('play', 'id'), COALESCE((SELECT MAX(id) FROM play), 1))" ) ) session.commit() yield session @pytest.fixture def sample_ratings_query(): """Factory method for sample ratings query data used in batter scouting tests.""" return { "count": 2, "ratings": [ { "id": 7673, "battingcard": { "id": 3837, "player": { "player_id": 395, "p_name": "Cedric Mullins", "cost": 256, "image": "https://pd.manticorum.com/api/v2/players/395/battingcard?d=2023-11-19", "image2": None, "mlbclub": "Baltimore Orioles", "franchise": "Baltimore Orioles", "cardset": { "id": 1, "name": "2021 Season", "description": "Cards based on the full 2021 season", "event": None, "for_purchase": True, "total_cards": 791, "in_packs": True, "ranked_legal": False, }, "set_num": 395, "rarity": { "id": 2, "value": 3, "name": "All-Star", "color": "FFD700", }, "pos_1": "CF", "pos_2": None, "pos_3": None, "pos_4": None, "pos_5": None, "pos_6": None, "pos_7": None, "pos_8": None, "headshot": "https://www.baseball-reference.com/req/202206291/images/headshots/2/24bf4355_mlbam.jpg", "vanity_card": None, "strat_code": "17929", "bbref_id": "mullice01", "fangr_id": None, "description": "2021", "quantity": 999, "mlbplayer": { "id": 396, "first_name": "Cedric", "last_name": "Mullins", "key_fangraphs": 17929, "key_bbref": "mullice01", "key_retro": "mullc002", "key_mlbam": 656775, "offense_col": 1, }, }, "variant": 0, "steal_low": 9, "steal_high": 12, "steal_auto": True, "steal_jump": 0.2222222222222222, "bunting": "C", "hit_and_run": "B", "running": 13, "offense_col": 1, "hand": "L", }, "vs_hand": "L", "pull_rate": 0.43888889, "center_rate": 0.32777778, "slap_rate": 0.23333333, "homerun": 2.25, "bp_homerun": 5.0, "triple": 0.0, "double_three": 0.0, "double_two": 2.4, "double_pull": 7.2, "single_two": 4.6, "single_one": 3.5, "single_center": 5.85, "bp_single": 5.0, "hbp": 2.0, "walk": 9.0, "strikeout": 23.0, "lineout": 3.0, "popout": 6.0, "flyout_a": 0.0, "flyout_bq": 0.15, "flyout_lf_b": 2.8, "flyout_rf_b": 3.75, "groundout_a": 0.0, "groundout_b": 9.0, "groundout_c": 13.5, "avg": 0.2851851851851852, "obp": 0.387037037037037, "slg": 0.5060185185185185, }, { "id": 7674, "battingcard": { "id": 3837, "player": { "player_id": 395, "p_name": "Cedric Mullins", "cost": 256, "image": "https://pd.manticorum.com/api/v2/players/395/battingcard?d=2023-11-19", "image2": None, "mlbclub": "Baltimore Orioles", "franchise": "Baltimore Orioles", "cardset": { "id": 1, "name": "2021 Season", "description": "Cards based on the full 2021 season", "event": None, "for_purchase": True, "total_cards": 791, "in_packs": True, "ranked_legal": False, }, "set_num": 395, "rarity": { "id": 2, "value": 3, "name": "All-Star", "color": "FFD700", }, "pos_1": "CF", "pos_2": None, "pos_3": None, "pos_4": None, "pos_5": None, "pos_6": None, "pos_7": None, "pos_8": None, "headshot": "https://www.baseball-reference.com/req/202206291/images/headshots/2/24bf4355_mlbam.jpg", "vanity_card": None, "strat_code": "17929", "bbref_id": "mullice01", "fangr_id": None, "description": "2021", "quantity": 999, "mlbplayer": { "id": 396, "first_name": "Cedric", "last_name": "Mullins", "key_fangraphs": 17929, "key_bbref": "mullice01", "key_retro": "mullc002", "key_mlbam": 656775, "offense_col": 1, }, }, "variant": 0, "steal_low": 9, "steal_high": 12, "steal_auto": True, "steal_jump": 0.2222222222222222, "bunting": "C", "hit_and_run": "B", "running": 13, "offense_col": 1, "hand": "L", }, "vs_hand": "R", "pull_rate": 0.43377483, "center_rate": 0.32119205, "slap_rate": 0.24503311, "homerun": 2.0, "bp_homerun": 7.0, "triple": 0.0, "double_three": 0.0, "double_two": 2.7, "double_pull": 7.95, "single_two": 3.3, "single_one": 0.0, "single_center": 8.6, "bp_single": 5.0, "hbp": 1.0, "walk": 12.9, "strikeout": 11.1, "lineout": 8.0, "popout": 11.0, "flyout_a": 0.0, "flyout_bq": 0.4, "flyout_lf_b": 4.05, "flyout_rf_b": 6.0, "groundout_a": 0.0, "groundout_b": 3.0, "groundout_c": 14.0, "avg": 0.2828703703703704, "obp": 0.4115740740740741, "slg": 0.5342592592592592, }, ], } def create_incomplete_team(session: Session): """Factory method for creating an incomplete team for constraint testing.""" return Team( id=446, abbrev="CLS", sname="Macho Men", lname="Columbus Macho Men", gmid=181818, gmname="Mason Socc", gsheet="asdf1234", wallet=6969, team_value=69420, collection_value=169420, color="https://i.postimg.cc/8kLZCYXh/S10CLS.png", season=7, event=False, career=0, ) def create_sample_play_for_scorebug(session: Session): """Factory method for creating a play object for scorebug testing.""" return Play( game_id=3, play_num=69, batter=session.get(Lineup, 1), batter_pos="DH", pitcher=session.get(Lineup, 20), catcher=session.get(Lineup, 11), starting_outs=1, inning_num=6, ) def get_next_play_id(session: Session): """Get the next available Play ID for SQLite tests.""" from sqlmodel import func max_id = session.exec(select(func.max(Play.id))).one() return (max_id or 0) + 1