[object Object]

← back to Dw Photo Capture

Batch capture: harden against camera death + low-confidence SKU

23bf80403e735243d5347766100a354075aa4391 · 2026-09-19 09:31:21 -0700 · Steve Abrams

- Camera 'ended' → toast + drop to retake gate (no silent frozen-frame scoring)
- gateTick stops scoring when !camLive (no frozen-frame auto-captures)
- Low-confidence guessed SKU → operator must confirm; never auto-fires/auto-saves
- Add batchDone flag so auto-fire stops at the batch target

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

Files touched

Diff

commit 23bf80403e735243d5347766100a354075aa4391
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Sep 19 09:31:21 2026 -0700

    Batch capture: harden against camera death + low-confidence SKU
    
    - Camera 'ended' → toast + drop to retake gate (no silent frozen-frame scoring)
    - gateTick stops scoring when !camLive (no frozen-frame auto-captures)
    - Low-confidence guessed SKU → operator must confirm; never auto-fires/auto-saves
    - Add batchDone flag so auto-fire stops at the batch target
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_018Xadpnv4YPkFPKu6jDygAH
---
 public/batch.html | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/public/batch.html b/public/batch.html
index b9890ae..3eb399f 100644
--- a/public/batch.html
+++ b/public/batch.html
@@ -261,7 +261,8 @@ async function startCameraInto(videoEl){
   track=stream.getVideoTracks()[0];
   videoEl.srcObject=stream;
   videoEl.onloadedmetadata=()=>{ videoEl.play().catch(()=>{}); };
-  track.addEventListener('ended',()=>{ camLive=false; setCamDot(); });
+  track.addEventListener('ended',()=>{ camLive=false; setCamDot();
+    if(!$('#vShoot').hidden){ toast('⚠ Camera stopped — tap “Turn on camera”'); $('#gate').hidden=false; setState('retake','Camera stopped — tap to restart'); } });
   camLive=true; setCamDot();
   requestWake();
   return true;
@@ -396,7 +397,7 @@ $('#cSave').onclick=()=>{
 
 // ════════════════════════ SHOOT VIEW ════════════════════════
 let gateTimer=null, prevLuma=null, stillCount=0, lastFireAt=0, busy=false, backoffUntil=0;
-let lastGate=null, curSku='', skuConf=1, lastOcrKey='', ocrPending=false, batchCost=0;
+let lastGate=null, curSku='', skuConf=1, lastOcrKey='', ocrPending=false, batchCost=0, batchDone=false;
 function enterShoot(){
   show('vShoot');
   $('#hBatch').textContent=(session.vendor+' '+(session.collection||'')).trim();
@@ -422,7 +423,7 @@ function drawShootOverlayLoop(){
 // ── the gate loop: motion + quality + drift, every tickMs on the downscaled canvas ──
 function startGateLoop(){ if(gateTimer) clearInterval(gateTimer); gateTimer=setInterval(gateTick, CFG.tickMs); }
 function gateTick(){
-  if(busy||$('#vShoot').hidden) return;
+  if(busy||$('#vShoot').hidden||!camLive) return;   // camera dead → stop scoring (no frozen-frame captures)
   const v=$('#v'); const g=gateGrab(v); if(!g){ return; } lastGate=g;
   const whole=stats(g.data,null);        // whole-frame: motion detection ONLY
   const tgt=stats(g.data, CAL_TARGET);   // the sample region
@@ -462,11 +463,12 @@ function gateTick(){
   if(still && !ocrPending && !fails.includes('no sample in frame')){ maybeOcr(g); }
   // ready?
   const ready = still && fails.length===0 && (curSku||$('#skuInput').value.trim());
-  setState(ready?'ready':(fails.length?'retake':'wait'), ready?'✓ Ready — hold still':(fails.length?FRIENDLY_FAIL(fails[0]):'Put a sample in the box'));
+  const lowSku = !!curSku && skuConf<CFG.skuConfMin;   // OCR guessed but wasn't sure → operator must confirm
+  setState(ready?'ready':(fails.length?'retake':'wait'), ready?(lowSku?'✓ Ready — check the code first':'✓ Ready — hold still'):(fails.length?FRIENDLY_FAIL(fails[0]):'Put a sample in the box'));
   $('#shutter').disabled = busy;
-  // hands-free auto-fire
+  // hands-free auto-fire — never auto-save a low-confidence guessed SKU, and stop at the target
   const now=Date.now();
-  if(ready && $('#autoFire').checked && now-lastFireAt>CFG.minCadenceMs && now>backoffUntil){
+  if(ready && !lowSku && !batchDone && $('#autoFire').checked && now-lastFireAt>CFG.minCadenceMs && now>backoffUntil){
     capture('auto', fails);
   }
 }

← 721a8a6 auto-data-snapshot: 2026-09-19T08:55:30 (2 data files) — dat  ·  back to Dw Photo Capture  ·  DEVICE-TEST: batch checklist for PSku→Info two-shot flow, si 243f75a →