# Backend - Paper Dynasty Game Engine ## Overview FastAPI-based real-time game backend handling WebSocket communication, game state management, and database persistence for both SBA and PD leagues. ## Technology Stack - **Framework**: FastAPI (Python 3.11+) - **WebSocket**: Socket.io (python-socketio) - **Database**: PostgreSQL 14+ with SQLAlchemy 2.0 (async) - **ORM**: SQLAlchemy with asyncpg driver - **Validation**: Pydantic v2 - **Testing**: pytest with pytest-asyncio - **Code Quality**: black, flake8, mypy ## Project Structure ``` backend/ ├── app/ │ ├── main.py # FastAPI app + Socket.io initialization │ ├── config.py # Settings with pydantic-settings │ │ │ ├── core/ # Game logic (Phase 2+) │ │ ├── game_engine.py # Main game simulation │ │ ├── state_manager.py # In-memory state │ │ ├── play_resolver.py # Play outcome resolution │ │ ├── dice.py # Secure random rolls │ │ └── validators.py # Rule validation │ │ │ ├── config/ # League configurations (Phase 2+) │ │ ├── base_config.py # Shared configuration │ │ ├── league_configs.py # SBA/PD specific │ │ └── result_charts.py # d20 outcome tables │ │ │ ├── models/ # Data models │ │ ├── db_models.py # SQLAlchemy ORM models │ │ ├── game_models.py # Pydantic game state models (Phase 2+) │ │ └── player_models.py # Polymorphic player models (Phase 2+) │ │ │ ├── websocket/ # WebSocket handling │ │ ├── connection_manager.py # Connection lifecycle │ │ ├── handlers.py # Event handlers │ │ └── events.py # Event definitions (Phase 2+) │ │ │ ├── api/ # REST API │ │ ├── routes/ │ │ │ ├── health.py # Health check endpoints │ │ │ ├── auth.py # Discord OAuth (Phase 1) │ │ │ └── games.py # Game CRUD (Phase 2+) │ │ └── dependencies.py # FastAPI dependencies │ │ │ ├── database/ # Database layer │ │ ├── session.py # Async session management │ │ └── operations.py # DB operations (Phase 2+) │ │ │ ├── data/ # External data (Phase 2+) │ │ ├── api_client.py # League REST API client │ │ └── cache.py # Caching layer │ │ │ └── utils/ # Utilities │ ├── logging.py # Logging setup │ └── auth.py # JWT utilities (Phase 1) │ ├── tests/ │ ├── unit/ # Unit tests │ ├── integration/ # Integration tests │ └── e2e/ # End-to-end tests │ ├── logs/ # Application logs (gitignored) ├── venv/ # Virtual environment (gitignored) ├── .env # Environment variables (gitignored) ├── .env.example # Environment template ├── requirements.txt # Production dependencies ├── requirements-dev.txt # Dev dependencies ├── Dockerfile # Container definition ├── docker-compose.yml # Redis for local dev └── pytest.ini # Pytest configuration ``` ## Key Architectural Patterns ### 1. Hybrid State Management - **In-Memory**: Active game states for fast access (<500ms response) - **PostgreSQL**: Persistent storage for recovery and history - **Pattern**: Write-through cache (update memory + async DB write) ### 2. Polymorphic Player Models ```python # Base class with abstract methods class BasePlayer(BaseModel, ABC): @abstractmethod def get_image_url(self) -> str: ... # League-specific implementations class SbaPlayer(BasePlayer): ... class PdPlayer(BasePlayer): ... # Factory pattern for instantiation Lineup.from_api_data(config, data) ``` ### 3. League-Agnostic Core - Game engine works for any league - League-specific logic in config classes - Result charts loaded per league ### 4. Async-First - All database operations use `async/await` - Database writes don't block game logic - Connection pooling for efficiency ## Development Workflow ### Daily Development ```bash # Activate virtual environment source venv/bin/activate # Start Redis (in separate terminal) docker-compose up # Run backend with hot-reload python -m app.main # Backend available at http://localhost:8000 # API docs at http://localhost:8000/docs ``` ### Testing ```bash # Run all tests pytest tests/ -v # Run with coverage pytest tests/ --cov=app --cov-report=html # Run specific test file pytest tests/unit/test_game_engine.py -v # Type checking mypy app/ # Code formatting black app/ tests/ # Linting flake8 app/ tests/ ``` ## Coding Standards ### Python Style - **Formatting**: Black with default settings - **Line Length**: 88 characters (black default) - **Imports**: Group stdlib, third-party, local (isort compatible) - **Type Hints**: Required for all public functions - **Docstrings**: Google style for classes and public methods ### Logging Pattern ```python import logging logger = logging.getLogger(f'{__name__}.ClassName') # Usage logger.info(f"User {user_id} connected") logger.error(f"Failed to process action: {error}", exc_info=True) ``` ### Error Handling - **Raise or Return**: Never return `Optional` unless specifically required - **Custom Exceptions**: Use for domain-specific errors - **Logging**: Always log exceptions with context ### Dataclasses ```python from dataclasses import dataclass @dataclass class GameState: game_id: str inning: int outs: int # ... fields ``` ## Database 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 ``` ### Model Definitions ```python from app.database.session import Base from sqlalchemy import Column, String, Integer class Game(Base): __tablename__ = "games" id = Column(UUID(as_uuid=True), primary_key=True) # ... columns ``` ## WebSocket Patterns ### Event Handler Registration ```python @sio.event async def some_event(sid, data): """Handle some_event from client""" try: # Validate data # Process action # Emit response await sio.emit('response_event', result, room=sid) except Exception as e: logger.error(f"Error handling event: {e}") await sio.emit('error', {'message': str(e)}, room=sid) ``` ### Broadcasting ```python # To specific game room await connection_manager.broadcast_to_game( game_id, 'game_state_update', state_data ) # To specific user await connection_manager.emit_to_user( sid, 'decision_required', decision_data ) ``` ## Environment Variables Required in `.env`: ```bash # Database DATABASE_URL=postgresql+asyncpg://user:pass@host:5432/dbname # Application SECRET_KEY=your-secret-key-at-least-32-chars # Discord OAuth DISCORD_CLIENT_ID=your-client-id DISCORD_CLIENT_SECRET=your-client-secret # League APIs SBA_API_URL=https://sba-api.example.com SBA_API_KEY=your-api-key PD_API_URL=https://pd-api.example.com PD_API_KEY=your-api-key ``` ## Performance Targets - **Action Response**: < 500ms from user action to state update - **WebSocket Delivery**: < 200ms - **Database Write**: < 100ms (async, non-blocking) - **State Recovery**: < 2 seconds - **Concurrent Games**: Support 10+ simultaneous games - **Memory**: < 1GB with 10 active games ## Security Considerations - **Authentication**: All WebSocket connections require valid JWT - **Authorization**: Verify team ownership before allowing actions - **Input Validation**: Pydantic models validate all inputs - **SQL Injection**: Prevented by SQLAlchemy ORM - **Dice Rolls**: Cryptographically secure random generation - **Server-Side Logic**: All game rules enforced server-side ## Common Tasks ### Adding a New API Endpoint 1. Create route in `app/api/routes/` 2. Define Pydantic request/response models 3. Add dependency injection if needed 4. Register router in `app/main.py` ### Adding a New WebSocket Event 1. Define event handler in `app/websocket/handlers.py` 2. Register with `@sio.event` decorator 3. Validate data with Pydantic 4. Add corresponding client handling in frontend ### Adding a New Database Model 1. Define SQLAlchemy model in `app/models/db_models.py` 2. Create Alembic migration: `alembic revision --autogenerate -m "description"` 3. Apply migration: `alembic upgrade head` ## Troubleshooting ### Import Errors - Ensure virtual environment is activated - Check `PYTHONPATH` if using custom structure - Verify all `__init__.py` files exist ### Database Connection Issues - Verify `DATABASE_URL` in `.env` is correct - Test connection: `psql $DATABASE_URL` - Check firewall/network access - Verify database exists ### WebSocket Not Connecting - Check CORS settings in `config.py` - Verify token is being sent from client - Check logs for connection errors - Ensure Socket.io versions match (client/server) ## References - **Implementation Guide**: `../.claude/implementation/01-infrastructure.md` - **Backend Architecture**: `../.claude/implementation/backend-architecture.md` - **WebSocket Protocol**: `../.claude/implementation/websocket-protocol.md` - **Database Design**: `../.claude/implementation/database-design.md` - **Full PRD**: `../prd-web-scorecard-1.1.md` --- **Current Phase**: Phase 1 - Core Infrastructure **Next Phase**: Phase 2 - Game Engine Core