Initialize both Nuxt 3 frontends (SBA and PD) with full configuration: Frontend Setup: - Initialized Nuxt 3 projects for both leagues (SBA and PD) - Installed dependencies: Tailwind CSS, Pinia, Socket.io-client, Axios - Configured league-specific settings in nuxt.config.ts - Created WebSocket plugins for real-time communication - Set up TypeScript with strict mode and type checking - Configured Tailwind CSS for styling Backend Updates: - Updated database models documentation in backend/CLAUDE.md - Enhanced db_models.py with additional relationship patterns Documentation: - Updated Phase 1 completion checklist (12/12 items - 100% complete) - Marked all infrastructure objectives as complete Running Services: - Backend (FastAPI + Socket.io): http://localhost:8000 - Frontend SBA: http://localhost:3000 - Frontend PD: http://localhost:3001 - Redis: port 6379 - PostgreSQL: Connected to remote server Phase 1 is now complete. Ready to proceed to Phase 2 (Game Engine Core). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
246 lines
9.2 KiB
Python
246 lines
9.2 KiB
Python
from sqlalchemy import Column, Integer, String, Boolean, DateTime, JSON, Text, ForeignKey, Float
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
import uuid
|
|
import pendulum
|
|
|
|
from app.database.session import Base
|
|
|
|
|
|
class GameCardsetLink(Base):
|
|
"""Link table for PD games - tracks which cardsets are allowed"""
|
|
__tablename__ = "game_cardset_links"
|
|
|
|
game_id = Column(UUID(as_uuid=True), ForeignKey("games.id", ondelete="CASCADE"), primary_key=True)
|
|
cardset_id = Column(Integer, primary_key=True)
|
|
priority = Column(Integer, default=1, index=True)
|
|
|
|
# Relationships
|
|
game = relationship("Game", back_populates="cardset_links")
|
|
|
|
|
|
class RosterLink(Base):
|
|
"""Tracks which cards each team is using in a game - PD only"""
|
|
__tablename__ = "roster_links"
|
|
|
|
game_id = Column(UUID(as_uuid=True), ForeignKey("games.id", ondelete="CASCADE"), primary_key=True)
|
|
card_id = Column(Integer, primary_key=True)
|
|
team_id = Column(Integer, nullable=False, index=True)
|
|
|
|
# Relationships
|
|
game = relationship("Game", back_populates="roster_links")
|
|
|
|
|
|
class Game(Base):
|
|
"""Game model"""
|
|
__tablename__ = "games"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
league_id = Column(String(50), nullable=False, index=True)
|
|
home_team_id = Column(Integer, nullable=False)
|
|
away_team_id = Column(Integer, nullable=False)
|
|
status = Column(String(20), nullable=False, default="pending", index=True)
|
|
game_mode = Column(String(20), nullable=False)
|
|
visibility = Column(String(20), nullable=False)
|
|
current_inning = Column(Integer)
|
|
current_half = Column(String(10))
|
|
home_score = Column(Integer, default=0)
|
|
away_score = Column(Integer, default=0)
|
|
|
|
# AI opponent configuration
|
|
home_team_is_ai = Column(Boolean, default=False)
|
|
away_team_is_ai = Column(Boolean, default=False)
|
|
ai_difficulty = Column(String(20), nullable=True)
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime, default=lambda: pendulum.now('UTC'), index=True)
|
|
started_at = Column(DateTime)
|
|
completed_at = Column(DateTime)
|
|
|
|
# Results
|
|
winner_team_id = Column(Integer)
|
|
game_metadata = Column(JSON, default=dict)
|
|
|
|
# Relationships
|
|
plays = relationship("Play", back_populates="game", cascade="all, delete-orphan")
|
|
lineups = relationship("Lineup", back_populates="game", cascade="all, delete-orphan")
|
|
cardset_links = relationship("GameCardsetLink", back_populates="game", cascade="all, delete-orphan")
|
|
roster_links = relationship("RosterLink", back_populates="game", cascade="all, delete-orphan")
|
|
session = relationship("GameSession", back_populates="game", uselist=False, cascade="all, delete-orphan")
|
|
|
|
|
|
class Play(Base):
|
|
"""Play model - tracks individual plays/at-bats"""
|
|
__tablename__ = "plays"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
game_id = Column(UUID(as_uuid=True), ForeignKey("games.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
play_number = Column(Integer, nullable=False)
|
|
|
|
# Game state at start of play
|
|
inning = Column(Integer, nullable=False, default=1)
|
|
half = Column(String(10), nullable=False)
|
|
outs_before = Column(Integer, nullable=False, default=0)
|
|
batting_order = Column(Integer, nullable=False, default=1)
|
|
away_score = Column(Integer, default=0)
|
|
home_score = Column(Integer, default=0)
|
|
|
|
# Players involved (ForeignKeys to Lineup)
|
|
batter_id = Column(Integer, ForeignKey("lineups.id"), nullable=False)
|
|
pitcher_id = Column(Integer, ForeignKey("lineups.id"), nullable=False)
|
|
catcher_id = Column(Integer, ForeignKey("lineups.id"), nullable=False)
|
|
defender_id = Column(Integer, ForeignKey("lineups.id"), nullable=True)
|
|
runner_id = Column(Integer, ForeignKey("lineups.id"), nullable=True)
|
|
|
|
# Base runners (ForeignKeys to Lineup for who's on base)
|
|
on_first_id = Column(Integer, ForeignKey("lineups.id"), nullable=True)
|
|
on_second_id = Column(Integer, ForeignKey("lineups.id"), nullable=True)
|
|
on_third_id = Column(Integer, ForeignKey("lineups.id"), nullable=True)
|
|
|
|
# Runner final positions (None = out, 1-4 = base)
|
|
on_first_final = Column(Integer, nullable=True)
|
|
on_second_final = Column(Integer, nullable=True)
|
|
on_third_final = Column(Integer, nullable=True)
|
|
batter_final = Column(Integer, nullable=True)
|
|
|
|
# Base state code for efficient queries (bit field: 1=1st, 2=2nd, 4=3rd, 7=loaded)
|
|
on_base_code = Column(Integer, default=0)
|
|
|
|
# Strategic decisions
|
|
defensive_choices = Column(JSON, default=dict)
|
|
offensive_choices = Column(JSON, default=dict)
|
|
|
|
# Play result
|
|
dice_roll = Column(String(50))
|
|
hit_type = Column(String(50))
|
|
result_description = Column(Text)
|
|
outs_recorded = Column(Integer, nullable=False, default=0)
|
|
runs_scored = Column(Integer, default=0)
|
|
|
|
# Defensive details
|
|
check_pos = Column(String(10), nullable=True)
|
|
error = Column(Integer, default=0)
|
|
|
|
# Batting statistics
|
|
pa = Column(Integer, default=0)
|
|
ab = Column(Integer, default=0)
|
|
hit = Column(Integer, default=0)
|
|
double = Column(Integer, default=0)
|
|
triple = Column(Integer, default=0)
|
|
homerun = Column(Integer, default=0)
|
|
bb = Column(Integer, default=0)
|
|
so = Column(Integer, default=0)
|
|
hbp = Column(Integer, default=0)
|
|
rbi = Column(Integer, default=0)
|
|
sac = Column(Integer, default=0)
|
|
ibb = Column(Integer, default=0)
|
|
gidp = Column(Integer, default=0)
|
|
|
|
# Baserunning statistics
|
|
sb = Column(Integer, default=0)
|
|
cs = Column(Integer, default=0)
|
|
|
|
# Pitching events
|
|
wild_pitch = Column(Integer, default=0)
|
|
passed_ball = Column(Integer, default=0)
|
|
pick_off = Column(Integer, default=0)
|
|
balk = Column(Integer, default=0)
|
|
|
|
# Ballpark power events
|
|
bphr = Column(Integer, default=0)
|
|
bpfo = Column(Integer, default=0)
|
|
bp1b = Column(Integer, default=0)
|
|
bplo = Column(Integer, default=0)
|
|
|
|
# Advanced analytics
|
|
wpa = Column(Float, default=0.0)
|
|
re24 = Column(Float, default=0.0)
|
|
|
|
# Earned/unearned runs
|
|
run = Column(Integer, default=0)
|
|
e_run = Column(Integer, default=0)
|
|
|
|
# Game situation flags
|
|
is_tied = Column(Boolean, default=False)
|
|
is_go_ahead = Column(Boolean, default=False)
|
|
is_new_inning = Column(Boolean, default=False)
|
|
in_pow = Column(Boolean, default=False)
|
|
|
|
# Play workflow
|
|
complete = Column(Boolean, default=False, index=True)
|
|
locked = Column(Boolean, default=False)
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime, default=lambda: pendulum.now('UTC'), index=True)
|
|
|
|
# Extensibility (use for custom runner data like jump status, etc.)
|
|
play_metadata = Column(JSON, default=dict)
|
|
|
|
# Relationships
|
|
game = relationship("Game", back_populates="plays")
|
|
batter = relationship("Lineup", foreign_keys=[batter_id], lazy="joined")
|
|
pitcher = relationship("Lineup", foreign_keys=[pitcher_id], lazy="joined")
|
|
catcher = relationship("Lineup", foreign_keys=[catcher_id], lazy="joined")
|
|
defender = relationship("Lineup", foreign_keys=[defender_id])
|
|
runner = relationship("Lineup", foreign_keys=[runner_id])
|
|
on_first = relationship("Lineup", foreign_keys=[on_first_id])
|
|
on_second = relationship("Lineup", foreign_keys=[on_second_id])
|
|
on_third = relationship("Lineup", foreign_keys=[on_third_id])
|
|
|
|
@property
|
|
def ai_is_batting(self) -> bool:
|
|
"""Determine if current batting team is AI-controlled"""
|
|
if self.half == 'top':
|
|
return self.game.away_team_is_ai
|
|
else:
|
|
return self.game.home_team_is_ai
|
|
|
|
@property
|
|
def ai_is_fielding(self) -> bool:
|
|
"""Determine if current fielding team is AI-controlled"""
|
|
if self.half == 'top':
|
|
return self.game.home_team_is_ai
|
|
else:
|
|
return self.game.away_team_is_ai
|
|
|
|
|
|
class Lineup(Base):
|
|
"""Lineup model - tracks player assignments in a game"""
|
|
__tablename__ = "lineups"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
game_id = Column(UUID(as_uuid=True), ForeignKey("games.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
team_id = Column(Integer, nullable=False, index=True)
|
|
card_id = Column(Integer, nullable=False)
|
|
position = Column(String(10), nullable=False)
|
|
batting_order = Column(Integer)
|
|
|
|
# Substitution tracking
|
|
is_starter = Column(Boolean, default=True)
|
|
is_active = Column(Boolean, default=True, index=True)
|
|
entered_inning = Column(Integer, default=1)
|
|
replacing_id = Column(Integer, nullable=True) # Lineup ID of player being replaced
|
|
after_play = Column(Integer, nullable=True) # Play number when substitution occurred
|
|
|
|
# Pitcher fatigue
|
|
is_fatigued = Column(Boolean, nullable=True)
|
|
|
|
# Extensibility
|
|
lineup_metadata = Column(JSON, default=dict)
|
|
|
|
# Relationships
|
|
game = relationship("Game", back_populates="lineups")
|
|
|
|
|
|
class GameSession(Base):
|
|
"""Game session tracking - real-time WebSocket state"""
|
|
__tablename__ = "game_sessions"
|
|
|
|
game_id = Column(UUID(as_uuid=True), ForeignKey("games.id", ondelete="CASCADE"), primary_key=True)
|
|
connected_users = Column(JSON, default=dict)
|
|
last_action_at = Column(DateTime, default=lambda: pendulum.now('UTC'), index=True)
|
|
state_snapshot = Column(JSON, default=dict)
|
|
|
|
# Relationships
|
|
game = relationship("Game", back_populates="session")
|