# AI Menu Translator - Scalable Architecture Refactoring Plan

## Executive Summary

This document outlines a comprehensive refactoring plan to transform the current monolithic structure into a scalable, service-oriented architecture that supports multiple independent services with clear boundaries, separate configurations, and unified error handling.

## Critical Requirements

### ⚠️ BACKWARD COMPATIBILITY - MUST PRESERVE
**DO NOT CHANGE THE INPUT AND OUTPUT OF EXISTING SERVICES**

These endpoints must maintain exact same behavior:
- `POST /translate-menu-item` - Single item translation
- `POST /translate-menu-items` - Batch translation
- `POST /wine-matcher` - Wine pairing recommendations
- `POST /menu/extract` - Menu extraction from images
- `GET /health` - Health check

All request/response schemas, status codes, and error formats must remain identical.

---

## Current Architecture Analysis

### Current Structure
```
app/
├── api/
│   └── routes/          # All routes mixed together
├── config/              # Global configuration
├── core/                # Exceptions, constants, cache
├── inference/           # Only menu extraction OpenAI client
├── models/              # All models mixed together
├── services/            # All services mixed together
└── utils/               # Utilities
```

### Identified Issues

1. **No Service Boundaries**
   - All services share the same configuration
   - Models are mixed together
   - No clear separation of concerns

2. **Configuration Management**
   - Single global config for all services
   - No per-service API keys (security issue)
   - Cannot scale individual services independently

3. **Authorization Duplication**
   - Each route duplicates `verify_authorization()` function
   - No centralized middleware

4. **Error Handling**
   - Inconsistent error responses
   - No unified error handling middleware
   - Mixed exception types

5. **Code Duplication**
   - OpenAI client duplicated (openai_service.py vs menu_openai_client.py)
   - Similar patterns repeated across services

---

## Target Architecture

### New Structure
```
app/
├── main.py                          # FastAPI app entry point
├── shared/                          # Shared utilities across services
│   ├── __init__.py
│   ├── middleware/                  # Centralized middleware
│   │   ├── __init__.py
│   │   ├── auth.py                  # Bearer token authentication
│   │   ├── error_handler.py         # Unified error handling
│   │   ├── logging.py               # Request/response logging
│   │   └── rate_limiter.py          # Rate limiting (future)
│   ├── clients/                     # Shared external clients
│   │   ├── __init__.py
│   │   └── openai_client.py         # Unified OpenAI client
│   ├── exceptions/                  # Centralized exceptions
│   │   ├── __init__.py
│   │   ├── base.py                  # Base exception classes
│   │   └── http.py                  # HTTP exception mapping
│   ├── utils/                       # Shared utilities
│   │   ├── __init__.py
│   │   ├── metrics.py
│   │   ├── retry.py
│   │   └── validation.py
│   └── config/                      # Global config loader
│       ├── __init__.py
│       ├── base.py                  # Base config classes
│       └── loader.py                # YAML config loader
│
├── services/                        # Service-oriented architecture
│   ├── __init__.py
│   │
│   ├── translation/                 # Translation Service
│   │   ├── __init__.py
│   │   ├── config.py                # Service-specific config
│   │   ├── models/
│   │   │   ├── __init__.py
│   │   │   ├── requests.py          # TranslationRequest, BatchTranslationRequest
│   │   │   └── responses.py         # TranslationResponse, BatchTranslationResponse
│   │   ├── routes.py                # API routes
│   │   ├── service.py               # Business logic
│   │   └── dependencies.py          # Service dependencies (allergens, etc.)
│   │
│   ├── wine_pairing/                # Wine Pairing Service
│   │   ├── __init__.py
│   │   ├── config.py                # Service-specific config
│   │   ├── models/
│   │   │   ├── __init__.py
│   │   │   ├── requests.py          # WinePairingRequest
│   │   │   └── responses.py         # WinePairingResponse
│   │   ├── routes.py                # API routes
│   │   └── service.py               # Business logic
│   │
│   ├── menu_extraction/             # Menu Extraction Service
│   │   ├── __init__.py
│   │   ├── config.py                # Service-specific config
│   │   ├── models/
│   │   │   ├── __init__.py
│   │   │   ├── requests.py          # Menu extraction models
│   │   │   ├── responses.py         # MenuExtractionResponse
│   │   │   └── schemas.py           # JSON schemas for OpenAI
│   │   ├── routes.py                # API routes
│   │   └── service.py               # Business logic
│   │
│   └── health/                      # Health Check Service
│       ├── __init__.py
│       └── routes.py                # Health endpoints
│
└── config/
    └── services.yaml                # Unified service configuration
```

---

## Configuration Architecture

### Service Configuration Model

Each service will have:
1. **Independent API Key** - Separate OpenAI key per service
2. **Model Selection** - Per-service model configuration
3. **Service Settings** - Custom settings per service
4. **Feature Flags** - Enable/disable services

### Configuration File Structure

**config/services.yaml**
```yaml
# Global application settings
app:
  name: "AI Menu Translator"
  version: "2.0.0"
  debug: false
  environment: "production"

# Global authorization (can be overridden per service)
auth:
  global_bearer_token_env: "AUTHORIZATION_KEY"  # Global token

# Service-specific configurations
services:
  translation:
    enabled: true
    auth:
      bearer_token_env: "TRANSLATION_SERVICE_KEY"  # Optional: separate key
      # If not provided, falls back to global auth
    openai:
      api_key_env: "OPENAI_API_KEY_TRANSLATION"
      models:
        translation: "gpt-4o-mini"
        allergens: "gpt-4o-mini"
      max_tokens: 1800
      temperature: 0
      timeout: 20.0
    concurrency:
      max_concurrent_requests: 10
    languages:
      supported: ["ru", "de", "gr", "rs", "ro", "ua", "pl", "fr", "it", "es", "il", "cz", "tr", "ar", "cn", "ja", "ga", "mk", "en"]
    allergens:
      - { number: 1, name: "gluten" }
      - { number: 2, name: "crustaceans" }
      # ... rest of allergens

  wine_pairing:
    enabled: true
    auth:
      bearer_token_env: "WINE_SERVICE_KEY"  # Optional: separate key
    openai:
      api_key_env: "OPENAI_API_KEY_WINE"
      models:
        wine_pairing: "gpt-4o"
        vivino_lookup: "gpt-4o"
      max_tokens: 2000
      temperature: 0
      timeout: 30.0
    features:
      vivino_lookup: true

  menu_extraction:
    enabled: true
    auth:
      bearer_token_env: "MENU_EXTRACTION_SERVICE_KEY"  # Optional
    openai:
      api_key_env: "OPENAI_API_KEY_MENU"
      models:
        menu_extraction: "gpt-4o-mini"
      max_tokens: 1500
      temperature: 0
      timeout: 25.0
    limits:
      max_images: 8
      max_file_size_mb: 20

# Shared configurations (used by all services if not overridden)
shared:
  logging:
    level: "INFO"
    format: "json"
    file_rotation: true
  cache:
    enabled: false
    ttl: 3600
    max_size: 1000
  monitoring:
    metrics_enabled: true
    tracing_enabled: false
```

### Environment Variables

Each service can have separate credentials:

```bash
# Global
AUTHORIZATION_KEY=global-secret-key

# Per-service authorization (optional, falls back to global)
TRANSLATION_SERVICE_KEY=translation-specific-key
WINE_SERVICE_KEY=wine-specific-key
MENU_EXTRACTION_SERVICE_KEY=menu-specific-key

# OpenAI API Keys (per service)
OPENAI_API_KEY_TRANSLATION=sk-translation-key
OPENAI_API_KEY_WINE=sk-wine-key
OPENAI_API_KEY_MENU=sk-menu-key

# Or use same key for all (fallback)
OPENAI_API_KEY=sk-default-key
```

---

## Unified Error Handling

### Error Response Format

All errors will follow this consistent format:

```json
{
  "error": {
    "code": "SERVICE_ERROR_CODE",
    "message": "Human-readable error message",
    "details": {
      "field": "value",
      "additional_context": "..."
    },
    "timestamp": "2025-12-04T12:00:00Z",
    "request_id": "unique-request-id"
  }
}
```

### Exception Hierarchy

**shared/exceptions/base.py**
```python
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):
    """Validation errors (400)"""
    def __init__(self, message: str, details: Optional[Dict] = None):
        super().__init__(message, "VALIDATION_ERROR", 400, details)


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


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


class ExternalServiceException(ServiceException):
    """External service errors (502)"""
    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):
    """Rate limit errors (429)"""
    def __init__(self, retry_after: Optional[int] = None):
        super().__init__(
            "Rate limit exceeded",
            "RATE_LIMIT_EXCEEDED",
            429,
            {"retry_after": retry_after}
        )
```

### Error Handler Middleware

**shared/middleware/error_handler.py**
```python
from fastapi import Request, status
from fastapi.responses import JSONResponse
from shared.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
            }
        }
    )
```

---

## Authentication Middleware

### Bearer Token Middleware

**shared/middleware/auth.py**
```python
from fastapi import Request, Header
from typing import Optional
from shared.exceptions.base import AuthenticationException
import structlog

logger = structlog.get_logger("auth_middleware")


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

    def __init__(self, service_configs: dict):
        self.service_configs = service_configs

    def verify_service_token(
        self,
        service_name: str,
        authorization: Optional[str]
    ) -> bool:
        """Verify bearer token for specific service"""

        if not authorization:
            logger.warning(
                "missing_authorization_header",
                service=service_name
            )
            raise AuthenticationException("Authorization header is required")

        if not authorization.startswith("Bearer "):
            logger.warning(
                "invalid_authorization_format",
                service=service_name,
                format=authorization[:20]
            )
            raise AuthenticationException("Invalid authorization format")

        token = authorization[7:]  # Remove "Bearer " prefix

        # Get service-specific token or fall back to global
        service_config = self.service_configs.get(service_name, {})
        expected_token = (
            service_config.get("auth", {}).get("bearer_token")
            or self.service_configs.get("global_bearer_token")
        )

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

        logger.debug("authentication_successful", service=service_name)
        return True


# Dependency for routes
async def verify_translation_auth(authorization: str = Header(None)):
    """Dependency for translation service authentication"""
    auth_manager.verify_service_token("translation", authorization)
    return True


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


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

---

## Unified OpenAI Client

### Single OpenAI Client for All Services

**shared/clients/openai_client.py**
```python
from typing import Dict, Any, Optional, Tuple
from openai import AsyncOpenAI, APIError
import structlog
from shared.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

        Returns:
            Tuple of (result_data, metrics)
        """
        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,
                operation_id=operation_id,
                **metrics
            )

            return result, metrics

        except APIError as e:
            if e.status_code == 429:
                logger.warning(
                    "openai_rate_limit",
                    service=service,
                    operation_id=operation_id
                )
                raise RateLimitException()

            logger.error(
                "openai_api_error",
                service=service,
                operation_id=operation_id,
                status_code=e.status_code,
                error=str(e)
            )
            raise ExternalServiceException(
                "OpenAI",
                str(e),
                {"status_code": e.status_code}
            )

        except Exception as e:
            logger.error(
                "openai_unexpected_error",
                service=service,
                operation_id=operation_id,
                error=str(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 per 1M tokens
        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)


# Global instance
openai_client = UnifiedOpenAIClient()
```

---

## Service Implementation Pattern

### Example: Translation Service

**services/translation/config.py**
```python
from typing import List, Dict, Any
from pydantic import BaseModel


class AllergenConfig(BaseModel):
    number: int
    name: str


class TranslationServiceConfig(BaseModel):
    enabled: bool = True

    # OpenAI Configuration
    openai_api_key: str
    translation_model: str
    allergen_model: str
    max_tokens: int = 1800
    temperature: float = 0
    timeout: float = 20.0

    # Service Configuration
    max_concurrent_requests: int = 10
    supported_languages: List[str]
    allergens: List[AllergenConfig]

    @classmethod
    def from_yaml(cls, config: Dict[str, Any], env_resolver) -> "TranslationServiceConfig":
        """Create config from YAML with environment variable resolution"""
        service_config = config["services"]["translation"]

        return cls(
            enabled=service_config.get("enabled", True),
            openai_api_key=env_resolver(service_config["openai"]["api_key_env"]),
            translation_model=service_config["openai"]["models"]["translation"],
            allergen_model=service_config["openai"]["models"]["allergens"],
            max_tokens=service_config["openai"].get("max_tokens", 1800),
            temperature=service_config["openai"].get("temperature", 0),
            timeout=service_config["openai"].get("timeout", 20.0),
            max_concurrent_requests=service_config["concurrency"]["max_concurrent_requests"],
            supported_languages=service_config["languages"]["supported"],
            allergens=[AllergenConfig(**a) for a in service_config["allergens"]]
        )
```

**services/translation/routes.py**
```python
from fastapi import APIRouter, Depends
from typing import Union
import structlog

from .models.requests import TranslationRequest, BatchTranslationRequest
from .models.responses import (
    TranslationResponse,
    AllergensOnlyResponse,
    MenuOnlyResponse,
    BatchTranslationResponse,
    BatchAllergensOnlyResponse,
    BatchMenuOnlyResponse
)
from .service import translation_service
from shared.middleware.auth import verify_translation_auth

router = APIRouter(tags=["translation"])
logger = structlog.get_logger("translation.routes")


@router.post(
    "/translate-menu-item",
    response_model=Union[TranslationResponse, AllergensOnlyResponse, MenuOnlyResponse],
    dependencies=[Depends(verify_translation_auth)]
)
async def translate_menu_item(request: TranslationRequest):
    """
    Translate a single menu item

    BACKWARD COMPATIBLE - Maintains exact same input/output format
    """
    logger.info("translate_menu_item_request", has_menu=bool(request.menuItem))

    result = await translation_service.translate_single_item(request)

    logger.info("translate_menu_item_response", type=type(result).__name__)

    return result


@router.post(
    "/translate-menu-items",
    response_model=Union[
        BatchTranslationResponse,
        BatchAllergensOnlyResponse,
        BatchMenuOnlyResponse
    ],
    dependencies=[Depends(verify_translation_auth)]
)
async def translate_menu_items(request: BatchTranslationRequest):
    """
    Translate multiple menu items

    BACKWARD COMPATIBLE - Maintains exact same input/output format
    """
    logger.info(
        "translate_menu_items_request",
        count=len(request.menu_items),
        language=request.language_from
    )

    result = await translation_service.translate_batch(request)

    logger.info("translate_menu_items_response", count=len(result.items))

    return result
```

---

## Migration Steps

### Phase 1: Preparation (No Breaking Changes)

1. **Create new directory structure**
   ```bash
   mkdir -p app/shared/{middleware,clients,exceptions,utils,config}
   mkdir -p app/services/{translation,wine_pairing,menu_extraction,health}
   mkdir -p app/services/translation/{models}
   mkdir -p app/services/wine_pairing/{models}
   mkdir -p app/services/menu_extraction/{models}
   ```

2. **Implement shared components**
   - Create unified OpenAI client
   - Create exception hierarchy
   - Create error handler middleware
   - Create auth middleware

3. **Create service configurations**
   - Update config/services.yaml with new structure
   - Implement config loaders
   - Set up environment variables

### Phase 2: Service Migration (One at a time)

For each service:

1. **Create service structure**
   - Move models to service folder
   - Create service-specific config
   - Implement service class
   - Create routes

2. **Test thoroughly**
   - Verify request/response formats match exactly
   - Test error scenarios
   - Validate authentication
   - Check logging

3. **Update main.py**
   - Add new service router
   - Keep old route until verified

4. **Deprecate old code**
   - Mark old files for removal
   - Update imports

### Phase 3: Cleanup

1. **Remove old code**
   - Delete old service files
   - Delete old route files
   - Remove unused utilities

2. **Update documentation**
   - API documentation
   - Configuration guide
   - Deployment guide

3. **Final testing**
   - Integration tests
   - Load tests
   - Production smoke tests

---

## Service-Specific Details

### Translation Service

**Endpoints:**
- `POST /translate-menu-item`
- `POST /translate-menu-items`

**Models:**
- `TranslationRequest`, `BatchTranslationRequest`
- `TranslationResponse`, `AllergensOnlyResponse`, `MenuOnlyResponse`
- `BatchTranslationResponse`, `BatchAllergensOnlyResponse`, `BatchMenuOnlyResponse`

**Dependencies:**
- Allergen service (can be submodule)
- OpenAI client
- Text processing utilities

**Configuration:**
- `translation_model`: Model for translations
- `allergen_model`: Model for allergen detection
- `supported_languages`: List of language codes
- `allergens`: Allergen definitions

---

### Wine Pairing Service

**Endpoints:**
- `POST /wine-matcher`

**Models:**
- `WinePairingRequest`
- `WinePairingResponse`
- `WinePairingFood`, `WineCardItem`

**Configuration:**
- `wine_pairing_model`: Model for pairing
- `vivino_lookup_model`: Model for Vivino lookup
- `vivino_lookup_enabled`: Feature flag

---

### Menu Extraction Service

**Endpoints:**
- `POST /menu/extract`

**Models:**
- `MenuExtractionResponse`
- `MenuItem`, `MenuCategory`, `MenuPayload`
- `MENU_JSON_SCHEMA`

**Configuration:**
- `menu_extraction_model`: Model for extraction
- `max_images`: Max images per request
- `max_file_size_mb`: Max file size

---

## Testing Strategy

### Unit Tests
```python
# Test each service independently
# Test configuration loading
# Test error handling
# Test authentication
```

### Integration Tests
```python
# Test full request/response cycle
# Test with real OpenAI API (optional)
# Test error scenarios
# Test rate limiting
```

### Backward Compatibility Tests
```python
# Compare old vs new endpoint responses
# Ensure exact match in all scenarios
# Test edge cases
# Validate error formats
```

---

## Deployment Checklist

### Pre-Deployment

- [ ] All unit tests pass
- [ ] All integration tests pass
- [ ] Backward compatibility verified
- [ ] Environment variables configured
- [ ] Configuration file updated
- [ ] Documentation updated

### Deployment

- [ ] Deploy to staging
- [ ] Run smoke tests
- [ ] Monitor logs
- [ ] Check metrics
- [ ] Verify all endpoints
- [ ] Test authentication

### Post-Deployment

- [ ] Monitor error rates
- [ ] Check response times
- [ ] Verify cost metrics
- [ ] Update monitoring dashboards
- [ ] Document any issues

---

## Rollback Plan

If issues are detected:

1. **Immediate Actions**
   - Revert to previous version
   - Check error logs
   - Notify stakeholders

2. **Investigation**
   - Identify root cause
   - Fix issues
   - Re-test thoroughly

3. **Re-deployment**
   - Deploy fix
   - Verify resolution
   - Monitor closely

---

## Files to Remove After Migration

### Old Service Files
```
app/services/openai_service.py          # Replace with shared/clients/openai_client.py
app/services/translation_service.py      # Move to services/translation/service.py
app/services/wine_pairing_service.py     # Move to services/wine_pairing/service.py
app/services/menu_extractor_service.py   # Move to services/menu_extraction/service.py
app/services/allergen_service.py         # Move to services/translation/dependencies.py
```

### Old Route Files
```
app/api/routes/translation.py            # Move to services/translation/routes.py
app/api/routes/wine_matcher.py           # Move to services/wine_pairing/routes.py
app/api/routes/menu_extraction.py        # Move to services/menu_extraction/routes.py
```

### Old Model Files
```
app/models/requests.py                   # Split into service-specific models
app/models/responses.py                  # Split into service-specific models
app/models/menu_dto.py                   # Move to services/menu_extraction/models/
app/models/menu_schema.py                # Move to services/menu_extraction/models/
```

### Old Infrastructure
```
app/inference/menu_openai_client.py      # Replace with unified client
app/core/exceptions.py                   # Replace with shared/exceptions/
app/api/routes/__init__.py               # No longer needed
app/api/__init__.py                      # No longer needed
```

### Unused Utilities (Verify before removing)
```
app/utils/version.py                     # Check if used
app/core/cache.py                        # Check if used
app/core/constants.py                    # Move VERSION to config
```

---

## Benefits of New Architecture

### Scalability
- ✅ Easy to add new services
- ✅ Services can be scaled independently
- ✅ Clear service boundaries

### Security
- ✅ Per-service API keys
- ✅ Per-service authentication
- ✅ Centralized auth middleware

### Maintainability
- ✅ Clean separation of concerns
- ✅ Easy to locate code
- ✅ Reduced code duplication

### Monitoring
- ✅ Per-service metrics
- ✅ Unified error tracking
- ✅ Clear audit trail

### Development
- ✅ Easy to test services independently
- ✅ Parallel development possible
- ✅ Clear contracts (models)

---

## Conclusion

This refactoring plan transforms the application from a monolithic structure to a scalable, service-oriented architecture while maintaining complete backward compatibility. The new structure supports:

1. **Independent Services** - Each service has clear boundaries
2. **Separate Configuration** - Per-service settings and credentials
3. **Unified Error Handling** - Consistent error responses
4. **Centralized Authentication** - Single auth middleware
5. **Scalability** - Easy to add new services
6. **Maintainability** - Clean code organization

The migration can be done incrementally without breaking existing functionality, ensuring zero downtime and maintaining production stability.
