# RBS REGAL — Phase A3 Implementation Proof
## P2: Photo → Auto Product Create (inside existing Floating AI only)

**Date:** 2026-06-14
**Phase Scope:** Floating AI camera flow gets a confirm popup with editable AI-detected fields + duplicate detection + opening stock + photo thumbnail. NO new module, NO UI redesign.

---

## 1. Files Changed

| Path | Action | Purpose |
|------|--------|---------|
| `/app/frontend/src/components/AutoProductCreateDialog.jsx` | CREATED (sub-component of Floating AI) | Editable confirm popup; auto-probes duplicates; saves via POST /api/items |
| `/app/frontend/src/components/AiFloatingChat.jsx` | MODIFIED | `create_draft` action now opens the dialog (was a blind POST); mounts `<AutoProductCreateDialog>` at end of render tree |
| `/app/frontend/src/components/CameraCapture.jsx` | MODIFIED | `lastSnapRef` stores the captured dataUrl so `fireAction` can forward `image` to the dialog |
| `/app/backend/routes.py` | MODIFIED | `POST /items` adds barcode-duplicate guard returning 409 with structured detail; NEW `GET /items/check-duplicate` endpoint |
| `/app/backend/ai_assistant.py` | MODIFIED | `/ai/vision-identify` system prompt now requests `mrp`, `barcode_value`, `gst_rate_visible`, `hsn_visible`, `net_quantity` |
| `/app/backend/tests/test_auto_product_create.py` | CREATED | 5 pytest cases — all GREEN |

---

## 2. Actual Code Proof

### 2.1 Backend — Barcode-duplicate guard with structured 409
```python
# /app/backend/routes.py
@router.post("/items")
async def create_item(payload: ItemIn, request: Request, company_id: str = Query(...), user=Depends(require_admin)):
    db = request.app.state.db
    if payload.barcode:
        existing_bc = await db.items.find_one(
            {"company_id": company_id, "barcode": payload.barcode},
            {"_id": 1, "name": 1, "current_stock": 1, "base_unit": 1},
        )
        if existing_bc:
            raise HTTPException(409, {
                "duplicate_kind": "barcode",
                "existing_id": str(existing_bc["_id"]),
                "existing_name": existing_bc.get("name"),
                "current_stock": existing_bc.get("current_stock", 0),
                "base_unit": existing_bc.get("base_unit", "PCS"),
                "message": f"Item with barcode {payload.barcode} already exists: {existing_bc.get('name')}. Update stock instead?",
            })
    ...
```

### 2.2 Backend — Duplicate-probe endpoint
```python
@router.get("/items/check-duplicate")
async def check_item_duplicate(request, company_id, barcode="", name="", user=Depends(require_admin)):
    db = request.app.state.db
    out = {"barcode_match": None, "name_match": None}
    if barcode.strip():
        bc = await db.items.find_one({"company_id": company_id, "barcode": barcode.strip()}, {…})
        if bc: out["barcode_match"] = _ser(bc)
    if name.strip():
        nm = await db.items.find_one({"company_id": company_id, "name": {"$regex": _re.escape(name.strip()), "$options": "i"}}, {…})
        if nm: out["name_match"] = _ser(nm)
    return out
```

### 2.3 Backend — Vision prompt enriched
```python
# /app/backend/ai_assistant.py — system prompt for /vision-identify
"Read visible text on the package — brand, MRP, barcode digits, tax labels, net weight, manufacturer — and include whatever you can see."
{
  "name", "category", "purpose", "unit", "related", "brand",
  "mrp": 0,            # Numeric, INR, strip ₹/Rs/commas
  "barcode_visible": false,
  "barcode_value": "",  # Digits under the barcode if legible
  "gst_rate_visible": null,  # 0/5/12/18/28 if printed, else null
  "hsn_visible": "",
  "net_quantity": "",   # "500 g" / "1 L"
  "confidence": 0.0
}
```

### 2.4 Frontend — AutoProductCreateDialog (the new confirm popup)
```jsx
// Initial form derived directly from the vision payload
function buildInitialForm(vision, imageDataUrl) {
    return {
        name: vision.name && vision.name !== "Unknown" ? vision.name : "",
        brand: vision.brand || "",
        barcode: vision.barcode_value || "",
        hsn: vision.hsn_visible || "",
        unit: (vision.unit || "PCS").toUpperCase(),
        category: vision.category || "General",
        gst_rate: vision.gst_rate_visible ?? 18,
        mrp: num(vision.mrp),
        sale_price: num(vision.mrp),
        purchase_price: 0,
        opening_stock: 0,
        photo_url: imageDataUrl || "",
        code: vision.barcode_value || "",
    };
}

// Auto-probe duplicates on open + on barcode/name change
useEffect(() => {
    if (!open || !companyId) return;
    const probe = async () => {
        const params = { company_id: companyId };
        if (form.barcode) params.barcode = form.barcode;
        if (form.name) params.name = form.name;
        const { data } = await api.get("/items/check-duplicate", { params });
        if (data?.barcode_match) setDuplicate({ kind: "barcode", ...data.barcode_match });
        else if (data?.name_match) setDuplicate({ kind: "name", ...data.name_match });
        else setDuplicate(null);
    };
    probe();
}, [open, form.barcode, form.name, companyId]);

// On save — respects backend 409 by parsing structured detail
const handleSave = async () => {
    try {
        const { data: created } = await api.post("/items", body, { params: { company_id: companyId } });
        toast.success(`Saved: ${created.name}`);
        onSaved?.(created);
    } catch (e) {
        if (e?.response?.status === 409 && err?.duplicate_kind === "barcode") {
            setDuplicate({ kind: "barcode", existing_id: err.existing_id, existing_name: err.existing_name, ... });
        }
    }
};
```

### 2.5 Frontend — Wiring in AiFloatingChat
```jsx
// State
const [productDialog, setProductDialog] = useState(null);  // { vision, imageDataUrl }

// handleCameraResult — opens dialog instead of blind POST
if (result.kind === "create_draft") {
    setProductDialog({ vision: result.payload, imageDataUrl: result.image });
    return;
}

// Render
<AutoProductCreateDialog
    open={!!productDialog}
    vision={productDialog?.vision}
    imageDataUrl={productDialog?.imageDataUrl}
    companyId={activeId}
    onClose={() => setProductDialog(null)}
    onSaved={(created) => setMessages((m) => [...m, { role: "assistant", text: `✅ **${created.name}** save ho gaya!` }])}
/>
```

---

## 3. Before vs After

| Capability | Before | After |
|-----------|--------|-------|
| AI camera → "Draft Item" CTA | Blind POST to /items with skeleton fields (name + unit only) | Editable popup showing AI-extracted **Name / Brand / Barcode / MRP / Sale Price / Category / Unit / GST / HSN / Opening Stock** + the captured photo as thumbnail |
| Duplicate detection | None — could create 2 items with same barcode silently | **Auto-probe** on open and on every barcode/name edit. Banner shows existing item + current stock + "Open existing item →" link. Server enforces 409 as a hard backstop. |
| Photo as thumbnail | Photo discarded after identify | Captured base64 dataUrl flows through `lastSnapRef → fireAction → setProductDialog → POST /items photo_url` |
| Opening stock | Not capturable from scan | Editable field; defaults to 0; persists to `current_stock` on save |
| Server contract | Generic 500/400 on duplicate | Structured **409** with `duplicate_kind, existing_id, existing_name, current_stock, base_unit, message` — dialog renders the banner accordingly |
| Vision prompt | Asked for brand only | Now also asks for **MRP, barcode digits, GST %, HSN, net quantity** — populates the dialog automatically |

---

## 4. Test Evidence

### 4.1 Pytest — full backend regression
```
$ cd /app/backend && python -m pytest tests/test_smoke.py tests/test_vision_identify.py tests/test_auto_product_create.py -q
..............................                                           [100%]
30 passed in 5.25s
```
- 21 smoke + 4 vision + **5 new auto-product-create** = **30/30 PASS**

### 4.2 Live API curl — exercise of the new endpoints
| Endpoint | Status | Body excerpt |
|----------|--------|--------------|
| `GET /api/items/check-duplicate?company_id=…&barcode=AUTOTEST<ts>` | 200 | `{barcode_match: {…name…}, name_match: null}` |
| `POST /api/items` (unique barcode) | 200 | `{id, name, barcode, mrp:250, photo_url:"data:image/png;base64,…", current_stock:5}` |
| `POST /api/items` (duplicate barcode) | **409** | `{detail:{duplicate_kind:"barcode", existing_id, existing_name, current_stock, base_unit, message}}` |
| `POST /api/ai/vision-identify` | 200 | Schema includes `mrp, barcode_value, gst_rate_visible, hsn_visible, net_quantity` |

### 4.3 testing-agent iteration_24 verdict (excerpt)
> "BACKEND: All 30 pytest cases GREEN … FRONTEND: Live verified all required testids exist … AutoProductCreateDialog is mounted in AiFloatingChat (open={!!productDialog}) and all 11 required testids exist … Save button correctly labels 'Resolve duplicate first' when duplicate.kind==='barcode' … REGRESSION: /sync still shows offline-prefs-card + trash-card."
> **`success_rate.backend: 30/30 (100%)`**
> **`success_rate.frontend: 7/7 (100%)`**
> **`retest_needed: false`** · **`action_items: ["No action required for the P2 Auto-Product-Create feature"]`**

### 4.4 data-testid contract (14 testids)
```
auto-product-dialog · auto-product-name · auto-product-brand · auto-product-barcode
auto-product-mrp · auto-product-sale-price · auto-product-category · auto-product-unit
auto-product-gst · auto-product-hsn · auto-product-opening-stock
auto-product-save · auto-product-cancel · auto-product-image · auto-product-duplicate-banner
```

---

## 5. Validation Matrix

| User Requirement | Status |
|------------------|--------|
| Camera se product photo le | ✅ existing CameraCapture flow |
| AI pehchaane: Product Name | ✅ vision prompt + dialog `auto-product-name` |
| AI pehchaane: Brand | ✅ `auto-product-brand` |
| AI pehchaane: MRP | ✅ vision prompt asks for MRP, dialog `auto-product-mrp` |
| AI pehchaane: Barcode (agar dikhe) | ✅ vision `barcode_value`, dialog `auto-product-barcode` |
| AI pehchaane: Category | ✅ `auto-product-category` |
| AI pehchaane: Unit | ✅ `auto-product-unit` |
| AI pehchaane: Tax/GST | ✅ vision `gst_rate_visible`, dialog `auto-product-gst` |
| AI pehchaane: Image | ✅ `auto-product-image` thumbnail bound to captured dataUrl |
| **Confirm popup** dikhe | ✅ `auto-product-dialog` modal |
| Save → Item module mein auto add | ✅ POST /api/items |
| Save → Stock module mein auto add | ✅ `opening_stock` field → `current_stock` |
| Duplicate item detect | ✅ client-side probe + server 409 + banner |
| No new module / no UI redesign | ✅ dialog is a child of AiFloatingChat |

---

## 6. Coverage Summary

| Metric | Result |
|--------|--------|
| Files modified | 3 (AiFloatingChat, CameraCapture, routes.py + ai_assistant.py) |
| Files created | 2 (AutoProductCreateDialog.jsx, test_auto_product_create.py) |
| New API endpoints | 1 (GET /items/check-duplicate); POST /items contract hardened (409) |
| New backend tests | 5 (all green) |
| New data-testids | 15 |
| Backward compatibility | 100% — existing /items POST clients without `barcode` are unaffected; old vision callers still get all original keys |

**Status: PASS** — P2 Photo → Auto Product Create implementation verified end-to-end.
