← back to Dw Photo Capture
Scan fixes: (1) scrub DW's OWN header from identification — our phone 888-373-4564 (+ fragments/phone-shaped tokens) never becomes a candidate code, and 'Designer Wallcoverings' never read as the pattern name; model# is what runs down the SKU. (2) partial search now fires AUTOMATICALLY when a scan doesn't confidently match (uses the OCR'd code via back_read). (3) live search-as-you-type (debounced 300ms + race-guard) in the manual search box
5cea2c8046c2d09b3a7ba89e97c99ee5dcc0ae1b · 2026-07-08 13:04:30 -0700 · Steve Abrams
Files touched
M public/index.htmlM server.js
Diff
commit 5cea2c8046c2d09b3a7ba89e97c99ee5dcc0ae1b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jul 8 13:04:30 2026 -0700
Scan fixes: (1) scrub DW's OWN header from identification — our phone 888-373-4564 (+ fragments/phone-shaped tokens) never becomes a candidate code, and 'Designer Wallcoverings' never read as the pattern name; model# is what runs down the SKU. (2) partial search now fires AUTOMATICALLY when a scan doesn't confidently match (uses the OCR'd code via back_read). (3) live search-as-you-type (debounced 300ms + race-guard) in the manual search box
---
public/index.html | 19 +++++++++++++++----
server.js | 20 ++++++++++++++++++++
2 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/public/index.html b/public/index.html
index 40266fc..3ee09a6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1535,7 +1535,10 @@ async function fbIdentify(){
$('#fbGo').disabled=true; $('#fbNote').textContent='Identifying…'; $('#fbResult').innerHTML='';
try{
const r=await(await fetch('/api/identify-multi',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({back:_fbBack,front:_fbFront,qr:_fbQR})})).json();
- if(!r.found){ $('#fbNote').textContent='No confident match — try a clearer/closer photo.'; $('#fbResult').innerHTML=fbVisualHtml(r.visual); $('#fbGo').disabled=false; return; }
+ if(!r.found){ const oc=r.code||(r.back_read&&(r.back_read.top||((r.back_read.candidates||[])[0])))||'';
+ if(oc){ $('#fbNote').innerHTML=`No exact match for <b>${oc}</b> — searching the wallpaper file…`; wallpaperSearch(oc); }
+ else { $('#fbNote').textContent='No confident match — try a clearer/closer photo, or type part of the SKU below.'; $('#fbResult').innerHTML=fbVisualHtml(r.visual); }
+ $('#fbGo').disabled=false; return; }
const p=r.primary, conf=(r.confidence||'').replace('-','');
const via=r.source==='qr'?'🔗 QR':r.source==='code'?'📄 label code':'🎨 visual';
$('#fbNote').innerHTML=`Source: ${via}${r.corroborated?' · ✓ front+back agree':''}`;
@@ -1566,10 +1569,13 @@ async function loadWallpaperDetails(p){ const box=$('#fbWpDetails')||$('#fbResul
}
// Partial / fuzzy search of the WALLPAPER file by any part of a SKU or description. Renders a candidate
// list into `box` (defaults to #fbResult for the manual search box).
+let _wpSeq=0;
async function wallpaperSearch(q, box){ box=box||$('#fbResult'); if(!box) return;
q=(q||'').trim(); if(q.replace(/[^A-Za-z0-9]/g,'').length<3){ box.innerHTML='<div class="wp-d-hd wp-d-empty">Type at least 3 characters.</div>'; return; }
- box.innerHTML='<div class="wp-d-hd">Searching the wallpaper file…</div>';
- let r; try{ r=await(await fetch('/api/wallpaper-search?q='+encodeURIComponent(q))).json(); }catch(e){ box.innerHTML='<div class="wp-d-hd wp-d-empty">Search failed.</div>'; return; }
+ const seq=++_wpSeq; // race guard: only the latest query renders
+ box.innerHTML=`<div class="wp-d-hd">Searching the wallpaper file for “${q}”…</div>`;
+ let r; try{ r=await(await fetch('/api/wallpaper-search?q='+encodeURIComponent(q))).json(); }catch(e){ if(seq===_wpSeq) box.innerHTML='<div class="wp-d-hd wp-d-empty">Search failed.</div>'; return; }
+ if(seq!==_wpSeq) return; // a newer keystroke already superseded this
const cands=(r&&r.candidates)||[];
if(!cands.length){ box.innerHTML=`<div class="wp-d-hd wp-d-empty">No wallpaper-file match for “${q}”.</div>`; return; }
const rowHtml=c=>{ const bits=[c.width&&('W '+c.width),c.repeat&&('R '+c.repeat),c.retail&&('$'+c.retail),c.supplier,c.line].filter(Boolean).join(' · ');
@@ -1621,7 +1627,12 @@ $('#fbBackInput').addEventListener('change',e=>fbRead(e.target.files[0],true));
$('#fbFrontInput').addEventListener('change',e=>fbRead(e.target.files[0],false));
$('#fbGo').addEventListener('click',fbIdentify);
$('#fbSearchBtn').addEventListener('click',()=>wallpaperSearch($('#fbSearchInput').value));
-$('#fbSearchInput').addEventListener('keydown',e=>{ if(e.key==='Enter'){ e.preventDefault(); wallpaperSearch(e.target.value); } });
+$('#fbSearchInput').addEventListener('keydown',e=>{ if(e.key==='Enter'){ e.preventDefault(); clearTimeout(_wpSearchT); wallpaperSearch(e.target.value); } });
+// live search-as-you-type: debounced so we don't fire a FileMaker scan on every keystroke
+let _wpSearchT=null;
+$('#fbSearchInput').addEventListener('input',e=>{ const v=e.target.value; clearTimeout(_wpSearchT);
+ if(v.replace(/[^A-Za-z0-9]/g,'').length<3){ if(!v) $('#fbResult').innerHTML=''; return; }
+ _wpSearchT=setTimeout(()=>wallpaperSearch(v),300); });
// ── Add / Update item: capture up to 10 photos + 6 videos → Shopify (draft-create OR live-update) ──
const MAX_PHOTOS=10, MAX_VIDEOS=6;
diff --git a/server.js b/server.js
index 9dc0227..d6a8761 100644
--- a/server.js
+++ b/server.js
@@ -353,6 +353,23 @@ const VENDOR_LEX = [
const STRONG_PFX = /^(GRS-|WD[A-Z]*\d|DWNAT|DW[A-Z]{2})/; // self-evidently a SKU, no brand needed
const CODE_TOKEN = /[A-Z0-9][A-Z0-9-]{2,13}/g; // candidate token shape
+// Designer Wallcoverings' OWN header (company name / phone / website) is printed on every DW sample
+// sticker — it is NEVER the product identity. The vendor MODEL # is what runs down the SKU, so scrub
+// our own branding first: our phone (any punctuation form + fragments), phone-shaped tokens, and the
+// DW name/site lines. Configurable via DW_PHONE.
+const DW_PHONE = (process.env.DW_PHONE || '8883734564'); // 888-373-4564
+const DW_BRAND_RE = /designer\s*wall\s*cover|designerwallcoverings|wallcoverings?\.com|\bDWC\b/i;
+const PHONE_TOKEN_RE = /^(1?[-.\s(]*8(?:00|88|77|66|55)[-.\s)]*\d{3}[-.\s]*\d{4}|\d{10})$/; // toll-free / 10-digit
+function isDwNoise(tok, dwLabel) {
+ const s = String(tok); const d = s.replace(/\D/g, '');
+ if (!d) return false;
+ if (d === DW_PHONE) return true; // our exact phone
+ if (d.length >= 7 && DW_PHONE.includes(d)) return true; // a 7+ digit fragment of it (373-4564)
+ if (PHONE_TOKEN_RE.test(s)) return true; // any 10-digit / toll-free number
+ if (dwLabel && /^\d+$/.test(s) && d.length >= 3 && DW_PHONE.includes(d)) return true; // phone fragment on OUR label
+ return false;
+}
+
// ── Field vocabulary: classify a label read into sku# / model# / name / color ──
// Common interior-design colorway names + basic colors, so a printed color name is caught.
const COLOR_WORDS = 'oatmeal|alabaster|greige|celadon|ecru|taupe|linen|flax|dove|pewter|charcoal|ebony|onyx|ivory|cream|bone|chalk|parchment|sand|camel|fawn|mushroom|mocha|espresso|chocolate|walnut|honey|amber|ochre|mustard|saffron|terracotta|rust|clay|brick|coral|blush|rose|blossom|mauve|plum|aubergine|burgundy|claret|scarlet|crimson|cardinal|sage|celery|moss|fern|olive|forest|emerald|jade|hunter|teal|aqua|turquoise|peacock|cerulean|cobalt|navy|indigo|denim|slate|sky|powder|periwinkle|lavender|lilac|violet|amethyst|gold|brass|bronze|copper|champagne|platinum|silver|graphite|smoke|stone|pearl|white|black|grey|gray|beige|blue|green|red|yellow|orange|purple|pink|brown|tan';
@@ -386,6 +403,7 @@ function classifyFields(rows, a) {
name = lines
.filter(l => /[A-Za-z]/.test(l) && !/\d{3,}/.test(l) && l.replace(/[^A-Za-z]/g, '').length >= 4)
.filter(l => !(vfirst && l.toUpperCase().includes(vfirst)))
+ .filter(l => !DW_BRAND_RE.test(l)) // never read OUR OWN name as the pattern
.filter(l => !(color && l.toLowerCase() === color.toLowerCase())) // drop a line that's ONLY the color
.filter(l => !COLOR_LABEL_RE.test(l) && !/^(colou?r|pattern|design|collection|sku|item|model|mfr)\b/i.test(l))
.filter(l => !(sku && l.toUpperCase().replace(/\s+/g, '').includes(sku)))
@@ -433,11 +451,13 @@ function analyzeOcr(rows) {
Math.min(t.length, 12);
// CODE NUMBERS ONLY — alnum tokens holding a digit (WDW2310, GRS-26220, 5255-16). Plain
// names and marketing prose are intentionally ignored: scan the code, never the text.
+ const dwLabel = DW_BRAND_RE.test(text) || text.replace(/\D/g, '').includes(DW_PHONE); // it's OUR sticker
const seen = new Set(); const codes = [];
for (const r of rows) {
if (r.bc) continue;
for (const t of (r.t.toUpperCase().match(CODE_TOKEN) || [])) {
if (!/\d/.test(t) || t.length < 4 || seen.has(t)) continue; // must hold a digit, >=4 chars
+ if (isDwNoise(t, dwLabel)) continue; // our phone / phone-shaped → never a SKU
seen.add(t); codes.push({ t, h: r.h, s: skuScore(t) });
}
}
← 8837b5f Scan: partial/fuzzy wallpaper-file lookup — new /api/wallpap
·
back to Dw Photo Capture
·
Scan lookup driven by the OCR'd label code: a VISUAL-only id a7f6374 →