[object Object]

← back to Designer Wallcoverings

color cleaner v2: (1) tri-state safety — removal now requires explicit COLOR=NO; ambiguous/empty verdicts KEEP+retry, never remove (guards the false-remove class of bug); (2) bake exact collection handles into COLOR_COLLECTIONS (incl pink, was null) + resolver prefers cfg.handle — robust on any store, handles are inconsistent (purple/black/coral vs blue-wallpaper-collection)

df60b0ab88d42fa3df18807b43287635aaf7295f · 2026-06-12 15:22:52 -0700 · Steve Abrams

Files touched

Diff

commit df60b0ab88d42fa3df18807b43287635aaf7295f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jun 12 15:22:52 2026 -0700

    color cleaner v2: (1) tri-state safety — removal now requires explicit COLOR=NO; ambiguous/empty verdicts KEEP+retry, never remove (guards the false-remove class of bug); (2) bake exact collection handles into COLOR_COLLECTIONS (incl pink, was null) + resolver prefers cfg.handle — robust on any store, handles are inconsistent (purple/black/coral vs blue-wallpaper-collection)
---
 DW-Programming/color-collection-cleaner-v2.js | 45 ++++++++++++++++-----------
 1 file changed, 27 insertions(+), 18 deletions(-)

diff --git a/DW-Programming/color-collection-cleaner-v2.js b/DW-Programming/color-collection-cleaner-v2.js
index ac65a218..5e3a5c92 100644
--- a/DW-Programming/color-collection-cleaner-v2.js
+++ b/DW-Programming/color-collection-cleaner-v2.js
@@ -44,19 +44,19 @@ const LEDGER = path.join(DATA_DIR, 'color-clean-ledger.jsonl');
 const BANNER_QUEUE = path.join(DATA_DIR, 'banner-recrawl-queue.jsonl');
 
 const COLOR_COLLECTIONS = {
-  blue:   { collectionId: 95278104641,  tags:['blue','navy','sky','indigo','cobalt','azure','cerulean','sapphire'], word:'BLUE',  consider:'navy, indigo, cobalt, sky blue, teal-blue, sapphire, royal blue, periwinkle', notColors:'brown, gold, silver, black, white, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange' },
-  green:  { collectionId: 164060037171, tags:['green','emerald','olive','sage','forest','mint','lime','jade','hunter'], word:'GREEN', consider:'emerald, olive, sage, forest, mint, lime, jade, hunter, moss, fern', notColors:'brown, gold, silver, black, white, blue, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange' },
-  yellow: { collectionId: 283180367923, tags:['yellow','mustard','lemon','saffron','amber','marigold','sunflower','buttercup'], word:'YELLOW', consider:'mustard, lemon, saffron, amber, marigold, sunflower, buttercup, canary', notColors:'brown, silver, black, white, blue, red, bronze, copper, beige, tan, grey, cork, natural, cream, green, pink, purple, orange' },
+  blue:   { collectionId: 95278104641,  handle:'blue-wallpaper-collection',  tags:['blue','navy','sky','indigo','cobalt','azure','cerulean','sapphire'], word:'BLUE',  consider:'navy, indigo, cobalt, sky blue, teal-blue, sapphire, royal blue, periwinkle', notColors:'brown, gold, silver, black, white, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange' },
+  green:  { collectionId: 164060037171, handle:'green-wallpaper-collection', tags:['green','emerald','olive','sage','forest','mint','lime','jade','hunter'], word:'GREEN', consider:'emerald, olive, sage, forest, mint, lime, jade, hunter, moss, fern', notColors:'brown, gold, silver, black, white, blue, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange' },
+  yellow: { collectionId: 283180367923, handle:'yellow-wallpaper-collection', tags:['yellow','mustard','lemon','saffron','amber','marigold','sunflower','buttercup'], word:'YELLOW', consider:'mustard, lemon, saffron, amber, marigold, sunflower, buttercup, canary', notColors:'brown, silver, black, white, blue, red, bronze, copper, beige, tan, grey, cork, natural, cream, green, pink, purple, orange' },
   cream:  { collectionId: 283307828659, tags:['cream','ivory','off-white','vanilla','eggshell','champagne'], word:'CREAM or IVORY', consider:'off-white, vanilla, eggshell, champagne, warm white', notColors:'brown, silver, black, blue, red, bronze, copper, tan, grey, cork, green, yellow, pink, purple, orange, navy, bright white' },
-  pink:   { collectionId: null,        tags:['pink','rose','blush','fuchsia','magenta','salmon','coral pink','mauve'], word:'PINK', consider:'rose, blush, fuchsia, magenta, salmon, hot pink, mauve, dusty rose', notColors:'brown, gold, silver, black, white, blue, green, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, purple, orange' },
-  red:    { collectionId: 264122269747, tags:['red','crimson','burgundy','scarlet','ruby','cherry','maroon','wine'], word:'RED', consider:'crimson, burgundy, scarlet, ruby, cherry, maroon, wine, vermilion', notColors:'brown, gold, silver, black, white, blue, green, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange' },
-  purple: { collectionId: 283307573299, tags:['purple','violet','lavender','plum','amethyst','lilac','aubergine','mauve'], word:'PURPLE', consider:'violet, lavender, plum, amethyst, lilac, aubergine, mauve, eggplant', notColors:'brown, gold, silver, black, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, orange' },
-  gold:   { collectionId: 164059906099, tags:['gold','golden','gilded','brass'], word:'GOLD or GOLDEN', consider:'brass, gilded, antique gold, warm metallic gold', notColors:'silver, black, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange, brown' },
-  grey:   { collectionId: 164059938867, tags:['grey','gray','charcoal','slate','silver','pewter','ash','graphite'], word:'GREY/GRAY', consider:'charcoal, slate, silver, pewter, ash, graphite, gunmetal', notColors:'brown, gold, black, white, blue, green, red, bronze, copper, beige, tan, cork, natural, cream, yellow, pink, purple, orange' },
-  black:  { collectionId: 283285651507, tags:['black','ebony','onyx','jet','noir'], word:'BLACK', consider:'ebony, onyx, jet, noir, charcoal-black', notColors:'brown, gold, silver, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange' },
-  brown:  { collectionId: 283307868211, tags:['brown','chocolate','cocoa','mocha','coffee','walnut','chestnut','espresso','umber'], word:'BROWN', consider:'chocolate, cocoa, mocha, coffee, walnut, chestnut, espresso, umber, sienna', notColors:'gold, silver, black, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange' },
-  orange: { collectionId: 283307999283, tags:['orange','tangerine','burnt orange','rust','terracotta','sienna','pumpkin'], word:'ORANGE', consider:'tangerine, burnt orange, rust, terracotta, sienna, pumpkin, paprika', notColors:'brown, gold, silver, black, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple' },
-  coral:  { collectionId: 282281377843, tags:['coral','peach','apricot'], word:'CORAL or PEACH', consider:'warm salmon-orange, apricot, soft orange-pink', notColors:'brown, gold, silver, black, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple' },
+  pink:   { collectionId: null,        handle:'pink-wallcoverings', tags:['pink','rose','blush','fuchsia','magenta','salmon','coral pink','mauve'], word:'PINK', consider:'rose, blush, fuchsia, magenta, salmon, hot pink, mauve, dusty rose', notColors:'brown, gold, silver, black, white, blue, green, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, purple, orange' },
+  red:    { collectionId: 264122269747, handle:'red-wallcoverings', tags:['red','crimson','burgundy','scarlet','ruby','cherry','maroon','wine'], word:'RED', consider:'crimson, burgundy, scarlet, ruby, cherry, maroon, wine, vermilion', notColors:'brown, gold, silver, black, white, blue, green, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange' },
+  purple: { collectionId: 283307573299, handle:'purple', tags:['purple','violet','lavender','plum','amethyst','lilac','aubergine','mauve'], word:'PURPLE', consider:'violet, lavender, plum, amethyst, lilac, aubergine, mauve, eggplant', notColors:'brown, gold, silver, black, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, orange' },
+  gold:   { collectionId: 164059906099, handle:'gold-wallpaper', tags:['gold','golden','gilded','brass'], word:'GOLD or GOLDEN', consider:'brass, gilded, antique gold, warm metallic gold', notColors:'silver, black, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange, brown' },
+  grey:   { collectionId: 164059938867, handle:'grey-wallcovering', tags:['grey','gray','charcoal','slate','silver','pewter','ash','graphite'], word:'GREY/GRAY', consider:'charcoal, slate, silver, pewter, ash, graphite, gunmetal', notColors:'brown, gold, black, white, blue, green, red, bronze, copper, beige, tan, cork, natural, cream, yellow, pink, purple, orange' },
+  black:  { collectionId: 283285651507, handle:'black', tags:['black','ebony','onyx','jet','noir'], word:'BLACK', consider:'ebony, onyx, jet, noir, charcoal-black', notColors:'brown, gold, silver, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange' },
+  brown:  { collectionId: 283307868211, handle:'brown-wallpaper-collection', tags:['brown','chocolate','cocoa','mocha','coffee','walnut','chestnut','espresso','umber'], word:'BROWN', consider:'chocolate, cocoa, mocha, coffee, walnut, chestnut, espresso, umber, sienna', notColors:'gold, silver, black, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple, orange' },
+  orange: { collectionId: 283307999283, handle:'orange', tags:['orange','tangerine','burnt orange','rust','terracotta','sienna','pumpkin'], word:'ORANGE', consider:'tangerine, burnt orange, rust, terracotta, sienna, pumpkin, paprika', notColors:'brown, gold, silver, black, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple' },
+  coral:  { collectionId: 282281377843, handle:'coral', tags:['coral','peach','apricot'], word:'CORAL or PEACH', consider:'warm salmon-orange, apricot, soft orange-pink', notColors:'brown, gold, silver, black, white, blue, green, red, bronze, copper, beige, tan, grey, cork, natural, cream, yellow, pink, purple' },
 };
 
 function shopifyREST(method, p) {
@@ -122,7 +122,12 @@ Answer on ONE line EXACTLY: "COLOR=YES|NO (dominant) BANNER=YES|NO"` },
     generationConfig: { temperature: 0.1, maxOutputTokens: 60, thinkingConfig: { thinkingBudget: 0 } } });
   for (let a = 1; a <= 5; a++) {
     try { const v = await callGemini(body);
-      return { color: /COLOR=YES/i.test(v), banner: /BANNER=YES/i.test(v), raw: v };
+      // Tri-state: only an EXPLICIT "COLOR=NO" removes. A "COLOR=YES" keeps. Anything
+      // else (empty/garbled/unexpected) is null=uncertain → caller keeps + retries,
+      // never removes on ambiguity. This is the belt-and-suspenders guard against the
+      // thinking-budget class of bug that caused mass false-removes. (2026-06-12)
+      const yes = /COLOR\s*[=:]\s*YES/i.test(v), no = /COLOR\s*[=:]\s*NO/i.test(v);
+      return { color: yes ? true : (no ? false : null), banner: /BANNER\s*[=:]\s*YES/i.test(v), raw: v };
     } catch (e) {
       if (e.code === 429) { await new Promise(r => setTimeout(r, Math.min(e.retryAfter || 15, 60) * 1000)); continue; }
       if (e.code >= 500 && a < 3) { await new Promise(r => setTimeout(r, 4000)); continue; }
@@ -153,7 +158,7 @@ async function resolveCollectionId(color, cfg) {
     if (r && r.collection && r.collection.id) return (_cidCache[color] = cfg.collectionId);
     console.log(`  [${color}] hardcoded id ${cfg.collectionId} not on this store — resolving by handle…`);
   }
-  const handles = [color, `${color}-wallpaper`, `${color}-wallcovering`, `${color}-wallcoverings`];
+  const handles = [cfg.handle, color, `${color}-wallpaper`, `${color}-wallcovering`, `${color}-wallcoverings`, `${color}-wallpaper-collection`].filter(Boolean);
   for (const h of handles) {
     for (const type of ['smart_collections', 'custom_collections']) {
       const r = await shopifyREST('GET', `/${type}.json?handle=${encodeURIComponent(h)}&fields=id,handle,title`);
@@ -179,7 +184,7 @@ async function processColor(color) {
   if (LIMIT) products = products.slice(0, LIMIT);
   console.log(`  to process: ${products.length} (already done ${done.size})`);
   const tagSet = new Set(cfg.tags.map(t => t.toLowerCase()));
-  let kept = 0, removed = 0, errors = 0, skipped = 0, banners = 0, idx = 0;
+  let kept = 0, removed = 0, errors = 0, skipped = 0, banners = 0, uncertain = 0, idx = 0;
 
   const queue = [...products];
   async function worker() {
@@ -192,10 +197,10 @@ async function processColor(color) {
       if (res.banner) {
         banners++; append(BANNER_QUEUE, { ts: new Date().toISOString(), color, product_id: p.id, title: p.title, vendor: p.vendor, image_src: src, raw: res.raw });
       }
-      if (res.color) {
+      if (res.color === true) {
         kept++; markProcessed(color, p.id);
         if (n <= 15 || n % 100 === 0) console.log(`  [${n}] KEEP ${p.title.slice(0,42)} — ${res.raw}`);
-      } else {
+      } else if (res.color === false) {
         const tags = (p.tags || '').split(',').map(t => t.trim()).filter(Boolean);
         const cleaned = tags.filter(t => !tagSet.has(t.toLowerCase()));
         const removedTags = tags.filter(t => tagSet.has(t.toLowerCase()));
@@ -211,11 +216,15 @@ async function processColor(color) {
           markProcessed(color, p.id);
           console.log(`  [${n}] ${DRY_RUN ? 'DRY-REMOVE' : 'REMOVE'} ${p.title.slice(0,40)} — ${res.raw} — [${removedTags.join(', ')}]`);
         } else { kept++; markProcessed(color, p.id); }
+      } else {
+        // res.color === null → uncertain verdict. NEVER remove on ambiguity.
+        // Leave unmarked so it is re-evaluated on the next run. (guard, 2026-06-12)
+        uncertain++; if (n % 25 === 0) console.log(`  [${n}] UNCERTAIN(keep) ${p.title.slice(0,40)} — ${res.raw}`);
       }
     }
   }
   await Promise.all(Array.from({ length: CONC }, worker));
-  const sum = `  ${color.toUpperCase()}: kept ${kept}, removed ${removed}, banners ${banners}, skipped ${skipped}, errors ${errors}`;
+  const sum = `  ${color.toUpperCase()}: kept ${kept}, removed ${removed}, banners ${banners}, skipped ${skipped}, uncertain ${uncertain}, errors ${errors}`;
   console.log(sum); return sum;
 }
 

← 88eb7001 make cleaner importable (require.main guard + module.exports  ·  back to Designer Wallcoverings  ·  color cleaner v2: model-fallback chain (flash-lite→flash→pro 3fd98379 →