# Beer Matcher Build Checklist

Follow this checklist to add the beer matcher service without missing any plumbing. Each subsection mirrors the existing wine matcher implementation; reference those files while executing the steps.

---

## 1. Service Package (`app/services/beer_pairing`)

1. `mkdir -p app/services/beer_pairing/models`.
2. Copy the module structure from `app/services/wine_pairing` and rename wine-specific symbols to beer equivalents.

### 1.1 `__init__.py`
Keep empty but ensure the package is importable.

### 1.2 `config.py`
```python
from typing import Dict, Any, Optional
from pydantic import BaseModel
import os


class BeerPairingServiceConfig(BaseModel):
    enabled: bool = True
    openai_api_key: str
    beer_pairing_model: str
    untappd_lookup_model: str
    max_tokens: int = 2000
    temperature: float = 0
    timeout: float = 30.0
    max_retries: int = 3
    retry_delay: float = 1.0
    untappd_lookup_enabled: bool = True

    @classmethod
    def from_config(cls, config: Dict[str, Any]) -> "BeerPairingServiceConfig":
        service_config = config["services"]["beer_pairing"]
        api_key_env = service_config["openai"].get("api_key_env", "OPENAI_API_KEY")
        api_key = os.getenv(api_key_env) or os.getenv("OPENAI_API_KEY")
        if not api_key:
            raise ValueError(f"Environment variable {api_key_env} not set")

        return cls(
            enabled=service_config.get("enabled", True),
            openai_api_key=api_key,
            beer_pairing_model=service_config["openai"]["models"]["beer_pairing"],
            untappd_lookup_model=service_config["openai"]["models"]["untappd_lookup"],
            max_tokens=service_config["openai"].get("max_tokens", 2000),
            temperature=service_config["openai"].get("temperature", 0),
            timeout=service_config["openai"].get("timeout", 30.0),
            max_retries=service_config["openai"].get("max_retries", 3),
            retry_delay=service_config["openai"].get("retry_delay", 1.0),
            untappd_lookup_enabled=service_config.get("features", {}).get("untappd_lookup", True),
        )
```

### 1.3 Request / Response Models
- `models/requests.py`: clone `WinePairingRequest` but rename:
  - `WinePairingFood` ➜ `BeerPairingFood`
  - `WineCardItem` ➜ `BeerCardItem`
  - `WinePairingRequest` ➜ `BeerPairingRequest`
  - `wines` list ➜ `beers`
  - Keep the `model_validator` that copies camelCase `userPrompt`.
- `models/responses.py`: rename `wine` field to `beer`, `vivino_rating` to `beer_rating`.

### 1.4 `routes.py`
- Define a new router tagged `beer_pairing`.
- Provide `set_beer_pairing_service`.
- Endpoint definition:
  ```python
  @router.post(
      "/beer-matcher",
      response_model=BeerPairingResponse,
      response_model_exclude_none=True,
      dependencies=[Depends(verify_beer_pairing_auth)]
  )
  async def match_beers(request: BeerPairingRequest):
      ...
  ```
- Mirror request logging / response logging fields from the wine router.

### 1.5 `service.py`
- Duplicate `WinePairingService` into `BeerPairingService`, adjust:
  - Logger = `structlog.get_logger("beer_pairing.service")`.
  - Exception class = `BeerPairingException`.
  - Method names: `_lookup_untappd_rating`, `match_beers`.
  - Metrics names: `beer_pairing_match`, `beer_pairing_untappd_lookup`.
  - Prompt text: replace sommelier references with “cicerone” and mention beers instead of wines.
  - JSON contract:
    - `beer` key instead of `wine`.
    - `beer_rating` instead of `vivino_rating`.
    - Replace references to “wine list” with “beer list”.
  - Rating lookup:
    ```python
    if rating_is_unknown and normalized_beer and self.config.untappd_lookup_enabled:
        fetched_rating = await self._lookup_untappd_rating(match.get("beer", ""), model_override=untappd_model)
    ```
  - Cache, normalization, and price lookups remain unchanged except for variable names.
  - Keep the random selection behavior identical to maintain parity.

---

## 2. Configuration & Environment

1. Create `config/services/beer_pairing.yaml` (see overview doc for template).
2. Register the service token + API key env vars:
   - `.env.example`: add `OPENAI_API_KEY_BEER`, `OPENAI_API_KEY_BEER_RATINGS` (or reuse), `BEER_SERVICE_KEY`.
   - CI/CD secrets / Docker compose files should forward those env vars.
3. If you maintain centralized docs (e.g., README, prompts/quick_start...), append the new variables there.

---

## 3. Shared Infrastructure Changes

1. `app/shared/exceptions/base.py`: add
   ```python
   class BeerPairingException(ServiceException):
       def __init__(self, message: str, details: Optional[Dict] = None):
           super().__init__(message, "BEER_PAIRING_ERROR", 500, details)
   ```
2. `app/shared/middleware/auth.py`:
   - Extend the `service_tokens` map to include `"beer_pairing"`.
   - Add `async def verify_beer_pairing_auth(...)` mirroring the existing helpers.
3. `app/services/__init__.py` does not need edits, but ensure `beer_pairing` imports do not create cycles.

---

## 4. FastAPI Wiring (`app/main.py`)

1. Imports:
   ```python
   from .services.beer_pairing.config import BeerPairingServiceConfig
   from .services.beer_pairing.service import BeerPairingService
   from .services.beer_pairing import routes as beer_pairing_routes
   ```
2. Auth tokens map:
   ```python
   service_tokens = {
       ...,
       "beer_pairing": get_env_var("BEER_SERVICE_KEY", required=False),
   }
   ```
3. Service initialization (after wine pairing keeps dependencies simple):
   ```python
   beer_pairing_config = BeerPairingServiceConfig.from_config(config)
   beer_pairing_service = BeerPairingService(beer_pairing_config)
   beer_pairing_routes.set_beer_pairing_service(beer_pairing_service)
   logger.info("beer_pairing_service_initialized", model=beer_pairing_config.beer_pairing_model)
   ```
4. Include router + startup telemetry:
   ```python
   app.include_router(beer_pairing_routes.router)
   ...
   services=["translation", "wine_pairing", "beer_pairing", ...]
   beer_pairing_model=beer_pairing_config.beer_pairing_model
   ```

---

## 5. Tests

1. `tests/services/test_beer_pairing_service.py`
   - Copy wine tests, rename imports/classes/method names.
   - Assert `beer` and `beer_rating` keys.
   - Keep RNG stub + prompt capture tests.
2. `tests/api/test_beer_matcher_route.py`
   - Copy `tests/api/test_wine_matcher_route.py`.
   - Update router import + stub class names.
   - Ensure endpoint is `/beer-matcher`.
3. Run targeted suite: `pytest tests/services/test_beer_pairing_service.py tests/api/test_beer_matcher_route.py`.

---

## 6. Verification & Rollout

1. Manual smoke test:
   ```bash
   curl --location 'http://localhost:8000/beer-matcher' \
     --header 'Authorization: Bearer <beer-token>' \
     --header 'Content-Type: application/json' \
     --data '{
       "language": "en",
       "foods": [{"name": "Burger"}],
       "beers": [{"name": "West Coast IPA"}]
     }'
   ```
2. Confirm logs contain `beer_pairing_started`, `beer_pairing_completed`.
3. Deploy with feature flag style:
   - Set `enabled: false` in YAML to gate the service if needed.
   - Roll tokens/keys before routing real traffic.
4. Update docs (README + prompts) referencing the new endpoint.
