strat-gameplay-webapp/backend/app/database/CLAUDE.md
Cal Corum 88a5207c2c CLAUDE: Refactor backend CLAUDE.md files for conciseness
Major reduction in CLAUDE.md file sizes to follow concise documentation standard:

| File | Before | After | Reduction |
|------|--------|-------|-----------|
| backend/CLAUDE.md | 2,467 | 123 | 95% |
| models/CLAUDE.md | 1,586 | 102 | 94% |
| websocket/CLAUDE.md | 2,094 | 119 | 94% |
| config/CLAUDE.md | 1,017 | 126 | 88% |
| database/CLAUDE.md | 946 | 130 | 86% |
| api/CLAUDE.md | 906 | 140 | 85% |

Total: 9,016 -> 740 lines (92% reduction)

All files now under 150 lines with:
- Essential patterns and usage
- Cross-references to related docs
- Quick-start examples
- Updated timestamps

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 16:10:08 -06:00

3.2 KiB

Database Module - Async PostgreSQL Persistence

Purpose

Async PostgreSQL persistence layer using SQLAlchemy 2.0. Handles all database operations with connection pooling and proper transaction management.

Structure

app/database/
├── __init__.py      # Package exports
├── session.py       # Async session management, Base declarative
└── operations.py    # DatabaseOperations class

Session Management

Async Session Pattern

from app.database.session import get_session

async def some_function():
    async with get_session() as session:
        result = await session.execute(query)
        # Auto-commits on success, rolls back on exception

Connection Pool

  • Driver: asyncpg
  • Pool: SQLAlchemy async pool
  • Configurable pool size via env vars

DatabaseOperations

Singleton class with all database operations.

Game Operations

from app.database.operations import DatabaseOperations
db_ops = DatabaseOperations()

await db_ops.create_game(game_id, league_id, home_team_id, away_team_id, ...)
await db_ops.update_game_state(game_id, inning, half, home_score, away_score)
game_data = await db_ops.load_game_state(game_id)

Play Operations

play_id = await db_ops.save_play(game_id, play_data, stats_data)
plays = await db_ops.get_plays(game_id, limit=100)

Lineup Operations

lineup_id = await db_ops.add_pd_lineup_card(game_id, team_id, card_id, position, batting_order)
lineup_id = await db_ops.add_sba_lineup_player(game_id, team_id, player_id, position, batting_order)
lineup = await db_ops.get_active_lineup(game_id, team_id)
await db_ops.deactivate_lineup_player(lineup_id)

Roster Operations

await db_ops.add_pd_roster_card(game_id, card_id, team_id)
await db_ops.add_sba_roster_player(game_id, player_id, team_id)
roster = await db_ops.get_pd_roster(game_id, team_id)

Session Operations

await db_ops.create_game_session(game_id)
await db_ops.update_session_snapshot(game_id, state_snapshot)

Common Patterns

Transaction Handling

async with AsyncSessionLocal() as session:
    try:
        # Multiple operations
        await session.commit()
    except Exception:
        await session.rollback()
        raise

Efficient Queries

# Use joinedload for relationships needed immediately
from sqlalchemy.orm import joinedload

query = select(Play).options(
    joinedload(Play.batter),
    joinedload(Play.pitcher)
)

Direct UPDATE

# More efficient than SELECT + modify + commit
stmt = update(Game).where(Game.id == game_id).values(inning=5)
await session.execute(stmt)

Database Schema

See ../models/CLAUDE.md for model details.

Table Purpose
games Game container, scores, status
plays At-bat records with 25+ stats
lineups Player assignments, substitutions
game_sessions WebSocket state
roster_links Eligible cards/players

Environment

DATABASE_URL=postgresql+asyncpg://user:pass@10.10.0.42:5432/paperdynasty_dev

References

  • Models: See ../models/CLAUDE.md
  • State Recovery: See ../core/state_manager.py

Tests: tests/integration/database/ | Updated: 2025-01-19