# Models - Data Models for Game Engine

## Purpose

Data models split into two systems:
- **Pydantic**: In-memory game state, API contracts, WebSocket serialization
- **SQLAlchemy**: Database persistence, relationships, audit trail

## Directory Structure

```
models/
├── __init__.py        # Central exports - import from here
├── game_models.py     # Pydantic in-memory state (GameState, decisions)
├── player_models.py   # Polymorphic players (BasePlayer → SbaPlayer, PdPlayer)
├── db_models.py       # SQLAlchemy ORM (Game, Play, Lineup)
└── roster_models.py   # Roster link models
```

## Key Models

### GameState (game_models.py)
Core in-memory state for active games.

**Critical Fields**:
- Identity: `game_id`, `league_id`
- Teams: `home_team_id`, `away_team_id`, `*_is_ai`
- Game state: `inning`, `half`, `outs`, `*_score`
- Runners: `on_first`, `on_second`, `on_third` (LineupPlayerState)
- Current: `current_batter`, `current_pitcher`, `current_catcher`
- Decisions: `pending_defensive_decision`, `pending_offensive_decision`

**Helper Methods**: 20+ including `get_batting_team_id()`, `add_runner()`, `is_game_over()`

### LineupPlayerState
Lightweight player reference in lineup.
- `lineup_id`, `card_id`, `position`, `batting_order`, `is_active`
- `position_rating` (Optional - PD league only)

### TeamLineupState
Team's active lineup with helpers: `get_batting_order()`, `get_pitcher()`, `get_batter()`

### Decision Models
- **DefensiveDecision**: `alignment`, `infield_depth`, `outfield_depth`, `hold_runners`
- **OffensiveDecision**: `action`, `steal_attempts`, `hit_and_run`, `bunt_attempt`

### Player Models (player_models.py)
Polymorphic architecture for league-agnostic game engine.

```
BasePlayer (ABC)
├── SbaPlayer - Simple (id, name, image, positions)
└── PdPlayer - Complex (scouting data, ratings)
```

Factory: `Player.from_api_data(config, data)`

### Database Models (db_models.py)

| Model | Purpose |
|-------|---------|
| Game | Game container with status, scores, AI flags |
| Play | At-bat record with 25+ stat fields |
| Lineup | Player assignments and substitutions |
| GameSession | WebSocket state tracking |
| RosterLink | Eligible cards/players (polymorphic) |

## Common Patterns

### Import from Package
```python
from app.models import GameState, Game, SbaPlayer  # ✅
from app.models.game_models import GameState       # ❌
```

### Data Resolution
GameState uses minimal LineupPlayerState refs. Get full player data from StateManager:
```python
lineup_state = state_manager.get_lineup(game_id, team_id)
player = lineup_state.get_player_by_lineup_id(lineup_id)
```

### Pydantic Validation
All models use field validators for data integrity:
```python
@field_validator('position')
@classmethod
def validate_position(cls, v):
    if v not in VALID_POSITIONS:
        raise ValueError(...)
    return v
```

## References

- **Game Recovery**: See `app/core/state_manager.py` for state rebuild
- **Database Schema**: See `app/database/CLAUDE.md` for table details
- **Type Checking**: See `backend/.claude/type-checking-guide.md`

---

**Tests**: `tests/unit/models/` | **Updated**: 2025-01-19
