[object Object]

← back to Dw Photo Capture

batch mode: OCR falls back to manual after a timeout (no more 30s hang)

70ed167afa2b7886a0d5d7d2e52ceacd83e68db8 · 2026-09-18 13:20:49 -0700 · Steve Abrams

Local dev defaulted OCR to macvision → local Ollama, now retired, so /api/identify
hung ~30s per sample. Add a 7s AbortController timeout on the auto-SKU call; on
timeout/failure it degrades to manual SKU entry instead of freezing the UI. Backend-
agnostic — when a real vision backend is wired, OCR just starts succeeding in-window.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uns3k4K15WBcFJNr3GcUd3

Files touched

Diff

commit 70ed167afa2b7886a0d5d7d2e52ceacd83e68db8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Sep 18 13:20:49 2026 -0700

    batch mode: OCR falls back to manual after a timeout (no more 30s hang)
    
    Local dev defaulted OCR to macvision → local Ollama, now retired, so /api/identify
    hung ~30s per sample. Add a 7s AbortController timeout on the auto-SKU call; on
    timeout/failure it degrades to manual SKU entry instead of freezing the UI. Backend-
    agnostic — when a real vision backend is wired, OCR just starts succeeding in-window.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01Uns3k4K15WBcFJNr3GcUd3
---
 public/batch.html | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/public/batch.html b/public/batch.html
index 0129262..e2edce3 100644
--- a/public/batch.html
+++ b/public/batch.html
@@ -202,7 +202,8 @@ const CFG = {
   driftLumaMax: 38,        // Δluma vs cal that forces re-cal
   driftWBMax: 0.16,        // per-channel WB ratio drift vs cal that forces re-cal
   skuConfMin: 0.55,        // OCR confidence below this → keep, but flag for manual confirm
-  maxTries: 6              // upload attempts before a shot is PARKED as failed (never dropped)
+  maxTries: 6,             // upload attempts before a shot is PARKED as failed (never dropped)
+  ocrTimeoutMs: 7000       // auto-SKU OCR: give up after this and fall back to manual entry (never hang)
 };
 
 // ── Session + calibration state (persisted) ──
@@ -444,13 +445,17 @@ async function maybeOcr(g){
     const small=document.createElement('canvas'); small.width=Math.round(cnv.width*scale); small.height=Math.round(cnv.height*scale);
     small.getContext('2d').drawImage(cnv,0,0,small.width,small.height);
     const dataUrl=small.toDataURL('image/jpeg',0.8);
-    const r=await fetch('/api/identify',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({dataUrl})});
-    const j=await r.json();
-    batchCost += (j.cost_usd||0.0006);           // $ cost tracking (Gemini ≈ $0.0006/call)
+    const ac=new AbortController(); const to=setTimeout(()=>ac.abort(), CFG.ocrTimeoutMs);
+    let j;
+    try{
+      const r=await fetch('/api/identify',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({dataUrl}),signal:ac.signal});
+      j=await r.json();
+    } finally { clearTimeout(to); }
+    batchCost += (j.cost_usd||0);                // $ cost (0 = local; external engines report their own)
     if(j.code){ curSku=j.code; skuConf=(j.confidence??0.6); $('#skuInput').value=curSku; }
     else { skuConf=0; }
     setSkuChip();
-  }catch(e){ setChip('#dSku','err'); $('#skuTxt').textContent='err'; }
+  }catch(e){ setChip('#dSku','warn'); $('#skuTxt').textContent='type SKU'; }   // OCR slow/unavailable → manual entry
   ocrPending=false;
 }
 function setSkuChip(){

← e43af57 auto-data-snapshot: 2026-09-18T11:01:55 (1 data files) — dat  ·  back to Dw Photo Capture  ·  Photo capture: vendor-first new-SKU creation across Shopify 0a65a03 →