# TechTitans POS — Real OpenAI End-to-End Verification

**Date:** 2026-11-07 · **Branch:** `techtitans/repair-r1` · Local commit only (no push/deploy). No API keys or customer PII in this document.

All existing AI features were verified against **real OpenAI responses** — at the adapter layer (direct, with real assets), through the live HTTP endpoints, and in the real browser UI. One confirmed integration defect was found and fixed (Whisper filename extension). No features were rebuilt.

## 1. Providers & models (as configured in `.env`, resolved live)

| Feature | Endpoint | Driver | Model | Configured |
|---|---|---|---|---|
| Repair — text draft | `POST repairs/intake/analyze-text` | openai | `gpt-4o-mini` | ✅ |
| Repair — photo vision | `POST repairs/intake/analyze-image` | openai | `gpt-4o-mini` | ✅ |
| Repair — voice transcription | `POST repairs/intake/transcribe` | openai | `whisper-1` | ✅ |
| Product — text draft | Smart Product Creator | openai | `gpt-4o-mini` | ✅ |
| Product — photo vision | Smart Product Creator | openai | `gpt-4o-mini` | ✅ |
| Product — image generation | Smart Product Creator | openai | `gpt-image-1-mini` | ✅ |

The single OpenAI key is held server-side and referenced by each feature via `${OPENAI_API_KEY}` interpolation. `gpt-image-1` is retired on this account; `gpt-image-1-mini` is used (bump to `gpt-image-2` for higher fidelity). There is **no Anthropic adapter** in this build, so Claude cannot drive any feature — text runs on `gpt-4o-mini`.

## 2. Confirmed defect found & FIXED — Whisper transcription

**Symptom:** real voice transcription failed (502 "Repair AI request failed").
**Root cause (proven at the raw API):** `OpenAiSpeechTranscriber` attached the audio as `intake-audio` with **no file extension**. OpenAI's `audio/transcriptions` endpoint rejects that:
- `filename="intake-audio"` → **HTTP 400** *"Unrecognized file format. Supported formats: flac, m4a, mp3, mp4, mpeg, mpga, oga, ogg, wav, webm"*
- `filename="intake-audio.mp3"` → **HTTP 200**, correct transcript.

**Fix:**
- `app/AI/Repair/Openai/OpenAiSpeechTranscriber.php` — attach the file with a whitelisted container extension, derived from (1) the original upload name, (2) the temp path, (3) a magic-byte sniff (EBML/WebM, Ogg, RIFF/WAV, ID3·MP3, ISO-BMFF/m4a), defaulting to `webm` (what browsers record).
- `app/Http/Controllers/Api/V1/Repairs/RepairIntakeAiController.php` — pass the original upload filename (`audio.webm`) through to the transcriber so the real browser extension is used.

**Re-verified live:** path-extension branch and context-filename branch both return the correct transcript; the end-to-end HTTP endpoint returns 200 with the spoken words. Regression test added (`test_transcribe_openai_attaches_a_recognised_file_extension`) asserting the outgoing multipart carries `filename="intake-audio.webm"` and never the bare form.

## 3. Adapter-level verification (direct calls, real assets, real OpenAI)

Exercised each adapter through the app's own service bindings with real inputs (a generated cracked-iPhone image reused for both vision analyzers; TTS→audio for Whisper):

| # | Adapter | Result | Latency |
|---|---|---|---|
| 1 | Repair draft (text) | `category=phone, brand=Apple, model="iPhone 13"` (stated), `imei=null`, `priority=high`, `promised=2026-12-01` | 3.5 s |
| 2 | Product draft (text) | `name="Anker 20000mAh power bank", brand=Anker, price=49.99 USD` | 3.8 s |
| 3 | Product image gen | real 1.4 MB PNG, `gpt-image-1-mini` | 26 s |
| 4 | Repair vision | `category=phone, brand=Apple, model=null, visible_damage="cracked screen", serial=null` | 2.3 s |
| 5 | Product vision | `product_kind=phone` (name/brand/price null — nothing invented) | 3.1 s |
| 6 | Transcription (after fix) | *"Hello. I dropped my Samsung Galaxy phone and the screen is cracked. Please assign it to Tariq."* | 1.0–1.9 s |

**No fabrication observed anywhere** — `imei`/`serial`/`price`/`model` stay null unless genuinely present; the vision model correctly *saw* the cracked screen.

## 4. Live browser verification (workspace `techtitans`, user `vmadmin2026`)

Headless Chromium against the running stack. Screenshots in `docs/techtitans/screenshots/ai-real-provider/`. **No ticket, product, customer, or device was created.**

| Section | Flow | Verdict | Evidence |
|---|---|---|---|
| 1 | Repair photo identify | ✅ PASS (UI) | brand Apple / category phone / damage "cracked screen" / colour black; model empty (not fabricated); serial shows **"Unverified — confirm"**; a manual Model correction survived a re-run. ~3.0 s. `01a–01c` |
| 2a | Repair text intake | ✅ PASS (UI) | customer=Dinesh, brand=Apple, model="iPhone 13" (stated), priority=high, promised 2026-12-01; technician/type/imei/serial/price all null; CRM match ran; reached Review without creating. ~2.9 s. `02a–02b` |
| 2b | Transcription endpoint | ✅ PASS (API) | `POST transcribe` (real token) → 200, transcript contains Samsung/Galaxy/cracked. Confirms the filename fix through the real endpoint. ~1.0 s. (API-level; headless mic injection intentionally skipped.) |
| 3 | Product photo recognition | ✅ PASS (UI) | draft `category=Phones, product_kind=phone`; name/brand/price/quantity null (in `missing`); editable; nothing persisted. ~3.6 s. `03a–03b` |
| 4 | Product image generation | ✅ PASS (UI) | real `gpt-image-1-mini` image previewed; Regenerate produced a new image; Use Image set it locally; not persisted until Save. ~11.5 s / ~11.3 s. `04a–04d` |
| 5 | Error handling & safety | ✅ PASS | empty input disables the action; a non-image upload → friendly "Use a JPG, PNG or WebP image." (no stack trace); loading spinners during multi-second calls; retry works; admin sees all controls. |

### Security (section 5f)
The browser agent scanned **194** request/response messages the browser saw for `sk-…` / `api_key` patterns in headers and bodies → **0 leaks**. The only bearer token present is the app's own session token. The OpenAI key never reaches the client — it stays server-side, as designed.

## 5. Automated tests

- **Backend** (Xdebug off): `php artisan test --filter=RepairIntakeAiTest` → **13 passed (80 assertions)**, including the new Whisper-filename regression test. Drivers are flipped per-test via `config()` and the one OpenAI test uses `Http::fake()` — **the suite never makes a real/billed call** (real-vs-mock boundary is clean). Pint ✓, PHPStan ✓ on the changed files.
- **Frontend**: `vitest run` over `src/pages/repairs`, `src/components/repairs`, `src/lib/repairs`, `src/pages/menu/SmartAddProductPage.test.tsx`, `ProductFormDrawer.aibrand.test.tsx` → **101 passed (15 files)**. These are deterministic jsdom tests with the API mocked — distinct from the real-provider verification above.

## 6. Latency & usage (observed)

Vision/text calls: **~1–4 s**. Image generation: **~11–26 s** (direct adapter run was 26 s; in-UI ~11 s). Transcription: **~1–2 s** for a ~6 s clip (Whisper `usage.seconds=6`). Token/credit usage isn't surfaced by these endpoints in-app; costs accrue on the OpenAI account per call.

## 7. Remaining limitations

- **Anthropic/Claude unused** — no adapter exists; all text runs on `gpt-4o-mini`. Building a Claude text adapter is a separate task.
- **Voice mic path** was verified at the transcription *endpoint* with a real audio file, not via live headless microphone capture (unreliable to inject real speech headlessly). The browser recording UI itself is unchanged and previously verified.
- **`gpt-image-1` retired** on this account → `gpt-image-1-mini` in use.
- Generated images are not persisted until staff explicitly save the product (confirmed).

## 8. Security / data handling

No API keys or customer PII appear in this report. The OpenAI key stays server-side (verified: 0 leaks across 194 browser-visible messages). Audio is not persisted unless `ai_repair.store_audio` is set. Nothing was pushed or deployed. `.env` (which holds the key) was **not** committed. **Reminder:** the OpenAI key was shared in chat earlier — rotate it in the OpenAI dashboard.

## 9. Local commit

- `app/AI/Repair/Openai/OpenAiSpeechTranscriber.php`, `app/Http/Controllers/Api/V1/Repairs/RepairIntakeAiController.php`, `tests/Feature/Repairs/RepairIntakeAiTest.php`, this report + screenshots → commit `093e80d` on `techtitans/repair-r1`. (No other feature code changed; the real-provider enablement lives in local `.env` only.)
