paper-dynasty-discord/tests/factory.py
2025-11-11 13:22:06 -06:00

735 lines
29 KiB
Python

import datetime
import os
import pytest
from sqlmodel import Session, SQLModel, create_engine, select, text
from testcontainers.postgres import PostgresContainer
from typing import Literal
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
)
incomplete_team = 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
)
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(incomplete_team)
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_cards = []
all_batscouting = []
all_pitscouting = []
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