# WeStore WMS – AI Layer Design

The AI layer is an **embedded, governed advisory system**. It reads WMS data and events, produces typed recommendations, and the same domain services that execute human actions execute approved recommendations. It never has its own write path into inventory.

---

## 1. Architecture

```
Domain events (outbox) ──► AI triggers ──► ai_jobs (queue) ──► AiGateway ──► Provider (Claude)
                                                                     │
                                                                     ▼
                                                           ai_invocations (log, cost)
                                                                     │
                                                                     ▼
                                                           ai_recommendations
                                                                     │
                                       ┌─────────────────────────────┼──────────────────────┐
                                       ▼                             ▼                      ▼
                               mode = advise                   mode = auto            mode = off
                          (shown to user, awaits        (applied via domain       (job skipped)
                           accept/reject)                service if confidence
                                                         ≥ threshold, else advise)
```

Components (`app/Domain/AI`):

| Component | Responsibility |
|-----------|---------------|
| `AiGateway` | Single entry point. Resolves policy, builds prompt from registry, calls provider, validates JSON schema, records invocation, enforces cost cap. |
| `Providers/AiProvider` (interface) | `complete(AiRequest): AiResponse`. Implementations: `ClaudeProvider` (Messages API over HTTP, tool-use for structured output), `FakeProvider` (deterministic, used in tests). |
| `Prompts/*` | One class per use case: `system()`, `user(array $context)`, `schema()`, `version`. Versioned in code so a recommendation can always be tied to the exact prompt that produced it. |
| `UseCases/*` | Builds context from WMS data, calls gateway, maps output to a `ProposedAction` DTO, stores `ai_recommendations`. |
| `Policies/AiPolicyResolver` | `ai_policies` per client × use case with warehouse override. |
| `RecommendationService` | `accept()`, `reject()`, `autoApply()`; executes `ProposedAction` through the owning domain service; records outcome back on the recommendation. |
| `UsageMeter` | Aggregates tokens/cost per client/day; blocks calls when `daily_cost_cap` is reached. |

---

## 2. Governance rules

1. **No autonomous inventory writes.** `autoApply()` is the only automatic path, it is gated by policy + confidence, and it calls the same services with `actor = ai:{use_case}` so the audit trail shows the AI as actor.
2. **Schema or nothing.** Provider output must validate against the use case's JSON schema. Invalid output → invocation status `invalid_output`, job fails, no recommendation.
3. **Every call is logged** (`ai_invocations`): client, use case, prompt version, model, input hash, input/output tokens, cost (from `config/ai.php` price table), latency, status.
4. **Cost caps** per client per day; a capped client silently degrades to `off` for the rest of the day and an ops alert is raised.
5. **Human override always wins.** A rejected recommendation records `rejected_reason`; the use case can learn from rejections (fed back as context in later prompts for the same client).
6. **PII minimisation**: consignee names/addresses are never sent to the provider except in the address-validation use case, which is opt-in per client.
7. **Retention**: raw prompt/response bodies are purged after `ai.retention_days` (default 90); metrics stay.

---

## 3. Use cases shipped in v1

| Use case | Trigger | Context sent | Output (schema) | Mode default |
|----------|---------|--------------|-----------------|--------------|
| `asn_extraction` | Document uploaded to a draft ASN (PDF/image/XLSX text) | Document text, client product catalogue (SKU + description + barcodes), UoM list | `{supplier_ref, expected_arrival, lines:[{sku_match, description, qty, uom, lot, expiry, confidence}]}` | advise |
| `putaway_advisor` | Receipt line QC passed / putaway task created | Product attributes, velocity class, candidate locations (rules engine output, max 25) with capacity/affinity data, current congestion | `{ranked:[{location_code, score, reasons[]}], confidence}` | advise |
| `slotting_optimizer` | Scheduled weekly per warehouse | 90-day pick velocity by SKU, current slot, location walk sequence, cube | `{moves:[{sku, from, to, expected_pick_time_saving_pct, reason}], confidence}` | advise |
| `exception_triage` | Short pick, receiving discrepancy, count variance above tolerance | The exception, last 30 days of similar exceptions for the SKU/location, recent transactions | `{category, probable_root_cause, recommended_action, draft_client_message, confidence}` | advise |
| `replenishment_forecast` | Nightly | Daily pick history per SKU (90 days), open orders, current min/max | `{sku, forecast_daily_demand, suggested_min, suggested_max, confidence}` | advise (auto allowed) |
| `nl_query` | User question in ops console | Question, catalogue of allowed views with column descriptions | `{sql, explanation, confidence}` → validated (SELECT-only, views allow-list, LIMIT injected) → executed as `wms_readonly` | advise |
| `labor_forecast` | Nightly | Next 7 days expected inbound/outbound volume, labor standards, roster | `{day, required_hours_by_activity, confidence}` | advise |
| `count_anomaly` | Cycle count posted | Variance history by location/SKU/user | `{anomalies:[{scope, pattern, risk, suggested_check}], confidence}` | advise |

**What stays deterministic (not AI):** allocation, putaway rules baseline, replenishment arithmetic, billing, state transitions. AI reranks or explains; it does not replace the rules.

---

## 4. Provider contract

```php
interface AiProvider {
    public function complete(AiRequest $request): AiResponse;
}

final class AiRequest { string $model; string $system; array $messages; array $jsonSchema; int $maxTokens; float $temperature; }
final class AiResponse { array $json; int $inputTokens; int $outputTokens; string $model; string $stopReason; string $raw; }
```

`ClaudeProvider` uses the Messages API with a single tool whose `input_schema` is the use case schema and `tool_choice` forcing that tool, so the response is structured JSON. Timeouts, retries (429/5xx with backoff) and circuit breaking (`ai.circuit.failure_threshold`) are in the provider.

---

## 5. Data model

- `ai_policies` (client_id?, warehouse_id?, use_case, mode, auto_threshold, daily_cost_cap_usd, settings jsonb)
- `ai_invocations` (use_case, prompt_version, model, client_id?, warehouse_id?, input_hash, input_tokens, output_tokens, cost_usd, latency_ms, status, error, request_body?, response_body?, created_at)
- `ai_recommendations` (use_case, invocation_id, client_id?, warehouse_id?, subject_type, subject_id, proposed_action jsonb, confidence, rationale, status: pending|accepted|rejected|auto_applied|expired|failed, decided_by, decided_at, decision_note, applied_result jsonb, expires_at)
- `ai_usage_daily` (client_id?, use_case, date, calls, input_tokens, output_tokens, cost_usd)

---

## 6. Failure behaviour

| Failure | Behaviour |
|---------|-----------|
| Provider down / timeout | Job retried 3× with backoff; then recommendation not produced; ops continue on rules. Circuit opens for 5 min after 5 consecutive failures. |
| Invalid schema output | Invocation `invalid_output`; one retry with the validation error appended; then give up. |
| Cost cap reached | Policy degrades to `off` for the day; alert. |
| Recommendation applied but domain service rejects (e.g., location now full) | Recommendation `failed` with the domain error; nothing partially written (service is transactional). |
| Stale recommendation (subject changed since) | `RecommendationService` checks `subject_version` before applying; mismatch → `expired`. |

---

## 7. Phase 2 candidates

Vision-based receiving (photo of pallet label → ASN match), voice picking prompts, carrier selection optimisation, client-facing chatbot over the client portal, automatic rate-card anomaly detection in billing, dock scheduling optimisation.
