CLAUDE: Extract database schema to reference document

- Created backend/.claude/DATABASE_SCHEMA.md with full table details
- Updated database/CLAUDE.md to reference the new schema doc
- Preserves valuable reference material while keeping CLAUDE.md concise

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-11-19 16:27:27 -06:00
parent 88a5207c2c
commit 9546d2a370
2 changed files with 259 additions and 0 deletions

View File

@ -0,0 +1,258 @@
# Database Schema Reference
Detailed database schema for the Paper Dynasty game engine. Based on proven Discord game implementation with enhancements for web real-time gameplay.
---
## Core Tables
### Game (`games`)
Primary game container with state tracking.
**Key Fields:**
- `id` (UUID): Primary key, prevents ID collisions across distributed systems
- `league_id` (String): 'sba' or 'pd', determines league-specific behavior
- `status` (String): 'pending', 'active', 'completed'
- `game_mode` (String): 'ranked', 'friendly', 'practice'
- `visibility` (String): 'public', 'private'
- `current_inning`, `current_half`: Current game state
- `home_score`, `away_score`: Running scores
**AI Support:**
- `home_team_is_ai` (Boolean): Home team controlled by AI
- `away_team_is_ai` (Boolean): Away team controlled by AI
- `ai_difficulty` (String): 'balanced', 'yolo', 'safe'
**Relationships:**
- `plays`: All plays in the game (cascade delete)
- `lineups`: All lineup entries (cascade delete)
- `cardset_links`: PD only - approved cardsets (cascade delete)
- `roster_links`: Roster tracking - cards (PD) or players (SBA) (cascade delete)
- `session`: Real-time WebSocket session (cascade delete)
---
### Play (`plays`)
Records every at-bat with full statistics and game state.
**Game State Snapshot:**
- `play_number`: Sequential play counter
- `inning`, `half`: Inning state
- `outs_before`: Outs at start of play
- `batting_order`: Current spot in order
- `away_score`, `home_score`: Score at play start
**Player References (FKs to Lineup):**
- `batter_id`, `pitcher_id`, `catcher_id`: Required players
- `defender_id`, `runner_id`: Optional for specific plays
- `on_first_id`, `on_second_id`, `on_third_id`: Base runners
**Runner Outcomes:**
- `on_first_final`, `on_second_final`, `on_third_final`: Final base (None = out, 1-4 = base)
- `batter_final`: Where batter ended up
- `on_base_code` (Integer): Bit field for efficient queries (1=1st, 2=2nd, 4=3rd, 7=loaded)
**Strategic Decisions:**
- `defensive_choices` (JSON): Alignment, holds, shifts
- `offensive_choices` (JSON): Steal attempts, bunts, hit-and-run
**Play Result:**
- `dice_roll` (String): Dice notation (e.g., "14+6")
- `hit_type` (String): GB, FB, LD, etc.
- `result_description` (Text): Human-readable result
- `outs_recorded`, `runs_scored`: Play outcome
- `check_pos` (String): Defensive position for X-check
**Batting Statistics (25+ fields):**
- `pa`, `ab`, `hit`, `double`, `triple`, `homerun`
- `bb`, `so`, `hbp`, `rbi`, `sac`, `ibb`, `gidp`
- `sb`, `cs`: Base stealing
- `wild_pitch`, `passed_ball`, `pick_off`, `balk`
- `bphr`, `bpfo`, `bp1b`, `bplo`: Ballpark power events
- `run`, `e_run`: Earned/unearned runs
**Advanced Analytics:**
- `wpa` (Float): Win Probability Added
- `re24` (Float): Run Expectancy 24 base-out states
**Game Situation Flags:**
- `is_tied`, `is_go_ahead`, `is_new_inning`: Context flags
- `in_pow`: Pitcher over workload
- `complete`, `locked`: Workflow state
**Helper Properties:**
```python
@property
def ai_is_batting(self) -> bool:
"""True if batting team is AI-controlled"""
return (self.half == 'top' and self.game.away_team_is_ai) or \
(self.half == 'bot' and self.game.home_team_is_ai)
@property
def ai_is_fielding(self) -> bool:
"""True if fielding team is AI-controlled"""
return not self.ai_is_batting
```
---
### Lineup (`lineups`)
Tracks player assignments and substitutions.
**Key Fields:**
- `game_id`, `team_id`, `card_id`: Links to game/team/card
- `position` (String): P, C, 1B, 2B, 3B, SS, LF, CF, RF, DH
- `batting_order` (Integer): 1-9
**Substitution Tracking:**
- `is_starter` (Boolean): Original lineup vs substitute
- `is_active` (Boolean): Currently in game
- `entered_inning` (Integer): When player entered
- `replacing_id` (Integer): Lineup ID of replaced player
- `after_play` (Integer): Exact play number of substitution
**Pitcher Management:**
- `is_fatigued` (Boolean): Triggers bullpen decisions
---
### GameCardsetLink (`game_cardset_links`)
PD league only - defines legal cardsets for a game.
**Key Fields:**
- `game_id`, `cardset_id`: Composite primary key
- `priority` (Integer): 1 = primary, 2+ = backup
**Usage:**
- SBA games: Empty (no cardset restrictions)
- PD games: Required (validates card eligibility)
---
### RosterLink (`roster_links`)
Tracks eligible cards (PD) or players (SBA) for a game.
**Polymorphic Design**: Single table supporting both leagues with application-layer type safety.
**Key Fields:**
- `id` (Integer): Surrogate primary key (auto-increment)
- `game_id` (UUID): Foreign key to games table
- `card_id` (Integer, nullable): PD league - card identifier
- `player_id` (Integer, nullable): SBA league - player identifier
- `team_id` (Integer): Which team owns this entity in this game
**Constraints:**
- `roster_link_one_id_required`: CHECK constraint ensures exactly one of `card_id` or `player_id` is populated (XOR logic)
- `uq_game_card`: UNIQUE constraint on (game_id, card_id) for PD
- `uq_game_player`: UNIQUE constraint on (game_id, player_id) for SBA
**Usage Pattern:**
```python
# PD league - add card to roster
roster_data = await db_ops.add_pd_roster_card(
game_id=game_id,
card_id=123,
team_id=1
)
# SBA league - add player to roster
roster_data = await db_ops.add_sba_roster_player(
game_id=game_id,
player_id=456,
team_id=2
)
# Get roster (league-specific)
pd_roster = await db_ops.get_pd_roster(game_id, team_id=1)
sba_roster = await db_ops.get_sba_roster(game_id, team_id=2)
```
**Design Rationale:**
- Single table avoids complex joins and simplifies queries
- Nullable columns with CHECK constraint ensures data integrity at database level
- Pydantic models (`PdRosterLinkData`, `SbaRosterLinkData`) provide type safety at application layer
- Surrogate key allows nullable columns (can't use nullable columns in composite PK)
---
### GameSession (`game_sessions`)
Real-time WebSocket state tracking.
**Key Fields:**
- `game_id` (UUID): Primary key, one-to-one with Game
- `connected_users` (JSON): Active WebSocket connections
- `last_action_at` (DateTime): Last activity timestamp
- `state_snapshot` (JSON): In-memory game state cache
---
## Common Query Patterns
### Async Session Usage
```python
from app.database.session import get_session
async def some_function():
async with get_session() as session:
result = await session.execute(query)
# session.commit() happens automatically
```
### Relationship Loading
```python
from sqlalchemy.orm import joinedload
# Efficient loading for broadcasting
play = await session.execute(
select(Play)
.options(
joinedload(Play.batter),
joinedload(Play.pitcher),
joinedload(Play.on_first),
joinedload(Play.on_second),
joinedload(Play.on_third)
)
.where(Play.id == play_id)
)
```
### Find Plays with Bases Loaded
```python
# Using on_base_code bit field
bases_loaded_plays = await session.execute(
select(Play).where(Play.on_base_code == 7) # 1+2+4 = 7
)
```
### Get Active Pitcher
```python
pitcher = await session.execute(
select(Lineup)
.where(
Lineup.game_id == game_id,
Lineup.team_id == team_id,
Lineup.position == 'P',
Lineup.is_active == True
)
)
```
### Calculate Box Score Stats
```python
stats = await session.execute(
select(
func.sum(Play.ab).label('ab'),
func.sum(Play.hit).label('hits'),
func.sum(Play.homerun).label('hr'),
func.sum(Play.rbi).label('rbi')
)
.where(
Play.game_id == game_id,
Play.batter_id == lineup_id
)
)
```
---
**Updated**: 2025-01-19

View File

@ -122,6 +122,7 @@ DATABASE_URL=postgresql+asyncpg://user:pass@10.10.0.42:5432/paperdynasty_dev
## References
- **Database Schema**: See `../../.claude/DATABASE_SCHEMA.md` for complete table details
- **Models**: See `../models/CLAUDE.md`
- **State Recovery**: See `../core/state_manager.py`