CLAUDE: Initialize Paper Dynasty web app with Model/Service Architecture

Establishes foundation for migrating baseball simulation from Discord bot to web application using service-oriented architecture pattern.

Key components:
- FastAPI application structure with dependency injection
- Service layer foundation with base classes and container
- Comprehensive directory documentation with README files
- PostgreSQL containerization with Docker Compose
- Testing structure for unit/integration/e2e tests
- Migration planning documentation
- Rotating log configuration per user requirements

Architecture follows Model/Service/Controller pattern to improve testability, maintainability, and scalability over original monolithic Discord app.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-09-27 21:44:12 -05:00
parent a2042401a7
commit c09f9d1302
38 changed files with 2739 additions and 0 deletions

View File

@ -0,0 +1,235 @@
# Model Migration Plan: Data Models + Business Logic Extraction
## Overview
This document tracks the migration of models from Discord app to web app, with careful separation of data models from business logic. All extracted business logic is tracked to ensure proper implementation in services.
## Migration Principle
**Models = Pure Data | Services = Business Logic**
- Models: Field definitions, relationships, basic validators
- Services: Complex logic, UI formatting, game management, AI decisions
---
## Phase 1: Foundation Data Models
### 1. `ManagerAi` - AI Configuration Data
**Model Migration**:
- ✅ Keep: AI parameter fields (steal, running, hold, etc.)
- ✅ Keep: Database relationships
- ❌ Remove: All decision methods
**Business Logic to Extract**:
| Original Method | Target Service | New Method | Status |
|-----------------|---------------|------------|---------|
| `check_jump()` | AIService | `check_steal_opportunity()` | 📋 TODO |
| `tag_from_second()` | AIService | `check_tag_from_second()` | 📋 TODO |
| `tag_from_third()` | AIService | `check_tag_from_third()` | 📋 TODO |
| `throw_at_uncapped()` | AIService | `decide_throw_target()` | 📋 TODO |
| `uncapped_advance()` | AIService | `decide_runner_advance()` | 📋 TODO |
| `defense_alignment()` | AIService | `set_defensive_alignment()` | 📋 TODO |
| `gb_decide_run()` | AIService | `decide_groundball_running()` | 📋 TODO |
| `gb_decide_throw()` | AIService | `decide_groundball_throw()` | 📋 TODO |
| `replace_pitcher()` | AIService | `should_replace_pitcher()` | 📋 TODO |
### 2. `Cardset` - Card Set Metadata
**Model Migration**:
- ✅ Keep: Basic metadata (id, name, ranked_legal)
- ✅ Keep: Relationships to games/players
**Business Logic to Extract**: None (pure data model)
### 3. `Team` - Team Identity Data
**Model Migration**:
- ✅ Keep: Team data fields (abbrev, names, wallet, etc.)
- ✅ Keep: Simple `description` property
- ❌ Remove: `embed` property (Discord UI)
**Business Logic to Extract**:
| Original Method/Property | Target Service | New Method | Status |
|-------------------------|---------------|------------|---------|
| `embed` property | UIService | `format_team_display()` | 📋 TODO |
---
## Phase 2: Player and Card Data
### 4. `Player` - Player Metadata
**Model Migration**:
- ✅ Keep: Player metadata (name, cost, positions, etc.)
- ✅ Keep: Simple `name_with_desc` property
- ❌ Remove: `name_card_link()` method (Discord markdown)
**Business Logic to Extract**:
| Original Method/Property | Target Service | New Method | Status |
|-------------------------|---------------|------------|---------|
| `name_card_link()` | UIService | `format_player_link()` | 📋 TODO |
| `batter_card_url` | UIService | `get_batter_card_image()` | 📋 TODO |
| `pitcher_card_url` | UIService | `get_pitcher_card_image()` | 📋 TODO |
### 5-8. Card and Rating Models
**Models**: `BattingCard`, `PitchingCard`, `BattingRatings`, `PitchingRatings`, `BatterScouting`, `PitcherScouting`, `Card`, `PositionRating`
**Migration**: Pure data models - no business logic extraction needed
---
## Phase 3: Game Structure (Critical)
### 9. `Game` - Game State and Management ⭐
**Model Migration**:
- ✅ Keep: Game metadata (teams, season, settings)
- ✅ Keep: Database relationships
- ❌ Remove: `channel_id` (Discord-specific)
- ❌ Extract: All business logic methods
**Business Logic to Extract**:
| Original Method/Property | Target Service | New Method | Status |
|-------------------------|---------------|------------|---------|
| `initialize_play()` | GameService | `initialize_game()` | 📋 TODO |
| `current_play_or_none()` | GameService | `get_current_play()` | 📋 TODO |
| `team_lineup()` | GameService | `format_team_lineup()` | 📋 TODO |
| `cardset_param_string` | GameService | `build_cardset_params()` | 📋 TODO |
| `human_team` property | GameService | `get_human_team()` | 📋 TODO |
| `league_name` property | GameService | `get_league_name()` | 📋 TODO |
**Critical Implementation Notes**:
- `initialize_play()` contains complex game setup logic (lines 171-230)
- `team_lineup()` handles lineup formatting with Discord links
- Game state management methods are core to gameplay
### 10-11. Link Models
**Models**: `GameCardsetLink`, `RosterLink`, `Lineup`
**Migration**: Pure relationship models - no extraction needed
---
## Phase 4: Gameplay State (Heaviest Extraction)
### 12. `Play` - Gameplay State and Statistics ⭐
**Model Migration**:
- ✅ Keep: Game state data (scores, outs, runners)
- ✅ Keep: Statistical tracking fields
- ✅ Keep: Relationships to lineups/game
- ❌ Extract: All computed properties and methods
**Business Logic to Extract**:
| Original Method/Property | Target Service | New Method | Status |
|-------------------------|---------------|------------|---------|
| `init_ai()` | GameplayService | `initialize_play_ai()` | 📋 TODO |
| `ai_run_diff` property | GameplayService | `calculate_run_differential()` | 📋 TODO |
| `ai_is_batting` property | GameplayService | `is_ai_batting()` | 📋 TODO |
| `could_walkoff` property | GameplayService | `check_walkoff_situation()` | 📋 TODO |
| `scorebug_ascii` property | UIService | `format_scoreboard()` | 📋 TODO |
**Critical Implementation Notes**:
- `scorebug_ascii` creates ASCII scoreboard display (lines 1223-1243)
- AI integration properties are used throughout gameplay
- Statistical calculations affect game flow
---
## New Web-Specific Models to Add
### Web Session and User Management
| Model | Purpose | Status |
|-------|---------|---------|
| `WebSession` | User session management | 📋 TODO |
| `UserPreferences` | User settings and preferences | 📋 TODO |
| `GameViewState` | UI state for live game viewing | 📋 TODO |
| `Notification` | Web notification queue | 📋 TODO |
---
## Service Implementation Tracker
### AIService - AI Decision Making
- [ ] `check_steal_opportunity()` - Complex steal decision logic
- [ ] `check_tag_from_second()` - Tag-up decisions from 2nd
- [ ] `check_tag_from_third()` - Tag-up decisions from 3rd
- [ ] `decide_throw_target()` - Uncapped runner throw decisions
- [ ] `decide_runner_advance()` - Uncapped advance decisions
- [ ] `set_defensive_alignment()` - Defensive positioning
- [ ] `decide_groundball_running()` - Groundball running decisions
- [ ] `decide_groundball_throw()` - Groundball throw decisions
- [ ] `should_replace_pitcher()` - Pitcher replacement logic
### GameService - Game Management
- [ ] `initialize_game()` - Game setup and first play creation
- [ ] `get_current_play()` - Current play retrieval
- [ ] `format_team_lineup()` - Lineup display formatting
- [ ] `build_cardset_params()` - Cardset parameter building
- [ ] `get_human_team()` - Human team identification
- [ ] `get_league_name()` - League name formatting
### GameplayService - Live Game Logic
- [ ] `initialize_play_ai()` - AI setup for plays
- [ ] `calculate_run_differential()` - Score differential calculation
- [ ] `is_ai_batting()` - AI batting turn detection
- [ ] `check_walkoff_situation()` - Walkoff scenario detection
### UIService - User Interface Formatting
- [ ] `format_team_display()` - Team display for web UI
- [ ] `format_player_link()` - Player links for web
- [ ] `get_batter_card_image()` - Batter card image URLs
- [ ] `get_pitcher_card_image()` - Pitcher card image URLs
- [ ] `format_scoreboard()` - Live scoreboard display
---
## Migration Validation Checklist
### ✅ Model Purity
- [ ] No business logic methods in models
- [ ] Only field definitions and relationships
- [ ] Basic validators for data integrity only
- [ ] No Discord dependencies
### ✅ Service Completeness
- [ ] All extracted methods implemented in services
- [ ] Services have comprehensive unit tests
- [ ] Service methods handle all edge cases from original
- [ ] Service integration tested
### ✅ Functionality Preservation
- [ ] All game mechanics preserved
- [ ] AI decision-making logic intact
- [ ] Statistical tracking complete
- [ ] UI formatting adapted for web
---
## Notes and Decisions
### Key Architectural Decisions:
1. **AI Logic Location**: All AI decision-making moves to AIService to enable easy testing and modification
2. **UI Separation**: All formatting logic moves to UIService to support multiple interfaces
3. **Game State Management**: Core game logic moves to GameService/GameplayService for business rule centralization
### Risk Areas:
1. **Game.initialize_play()**: Complex method with lineup validation and AI setup
2. **Play model**: Heavily used throughout gameplay with many computed properties
3. **AI integration**: AI methods are called throughout game flow and must be properly service-integrated
### Success Metrics:
- All models are pure data containers
- All business logic has corresponding service methods
- No functionality lost in migration
- Services are independently testable

525
.claude/web-migration-v2.md Normal file
View File

@ -0,0 +1,525 @@
# Paper Dynasty Web App Migration Plan v2.0
## Model/Service Architecture Edition
## Project Overview
Migrating the baseball gameplay functionality from the Discord app (`../discord-app/`) to a standalone web application using **FastAPI + Model/Service Architecture**. This approach provides better separation of concerns, testability, and scalability compared to the original monolithic migration plan.
## Architecture Overview
### Why Model/Service Architecture?
Based on analysis of the Discord app complexity (15K+ line files), a service-oriented approach will:
- **Improve Testability**: Services can be unit tested independently
- **Enhance Maintainability**: Clear separation between business logic and web framework
- **Enable Scalability**: Easy to add caching, background tasks, API versioning
- **Future-Proof**: Ready for mobile API, microservices, or different frontends
### Technology Stack
- **Backend**: FastAPI + SQLModel + PostgreSQL
- **Architecture**: Model/Service/Controller pattern
- **Frontend**: Jinja2 templates + HTMX + TailwindCSS
- **Testing**: pytest with comprehensive service unit tests
- **Real-time**: HTMX polling with WebSocket upgrade path
---
## Progress Tracking
### 🚧 Current Phase
- [ ] **Phase 1: Service Foundation & Models** (Days 1-4)
### 📋 Upcoming Phases
- [ ] **Phase 2: Core Game Services** (Days 5-9)
- [ ] **Phase 3: Web Interface with Services** (Days 10-14)
- [ ] **Phase 4: Advanced Features & Optimization** (Days 15-17)
---
## Phase 1: Service Foundation & Models (Days 1-4)
### Day 1: Project Setup & Service Foundation
#### Tasks
- [ ] Initialize FastAPI project with service-oriented structure
- [ ] Set up dependency injection patterns for services
- [ ] Create base service classes and interfaces
- [ ] Configure logging with rotating handlers
- [ ] Set up virtual environment and requirements
#### Service Architecture Setup
```
app/
├── services/
│ ├── __init__.py
│ ├── base_service.py # Base service class
│ └── service_container.py # Dependency injection
├── models/
├── routers/
├── engine/
└── repositories/ # Optional data access layer
```
#### Code Review Checklist - Foundation
- [ ] Service base classes follow single responsibility principle
- [ ] Dependency injection properly configured
- [ ] Logging follows rotating pattern from user's CLAUDE.md
- [ ] Virtual environment properly configured
- [ ] Project structure supports service-oriented development
#### Success Criteria
- [ ] FastAPI server starts without errors
- [ ] Base service classes created and testable
- [ ] Dependency injection working
- [ ] Service unit test framework established
---
### Day 2: Database Models Migration
#### Tasks
- [ ] Migrate core gameplay models from `../discord-app/in_game/gameplay_models.py`
- [ ] Remove Discord dependencies (discord.Embed, etc.)
- [ ] Add web-specific models (sessions, preferences, notifications)
- [ ] Set up PostgreSQL with Docker for testing
- [ ] Create model validation and relationship tests
#### Models to Migrate
- `Game`, `Team`, `Player`, `Lineup`, `Play` (core gameplay)
- `Card`, `Cardset`, `ManagerAi` (game content)
- `WebSession`, `UserPreferences`, `GameViewState` (web-specific)
#### Code Review Checklist - Models
- [ ] All Discord imports removed
- [ ] Field types and constraints validated for PostgreSQL
- [ ] Relationships properly defined with foreign keys
- [ ] Web-specific models added for session management
- [ ] Model tests cover creation, relationships, and validation
#### Success Criteria
- [ ] All models work with PostgreSQL
- [ ] 90%+ test coverage on model functionality
- [ ] Foreign key relationships properly defined
- [ ] Web session models support user management
---
### Day 3: Core Engine Migration (Stateless)
#### Tasks
- [ ] Migrate `../discord-app/dice.py``engine/dice.py`
- [ ] Migrate `../discord-app/in_game/simulations.py``engine/simulation.py`
- [ ] Create `engine/calculations.py` for statistics and WPA
- [ ] Ensure engine components are stateless and pure
- [ ] Add comprehensive engine unit tests
#### Engine Components
- **dice.py**: Dice rolling, fielding checks, X-chart integration
- **simulation.py**: Pitcher vs batter mechanics, outcome calculation
- **calculations.py**: Statistics, WPA, game metrics
#### Code Review Checklist - Engine
- [ ] All engine functions are pure (no side effects)
- [ ] Random operations use proper seeding for testing
- [ ] No Discord dependencies remain
- [ ] Type hints added for all functions
- [ ] Edge cases in game rules properly handled
#### Success Criteria
- [ ] Engine components are stateless and testable
- [ ] Dice probability distributions validated
- [ ] Pitcher vs batter simulation accuracy confirmed
- [ ] 95%+ test coverage on core engine logic
---
### Day 4: Service Layer Foundation
#### Tasks
- [ ] Create `services/game_service.py` with basic game management
- [ ] Create `services/user_service.py` for session management
- [ ] Create `services/auth_service.py` for Discord OAuth
- [ ] Implement service dependency injection in FastAPI
- [ ] Add service unit tests with mocked dependencies
#### Service Interfaces
```python
class GameService:
async def create_game(self, away_team_id: int, home_team_id: int) -> Game
async def get_game(self, game_id: int) -> Game
async def list_active_games(self) -> List[Game]
class UserService:
async def create_session(self, discord_user_id: int) -> WebSession
async def get_user_preferences(self, session_id: str) -> UserPreferences
class AuthService:
async def authenticate_discord(self, oauth_code: str) -> WebSession
async def validate_session(self, session_id: str) -> bool
```
#### Code Review Checklist - Services
- [ ] Services have clear, single responsibilities
- [ ] All service methods have type hints
- [ ] Services use dependency injection for database sessions
- [ ] Service unit tests use mocked dependencies
- [ ] Business logic separated from data access
#### Success Criteria
- [ ] Basic services implement core functionality
- [ ] Services can be unit tested independently
- [ ] Dependency injection working in FastAPI routes
- [ ] Service interfaces support expected web app features
---
## Phase 2: Core Game Services (Days 5-9)
### Day 5: Gameplay Service Architecture
#### Tasks
- [ ] Extract complex gameplay logic from `../discord-app/command_logic/logic_gameplay.py`
- [ ] Create `services/gameplay_service.py` for core game flow
- [ ] Implement play execution and validation
- [ ] Add inning management and state transitions
- [ ] Create comprehensive gameplay service tests
#### GameplayService Responsibilities
- Play execution and validation
- Game state management
- Inning progression logic
- Score calculation and updates
- Game completion detection
---
### Day 6: AI Service Implementation
#### Tasks
- [ ] Migrate `../discord-app/in_game/ai_manager.py``services/ai_service.py`
- [ ] Remove Discord interaction dependencies
- [ ] Implement AI decision-making for automated teams
- [ ] Add AI personality and difficulty settings
- [ ] Create AI behavior validation tests
#### AIService Responsibilities
- Manager AI decision making
- Lineup optimization
- Pitching decisions
- Defensive positioning
- Strategic game management
---
### Day 7: Advanced Game Services
#### Tasks
- [ ] Create `services/simulation_service.py` for game simulation orchestration
- [ ] Implement `services/statistics_service.py` for player/team stats
- [ ] Add `services/notification_service.py` for real-time updates
- [ ] Integrate services with each other using dependency injection
- [ ] Add integration tests for service interactions
---
### Days 8-9: Service Integration & Testing
#### Tasks
- [ ] Complete end-to-end service integration
- [ ] Add performance optimization for service calls
- [ ] Implement caching strategies for expensive operations
- [ ] Create comprehensive integration test suite
- [ ] Validate service architecture with complex game scenarios
#### Code Review Checklist - Service Integration
- [ ] Services interact cleanly without tight coupling
- [ ] Database queries optimized (no N+1 issues)
- [ ] Error handling propagates correctly through service layers
- [ ] Performance acceptable for concurrent users
- [ ] All service interactions have integration tests
#### Success Criteria
- [ ] Complete game can be simulated using services
- [ ] All services have 90%+ unit test coverage
- [ ] Integration tests validate full game flows
- [ ] Performance benchmarks met for service calls
---
## Phase 3: Web Interface with Services (Days 10-14)
### Day 10: FastAPI Route Architecture
#### Tasks
- [ ] Create `routers/games.py` using GameService
- [ ] Create `routers/auth.py` using AuthService
- [ ] Create `routers/api.py` for HTMX endpoints
- [ ] Implement dependency injection for services in routes
- [ ] Add route-level error handling and validation
#### Route Design Pattern
```python
@router.post("/games/start")
async def start_game(
request: StartGameRequest,
game_service: GameService = Depends(get_game_service),
user_service: UserService = Depends(get_user_service)
):
# Thin controller - delegate to services
session = await user_service.validate_session(request.session_id)
game = await game_service.create_game(
away_team_id=request.away_team_id,
home_team_id=request.home_team_id
)
return {"game_id": game.id}
```
---
### Day 11: Authentication & Session Management
#### Tasks
- [ ] Implement Discord OAuth flow using AuthService
- [ ] Create session management with UserService
- [ ] Add user preference handling
- [ ] Implement authentication middleware
- [ ] Add authentication integration tests
---
### Day 12-13: Game Interface Implementation
#### Tasks
- [ ] Create game lobby system using GameService
- [ ] Implement real-time game board with GameplayService
- [ ] Add player action forms with service validation
- [ ] Create scoreboard using StatisticsService
- [ ] Implement HTMX polling for live updates
---
### Day 14: Web Interface Polish
#### Tasks
- [ ] Add comprehensive error handling in routes
- [ ] Implement responsive design with TailwindCSS
- [ ] Add client-side validation with HTMX
- [ ] Create user preference management interface
- [ ] Add end-to-end web interface tests
#### Code Review Checklist - Web Interface
- [ ] Routes are thin, delegating business logic to services
- [ ] HTMX requests handle errors gracefully
- [ ] Forms have proper validation (client and server-side)
- [ ] UI is responsive and mobile-friendly
- [ ] Game state updates are atomic through services
#### Success Criteria
- [ ] Complete single-player game flow working
- [ ] Discord OAuth login/logout functional
- [ ] Real-time updates working via HTMX polling
- [ ] Responsive design works on mobile and desktop
- [ ] All web interfaces use services (no direct database access)
---
## Phase 4: Advanced Features & Optimization (Days 15-17)
### Day 15: Repository Layer (Optional)
#### Tasks
- [ ] Evaluate need for repository pattern for complex queries
- [ ] Implement `repositories/game_repository.py` if needed
- [ ] Add query optimization and caching
- [ ] Refactor services to use repositories
- [ ] Add repository unit tests
#### Repository Pattern (If Needed)
```python
class GameRepository:
def __init__(self, session: Session):
self.session = session
async def get_games_with_stats(self, filters: GameFilters) -> List[Game]:
# Complex query logic separated from service
pass
class GameService:
def __init__(self, session: Session, game_repo: GameRepository):
self.session = session
self.game_repo = game_repo
async def get_leaderboard(self, filters: GameFilters) -> List[GameStats]:
# Service uses repository for complex queries
games = await self.game_repo.get_games_with_stats(filters)
return self._calculate_leaderboard(games)
```
---
### Day 16: Performance & Caching
#### Tasks
- [ ] Add Redis caching for expensive service operations
- [ ] Implement database query optimization
- [ ] Add performance monitoring and metrics
- [ ] Optimize service call patterns
- [ ] Add load testing for concurrent users
---
### Day 17: Final Polish & Documentation
#### Tasks
- [ ] Complete comprehensive test suite (unit, integration, e2e)
- [ ] Add API documentation with FastAPI auto-generation
- [ ] Performance optimization and final benchmarking
- [ ] Code review and refactoring
- [ ] Deployment preparation
---
## Testing Architecture
### Test Structure
```
tests/
├── unit/ # Fast, isolated unit tests
│ ├── services/ # Service unit tests (most important)
│ │ ├── test_game_service.py
│ │ ├── test_gameplay_service.py
│ │ ├── test_ai_service.py
│ │ └── test_user_service.py
│ ├── engine/ # Engine unit tests
│ │ ├── test_dice.py
│ │ ├── test_simulation.py
│ │ └── test_calculations.py
│ └── models/ # Model validation tests
│ └── test_models.py
├── integration/ # Service + database integration
│ ├── test_game_flow.py
│ ├── test_auth_flow.py
│ └── test_service_integration.py
└── e2e/ # Full application tests
├── test_game_creation.py
├── test_gameplay_flow.py
└── test_user_journey.py
```
### Testing Priorities
#### Unit Tests (90%+ Coverage Required)
- **Critical**: All service methods, core engine functions
- **Important**: Model validation, business logic edge cases
- **Nice to have**: Repository methods (if implemented)
#### Integration Tests (80%+ Coverage)
- **Critical**: Service interactions, database transactions
- **Important**: Authentication flows, game state persistence
- **Nice to have**: Performance characteristics
#### E2E Tests (Happy Path Coverage)
- **Critical**: Complete game creation and play flow
- **Important**: User authentication and session management
- **Nice to have**: Error handling and edge cases
---
## Architecture Decisions
### Service Layer Benefits
- **Testability**: Services can be unit tested with mocked dependencies
- **Maintainability**: Business logic centralized, not scattered across routes
- **Scalability**: Easy to add caching, background tasks, monitoring
- **Flexibility**: Services can be used by different interfaces (web, API, CLI)
### Repository Pattern (Optional)
- **When to use**: Complex queries, multiple data sources, query optimization
- **When to skip**: Simple CRUD operations, small project scope
- **Decision**: Evaluate during Day 15 based on query complexity
### Dependency Injection Strategy
- **FastAPI Depends()**: For HTTP request-scoped dependencies
- **Service constructors**: For service-to-service dependencies
- **Configuration**: Environment-based configuration injection
---
## Migration Notes & Decisions
### Service Extraction Strategy
1. **Identify Business Logic**: Extract from Discord command handlers
2. **Create Service Interface**: Define clear method signatures
3. **Write Tests First**: Test-driven service development
4. **Implement Service**: Pure business logic, no framework dependencies
5. **Integrate with Routes**: Thin controllers using services
### Discord App Analysis for Service Extraction
#### High-Value Extractions
- `command_logic/logic_gameplay.py` (4,186 lines) → Multiple services
- `in_game/ai_manager.py``services/ai_service.py`
- Game creation/management → `services/game_service.py`
- User/session management → `services/user_service.py`
#### Keep as Stateless Engine
- `dice.py``engine/dice.py` (pure functions)
- `in_game/simulations.py``engine/simulation.py` (calculations)
---
## Success Criteria
### Architecture Quality
- [ ] Clear separation: Models → Services → Controllers (Routes)
- [ ] Services have single, well-defined responsibilities
- [ ] Business logic not mixed with web framework code
- [ ] Dependency injection working throughout application
### Code Quality Standards
- [ ] Services have 90%+ unit test coverage
- [ ] Integration tests cover service interactions
- [ ] Type hints on all service methods
- [ ] Error handling appropriate for each layer
- [ ] Performance acceptable for multiple concurrent users
### Migration Success
- [ ] Working single-player baseball game with Discord OAuth
- [ ] Live scoreboard with HTMX real-time updates
- [ ] Clean, service-oriented codebase ready for scaling
- [ ] Comprehensive test coverage across all layers
- [ ] Responsive web interface (mobile + desktop)
- [ ] No Discord-specific dependencies remaining
### Service Architecture Validation
- [ ] Services can be unit tested independently
- [ ] Business logic easily modifiable without touching routes
- [ ] New features can be added by extending services
- [ ] Multiple interfaces (web, API) can use same services
---
## Future Enhancements (Post-MVP)
### Microservices Evolution
- Services are designed to be extracted into microservices later
- Clean service interfaces support API versioning
- Database access patterns support service splitting
### Advanced Features
- Background task processing using service layer
- Multiple client support (mobile app, desktop)
- Real-time multiplayer using service orchestration
- Analytics and monitoring built into service layer
---
## Notes for Future AI Agents
### Service-First Development
1. **Always start with services** when adding new features
2. **Write service tests first** before implementation
3. **Keep routes thin** - delegate to services
4. **Test services independently** with mocked dependencies
5. **Use dependency injection** for all service interactions
### Migration Best Practices
1. **Extract business logic** from Discord command handlers
2. **Remove all Discord dependencies** before service implementation
3. **Test service logic** independently of web framework
4. **Integrate services** into routes using FastAPI Depends()
5. **Validate service architecture** with integration tests
### Maintenance Guidelines
- Update service tests when changing business logic
- Keep service interfaces stable for backward compatibility
- Add new features by extending services, not routes
- Monitor service performance and add caching as needed
- Document service interactions and dependencies
---
Remember: This migration uses **Model/Service Architecture** to create a scalable, maintainable web application. Always prioritize clean service design and comprehensive testing over speed of development. The service layer is the foundation that will support future growth and feature additions.

21
.env.example Normal file
View File

@ -0,0 +1,21 @@
# Environment configuration for Paper Dynasty Web App
# Copy this file to .env and update values as needed
# Database Configuration
DATABASE_URL=postgresql+psycopg://paper_dynasty_user:paper_dynasty_dev_password@localhost:5432/paper_dynasty
DATABASE_TEST_URL=postgresql+psycopg://paper_dynasty_user:paper_dynasty_dev_password@localhost:5432/paper_dynasty_test
# Application Settings
DEBUG=true
ENVIRONMENT=development
SECRET_KEY=development-secret-key-change-in-production
SESSION_EXPIRE_HOURS=24
# Discord OAuth (fill in when implementing authentication)
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_REDIRECT_URI=http://localhost:8001/auth/callback
# Game Settings
MAX_CONCURRENT_GAMES=100
GAME_TIMEOUT_HOURS=4

193
CLAUDE.md Normal file
View File

@ -0,0 +1,193 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is the **Paper Dynasty Web App**, a baseball simulation game migrated from a Discord bot (`../discord-app/`) to a standalone web application using **FastAPI + Model/Service Architecture**. The migration follows a service-oriented approach that prioritizes testability, maintainability, and scalability.
## Development Commands
### Environment Setup
```bash
# Start PostgreSQL database container
docker compose up -d postgres
# Activate virtual environment
source venv/bin/activate
# Install dependencies (including PostgreSQL driver)
pip install -r requirements.txt
# Copy environment configuration
cp .env.example .env
# Edit .env file if needed
# Run development server
python -m app.main
# Server runs on http://localhost:8001
```
### Testing
```bash
# Run all tests
pytest
# Run tests with coverage
pytest --cov=app
# Run specific test module
pytest tests/unit/services/test_game_service.py
# Run specific test
pytest tests/unit/services/test_game_service.py::test_create_game_success
```
### Database
```bash
# Start/stop PostgreSQL container
docker compose up -d postgres # Start pddev-postgres container
docker compose stop postgres # Stop container
docker compose down -v # Stop and remove data (fresh start)
# Create database tables (once models are implemented)
python -c "from app.services.service_container import create_db_and_tables; create_db_and_tables()"
# Generate migration (once Alembic is configured)
alembic revision --autogenerate -m "Description"
# Apply migrations
alembic upgrade head
# Connect to database directly
docker compose exec postgres psql -U paper_dynasty_user -d paper_dynasty
```
## Architecture Overview
### Model/Service Architecture Pattern
This project implements a **service-oriented architecture** with clear separation of concerns:
- **Models** (`app/models/`): Pure data models using SQLModel
- **Services** (`app/services/`): Business logic layer with dependency injection
- **Controllers** (`app/routers/`): Thin FastAPI route handlers that delegate to services
- **Engine** (`app/engine/`): Stateless game simulation functions
- **Repositories** (`app/repositories/`): Optional data access layer for complex queries
### Service Layer Design
All services inherit from `BaseService` and follow these patterns:
- Constructor dependency injection via SQLModel Session
- Standardized logging with `f'{__name__}.{self.__class__.__name__}'` format
- Business logic separated from HTTP concerns
- Unit testable with mocked dependencies
### Dependency Injection
Services are injected into routes using FastAPI's `Depends()` system via `service_container.py`:
```python
from app.services.service_container import GameServiceDep
@router.post("/games/start")
async def start_game(game_service: GameServiceDep):
# Service contains business logic, route handles HTTP concerns
```
### Migration from Discord App
When migrating code from `../discord-app/`:
1. **Extract business logic** from Discord command handlers into services
2. **Remove Discord dependencies** (discord.py, interaction objects)
3. **Create service methods** with pure business logic
4. **Add comprehensive unit tests** for service methods
5. **Create thin route handlers** that delegate to services
## Current Migration Status
**Phase 1: Service Foundation** ✅ COMPLETE
- FastAPI project structure established
- Base service classes and dependency injection configured
- Logging with rotating handlers implemented
- Virtual environment and core dependencies installed
**Phase 2: Core Game Services** 🚧 NEXT
- Extract business logic from `../discord-app/command_logic/logic_gameplay.py`
- Implement GameService, GameplayService, AIService
- Migrate core gameplay models from Discord app
- Add comprehensive service unit tests
## Key Files and Patterns
### Service Container (`app/services/service_container.py`)
- Manages database sessions and service lifecycles
- Provides dependency injection for FastAPI routes
- Configure SQLModel engine for PostgreSQL/SQLite
### Base Service (`app/services/base_service.py`)
- Abstract base class for all services
- Standardized logging and validation patterns
- Session management and error handling
### Constants (`app/config/constants.py`)
- Migrated constants from Discord app (not assumed values)
- Environment-based configuration via Settings class
- Game constants, MLB teams, rarity values from original source
### Logging Configuration (`app/config/logging_config.py`)
- Rotating file handlers per user requirements
- Console and file output with structured formatting
- Service-specific logger naming patterns
## Testing Strategy
### Unit Tests (`tests/unit/`)
- **Services**: Test business logic with mocked database sessions
- **Engine**: Test stateless game simulation functions
- **Models**: Test data validation and relationships
### Integration Tests (`tests/integration/`)
- Service interactions with real database (isolated transactions)
- Authentication flows and session management
### End-to-End Tests (`tests/e2e/`)
- Complete user journeys through web interface
- Game creation and gameplay flows
## Development Guidelines
### Service Development
- All services extend `BaseService`
- Use dependency injection for service-to-service communication
- Log operations with `self._log_operation()` and `self._log_error()`
- Follow "Raise or Return" pattern (no optional return values)
### Documentation Maintenance
- **Update README.md files** when making substantial changes to any directory
- Each directory with significant content has a README.md explaining its purpose and patterns
- Keep documentation current with architecture decisions and new patterns
- Update relevant README when adding new services, routes, or changing patterns
- **Reference per-directory README.md files** - Future AI agents should read the README in each directory to understand its purpose, patterns, and responsibilities before making changes
### File Size Management
- **Keep all files as small as feasibly possible** for maximum maintainability
- Break large files into smaller, focused modules
- Single responsibility principle applies to files as well as classes
- Prefer multiple small service files over monolithic service classes
- Split complex models into separate files when they serve different domains
### Code Migration
- Always check `../discord-app/` for actual implementation before assuming logic
- Extract Discord command handlers into service methods
- Test service logic independently of web framework
- Keep routes thin - delegate business logic to services
### Database
- Currently configured for SQLite development
- PostgreSQL driver (`psycopg2-binary`) commented out until proper DB setup
- Use SQLModel for all data models
### Logging
- Use rotating file handlers as per user requirements
- Format: `f'{__name__}.{className or functionName}'`
- Log service operations and errors consistently

573
DEVELOPMENT_GUIDE_V2.md Normal file
View File

@ -0,0 +1,573 @@
# Paper Dynasty Web App Development Guide v2.0
## Model/Service Architecture Edition
## Overview
This guide provides instructions for AI agents working on the Paper Dynasty web app migration project using a **Model/Service Architecture**. This approach separates business logic into services, improving testability, maintainability, and scalability over the original monolithic approach.
## Quick Start for AI Agents
### Before Starting Any Session:
1. **Check Progress**: Read `./.claude/plans/web-migration-v2.md` to understand current status
2. **Review TODO List**: Use TodoWrite tool to track your progress throughout the session
3. **Understand Context**: This is a migration from `../discord-app/` to a web interface using **Model/Service Architecture**
4. **Follow Standards**: Use the code review methodology defined below
### Current Project Status
- **Migration Type**: Fork approach with **Model/Service Architecture**
- **Current Phase**: Check `./.claude/plans/web-migration-v2.md` for latest status
- **Architecture**: Clean separation between Models, Services, Controllers (FastAPI routes)
- **Next Tasks**: Always work from the current phase checklist in the migration plan
---
## Architecture Overview
### Model/Service Architecture Benefits
- **Separation of Concerns**: Clear boundaries between data, business logic, and web interface
- **Testability**: Services can be unit tested independently of database and web framework
- **Scalability**: Easy to add caching, background tasks, API versioning
- **Maintainability**: Business logic centralized in services, not scattered across routes
- **Future-Proofing**: Ready for mobile API, microservices, or different frontends
### Project Structure
```
gameplay-website/
├── .claude/
│ └── web-migration-v2.md
├── app/
│ ├── main.py # FastAPI application setup
│ ├── models/ # Pure data models (SQLModel)
│ │ ├── game.py # Game, Team, Player, Lineup, Play
│ │ ├── web_models.py # Session, Preferences, Notifications
│ │ └── exceptions.py # Custom exceptions
│ ├── services/ # Business logic layer ⭐ NEW
│ │ ├── __init__.py
│ │ ├── game_service.py # Game creation, management, queries
│ │ ├── gameplay_service.py # Core gameplay simulation & flow
│ │ ├── ai_service.py # AI decision making
│ │ ├── user_service.py # User sessions, preferences
│ │ ├── notification_service.py # Web notifications
│ │ └── auth_service.py # Discord OAuth, session management
│ ├── repositories/ # Data access layer ⭐ NEW (optional)
│ │ ├── __init__.py
│ │ ├── game_repository.py # Complex game queries
│ │ └── user_repository.py # User/session queries
│ ├── routers/ # FastAPI route handlers ⭐ ENHANCED
│ │ ├── __init__.py
│ │ ├── games.py # Game-related endpoints
│ │ ├── auth.py # Authentication routes
│ │ ├── api.py # JSON API for HTMX
│ │ └── pages.py # HTML page routes
│ ├── engine/ # Core simulation engine (stateless)
│ │ ├── dice.py # Dice rolling, fielding checks
│ │ ├── simulation.py # Pitcher vs batter mechanics
│ │ └── calculations.py # Statistics, WPA calculations
│ ├── config/
│ │ └── constants.py
│ ├── static/
│ └── templates/
├── tests/ # Comprehensive test structure
│ ├── unit/ # Unit tests for services
│ ├── integration/ # Integration tests
│ └── e2e/ # End-to-end tests
├── requirements.txt
└── DEVELOPMENT_GUIDE_V2.md # This file
```
---
## Code Review Methodology
### When Migrating Code from Discord App:
#### 1. Service Extraction First
```python
# ❌ Old approach: Business logic in routes
@app.post("/games/start")
async def start_game(request: Request):
# 50+ lines of game creation logic mixed with HTTP handling
# ✅ New approach: Clean service separation
@router.post("/games/start")
async def start_game(request: StartGameRequest):
game = await game_service.create_game(
away_team_id=request.away_team_id,
home_team_id=request.home_team_id,
game_type=request.game_type
)
return {"game_id": game.id, "status": "created"}
```
#### 2. Service Layer Design
```python
# ✅ Service pattern example
from sqlmodel import Session
from typing import Optional, List
from ..models.game import Game, Team
from ..repositories.game_repository import GameRepository
class GameService:
def __init__(self, session: Session):
self.session = session
self.game_repo = GameRepository(session)
async def create_game(
self,
away_team_id: int,
home_team_id: int,
game_type: str
) -> Game:
"""Create a new game with validation and business rules"""
# Validation
await self._validate_teams_available(away_team_id, home_team_id)
# Business logic
game = Game(
away_team_id=away_team_id,
home_team_id=home_team_id,
game_type=game_type,
season=self._get_current_season()
)
# Persistence
return await self.game_repo.create(game)
async def _validate_teams_available(self, away_id: int, home_id: int):
"""Private method for business rule validation"""
# Implementation here
pass
```
#### 3. Dependency Cleanup
```python
# ❌ Remove these Discord-specific imports
import discord
from discord.ext import commands
from discord import app_commands
# ✅ Replace with web-appropriate imports
from fastapi import FastAPI, Request, Response, Depends
from sqlmodel import Session
from typing import List, Optional
```
#### 4. Error Handling Review
```python
# ❌ Discord-specific error handling
await interaction.response.send_message("Error occurred", ephemeral=True)
# ✅ Service-layer error handling
from ..models.exceptions import GameNotFoundException
class GameService:
async def get_game(self, game_id: int) -> Game:
game = await self.game_repo.get_by_id(game_id)
if not game:
raise GameNotFoundException(f"Game {game_id} not found")
return game
# ✅ Route-layer error handling
@router.get("/games/{game_id}")
async def get_game(game_id: int, game_service: GameService = Depends()):
try:
game = await game_service.get_game(game_id)
return game
except GameNotFoundException as e:
raise HTTPException(status_code=404, detail=str(e))
```
#### 5. Logging Standards
Follow the user's CLAUDE.md requirements:
```python
import logging
logger = logging.getLogger(f'{__name__}.GameService')
class GameService:
async def create_game(self, away_team_id: int, home_team_id: int) -> Game:
logger.info(f'GameService.create_game - Creating game: {away_team_id} vs {home_team_id}')
try:
# Business logic
result = await self._create_game_logic(away_team_id, home_team_id)
logger.info(f'GameService.create_game - Game created successfully: {result.id}')
return result
except Exception as e:
logger.error(f'GameService.create_game - Failed to create game: {str(e)}')
raise
```
#### 6. Service Testing Pattern
```python
# ✅ Unit test services independently
import pytest
from unittest.mock import Mock
from app.services.game_service import GameService
from app.models.game import Game
@pytest.fixture
def mock_session():
return Mock()
@pytest.fixture
def game_service(mock_session):
return GameService(mock_session)
@pytest.mark.asyncio
async def test_create_game_success(game_service):
# Test business logic without database
result = await game_service.create_game(
away_team_id=1,
home_team_id=2,
game_type="ranked"
)
assert result.away_team_id == 1
assert result.home_team_id == 2
```
### Code Review Checklist Template
For each migrated file, check:
- [ ] Business logic extracted into appropriate service
- [ ] Service has clear, single responsibility
- [ ] All Discord imports removed
- [ ] Error handling appropriate for service layer
- [ ] Logging follows project standards
- [ ] Services have comprehensive unit tests
- [ ] Routes are thin, delegating to services
- [ ] Database access goes through repositories (if using repository pattern)
- [ ] Type hints on all service methods
- [ ] No obvious performance issues
---
## Service Layer Guidelines
### Service Responsibilities
#### GameService
- Game creation and validation
- Game state management
- Game queries and filtering
- Game lifecycle (start, pause, complete)
#### GameplayService
- Core gameplay simulation
- Play execution and validation
- Inning management
- Score calculation
#### AIService
- AI decision making
- Manager AI logic
- Automated gameplay
#### UserService
- User session management
- User preferences
- Authentication state
#### NotificationService
- Web notifications
- Game event notifications
- Real-time updates
### Service Design Principles
1. **Single Responsibility**: Each service has one clear purpose
2. **Dependency Injection**: Services receive dependencies via constructor
3. **Stateless**: Services don't maintain instance state between calls
4. **Testable**: Services can be unit tested with mocked dependencies
5. **Pure Business Logic**: No HTTP, database, or UI concerns in services
### Service Interaction Patterns
```python
# ✅ Service-to-service communication
class GameplayService:
def __init__(self, session: Session, ai_service: AIService):
self.session = session
self.ai_service = ai_service
async def execute_play(self, game_id: int, play_data: dict) -> PlayResult:
# Get current game state
game = await self.game_repo.get_by_id(game_id)
# Check if AI needs to make decisions
if game.ai_team:
ai_decision = await self.ai_service.get_defensive_decision(game)
play_data.update(ai_decision)
# Execute the play
return await self._execute_play_logic(game, play_data)
```
---
## Testing Architecture
### Test Structure
```
tests/
├── unit/ # Fast, isolated unit tests
│ ├── services/
│ │ ├── test_game_service.py
│ │ ├── test_gameplay_service.py
│ │ └── test_ai_service.py
│ ├── engine/
│ │ ├── test_dice.py
│ │ └── test_simulation.py
│ └── models/
│ └── test_game_models.py
├── integration/ # Database + service integration
│ ├── test_game_flow.py
│ └── test_auth_flow.py
└── e2e/ # Full application tests
├── test_game_creation.py
└── test_gameplay_flow.py
```
### Testing Patterns
#### Unit Testing Services
```python
# test_game_service.py
@pytest.fixture
def mock_game_repo():
return Mock(spec=GameRepository)
@pytest.fixture
def game_service(mock_game_repo):
return GameService(session=Mock(), game_repo=mock_game_repo)
@pytest.mark.asyncio
async def test_create_game_validates_teams(game_service, mock_game_repo):
# Test business logic without database
mock_game_repo.get_by_id.return_value = None # Team not found
with pytest.raises(TeamNotFoundException):
await game_service.create_game(999, 1000, "ranked")
```
#### Integration Testing
```python
# test_game_flow.py
@pytest.mark.asyncio
async def test_complete_game_creation_flow(db_session):
# Test with real database but isolated transaction
game_service = GameService(db_session)
# Create test data
team1 = await create_test_team(db_session)
team2 = await create_test_team(db_session)
# Test full flow
game = await game_service.create_game(team1.id, team2.id, "ranked")
assert game.id is not None
assert game.away_team_id == team1.id
```
### What to Test by Layer
#### Services (Unit Tests)
- **Critical**: Business logic validation, error handling, service interactions
- **Important**: Edge cases, complex calculations
- **Nice to have**: Performance characteristics
#### Engine (Unit Tests)
- **Critical**: Game simulation accuracy, dice probability, calculation correctness
- **Important**: Edge cases in game rules, random seed consistency
- **Nice to have**: Performance optimization
#### Integration Tests
- **Critical**: Database operations, service composition, authentication flow
- **Important**: Error propagation, transaction handling
- **Nice to have**: Caching behavior
---
## Migration Strategy
### Phase 1: Service Foundation
1. Create basic service interfaces
2. Extract simple business logic from Discord app
3. Set up dependency injection patterns
4. Create service unit tests
### Phase 2: Core Service Implementation
1. Implement GameService with full game management
2. Implement GameplayService with simulation logic
3. Implement AIService with decision making
4. Add comprehensive service tests
### Phase 3: Web Integration
1. Create FastAPI routes that use services
2. Add authentication and session services
3. Implement HTMX endpoints with service calls
4. Integration testing
### Phase 4: Advanced Features
1. Add repository layer if needed for complex queries
2. Implement notification service
3. Add caching and performance optimization
4. End-to-end testing
---
## Performance Considerations
### Service Layer Performance
- Use connection pooling for database sessions
- Implement caching at service level for expensive operations
- Consider async/await for I/O bound operations
- Monitor service call patterns for optimization opportunities
### Database Optimization
- Repositories handle complex queries and optimization
- Use eager loading for frequently accessed relationships
- Implement query result caching where appropriate
- Monitor N+1 query patterns
---
## Development Workflow
### Starting a New Session
1. Read current progress in `./.claude/web-migration-v2.md`
2. Update TodoWrite with current tasks focusing on service implementation
3. Work on the current phase tasks in order
4. Follow the service-first approach for any new business logic
5. Update progress in migration plan when completing major milestones
### Service Development Process
1. **Design**: Define service interface and responsibilities
2. **Test**: Write unit tests for expected behavior
3. **Implement**: Build service following single responsibility principle
4. **Integrate**: Connect service to routes and other services
5. **Validate**: Run full test suite and integration tests
### Virtual Environment Setup
Always check for virtual environment:
```bash
# Check for existing venv
ls -la | grep venv
# Create if doesn't exist (user works with discord.py, wants venv)
python -m venv venv
source venv/bin/activate # Linux/Mac
# or
venv\Scripts\activate # Windows
```
### Testing Requirements
User's CLAUDE.md specifies:
- "You do not need to ask for permission to run tests"
- Use pytest for all testing
- Include unit tests for services, integration tests for full flows
- Test critical game logic thoroughly
- Aim for 80%+ test coverage on services
### Git Commit Standards
User's CLAUDE.md specifies:
- Prefix commit messages with "CLAUDE: "
- Focus on the "why" rather than the "what"
- Example: "CLAUDE: Extract game creation logic into GameService for better testability"
---
## Migration-Specific Guidelines
### Files to Prioritize for Service Extraction
**High Priority (Extract to Services First)**:
- `../discord-app/command_logic/logic_gameplay.py``services/gameplay_service.py`
- `../discord-app/in_game/ai_manager.py``services/ai_service.py`
- Game creation/management logic → `services/game_service.py`
**Medium Priority (Core Engine - Keep Stateless)**:
- `../discord-app/in_game/simulations.py``engine/simulation.py`
- `../discord-app/dice.py``engine/dice.py`
**Lower Priority (Supporting Services)**:
- Authentication logic → `services/auth_service.py`
- User management → `services/user_service.py`
- Notifications → `services/notification_service.py`
### Service Extraction Patterns
#### Pattern 1: Discord Command → Service Method
```python
# ❌ Discord app pattern
@app_commands.command(name="start_game")
async def start_game(interaction: discord.Interaction):
# 50+ lines of business logic mixed with Discord API calls
# ✅ Web app pattern
class GameService:
async def create_game(self, away_team_id: int, home_team_id: int) -> Game:
# Pure business logic, no Discord dependencies
@router.post("/games/start")
async def start_game(request: StartGameRequest, game_service: GameService = Depends()):
return await game_service.create_game(request.away_team_id, request.home_team_id)
```
#### Pattern 2: Game Logic → Service + Engine
```python
# ❌ Mixed business logic and game engine
def process_at_bat(interaction, game_id, batter_decision):
# Mixed: Discord UI + game rules + database + simulation
# ✅ Separated concerns
class GameplayService:
async def process_at_bat(self, game_id: int, batter_decision: str) -> PlayResult:
# Business logic: validation, state management
def _simulate_at_bat(self, pitcher_stats, batter_stats) -> SimulationResult:
# Pure game engine logic
```
#### Pattern 3: AI Decision Making → AIService
```python
# ✅ Clean AI service
class AIService:
async def get_pitching_decision(self, game_state: GameState) -> PitchingDecision:
# AI logic without Discord dependencies
async def get_batting_decision(self, game_state: GameState) -> BattingDecision:
# AI logic without Discord dependencies
```
---
## Success Metrics
### Architecture Quality
- Clear separation between models, services, routes
- Services have single responsibilities
- Business logic not mixed with web framework code
- Comprehensive service unit tests
### Code Quality Standards
- All services have 90%+ test coverage
- Type hints required for all service methods
- Error handling appropriate for service layer
- Performance acceptable for multiple concurrent users
- No Discord-specific dependencies remaining
### Migration Success
- Working single-player baseball game with Discord OAuth
- Live scoreboard with news ticker
- Clean, service-oriented codebase ready for scaling
- Comprehensive test coverage across all layers
- Responsive web interface (mobile + desktop)
---
## Emergency Contacts & Resources
- **Project Owner**: Check project README for contact info
- **Discord App Source**: `../discord-app/` directory
- **Migration Plan**: `./.claude/web-migration-v2.md`
- **User Preferences**: `/home/cal/.claude/CLAUDE.md`
- **Architecture Reference**: This file (DEVELOPMENT_GUIDE_V2.md)
Remember: This is a migration project with the goal of creating a scalable, maintainable web application using Model/Service Architecture. Always prioritize clean architecture and comprehensive testing over speed of development.

46
app/README.md Normal file
View File

@ -0,0 +1,46 @@
# App Directory
This directory contains the main application code for the Paper Dynasty web app, following the **Model/Service Architecture** pattern.
## Architecture Overview
The app follows a clean separation of concerns:
- **Models** (`models/`) - Pure data models using SQLModel
- **Services** (`services/`) - Business logic layer with dependency injection
- **Controllers** (`routers/`) - Thin FastAPI route handlers
- **Engine** (`engine/`) - Stateless game simulation functions
- **Configuration** (`config/`) - Application settings and constants
## Key Files
- `main.py` - FastAPI application setup and configuration
- Entry point for the web application
## Directory Structure
```
app/
├── main.py # FastAPI application setup
├── config/ # Configuration and constants
├── models/ # SQLModel data models
├── services/ # Business logic layer (⭐ Core of architecture)
├── repositories/ # Optional data access layer
├── routers/ # FastAPI route handlers
├── engine/ # Stateless game simulation
├── static/ # Static web assets
└── templates/ # Jinja2 HTML templates
```
## Service-First Development
When adding new features:
1. **Start with services** - implement business logic in the service layer
2. **Test services independently** - unit test with mocked dependencies
3. **Create thin routes** - delegate to services via dependency injection
4. **Keep engine stateless** - pure functions for game calculations
## Migration Context
This application is migrated from a Discord bot (`../discord-app/`) with the goal of extracting Discord-specific business logic into clean, testable services that can support multiple interfaces (web, API, mobile, etc.).

0
app/__init__.py Normal file
View File

51
app/config/README.md Normal file
View File

@ -0,0 +1,51 @@
# Config Directory
This directory contains application configuration, constants, and settings management.
## Files
### `constants.py`
- **Settings class**: Environment-based configuration using `os.getenv()`
- **GameConstants**: Migrated constants from Discord app (`../discord-app/constants.py`)
- **MLB Teams**: Team lookup tables and metadata
- **Database URLs**: PostgreSQL connection strings for development and testing
### `logging_config.py`
- **Rotating file handlers**: Per user's CLAUDE.md requirements
- **Console and file output**: Structured logging with proper formatting
- **Service-specific loggers**: Standardized naming patterns
## Key Features
### Environment Configuration
The `Settings` class provides environment-based configuration:
```python
from app.config.constants import settings
# Database connection
DATABASE_URL = settings.DATABASE_URL
# Discord OAuth
DISCORD_CLIENT_ID = settings.DISCORD_CLIENT_ID
```
### Migrated Constants
All game constants are migrated from the actual Discord app source, not assumed values:
- Season configuration (SBA_SEASON, PD_SEASON)
- Cardset configurations and rankings
- Player rarity values
- MLB team lookup tables
### Logging Standards
Follows rotating log pattern with service-specific naming:
```python
logger = logging.getLogger(f'{__name__}.{self.__class__.__name__}')
```
## Environment Variables
Copy `.env.example` to `.env` and configure:
- `DATABASE_URL` - PostgreSQL connection string
- `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET` - OAuth credentials
- `DEBUG` - Enable debug mode
- `SECRET_KEY` - Application secret for sessions

0
app/config/__init__.py Normal file
View File

122
app/config/constants.py Normal file
View File

@ -0,0 +1,122 @@
"""
Application constants and configuration values.
Migrated from Discord app constants.py
"""
import os
from typing import Optional, Literal
class Settings:
"""Application settings and configuration."""
# Database settings
DATABASE_URL: str = os.getenv(
"DATABASE_URL",
"postgresql+psycopg://paper_dynasty_user:paper_dynasty_dev_password@localhost:5432/paper_dynasty"
)
DATABASE_TEST_URL: str = os.getenv(
"DATABASE_TEST_URL",
"postgresql+psycopg://paper_dynasty_user:paper_dynasty_dev_password@localhost:5432/paper_dynasty_test"
)
# Discord OAuth settings
DISCORD_CLIENT_ID: Optional[str] = os.getenv("DISCORD_CLIENT_ID")
DISCORD_CLIENT_SECRET: Optional[str] = os.getenv("DISCORD_CLIENT_SECRET")
DISCORD_REDIRECT_URI: Optional[str] = os.getenv("DISCORD_REDIRECT_URI")
# Security settings
SECRET_KEY: str = os.getenv("SECRET_KEY", "development-secret-key-change-in-production")
SESSION_EXPIRE_HOURS: int = int(os.getenv("SESSION_EXPIRE_HOURS", "24"))
# Application settings
DEBUG: bool = os.getenv("DEBUG", "false").lower() == "true"
ENVIRONMENT: str = os.getenv("ENVIRONMENT", "development")
# Game settings
MAX_CONCURRENT_GAMES: int = int(os.getenv("MAX_CONCURRENT_GAMES", "100"))
GAME_TIMEOUT_HOURS: int = int(os.getenv("GAME_TIMEOUT_HOURS", "4"))
# Global settings instance
settings = Settings()
# Constants migrated from Discord app
class GameConstants:
"""Game constants migrated from Discord app constants.py"""
# Season Configuration (from Discord app)
SBA_SEASON = 11
PD_SEASON = 9
LIVE_CARDSET_ID = 24
LIVE_PROMO_CARDSET_ID = 25
MAX_CARDSET_ID = 30
# Ranked cardsets for web app
RANKED_CARDSETS = [20, 21, 22, 17, 18, 19]
# Application Configuration
SBA_COLOR = 'a6ce39'
PD_PLAYERS = 'Paper Dynasty Players'
# External URLs and Resources
PD_IMAGE_BUCKET = 'https://paper-dynasty.s3.us-east-1.amazonaws.com/static-images'
# Player Rarity Values (from Discord app)
RARITY = {
'HoF': 8,
'MVP': 5,
'All-Star': 3,
'Starter': 2,
'Reserve': 1,
'Replacement': 0
}
# Color Definitions
COLORS = {
'sba': int('a6ce39', 16),
'yellow': int('FFEA00', 16),
'red': int('C70039', 16),
'white': int('FFFFFF', 16)
}
# MLB Teams Lookup (migrated from Discord app)
ALL_MLB_TEAMS = {
'Arizona Diamondbacks': ['ARI', 'Diamondbacks'],
'Atlanta Braves': ['ATL', 'MLN', 'Braves'],
'Baltimore Orioles': ['BAL', 'Orioles'],
'Boston Red Sox': ['BOS', 'Red Sox'],
'Chicago Cubs': ['CHC', 'Cubs'],
'Chicago White Sox': ['CHW', 'White Sox'],
'Cincinnati Reds': ['CIN', 'Reds'],
'Cleveland Guardians': ['CLE', 'Guardians'],
'Colorado Rockies': ['COL', 'Rockies'],
'Detroit Tigers': ['DET', 'Tigers'],
'Houston Astros': ['HOU', 'Astros'],
'Kansas City Royals': ['KCR', 'Royals'],
'Los Angeles Angels': ['LAA', 'CAL', 'Angels'],
'Los Angeles Dodgers': ['LAD', 'Dodgers'],
'Miami Marlins': ['MIA', 'Marlins'],
'Milwaukee Brewers': ['MIL', 'MKE', 'Brewers'],
'Minnesota Twins': ['MIN', 'Twins'],
'New York Mets': ['NYM', 'Mets'],
'New York Yankees': ['NYY', 'Yankees'],
'Oakland Athletics': ['OAK', 'Athletics'],
'Philadelphia Phillies': ['PHI', 'Phillies'],
'Pittsburgh Pirates': ['PIT', 'Pirates'],
'San Diego Padres': ['SDP', 'Padres'],
'Seattle Mariners': ['SEA', 'Mariners'],
'San Francisco Giants': ['SFG', 'Giants'],
'St Louis Cardinals': ['STL', 'Cardinals'],
'Tampa Bay Rays': ['TBR', 'Rays'],
'Texas Rangers': ['TEX', 'Senators', 'Rangers'],
'Toronto Blue Jays': ['TOR', 'Jays'],
'Washington Nationals': ['WSN', 'WAS', 'Nationals'],
}
# Type Definitions (from Discord app)
DEFENSE_LITERAL = Literal['Pitcher', 'Catcher', 'First Base', 'Second Base', 'Third Base', 'Shortstop', 'Left Field', 'Center Field', 'Right Field']
DEFENSE_NO_PITCHER_LITERAL = Literal['Catcher', 'First Base', 'Second Base', 'Third Base', 'Shortstop', 'Left Field', 'Center Field', 'Right Field']

View File

@ -0,0 +1,62 @@
"""
Logging configuration with rotating file handlers.
Follows user's CLAUDE.md requirements for rotating loggers.
"""
import logging
import logging.handlers
import os
from pathlib import Path
def setup_logging(
level: int = logging.INFO,
max_bytes: int = 10 * 1024 * 1024, # 10MB
backup_count: int = 5
) -> None:
"""
Set up rotating file logging for the application.
Args:
level: Logging level (default: INFO)
max_bytes: Maximum bytes per log file before rotation
backup_count: Number of backup files to keep
"""
# Create logs directory if it doesn't exist
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)
# Configure root logger
root_logger = logging.getLogger()
root_logger.setLevel(level)
# Remove existing handlers to avoid duplicates
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# Console handler for development
console_handler = logging.StreamHandler()
console_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(console_formatter)
root_logger.addHandler(console_handler)
# Rotating file handler
file_handler = logging.handlers.RotatingFileHandler(
filename=log_dir / "paper_dynasty.log",
maxBytes=max_bytes,
backupCount=backup_count
)
file_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s'
)
file_handler.setFormatter(file_formatter)
root_logger.addHandler(file_handler)
# Set specific logger levels
logging.getLogger("uvicorn").setLevel(logging.INFO)
logging.getLogger("sqlalchemy").setLevel(logging.WARNING)
logger = logging.getLogger(f'{__name__}.setup_logging')
logger.info("Logging configuration completed")

0
app/engine/__init__.py Normal file
View File

65
app/main.py Normal file
View File

@ -0,0 +1,65 @@
"""
FastAPI application setup for Paper Dynasty web app.
Implements Model/Service Architecture with dependency injection.
"""
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from app.routers import auth, games, api, pages
from app.config.logging_config import setup_logging
logger = logging.getLogger(f'{__name__}.main')
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan manager."""
# Startup
setup_logging()
logger.info("FastAPI application starting up")
yield
# Shutdown
logger.info("FastAPI application shutting down")
def create_app() -> FastAPI:
"""Create and configure the FastAPI application."""
app = FastAPI(
title="Paper Dynasty",
description="Baseball simulation web game with Model/Service Architecture",
version="2.0.0",
lifespan=lifespan
)
# Include routers
app.include_router(auth.router, prefix="/auth", tags=["authentication"])
app.include_router(games.router, prefix="/games", tags=["games"])
app.include_router(api.router, prefix="/api", tags=["api"])
app.include_router(pages.router, tags=["pages"])
# Static files and templates
app.mount("/static", StaticFiles(directory="app/static"), name="static")
return app
# Create the application instance
app = create_app()
@app.get("/health")
async def health_check():
"""Health check endpoint."""
return {"status": "healthy", "service": "paper-dynasty-web"}
if __name__ == "__main__":
import uvicorn
uvicorn.run("app.main:app", host="0.0.0.0", port=8001, reload=True)

0
app/models/__init__.py Normal file
View File

View File

138
app/routers/README.md Normal file
View File

@ -0,0 +1,138 @@
# Routers Directory
This directory contains **FastAPI route handlers** following the Model/Service Architecture pattern. Routes are thin controllers that handle HTTP concerns and delegate business logic to services.
## Architecture Pattern
### Route Responsibilities
- **HTTP Handling**: Request/response processing, status codes
- **Input Validation**: Request data validation and parsing
- **Service Delegation**: Delegate business logic to services
- **Response Formatting**: Transform service results for HTTP responses
### Design Principles
- **Thin Controllers**: Minimal logic, delegate to services
- **Dependency Injection**: Use FastAPI `Depends()` for services
- **Error Handling**: Transform service exceptions to HTTP errors
- **Documentation**: FastAPI auto-generates OpenAPI documentation
## Current Files
### `auth.py`
Authentication routes:
- **Discord OAuth**: Login redirect and callback handling
- **Session Management**: Login/logout functionality
- **Uses**: `AuthService` for business logic
### `games.py`
Game-related endpoints:
- **Game Creation**: Start new games
- **Game Retrieval**: Get game details and listings
- **Uses**: `GameService` for business logic
### `api.py`
JSON API endpoints for HTMX:
- **Live Updates**: Scoreboard and game state
- **Player Actions**: Execute gameplay actions
- **Uses**: `GameplayService` for real-time updates
### `pages.py`
HTML page routes with Jinja2 templates:
- **Home Page**: Main application entry point
- **Game Interface**: Live game viewing
- **Template Rendering**: Server-side HTML generation
## Route Pattern Example
```python
from fastapi import APIRouter, HTTPException
from app.services.service_container import GameServiceDep
router = APIRouter()
@router.post("/games/start")
async def start_game(
request: StartGameRequest,
game_service: GameServiceDep
):
"""Start a new game - delegates to GameService."""
try:
game = await game_service.create_game(
away_team_id=request.away_team_id,
home_team_id=request.home_team_id
)
return {"game_id": game.id, "status": "created"}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
```
## Service Integration
Routes use dependency injection to access services:
```python
# Service dependencies from service_container.py
GameServiceDep = Annotated[GameService, Depends(get_game_service)]
UserServiceDep = Annotated[UserService, Depends(get_user_service)]
AuthServiceDep = Annotated[AuthService, Depends(get_auth_service)]
```
## Route Organization
### `/auth` - Authentication
- `GET /auth/login` - Discord OAuth redirect
- `GET /auth/callback` - OAuth callback handler
- `POST /auth/logout` - Logout and session cleanup
### `/games` - Game Management
- `POST /games/start` - Create new game
- `GET /games/{game_id}` - Get game details
- `GET /games/` - List active games
### `/api` - HTMX JSON API
- `GET /api/scoreboard/{game_id}` - Live scoreboard data
- `POST /api/play` - Execute gameplay action
### `/` - HTML Pages
- `GET /` - Home page
- `GET /game/{game_id}` - Game interface page
## Error Handling
Routes should transform service exceptions to appropriate HTTP responses:
```python
try:
result = await service.do_something()
return result
except NotFoundException as e:
raise HTTPException(status_code=404, detail=str(e))
except ValidationError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Unexpected error: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
```
## Testing Routes
Routes should be tested with service dependencies mocked:
```python
from fastapi.testclient import TestClient
from unittest.mock import Mock
def test_start_game(mock_game_service):
client = TestClient(app)
# Mock service response
mock_game_service.create_game.return_value = Game(id=1)
response = client.post("/games/start", json={
"away_team_id": 1,
"home_team_id": 2
})
assert response.status_code == 200
assert response.json()["game_id"] == 1
```

0
app/routers/__init__.py Normal file
View File

25
app/routers/api.py Normal file
View File

@ -0,0 +1,25 @@
"""
JSON API routes for HTMX endpoints.
Uses various services following Model/Service Architecture.
"""
import logging
from fastapi import APIRouter, HTTPException
logger = logging.getLogger(f'{__name__}.api_router')
router = APIRouter()
@router.get("/scoreboard/{game_id}")
async def get_scoreboard(game_id: int):
"""Get live scoreboard data for HTMX updates."""
# TODO: Implement scoreboard API using GameplayService
raise HTTPException(status_code=501, detail="Scoreboard API not yet implemented")
@router.post("/play")
async def execute_play():
"""Execute a gameplay action."""
# TODO: Implement play execution using GameplayService
raise HTTPException(status_code=501, detail="Play execution not yet implemented")

33
app/routers/auth.py Normal file
View File

@ -0,0 +1,33 @@
"""
Authentication routes for Discord OAuth and session management.
Uses AuthService following Model/Service Architecture.
"""
import logging
from fastapi import APIRouter, HTTPException, Depends
from app.services.service_container import AuthServiceDep
logger = logging.getLogger(f'{__name__}.auth_router')
router = APIRouter()
@router.get("/login")
async def login_redirect():
"""Redirect to Discord OAuth login."""
# TODO: Implement Discord OAuth redirect
raise HTTPException(status_code=501, detail="Discord OAuth not yet implemented")
@router.get("/callback")
async def auth_callback(code: str, auth_service: AuthServiceDep):
"""Handle Discord OAuth callback."""
# TODO: Implement OAuth callback handling using AuthService
raise HTTPException(status_code=501, detail="OAuth callback not yet implemented")
@router.post("/logout")
async def logout():
"""Logout user and invalidate session."""
# TODO: Implement logout using AuthService
raise HTTPException(status_code=501, detail="Logout not yet implemented")

33
app/routers/games.py Normal file
View File

@ -0,0 +1,33 @@
"""
Game-related routes using GameService.
Follows Model/Service Architecture with thin controllers.
"""
import logging
from fastapi import APIRouter, HTTPException, Depends
from app.services.service_container import GameServiceDep
logger = logging.getLogger(f'{__name__}.games_router')
router = APIRouter()
@router.post("/start")
async def start_game(game_service: GameServiceDep):
"""Start a new game using GameService."""
# TODO: Implement game creation using GameService
raise HTTPException(status_code=501, detail="Game creation not yet implemented")
@router.get("/{game_id}")
async def get_game(game_id: int, game_service: GameServiceDep):
"""Get game details by ID."""
# TODO: Implement game retrieval using GameService
raise HTTPException(status_code=501, detail="Game retrieval not yet implemented")
@router.get("/")
async def list_games(game_service: GameServiceDep):
"""List active games."""
# TODO: Implement game listing using GameService
raise HTTPException(status_code=501, detail="Game listing not yet implemented")

30
app/routers/pages.py Normal file
View File

@ -0,0 +1,30 @@
"""
HTML page routes using Jinja2 templates.
Uses services following Model/Service Architecture.
"""
import logging
from fastapi import APIRouter, Request, Depends
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
logger = logging.getLogger(f'{__name__}.pages_router')
router = APIRouter()
templates = Jinja2Templates(directory="app/templates")
@router.get("/", response_class=HTMLResponse)
async def home_page(request: Request):
"""Home page."""
return templates.TemplateResponse("home.html", {"request": request})
@router.get("/game/{game_id}", response_class=HTMLResponse)
async def game_page(request: Request, game_id: int):
"""Game interface page."""
# TODO: Get game data using GameService
return templates.TemplateResponse("game.html", {
"request": request,
"game_id": game_id
})

109
app/services/README.md Normal file
View File

@ -0,0 +1,109 @@
# Services Directory
This directory contains the **business logic layer** of the Model/Service Architecture. Services are the core of the application architecture, containing all business rules and logic separated from HTTP concerns and database details.
## Architecture Pattern
### Service Responsibilities
- **Business Logic**: Core application logic and rules
- **Data Validation**: Input validation and business rule enforcement
- **Service Orchestration**: Coordination between multiple services
- **Error Handling**: Business-appropriate error handling and logging
### Design Principles
- **Single Responsibility**: Each service has one clear purpose
- **Dependency Injection**: Services receive dependencies via constructor
- **Stateless**: Services don't maintain instance state between calls
- **Testable**: Services can be unit tested with mocked dependencies
- **Pure Business Logic**: No HTTP, database, or UI concerns
## Current Files
### `base_service.py`
Abstract base class providing common functionality:
- **Standardized logging**: `self._log_operation()` and `self._log_error()`
- **Session management**: SQLModel database session handling
- **Validation helpers**: Common validation patterns
### `service_container.py`
Dependency injection container:
- **Database sessions**: Session lifecycle management
- **Service dependencies**: FastAPI `Depends()` integration
- **Type annotations**: Service dependency type aliases
## Planned Services
### Core Game Services
- **GameService**: Game creation, management, queries
- **GameplayService**: Core gameplay simulation & flow
- **AIService**: AI decision making and automation
### Supporting Services
- **UserService**: User sessions, preferences
- **AuthService**: Discord OAuth, session management
- **NotificationService**: Web notifications and real-time updates
## Service Pattern Example
```python
from app.services.base_service import BaseService
from app.services.service_container import SessionDep
class GameService(BaseService):
def __init__(self, session: Session):
super().__init__(session)
async def create_game(self, away_team_id: int, home_team_id: int) -> Game:
self._log_operation("create_game", f"Teams: {away_team_id} vs {home_team_id}")
# Business logic here
game = Game(away_team_id=away_team_id, home_team_id=home_team_id)
# Database persistence
self.session.add(game)
self.session.commit()
return game
```
## Dependency Injection
Services are injected into routes using FastAPI's `Depends()`:
```python
from app.services.service_container import GameServiceDep
@router.post("/games/start")
async def start_game(game_service: GameServiceDep):
# Service contains business logic, route handles HTTP concerns
return await game_service.create_game(away_team_id=1, home_team_id=2)
```
## Testing Services
Services should be unit tested independently:
```python
@pytest.fixture
def mock_session():
return Mock(spec=Session)
@pytest.fixture
def game_service(mock_session):
return GameService(mock_session)
async def test_create_game(game_service):
# Test business logic without database
result = await game_service.create_game(1, 2)
assert result.away_team_id == 1
```
## Migration from Discord App
When migrating from `../discord-app/`:
1. **Extract business logic** from Discord command handlers
2. **Remove Discord dependencies** (discord.py, interaction objects)
3. **Create service methods** with pure business logic
4. **Add comprehensive unit tests** for service methods
5. **Create thin route handlers** that delegate to services

0
app/services/__init__.py Normal file
View File

View File

@ -0,0 +1,66 @@
"""
Base service class for the Model/Service Architecture.
Provides common functionality and patterns for all services.
"""
import logging
from abc import ABC
from typing import Any, Optional
from sqlmodel import Session
class BaseService(ABC):
"""
Base class for all services in the application.
Provides common functionality like logging and database session management.
"""
def __init__(self, session: Session):
"""
Initialize the base service.
Args:
session: SQLModel database session
"""
self.session = session
self.logger = logging.getLogger(f'{__name__}.{self.__class__.__name__}')
def _log_operation(self, operation: str, details: Optional[str] = None) -> None:
"""
Log a service operation with standardized format.
Args:
operation: The operation being performed
details: Optional additional details
"""
message = f"{self.__class__.__name__}.{operation}"
if details:
message += f" - {details}"
self.logger.info(message)
def _log_error(self, operation: str, error: Exception) -> None:
"""
Log a service error with standardized format.
Args:
operation: The operation that failed
error: The exception that occurred
"""
self.logger.error(
f"{self.__class__.__name__}.{operation} - Failed: {str(error)}"
)
def _validate_required_fields(self, data: dict, required_fields: list[str]) -> None:
"""
Validate that required fields are present in data.
Args:
data: Dictionary to validate
required_fields: List of required field names
Raises:
ValueError: If any required field is missing
"""
missing_fields = [field for field in required_fields if field not in data or data[field] is None]
if missing_fields:
raise ValueError(f"Missing required fields: {', '.join(missing_fields)}")

View File

@ -0,0 +1,90 @@
"""
Dependency injection container for services.
Manages service lifecycles and dependencies using FastAPI's dependency injection.
"""
import logging
from functools import lru_cache
from typing import Annotated
from fastapi import Depends
from sqlmodel import Session, SQLModel, create_engine
from sqlalchemy.pool import StaticPool
from app.config.constants import settings
logger = logging.getLogger(f'{__name__}.service_container')
# Database setup
engine = create_engine(
settings.DATABASE_URL,
echo=settings.DEBUG,
poolclass=StaticPool if "sqlite" in settings.DATABASE_URL else None,
connect_args={"check_same_thread": False} if "sqlite" in settings.DATABASE_URL else {}
)
def create_db_and_tables():
"""Create database tables."""
SQLModel.metadata.create_all(engine)
def get_session() -> Session:
"""
Dependency to get database session.
Each request gets its own session that's automatically closed.
"""
with Session(engine) as session:
try:
yield session
except Exception as e:
logger.error(f"Database session error: {str(e)}")
session.rollback()
raise
finally:
session.close()
# Type aliases for dependency injection
SessionDep = Annotated[Session, Depends(get_session)]
# Service dependencies - these will be implemented as services are created
def get_game_service(session: SessionDep):
"""Get GameService instance."""
from app.services.game_service import GameService
return GameService(session)
def get_user_service(session: SessionDep):
"""Get UserService instance."""
from app.services.user_service import UserService
return UserService(session)
def get_auth_service(session: SessionDep):
"""Get AuthService instance."""
from app.services.auth_service import AuthService
return AuthService(session)
def get_gameplay_service(session: SessionDep):
"""Get GameplayService instance."""
from app.services.gameplay_service import GameplayService
return GameplayService(session)
def get_ai_service(session: SessionDep):
"""Get AIService instance."""
from app.services.ai_service import AIService
return AIService(session)
# Type aliases for service dependencies
GameServiceDep = Annotated[object, Depends(get_game_service)]
UserServiceDep = Annotated[object, Depends(get_user_service)]
AuthServiceDep = Annotated[object, Depends(get_auth_service)]
GameplayServiceDep = Annotated[object, Depends(get_gameplay_service)]
AIServiceDep = Annotated[object, Depends(get_ai_service)]

13
app/templates/home.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Paper Dynasty - Baseball Simulation</title>
</head>
<body>
<h1>Paper Dynasty</h1>
<p>Baseball simulation web app - Coming Soon!</p>
<p>Model/Service Architecture implementation</p>
</body>
</html>

22
docker-compose.yml Normal file
View File

@ -0,0 +1,22 @@
services:
postgres:
image: postgres:15
container_name: pddev-postgres
environment:
POSTGRES_USER: paper_dynasty_user
POSTGRES_PASSWORD: paper_dynasty_dev_password
POSTGRES_DB: paper_dynasty
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paper_dynasty_user -d paper_dynasty"]
interval: 30s
timeout: 10s
retries: 5
volumes:
postgres_data:

26
requirements.txt Normal file
View File

@ -0,0 +1,26 @@
# FastAPI and web framework dependencies
fastapi==0.104.1
uvicorn[standard]==0.24.0
jinja2==3.1.2
python-multipart==0.0.6
# Database and ORM
sqlmodel==0.0.14
psycopg[binary]==3.2.3
alembic==1.13.1
# Authentication and security
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
httpx==0.25.2
# Development and testing
pytest==7.4.3
pytest-asyncio==0.21.1
pytest-cov==4.1.0
# Logging and monitoring
structlog==23.2.0
# Static files and templates
aiofiles==23.2.1

46
scripts/README.md Normal file
View File

@ -0,0 +1,46 @@
# Scripts Directory
This directory contains database initialization scripts and other utility scripts for the Paper Dynasty web app.
## Files
### `init-db.sql`
PostgreSQL initialization script that runs when the Docker container is first created:
- **Creates test database**: `paper_dynasty_test` for integration testing
- **Grants permissions**: Ensures `paper_dynasty_user` has access to both databases
- **Runs automatically**: Executed by Docker Compose on first container startup
## Database Setup
The `init-db.sql` script is automatically executed when the PostgreSQL container starts for the first time via Docker's `docker-entrypoint-initdb.d` mechanism.
### What it creates:
```sql
-- Main development database (created by POSTGRES_DB env var)
-- paper_dynasty
-- Test database for integration tests
CREATE DATABASE paper_dynasty_test;
-- User permissions
GRANT ALL PRIVILEGES ON DATABASE paper_dynasty TO paper_dynasty_user;
GRANT ALL PRIVILEGES ON DATABASE paper_dynasty_test TO paper_dynasty_user;
```
### Container Integration
The script is mounted into the PostgreSQL container via `docker-compose.yml`:
```yaml
volumes:
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
```
## Future Scripts
This directory can be extended with additional utility scripts:
- **Migration scripts**: Database schema migrations
- **Data seeding**: Sample data for development
- **Backup scripts**: Database backup utilities
- **Development helpers**: Setup and maintenance scripts

9
scripts/init-db.sql Normal file
View File

@ -0,0 +1,9 @@
-- Initial database setup for Paper Dynasty
-- This script runs when the PostgreSQL container is first created
-- Create any additional databases needed for testing
CREATE DATABASE paper_dynasty_test;
-- Grant permissions
GRANT ALL PRIVILEGES ON DATABASE paper_dynasty TO paper_dynasty_user;
GRANT ALL PRIVILEGES ON DATABASE paper_dynasty_test TO paper_dynasty_user;

206
tests/README.md Normal file
View File

@ -0,0 +1,206 @@
# Tests Directory
This directory contains the comprehensive test suite for the Paper Dynasty web app, organized by testing scope and purpose following the Model/Service Architecture.
## Testing Strategy
### Test Pyramid Structure
- **Unit Tests (80%+ coverage)**: Fast, isolated tests for services and engine
- **Integration Tests (70%+ coverage)**: Service + database interactions
- **End-to-End Tests (Happy path)**: Complete user workflows
### Testing Priorities
1. **Services**: Core business logic with mocked dependencies
2. **Engine**: Stateless game simulation functions
3. **Integration**: Service interactions with real database
4. **Routes**: HTTP handling with mocked services
## Directory Structure
```
tests/
├── unit/ # Fast, isolated unit tests
│ ├── services/ # Service unit tests (MOST IMPORTANT)
│ ├── engine/ # Engine unit tests
│ └── models/ # Model validation tests
├── integration/ # Service + database integration
└── e2e/ # Full application tests
```
## Unit Tests (`unit/`)
### Services (`unit/services/`)
**Critical for Model/Service Architecture**
Tests business logic independently of database and web framework:
```python
# test_game_service.py
@pytest.fixture
def mock_session():
return Mock(spec=Session)
@pytest.fixture
def game_service(mock_session):
return GameService(mock_session)
@pytest.mark.asyncio
async def test_create_game_validates_teams(game_service):
# Test business logic without database
with pytest.raises(ValidationError):
await game_service.create_game(999, 1000) # Invalid teams
```
### Engine (`unit/engine/`)
Tests stateless game simulation functions:
```python
# test_dice.py
def test_dice_probability_distribution():
# Test dice rolling mechanics
results = [roll_dice() for _ in range(1000)]
assert 1 <= min(results) <= max(results) <= 6
# test_simulation.py
def test_pitcher_vs_batter_mechanics():
# Test core game simulation
result = simulate_at_bat(pitcher_stats, batter_stats)
assert result.outcome in ['hit', 'out', 'walk', 'strikeout']
```
### Models (`unit/models/`)
Tests data validation and relationships:
```python
# test_models.py
def test_game_model_validation():
# Test SQLModel validation
with pytest.raises(ValidationError):
Game(away_team_id=None) # Required field
```
## Integration Tests (`integration/`)
Tests service interactions with real database (isolated transactions):
```python
# test_game_flow.py
@pytest.mark.asyncio
async def test_complete_game_creation_flow(db_session):
game_service = GameService(db_session)
# Create test data
team1 = await create_test_team(db_session)
team2 = await create_test_team(db_session)
# Test full flow with real database
game = await game_service.create_game(team1.id, team2.id)
assert game.id is not None
assert game.away_team_id == team1.id
```
## End-to-End Tests (`e2e/`)
Tests complete user journeys through web interface:
```python
# test_game_creation.py
def test_user_can_create_game(client):
# Test complete user workflow
response = client.post("/auth/login") # Login
response = client.post("/games/start", json={...}) # Create game
response = client.get(f"/game/{game_id}") # View game
assert "Game created successfully" in response.text
```
## Running Tests
### All Tests
```bash
pytest
```
### Specific Test Types
```bash
# Unit tests only (fast)
pytest tests/unit/
# Integration tests only
pytest tests/integration/
# End-to-end tests only
pytest tests/e2e/
# Specific service tests
pytest tests/unit/services/test_game_service.py
# Single test function
pytest tests/unit/services/test_game_service.py::test_create_game_success
```
### With Coverage
```bash
# Coverage report
pytest --cov=app
# Coverage with HTML report
pytest --cov=app --cov-report=html
```
## Test Configuration
### Fixtures
Common test fixtures for database, services, and test data:
```python
# conftest.py
@pytest.fixture
def db_session():
# Isolated database session for integration tests
pass
@pytest.fixture
def mock_game_service():
# Mocked service for route testing
pass
@pytest.fixture
def test_game_data():
# Sample game data for tests
pass
```
### Test Database
Integration tests use a separate test database:
- `DATABASE_TEST_URL` environment variable
- Isolated transactions (rollback after each test)
- Clean state for each test
## Testing Best Practices
### Service Testing
- **Mock database sessions** for unit tests
- **Test business logic** independently of framework
- **Validate error handling** and edge cases
- **Test service interactions** with integration tests
### Test Data Management
- **Use factories** for creating test data
- **Isolate test state** (no shared mutable state)
- **Clean up after tests** (database rollback)
### Coverage Goals
- **Services**: 90%+ coverage (core business logic)
- **Engine**: 95%+ coverage (critical game mechanics)
- **Routes**: 80%+ coverage (HTTP handling)
- **Models**: 85%+ coverage (data validation)
## Migration Testing
When migrating from Discord app:
1. **Extract and test business logic** in service unit tests
2. **Validate game mechanics** with engine tests
3. **Test service integration** with database
4. **Ensure web interface** works with e2e tests

0
tests/__init__.py Normal file
View File

0
tests/e2e/__init__.py Normal file
View File

View File

0
tests/unit/__init__.py Normal file
View File

View File

View File

View File