# TechTitans — Photo-first repair: device identification → confirmation → fault

**Goal:** after a photo is taken/uploaded, identify the device with the configured vision provider, then
have staff **confirm or correct** it **before** asking for the problem.

**Workflow:** Take/Upload Photo → **Identify Device** → **Confirm Identification** → Describe Problem →
Match Customer → Review → Create.

**Branch:** `techtitans/repair-r1`. **Commit:** frontend **`752bcd1`** (frontend-only — reuses the R1
vision endpoint + components + ticket API). Not pushed.

## 1. Exact working workflow

1. **Capture** — `RepairPhotoCapture` (take/upload, multiple photos, previews, remove, labels). Primary
   action **Identify Device** (when vision is configured) posts each photo to
   `POST /repairs/intake/analyze-image` and populates suggested **category / brand / model** + **visible
   condition**. It **never invents** model / IMEI / serial / internal specs / diagnosis / cost — unknowns
   stay empty and uncertain ones are flagged. Additional photos can be added to improve identification.
2. **Confirm** — "**Is this the correct device?**" shows the original photos + editable
   category / brand / model / colour / IMEI / serial + condition; **uncertain fields carry a "Needs
   confirmation" chip**. Actions: **Yes, continue** / **Take another photo** (device fields are directly
   editable = "Edit Device Details"). Staff corrections are **preserved** — AI never overwrites an edited
   value. Category/brand use the existing repair lookups; **brands/categories are never auto-created** from
   AI output.
3. **Problem** — only after the device is confirmed: "**What is wrong with the device?**" — describe /
   record voice / transcribe (when configured) + visible-condition notes + repair type. Recorded as the
   **reported fault, not a confirmed diagnosis**.
4. **Customer** — search by name / phone / email (CRM), or add new. Ambiguous matches are **never
   auto-selected**; a pre-selected customer is **retained**.
5. **Review & create** — compact summary (customer, photos, device, fault, type, priority, condition,
   uncertain notes) → **explicit Create**. Uses the existing R1 `POST /repairs/tickets` + device create +
   attachment upload, **idempotent** (one stable request-id → no duplicate tickets).

A sub-step bar (**Device → Problem → Customer → Review**), Back/Continue, and **Change method** keep
navigation clear; all state is lifted to the page so photos/draft survive moving between stages/methods.

## 2. Vision provider configuration status

- The R1 repair vision abstraction + OpenAI adapter + `analyze-image` endpoint are reused unchanged;
  capability is read from `GET /repairs/intake/capabilities`.
- **No vision provider is configured on this machine** (no AI keys). So live, the flow correctly shows
  **"AI recognition unavailable — AI photo recognition isn't configured on the server. Enter the device
  details below — your photos are kept"** and drops into **manual device entry without losing the photos**
  (screenshot below). **No mock result is ever presented as a real analysis.**
- **Enable real vision:** set `AI_REPAIR_VISION_DRIVER=openai` + `AI_REPAIR_VISION_API_KEY` (backend-only;
  never in logs, bundles or Git). Then "Identify Device" runs the live model and fills the confirmation.

## 3. Real AI vs mock — what was tested

| Path | How verified |
|---|---|
| **Identify → confirm (AI suggestions + uncertain chip) → edit → fault → customer → review → create** | **Deterministic mock** (vision driver `mock`, test-bound) — `NewRepairPhotoFirst.test.tsx`. |
| **Not configured → manual device entry, photos kept** | **Live** (real env, no key) — screenshot; + unit test. |
| **Real OpenAI vision** | **NOT tested** — no API key configured. The adapter/endpoint are R1, unchanged; the live call is unverified (credentials are the only blocker). |

## 4. Files changed

- `frontend/src/pages/repairs/NewRepairPage.tsx` — photo-first stage machine
  (`capture → confirm → fault → customer → review`), `identifyDevice` (vision analyze → populate device,
  never clobbering edits; 503 / vision-off → manual, photos kept), confirmation UI with "Needs
  confirmation" chips, fault/customer/review stages reusing `SmartDraftPanel` / `RepairVoiceCapture` /
  `LookupSelect` / the existing create (`submit`).
- `frontend/src/pages/repairs/repairs.css` — `.repairs-substeps`, `.repairs-confirm`, `.repairs-confirm__chip`.
- Tests: `NewRepairPhotoFirst.test.tsx` *(new)*; `NewRepairPage.test.tsx` photo-method test updated.

## 5. Tests & results

- **Frontend:** `tsc` ✓, `eslint` ✓. `vitest run src/pages/repairs src/components/repairs` →
  **12 files, 62 tests passing**. New coverage: upload → identify device; confirmation shows suggestions +
  "Needs confirmation"; **no auto-create** of category/brand from AI; correct the uncertain model →
  correction persists into the device create; fault after confirm; customer match; review → create; photo
  **persists** on the ticket (attachment POST); **single ticket** on double-click; **not-configured
  fallback** keeps the photo and allows manual identification.
- **Backend:** unchanged; existing R1 repair tests remain green.
- **Live browser (Playwright, techtitans):** `screenshots/repair-methods/photo-capture.png` (capture stage)
  and `photo-confirm.png` (confirmation stage with the honest not-configured banner + editable device
  fields + photo).

## 6. Known limitations

- **Colour** is manual — the R1 `RepairDraft` vision contract returns category/brand/model/condition, not
  colour, so no colour is suggested (kept as a manual field rather than extending the contract).
- Real OpenAI vision is unverified pending credentials (§2).
- "Retake" per photo is remove-then-add (no explicit per-photo replace).
- Tenant/permission isolation is enforced by the existing R1 middleware (`repairs.manage` + `resolve.location`
  + `reject.tenant_id`) — unchanged; only TechTitans dev data (`techpos` / `techtitans`) used; NH POS and
  WordPress untouched.

## 7. Commit hash

- Frontend: **`752bcd1`** (branch `techtitans/repair-r1`). Committed locally only — not pushed or deployed.
