# Implementation Plan: Binary Response + Mock Mode + Usage Metadata

## Overview
Three changes to the `create_restaurant_offer` service:
1. Return raw binary PNG instead of a file URL
2. Add `mockData` boolean input — skips OpenAI, returns a pre-existing file
3. Include API usage metadata (model, tokens, price) in the response headers

---

## Change 1 — `models/requests.py`

Add `mock_data: bool` field with camelCase alias `mockData`.

```python
mock_data: bool = Field(
    default=False,
    description="If true, skip OpenAI and return mock image from menues/restaurant123/offer_9a8e94e6e2524de2abd34e584b315f79.png"
)
```

Also add alias mapping in `apply_aliases`:
```python
if "mock_data" not in mapped and "mockData" in mapped:
    mapped["mock_data"] = mapped["mockData"]
```

---

## Change 2 — `service.py`

### 2a. New return shape
Change `create_restaurant_offer()` to return:
```python
{
    "image_bytes": bytes,      # raw PNG binary
    "model": str,              # model used (or "mock")
    "input_tokens": int,       # from response.usage.input_tokens (0 for mock)
    "output_tokens": int,      # from response.usage.output_tokens (0 for mock)
    "total_tokens": int,       # from response.usage.total_tokens (0 for mock)
    "price_usd": float | None  # calculated price or None if unknown
}
```

### 2b. Mock path (add at start of `create_restaurant_offer`)
```python
MOCK_IMAGE_PATH = Path("menues/restaurant123/offer_9a8e94e6e2524de2abd34e584b315f79.png")

if request.mock_data:
    logger.info("create_restaurant_offer_mock_mode", z_id=request.z_id)
    image_bytes = MOCK_IMAGE_PATH.read_bytes()
    return {
        "image_bytes": image_bytes,
        "model": "mock",
        "input_tokens": 0,
        "output_tokens": 0,
        "total_tokens": 0,
        "price_usd": 0.0,
    }
```

### 2c. Extract usage after `client.images.generate()`
```python
usage = getattr(response, "usage", None)
input_tokens = getattr(usage, "input_tokens", 0) or 0
output_tokens = getattr(usage, "output_tokens", 0) or 0
total_tokens = getattr(usage, "total_tokens", 0) or 0

# gpt-image-1 pricing (as of 2025): input $5/1M tokens, output $40/1M tokens
# Adjust if pricing changes
price_usd = None
if usage:
    price_usd = round((input_tokens * 5 + output_tokens * 40) / 1_000_000, 6)
```

### 2d. Change return value
Replace the final `return {"image_url": stored_image_url}` with returning
`composed_bytes` directly (no longer save to disk):
```python
return {
    "image_bytes": composed_bytes,
    "model": selected_model,
    "input_tokens": input_tokens,
    "output_tokens": output_tokens,
    "total_tokens": total_tokens,
    "price_usd": price_usd,
}
```

Note: `_persist_generated_image` and `_build_storage_target` become unused — remove them.

---

## Change 3 — `routes.py`

### 3a. Imports
```python
from fastapi import APIRouter, Depends, Response
```

### 3b. Remove `response_model` from decorator
The endpoint now returns a raw `Response`, not a Pydantic model:
```python
@router.post(
    "/create",
    response_class=Response,
    dependencies=[Depends(verify_create_restaurant_offer_auth)]
)
```

### 3c. Build and return Response with metadata headers
```python
async def create_restaurant_offer(request: CreateRestaurantOfferRequest):
    result = await create_restaurant_offer_service.create_restaurant_offer(request)

    headers = {
        "X-Model": result["model"],
        "X-Tokens-Input": str(result["input_tokens"]),
        "X-Tokens-Output": str(result["output_tokens"]),
        "X-Tokens-Total": str(result["total_tokens"]),
        "X-Price-USD": str(result["price_usd"]) if result["price_usd"] is not None else "",
        "X-Version": VERSION,
    }

    logger.info(
        "create_restaurant_offer_response",
        z_id=request.z_id,
        model=result["model"],
        total_tokens=result["total_tokens"],
        price_usd=result["price_usd"],
        mock=request.mock_data,
    )

    return Response(
        content=result["image_bytes"],
        media_type="image/png",
        headers=headers,
    )
```

---

## Change 4 — `models/responses.py`

No longer used by the route. Keep the file but mark it deprecated, or delete it.
Decision: **delete** since it is fully replaced by the `Response` approach.

---

## File Summary

| File | Action |
|------|--------|
| `models/requests.py` | Add `mock_data: bool = False` + camelCase alias |
| `models/responses.py` | Delete (no longer used) |
| `service.py` | Add mock branch, extract usage, return bytes+metadata dict, remove persist helpers |
| `routes.py` | Switch to `Response`, remove `response_model`, add metadata headers |

---

## Mock File
Path: `menues/restaurant123/offer_9a8e94e6e2524de2abd34e584b315f79.png`
Status: file already exists on disk ✓

---

## Notes
- No changes to `config.py` or `create_restaurant_offer.yaml`
- `_persist_generated_image` and `_build_storage_target` helpers become dead code — remove them
- `menues_root_dir` and `menues_url_prefix` config fields are now unused — can clean up later
- Price calculation uses `gpt-image-1` token rates; if model differs, price may be inaccurate — expose raw token counts so callers can recalculate
- For mock mode, even logo download and brand color extraction are skipped (full bypass)
