# Data Model Mappers

This module provides mappers for converting between database entities and API schemas, following the architecture optimization requirements.

## Overview

The mapper module implements the separation of concerns between:
- **Database Entities** (`src/models/entities.py`): SQLModel classes representing database tables
- **API Schemas** (`src/models/schemas.py`): Pydantic models for API request/response validation

## Architecture

```
┌─────────────────────┐
│   API Layer         │
│  (Controllers)      │
└──────────┬──────────┘
           │ Uses Schemas
           ▼
┌─────────────────────┐
│   Mappers           │
│  (Conversion Logic) │
└──────────┬──────────┘
           │ Uses Entities
           ▼
┌─────────────────────┐
│  Repository Layer   │
│  (Data Access)      │
└─────────────────────┘
```

## Available Mappers

### ProjectMapper

Converts between `ProjectDB` entity and `ProjectData` schema.

**Methods:**
- `to_schema(db, include_relations=False)`: Convert entity to schema
- `to_entity(schema, project_id=None, user_id=None)`: Convert create request to entity
- `update_entity(db, schema)`: Update entity with data from update request

**Example:**
```python
from src.mappers import ProjectMapper
from src.models.schemas import CreateProjectRequest

# Create entity from request
request = CreateProjectRequest(name="My Project", type="video")
project_db = ProjectMapper.to_entity(request)

# Convert entity to response schema
project_data = ProjectMapper.to_schema(project_db, include_relations=True)
```

### AssetMapper

Converts between `AssetDB` entity and `Asset` schemas (CharacterAsset, SceneAsset, PropAsset, OtherAsset).

**Methods:**
- `to_schema(db)`: Convert entity to appropriate asset schema based on type
- `to_entity(schema, project_id, asset_id=None)`: Convert create request to entity
- `update_entity(db, schema)`: Update entity with data from update request

**Example:**
```python
from src.mappers import AssetMapper
from src.models.schemas import CreateCharacterAssetRequest

# Create entity from request
request = CreateCharacterAssetRequest(
    type="character",
    name="Hero",
    desc="Main character",
    age="25"
)
asset_db = AssetMapper.to_entity(request, project_id="proj_123")

# Convert entity to response schema
asset = AssetMapper.to_schema(asset_db)
```

### EpisodeMapper

Converts between `EpisodeDB` entity and `Episode` schema.

**Methods:**
- `to_schema(db)`: Convert entity to schema
- `to_entity(schema, project_id, episode_id=None)`: Convert create request to entity
- `update_entity(db, schema)`: Update entity with data from update request

### StoryboardMapper

Converts between `StoryboardDB` entity and `Storyboard` schema.

**Methods:**
- `to_schema(db)`: Convert entity to schema
- `to_entity(schema, project_id, storyboard_id=None)`: Convert create request to entity
- `update_entity(db, schema)`: Update entity with data from update request

### TaskMapper

Converts between `TaskDB` entity and `Task` schema.

**Methods:**
- `to_schema(db)`: Convert entity to schema
- `to_entity(task_type, model, params, ...)`: Create entity from task parameters
- `update_status(db, status, result=None, error=None, provider_task_id=None)`: Update task status
- `increment_retry(db)`: Increment retry count

**Example:**
```python
from src.mappers import TaskMapper

# Create task entity
task_db = TaskMapper.to_entity(
    task_type="image",
    model="flux-dev",
    params={"prompt": "test"},
    status="pending"
)

# Update task status
TaskMapper.update_status(task_db, status="processing", provider_task_id="123")

# Convert to schema for API response
task = TaskMapper.to_schema(task_db)
```

### CanvasMapper

Converts between `CanvasDB` entity and `CanvasState` schema.

**Methods:**
- `to_schema(db)`: Convert entity to schema
- `to_entity(schema)`: Convert schema to entity
- `update_entity(db, schema)`: Update entity with data from schema

### CanvasMetadataMapper

Converts between `CanvasMetadataDB` entity and `CanvasMetadata` schema.

**Methods:**
- `to_schema(db)`: Convert entity to schema
- `to_entity(schema, project_id, canvas_id=None)`: Convert create request to entity
- `create_asset_canvas(project_id, asset_id, asset_name, canvas_id=None)`: Create asset canvas metadata
- `create_storyboard_canvas(project_id, storyboard_id, storyboard_shot, canvas_id=None)`: Create storyboard canvas metadata
- `update_entity(db, schema)`: Update entity with data from update request
- `update_access(db)`: Update canvas access tracking

## Design Principles

### 1. Single Responsibility

Each mapper is responsible for converting between one entity type and its corresponding schema(s).

### 2. Explicit Conversion

All conversions are explicit through mapper methods. No implicit conversions or magic methods.

### 3. Type Safety

Mappers preserve type information and use Pydantic validation for schemas.

### 4. Separation of Concerns

- **Entities**: Database structure and relationships
- **Schemas**: API contracts and validation
- **Mappers**: Conversion logic only

### 5. Testability

Mappers are pure functions (no side effects) and easily testable in isolation.

## Usage Guidelines

### In Controllers (API Layer)

Controllers should:
1. Receive request schemas
2. Use mappers to convert to entities
3. Pass entities to service/repository layer
4. Use mappers to convert entities back to response schemas

```python
@router.post("/projects")
async def create_project(request: CreateProjectRequest):
    # Convert request to entity
    project_db = ProjectMapper.to_entity(request)
    
    # Save to database (via repository)
    saved_project = repository.create(project_db)
    
    # Convert entity to response
    project_data = ProjectMapper.to_schema(saved_project)
    
    return BaseResponse(data=project_data)
```

### In Repositories (Data Access Layer)

Repositories should:
1. Work with entities internally
2. Use mappers when returning data to service layer

```python
def get_project(self, project_id: str) -> Optional[ProjectData]:
    project_db = session.get(ProjectDB, project_id)
    if not project_db:
        return None
    
    # Use mapper to convert to schema
    return ProjectMapper.to_schema(project_db, include_relations=True)
```

### In Services (Business Logic Layer)

Services should:
1. Work with schemas (domain objects)
2. Use mappers when interacting with repositories

## Benefits

### 1. Maintainability

- Single source of truth for conversion logic
- Easy to update when models change
- Clear separation of concerns

### 2. Consistency

- All conversions follow the same pattern
- Reduces code duplication
- Ensures consistent field mapping

### 3. Testability

- Mappers can be tested independently
- No database required for mapper tests
- Easy to verify conversion correctness

### 4. Type Safety

- Full TypeScript/Python type checking
- Pydantic validation for schemas
- SQLModel validation for entities

## Testing

Run mapper tests:

```bash
python backend/test_mappers.py
```

All mappers have comprehensive unit tests covering:
- Entity to schema conversion
- Schema to entity conversion
- Entity updates
- Edge cases and error handling

## Migration Guide

When updating existing code to use mappers:

1. **Identify manual conversions**: Look for code that manually creates schemas from entities
2. **Replace with mapper calls**: Use appropriate mapper method
3. **Update imports**: Import mapper instead of creating schemas directly
4. **Test thoroughly**: Ensure all fields are correctly mapped

### Before:
```python
project_data = ProjectData(
    id=project_db.id,
    name=project_db.name,
    description=project_db.description,
    # ... many more fields
)
```

### After:
```python
from src.mappers import ProjectMapper

project_data = ProjectMapper.to_schema(project_db)
```

## Requirements Satisfied

This mapper module satisfies the following requirements from the architecture optimization spec:

- **Requirement 5.1**: Separate database entities from API schemas ✓
- **Requirement 5.2**: Define database entities in dedicated entities module ✓
- **Requirement 5.3**: Define API schemas in dedicated schemas module ✓
- **Requirement 5.4**: Use explicit mapper functions for conversion ✓
- **Requirement 5.5**: Each field defined in exactly one authoritative location ✓
- **Requirement 5.6**: Use inheritance/composition to share common fields ✓
