# TechTitans — Repair photo recognition: label extraction + provider status

**Branch:** `techtitans/repair-r1`. Scope: diagnose why uploaded device/label photos produce no
identification, and improve label extraction (brand/model/colour/serial) for when a provider is keyed.

## 1. Root cause of "AI recognition unavailable"

**It is a configuration state, not a bug.** The repair vision capability is **not configured** on this
machine:

- `.env` has **no** `AI_REPAIR_VISION_*` / `OPENAI_API_KEY` (checked by name only — no value printed); OS
  env unset.
- `config/ai_repair.php` → `vision.driver` defaults to **`none`**.
- Live `GET /api/v1/repairs/intake/capabilities` → `vision.configured = false, driver = none`.
- Live `POST /api/v1/repairs/intake/analyze-image` → **503 `ai_repair_vision_not_configured`** (fails
  closed; no fabricated result).

So the UI correctly shows **"AI recognition unavailable — AI photo recognition isn't configured on the
server"** and drops into manual entry with the photo kept. The fields stay empty because no provider ran.

## 2. Was a provider configured? No — real recognition is BLOCKED

No API key is available, so **real device/label recognition could not be tested** and is **not claimed to
work**. The integration path is verified sound (adapter request/coercion + mock + fake-HTTP unit test),
but live recognition quality is **unverified** pending credentials (§6 to enable).

## 3. Improvement implemented — manufacturer-label extraction (Task 3/4)

The vision contract previously returned only category/brand/model/condition. It now also reads **visible
label/sticker attributes** and populates the confirmation screen:

- **New fields in the vision contract:** `color`, `serial_no` (in addition to category/brand/model/
  visible_damage/condition).
- **OpenAI prompt** updated to handle **whole-device AND label/spec-sticker photos** — read printed brand,
  model number, colour and serial.
- **Serial safety:** a serial is output **only when clearly printed and fully legible**, is **never
  guessed**, and is **ALWAYS forced into `uncertain`** by the backend coercer (`RepairDraftShape::
  coerceVision`) — i.e. treated as **unverified until staff confirm**. The confirmation UI shows an
  **"Unverified — confirm"** chip on the Serial field and "Needs confirmation" on Colour when flagged.
- **Still never represented:** `imei`, `diagnosis`, `price`, `technician`, `warranty` — the coercer
  **drops** them even if a provider returns them (proven by test).
- **No auto-create:** category/brand remain staff-confirmed against the existing lookups; AI never creates
  a brand/category. Manual corrections are preserved (AI never overwrites an edited value).
- Confirmed colour/serial flow into the existing device create (`POST /customers/:id/devices`) unchanged.

## 4. Files changed

Backend:
- `app/AI/Repair/Openai/RepairDraftShape.php` — `coerceVision` adds `color` + `serial_no`; forces
  `serial_no` into `uncertain`.
- `app/AI/Repair/Openai/OpenAiRepairImageAnalyzer.php` — prompt reads labels; adds `color`/`serial_no`
  keys with never-guess + always-unverified serial rules.
- `app/AI/Repair/Contracts/RepairImageAnalyzer.php` — contract docblock (adds color/serial_no; imei
  stays forbidden).
- `app/AI/Repair/Mock/MockRepairImageAnalyzer.php` — returns `color`, `serial_no: null` (never invents a
  serial).
- `tests/Feature/Repairs/RepairIntakeAiTest.php` — vision test updated for the new contract; **new**
  `Http::fake` test proving the real adapter extracts Dell-label brand/model/colour/serial, flags the
  serial unverified, and strips imei/diagnosis/price.

Frontend:
- `src/lib/api/repairs.ts` — `RepairDraft` gains optional `color` + `serial_no`.
- `src/pages/repairs/NewRepairPage.tsx` — `mergeDraft` + `identifyDevice` carry/populate colour & serial
  (without clobbering edits); confirm UI shows chips on Colour/Serial.
- `src/pages/repairs/NewRepairPhotoFirst.test.tsx` — **new** label-photo test (colour + serial populate,
  serial flagged unverified, values persist to the device create).
- `e2e/repair-photo-recognition.spec.ts` — **new** live screenshot of the honest not-configured fallback.

## 5. Tests & verification

- **Backend** `php artisan test --filter=RepairIntakeAiTest` → **12 passed (76 assertions)**, incl. the new
  `vision openai extracts label fields and strips forbidden` (`Http::fake`, no real key/network).
- **Frontend** `tsc` ✓, `eslint` ✓, `vitest run src/pages/repairs src/components/repairs` → **63 passed**
  (incl. the new label-extraction test).
- **Live (real servers, techtitans):** `capabilities.vision.configured=false`; `analyze-image` → 503;
  Playwright `repair-photo-recognition.spec.ts` passed — screenshot
  `frontend/test-results/repair-photo/confirm-not-configured.png` shows the honest banner, the kept photo,
  and the editable Category/Brand/Model/Colour/IMEI/Serial fields.
- **Real-provider recognition** (Dell label, laptop, phone, unreadable label, no-model, brand/category
  match, serial confirmation): **NOT run — blocked, no API key.** The deterministic + fake-HTTP tests are
  clearly labelled as such and are not a substitute for real-provider verification.

## 6. Enable + verify real recognition (exact settings)

`backend/.env` (server-side only — never commit the key):

```
AI_REPAIR_VISION_DRIVER=openai
AI_REPAIR_VISION_API_KEY=sk-...          # key with access to a vision-capable model
# optional: AI_REPAIR_VISION_MODEL=gpt-4o-mini  (default; must be vision-capable)
```

Then `php artisan config:clear` and restart `php artisan serve --host=127.0.0.1 --port=8010`.
`capabilities.vision.configured` flips to `true`; "Identify Device" runs the live model. Provide a
non-sensitive Dell-label photo and real extraction (brand/model/colour/serial) can be verified end-to-end.

## 7. Remaining uncertain / limitations

- Real recognition accuracy unverified (no key) — the single blocker.
- `product_family` from Task 3 is folded into `model` (no separate field); IMEI remains manual-only
  (never AI-extracted) by design.
- Extracted serials are **unverified** by definition — staff must confirm before they are trusted.
- Credentials stay server-side; none present in `.env`, logs, bundles or Git. Not pushed or deployed.
