# Quick Start: Refactoring Implementation Guide

## Overview

This guide provides a step-by-step implementation plan for refactoring to the new architecture.

---

## Prerequisites

Before starting:
- [ ] Read `refactor_instructions.md` completely
- [ ] Read `architecture_comparison.md` to understand changes
- [ ] Backup current codebase
- [ ] Ensure all current tests pass
- [ ] Create a new git branch: `git checkout -b refactor/scalable-architecture`

---

## Implementation Order

### Step 1: Create Shared Infrastructure (Day 1-2)

#### 1.1 Create Directory Structure
```bash
mkdir -p app/shared/{middleware,clients,exceptions,utils,config}
mkdir -p app/services/{translation,wine_pairing,menu_extraction,health}

# Create __init__.py files
touch app/shared/__init__.py
touch app/shared/middleware/__init__.py
touch app/shared/clients/__init__.py
touch app/shared/exceptions/__init__.py
touch app/shared/utils/__init__.py
touch app/shared/config/__init__.py
```

#### 1.2 Create Base Exceptions
**File: `app/shared/exceptions/base.py`**
```python
from typing import Dict, Any, Optional


class ServiceException(Exception):
    """Base exception for all service errors"""

    def __init__(
        self,
        message: str,
        error_code: str,
        status_code: int = 500,
        details: Optional[Dict[str, Any]] = None
    ):
        self.message = message
        self.error_code = error_code
        self.status_code = status_code
        self.details = details or {}
        super().__init__(message)


class ValidationException(ServiceException):
    def __init__(self, message: str, details: Optional[Dict] = None):
        super().__init__(message, "VALIDATION_ERROR", 400, details)


class AuthenticationException(ServiceException):
    def __init__(self, message: str = "Unauthorized"):
        super().__init__(message, "AUTHENTICATION_ERROR", 401)


class NotFoundException(ServiceException):
    def __init__(self, message: str, details: Optional[Dict] = None):
        super().__init__(message, "NOT_FOUND", 404, details)


class ExternalServiceException(ServiceException):
    def __init__(self, service: str, message: str, details: Optional[Dict] = None):
        super().__init__(
            f"{service} error: {message}",
            "EXTERNAL_SERVICE_ERROR",
            502,
            {"service": service, **(details or {})}
        )


class RateLimitException(ServiceException):
    def __init__(self, retry_after: Optional[int] = None):
        super().__init__(
            "Rate limit exceeded",
            "RATE_LIMIT_EXCEEDED",
            429,
            {"retry_after": retry_after}
        )
```

#### 1.3 Create Error Handler Middleware
**File: `app/shared/middleware/error_handler.py`**
```python
from fastapi import Request, status
from fastapi.responses import JSONResponse
from ..exceptions.base import ServiceException
import structlog
import uuid
from datetime import datetime

logger = structlog.get_logger("error_handler")


async def service_exception_handler(request: Request, exc: ServiceException):
    """Handle all ServiceException instances"""
    request_id = str(uuid.uuid4())

    logger.error(
        "service_exception",
        error_code=exc.error_code,
        message=exc.message,
        status_code=exc.status_code,
        details=exc.details,
        request_id=request_id,
        path=request.url.path,
        method=request.method
    )

    return JSONResponse(
        status_code=exc.status_code,
        content={
            "error": {
                "code": exc.error_code,
                "message": exc.message,
                "details": exc.details,
                "timestamp": datetime.utcnow().isoformat() + "Z",
                "request_id": request_id
            }
        }
    )


async def generic_exception_handler(request: Request, exc: Exception):
    """Handle unexpected exceptions"""
    request_id = str(uuid.uuid4())

    logger.error(
        "unexpected_exception",
        error=str(exc),
        error_type=type(exc).__name__,
        request_id=request_id,
        path=request.url.path,
        method=request.method,
        exc_info=True
    )

    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={
            "error": {
                "code": "INTERNAL_SERVER_ERROR",
                "message": "An unexpected error occurred",
                "details": {"type": type(exc).__name__},
                "timestamp": datetime.utcnow().isoformat() + "Z",
                "request_id": request_id
            }
        }
    )
```

#### 1.4 Create Unified OpenAI Client
**File: `app/shared/clients/openai_client.py`**
```python
import json
from typing import Dict, Any, Optional, Tuple
from openai import AsyncOpenAI, APIError
import structlog
from ..exceptions.base import ExternalServiceException, RateLimitException

logger = structlog.get_logger("openai_client")


class UnifiedOpenAIClient:
    """Unified OpenAI client for all services"""

    def __init__(self):
        self._clients: Dict[str, AsyncOpenAI] = {}

    def get_client(self, api_key: str) -> AsyncOpenAI:
        """Get or create OpenAI client for specific API key"""
        if api_key not in self._clients:
            self._clients[api_key] = AsyncOpenAI(api_key=api_key)
        return self._clients[api_key]

    async def json_completion(
        self,
        *,
        api_key: str,
        model: str,
        prompt: str,
        operation_id: str,
        service: str,
        max_tokens: Optional[int] = None,
        temperature: float = 0,
        timeout: Optional[float] = None,
        json_schema: Optional[Dict[str, Any]] = None
    ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
        """Make JSON completion request"""
        client = self.get_client(api_key)

        logger.info(
            "openai_request",
            service=service,
            operation_id=operation_id,
            model=model
        )

        try:
            kwargs = {
                "model": model,
                "messages": [{"role": "user", "content": prompt}],
                "temperature": temperature,
                "response_format": {"type": "json_object"}
            }

            if max_tokens:
                kwargs["max_tokens"] = max_tokens

            if json_schema:
                kwargs["response_format"] = {
                    "type": "json_schema",
                    "json_schema": json_schema
                }

            response = await client.chat.completions.create(**kwargs)

            result = json.loads(response.choices[0].message.content)

            metrics = {
                "input_tokens": response.usage.prompt_tokens,
                "output_tokens": response.usage.completion_tokens,
                "total_tokens": response.usage.total_tokens,
                "model": response.model,
                "cost": self._calculate_cost(
                    response.model,
                    response.usage.prompt_tokens,
                    response.usage.completion_tokens
                )
            }

            logger.info("openai_response", service=service, **metrics)
            return result, metrics

        except APIError as e:
            if e.status_code == 429:
                raise RateLimitException()
            raise ExternalServiceException("OpenAI", str(e), {"status_code": e.status_code})
        except Exception as e:
            raise ExternalServiceException("OpenAI", str(e))

    @staticmethod
    def _calculate_cost(model: str, input_tokens: int, output_tokens: int) -> float:
        """Calculate cost based on model pricing"""
        pricing = {
            "gpt-4o": {"input": 2.50, "output": 10.00},
            "gpt-4o-mini": {"input": 0.150, "output": 0.600},
            "gpt-4-turbo": {"input": 10.00, "output": 30.00},
        }
        model_key = next((k for k in pricing.keys() if k in model), "gpt-4o-mini")
        rates = pricing[model_key]
        cost = (
            (input_tokens / 1_000_000) * rates["input"] +
            (output_tokens / 1_000_000) * rates["output"]
        )
        return round(cost, 6)


openai_client = UnifiedOpenAIClient()
```

#### 1.5 Create Config Loader
**File: `app/shared/config/loader.py`**
```python
import os
import yaml
from pathlib import Path
from typing import Dict, Any


def load_service_config(config_path: str = None) -> Dict[str, Any]:
    """Load service configuration from YAML"""
    if config_path is None:
        config_path = Path(__file__).parent.parent.parent.parent / "config" / "services.yaml"

    with open(config_path, 'r', encoding='utf-8') as f:
        config = yaml.safe_load(f)

    return config


def get_env_var(var_name: str, required: bool = True, default: str = None) -> str:
    """Get environment variable with validation"""
    value = os.getenv(var_name, default)
    if required and not value:
        raise ValueError(f"Required environment variable {var_name} is not set")
    return value
```

#### 1.6 Create Authentication Middleware
**File: `app/shared/middleware/auth.py`**
```python
from fastapi import Header
from typing import Optional
from ..exceptions.base import AuthenticationException
import structlog

logger = structlog.get_logger("auth_middleware")


class ServiceAuthManager:
    """Manages authentication for services"""

    def __init__(self, global_token: str, service_tokens: Dict[str, Optional[str]]):
        self.global_token = global_token
        self.service_tokens = service_tokens

    def verify_service_token(self, service_name: str, authorization: Optional[str]) -> bool:
        """Verify bearer token for specific service"""
        if not authorization:
            raise AuthenticationException("Authorization header is required")

        if not authorization.startswith("Bearer "):
            raise AuthenticationException("Invalid authorization format")

        token = authorization[7:]

        # Get service-specific token or fall back to global
        expected_token = self.service_tokens.get(service_name) or self.global_token

        if token != expected_token:
            logger.warning("invalid_bearer_token", service=service_name)
            raise AuthenticationException("Invalid bearer token")

        return True


# Global auth manager (will be initialized in main.py)
auth_manager: Optional[ServiceAuthManager] = None


def init_auth_manager(global_token: str, service_tokens: Dict[str, Optional[str]]):
    """Initialize global auth manager"""
    global auth_manager
    auth_manager = ServiceAuthManager(global_token, service_tokens)


# Service-specific dependencies
async def verify_translation_auth(authorization: str = Header(None)) -> bool:
    """Dependency for translation service authentication"""
    return auth_manager.verify_service_token("translation", authorization)


async def verify_wine_pairing_auth(authorization: str = Header(None)) -> bool:
    """Dependency for wine pairing service authentication"""
    return auth_manager.verify_service_token("wine_pairing", authorization)


async def verify_menu_extraction_auth(authorization: str = Header(None)) -> bool:
    """Dependency for menu extraction service authentication"""
    return auth_manager.verify_service_token("menu_extraction", authorization)
```

---

### Step 2: Create New Configuration (Day 3)

#### 2.1 Create services.yaml
**File: `config/services.yaml`**

Copy the structure from `refactor_instructions.md` section "Configuration File Structure"

#### 2.2 Update Environment Variables
Create `.env.example`:
```bash
# Global
AUTHORIZATION_KEY=your-global-key

# Per-service (optional)
TRANSLATION_SERVICE_KEY=translation-key
WINE_SERVICE_KEY=wine-key
MENU_EXTRACTION_SERVICE_KEY=menu-key

# OpenAI API Keys
OPENAI_API_KEY_TRANSLATION=sk-translation
OPENAI_API_KEY_WINE=sk-wine
OPENAI_API_KEY_MENU=sk-menu

# Or single key
OPENAI_API_KEY=sk-default
```

---

### Step 3: Migrate Translation Service (Day 4-5)

#### 3.1 Create Translation Service Structure
```bash
mkdir -p app/services/translation/models
touch app/services/translation/__init__.py
touch app/services/translation/config.py
touch app/services/translation/service.py
touch app/services/translation/routes.py
touch app/services/translation/dependencies.py
touch app/services/translation/models/__init__.py
touch app/services/translation/models/requests.py
touch app/services/translation/models/responses.py
```

#### 3.2 Move and Adapt Code
1. Copy request/response models from `app/models/requests.py` and `app/models/responses.py`
2. Copy service logic from `app/services/translation_service.py`
3. Copy allergen logic from `app/services/allergen_service.py` to `dependencies.py`
4. Create routes from `app/api/routes/translation.py`
5. Update all imports to use new paths

#### 3.3 Test Translation Service
```python
# Test script
import requests

# Test single item
response = requests.post(
    "http://localhost:8000/translate-menu-item",
    headers={"Authorization": "Bearer your-key"},
    json={
        "menuItem": "Test",
        "description": "Test description"
    }
)
print(response.json())

# Compare with old response format
```

---

### Step 4: Migrate Wine Pairing Service (Day 6-7)

Follow same pattern as translation service.

---

### Step 5: Migrate Menu Extraction Service (Day 8-9)

Follow same pattern as translation service.

---

### Step 6: Update Main Application (Day 10)

#### 6.1 Update main.py
```python
from fastapi import FastAPI
from shared.middleware.error_handler import (
    service_exception_handler,
    generic_exception_handler
)
from shared.exceptions.base import ServiceException
from shared.middleware.auth import init_auth_manager
from shared.config.loader import load_service_config, get_env_var
from services.translation.routes import router as translation_router
from services.wine_pairing.routes import router as wine_pairing_router
from services.menu_extraction.routes import router as menu_extraction_router
from services.health.routes import router as health_router

# Load configuration
config = load_service_config()

# Initialize auth
global_token = get_env_var("AUTHORIZATION_KEY")
service_tokens = {
    "translation": get_env_var("TRANSLATION_SERVICE_KEY", required=False),
    "wine_pairing": get_env_var("WINE_SERVICE_KEY", required=False),
    "menu_extraction": get_env_var("MENU_EXTRACTION_SERVICE_KEY", required=False),
}
init_auth_manager(global_token, service_tokens)

# Create app
app = FastAPI(title="AI Menu Translator", version="2.0.0")

# Add exception handlers
app.add_exception_handler(ServiceException, service_exception_handler)
app.add_exception_handler(Exception, generic_exception_handler)

# Include routers
app.include_router(translation_router)
app.include_router(wine_pairing_router)
app.include_router(menu_extraction_router)
app.include_router(health_router)
```

---

### Step 7: Testing & Validation (Day 11-12)

#### 7.1 Create Test Suite
```python
# tests/test_backward_compatibility.py

def test_translation_single_item():
    """Test that translation maintains same format"""
    # Test with old and new implementation
    # Compare responses

def test_translation_batch():
    """Test batch translation"""
    pass

def test_wine_pairing():
    """Test wine pairing"""
    pass

def test_menu_extraction():
    """Test menu extraction"""
    pass
```

#### 7.2 Manual Testing Checklist
- [ ] Test all endpoints with curl
- [ ] Verify authentication works
- [ ] Test error responses
- [ ] Check logging format
- [ ] Validate metrics

---

### Step 8: Cleanup (Day 13)

#### 8.1 Remove Old Code
```bash
# Delete old files
rm -rf app/api/
rm -rf app/models/
rm -rf app/inference/
rm -rf app/core/
rm app/services/openai_service.py
rm app/services/translation_service.py
rm app/services/wine_pairing_service.py
rm app/services/menu_extractor_service.py
rm app/services/allergen_service.py
rm app/config/settings.py

# Remove other_project
rm -rf other_project/
```

#### 8.2 Update Documentation
- [ ] Update README.md
- [ ] Update API documentation
- [ ] Update deployment guide

---

## Daily Checklist

### Day 1-2: Shared Infrastructure
- [ ] Create directory structure
- [ ] Implement base exceptions
- [ ] Create error handler middleware
- [ ] Create OpenAI client
- [ ] Create config loader
- [ ] Create auth middleware
- [ ] Test shared components

### Day 3: Configuration
- [ ] Create services.yaml
- [ ] Update environment variables
- [ ] Test configuration loading

### Day 4-5: Translation Service
- [ ] Create service structure
- [ ] Move models
- [ ] Move service logic
- [ ] Create routes
- [ ] Test translation service
- [ ] Verify backward compatibility

### Day 6-7: Wine Pairing Service
- [ ] Create service structure
- [ ] Move models
- [ ] Move service logic
- [ ] Create routes
- [ ] Test wine pairing service
- [ ] Verify backward compatibility

### Day 8-9: Menu Extraction Service
- [ ] Create service structure
- [ ] Move models
- [ ] Move service logic
- [ ] Create routes
- [ ] Test menu extraction service
- [ ] Verify backward compatibility

### Day 10: Main Application
- [ ] Update main.py
- [ ] Register error handlers
- [ ] Register routers
- [ ] Test full application
- [ ] Check logs

### Day 11-12: Testing
- [ ] Write integration tests
- [ ] Run all tests
- [ ] Manual testing
- [ ] Performance testing
- [ ] Security testing

### Day 13: Cleanup
- [ ] Delete old files
- [ ] Update documentation
- [ ] Create migration notes
- [ ] Prepare for deployment

---

## Troubleshooting

### Common Issues

**Import errors:**
```python
# Make sure __init__.py exists in all directories
# Use absolute imports: from app.shared.xxx import yyy
```

**Configuration not loading:**
```python
# Check YAML syntax
# Verify environment variables are set
# Check file paths
```

**Authentication failing:**
```python
# Verify bearer token format: "Bearer <token>"
# Check environment variables
# Review auth middleware logs
```

**OpenAI errors:**
```python
# Verify API keys are set
# Check model names match configuration
# Review rate limits
```

---

## Success Criteria

- [ ] All existing endpoints work with same input/output
- [ ] All tests pass
- [ ] Error responses are consistent
- [ ] Authentication works per service
- [ ] Logging is clear and structured
- [ ] Configuration is separated by service
- [ ] Code is clean and organized
- [ ] Documentation is updated

---

## Rollback Plan

If issues occur:

1. Keep old code in separate branch
2. Tag current version: `git tag v1.0-before-refactor`
3. Can quickly revert: `git checkout v1.0-before-refactor`
4. Deploy previous version

---

## Questions?

Review:
- `refactor_instructions.md` - Complete refactoring guide
- `architecture_comparison.md` - Current vs target architecture

Remember: **MAINTAIN BACKWARD COMPATIBILITY AT ALL TIMES**
