← back to Designerwallcoverings
Sanderson importer: fix color-tag source + selective image dedup (TK-11070)
349d204f1b930cd75e81821cfa137491f1d9afda · 2026-09-01 10:43:12 -0700 · Steve Abrams
Two root-cause fixes to scripts/sanderson-onboard/build-payloads.mjs:
1. color: facet now sources from the REAL scraped colorway (color_name) instead
of the AI-vision background-color guess (ai_background_color). Falls back to
emitting no color: tag rather than a wrong one when color_name is absent.
Also demotes ai_colors bare palette tags and strips ai_tags entries that
exactly match an AI-palette name (Mist/Sky/Duck Egg, Ivory/Stone/Oatmeal),
which were reading as stray-colorway pollution.
2. images() is now SELECTIVE, not blanket. gallery_images was a rolling window
of adjacent-pattern carousel plates + CN* paint chips (verified: vendor
JSON-LD declares a single hero == image_url; the only legit extra shots embed
THIS product's own SAW code in the filename, 337/337 matching their own row).
Keeps image_url hero (pos 1) + gallery images whose filename embeds this row's
own SAW article code; drops all foreign plates. Preserves 191 products' legit
multi-angle shots; never drops to 0 images (all 594 retain >=1).
Dry-run verified: 594 payloads, 0 zero-image, 0 missing color: facet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M scripts/sanderson-onboard/build-payloads.mjs
Diff
commit 349d204f1b930cd75e81821cfa137491f1d9afda
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Sep 1 10:43:12 2026 -0700
Sanderson importer: fix color-tag source + selective image dedup (TK-11070)
Two root-cause fixes to scripts/sanderson-onboard/build-payloads.mjs:
1. color: facet now sources from the REAL scraped colorway (color_name) instead
of the AI-vision background-color guess (ai_background_color). Falls back to
emitting no color: tag rather than a wrong one when color_name is absent.
Also demotes ai_colors bare palette tags and strips ai_tags entries that
exactly match an AI-palette name (Mist/Sky/Duck Egg, Ivory/Stone/Oatmeal),
which were reading as stray-colorway pollution.
2. images() is now SELECTIVE, not blanket. gallery_images was a rolling window
of adjacent-pattern carousel plates + CN* paint chips (verified: vendor
JSON-LD declares a single hero == image_url; the only legit extra shots embed
THIS product's own SAW code in the filename, 337/337 matching their own row).
Keeps image_url hero (pos 1) + gallery images whose filename embeds this row's
own SAW article code; drops all foreign plates. Preserves 191 products' legit
multi-angle shots; never drops to 0 images (all 594 retain >=1).
Dry-run verified: 594 payloads, 0 zero-image, 0 missing color: facet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/sanderson-onboard/build-payloads.mjs | 51 ++++++++++++++++++++++++----
1 file changed, 45 insertions(+), 6 deletions(-)
diff --git a/scripts/sanderson-onboard/build-payloads.mjs b/scripts/sanderson-onboard/build-payloads.mjs
index e3330e2..087e92d 100644
--- a/scripts/sanderson-onboard/build-payloads.mjs
+++ b/scripts/sanderson-onboard/build-payloads.mjs
@@ -45,7 +45,7 @@ const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&':'&
function rows() {
const cols = `id, mfr_sku, dw_sku, pattern_name, color_name, collection, width, roll_length,
- pattern_repeat, material, image_url, gallery_images, retail_usd, cost_usd, ai_description,
+ pattern_repeat, material, image_url, product_url, gallery_images, retail_usd, cost_usd, ai_description,
to_json(ai_colors) ai_colors, ai_background_color, to_json(ai_styles) ai_styles,
to_json(ai_patterns) ai_patterns, to_json(ai_tags) ai_tags, to_json(styles) styles,
settlement_verdict`;
@@ -69,12 +69,28 @@ function tags(r) {
const t = new Set(['Wallcovering', 'Wallcoverings', 'Sanderson', 'Sanderson Wallcovering']);
if (!bad(r.collection)) t.add(titleCase(r.collection));
if (!bad(r.material)) t.add(titleCase(r.material));
- if (!bad(r.ai_background_color)) t.add('color:' + titleCase(r.ai_background_color));
- for (const c of arr(r.ai_colors)) { const n = c && (c.name || c.color); if (!bad(n)) t.add(titleCase(n)); }
+ // color: facet MUST come from the REAL scraped colorway (color_name), NOT AI vision guess (TK-11070).
+ // Falls back gracefully to nothing (never emits a wrong color:) if the real colorway is missing.
+ if (!bad(r.color_name)) t.add('color:' + titleCase(r.color_name));
+ // NOTE (TK-11070): ai_colors palette names below (Alabaster/Sky/Duck Egg …) are DEMOTED — they were
+ // producing stray-colorway pollution tags. They are no longer added as bare tags; the color: facet is
+ // now the single authoritative colorway. Palette hues remain available via custom.color_details metafield.
for (const s of arr(r.ai_styles)) if (!bad(s)) t.add(titleCase(typeof s === 'string' ? s : s.name));
for (const s of arr(r.styles)) if (!bad(s)) t.add(titleCase(s));
for (const p of arr(r.ai_patterns)) if (!bad(p)) t.add(titleCase(typeof p === 'string' ? p : p.name));
- for (const g of arr(r.ai_tags)) if (!bad(g) && typeof g === 'string') t.add(titleCase(g));
+ // ai_tags carries useful non-color hints (e.g. "Animals","Florals") BUT the scraper also seeded it
+ // with AI palette color-words (Mist/Sky/Duck Egg, Ivory/Stone/Oatmeal) that read as stray-colorway
+ // pollution. Strip a tag iff it exactly matches an AI-palette name — deterministic, not a guess
+ // (TK-11070). The single authoritative colorway is the color: facet above.
+ const palette = new Set(
+ [...arr(r.ai_colors).map(c => c && (c.name || c.color)), r.ai_background_color]
+ .filter(Boolean).map(x => titleCase(x)));
+ for (const g of arr(r.ai_tags)) {
+ if (bad(g) || typeof g !== 'string') continue;
+ const tc = titleCase(g);
+ if (palette.has(tc)) continue; // drop AI palette color-words
+ t.add(tc);
+ }
return [...t].filter(Boolean).join(', '); // always ≥4 (base set) → satisfies 5-field ≥2 tags
}
@@ -125,11 +141,34 @@ function metafields(r) {
return mf;
}
+// Image selection (TK-11070 — SELECTIVE, not blanket).
+// Sanderson's gallery_images was filled by the scraper with a rolling window of ADJACENT-pattern
+// plate images off the shared collection carousel. Verified against the DB + vendor JSON-LD:
+// • the vendor product page's JSON-LD "image" declares a SINGLE authoritative hero == this row's
+// image_url (matches exactly on every fetchable page);
+// • of the 4,471 gallery entries fleet-wide, 337 embed THIS product's OWN SAW article code in the
+// filename (e.g. "...DDIW217290_1_SAW0189_02_SANDERSON_101_Dalmatians_WALLPAPER_...") and 337/337
+// of those match their own row's mfr_sku EXACTLY — they are genuine extra shots of THIS product;
+// • the remaining ~4,134 carry NO SAW code (or a foreign one) = cross-pattern carousel plates and
+// CN* paint-chip swatches — pure pollution, NO room-settings.
+// So the reliable, offline, 100%-precise "belongs to this product" signal is: the row's own SAW
+// article code embedded in the filename. Keep hero + own-SAW-coded gallery images; drop all else.
+// (A per-plate numeric-prefix family key was rejected — DABW217232/246/252 share the DABW217 prefix
+// yet are DIFFERENT patterns, so that heuristic wrongly kept foreign plates.)
+const SAW_IN_NAME = /SAW\s*0*\d{3,4}[-_]\d{1,3}/i;
+function sawKey(s) { // normalize "SAW0189-02" | "SAW0189_02" → "SAW018902"
+ const m = String(s || '').match(SAW_IN_NAME);
+ return m ? m[0].toUpperCase().replace(/[^A-Z0-9]/g, '') : null;
+}
function images(r) {
const seen = new Set(); const imgs = [];
const push = u => { if (u && !seen.has(u)) { seen.add(u); imgs.push({ src: u, alt: esc(titleCase(r.pattern_name) || r.mfr_sku) }); } };
- push(r.image_url);
- for (const g of arr(r.gallery_images)) push(g);
+ push(r.image_url); // vendor-canonical hero — always position 1
+ const own = sawKey(r.mfr_sku); // this product's own article, e.g. SAW018902
+ for (const g of arr(r.gallery_images)) {
+ // keep ONLY gallery images whose filename embeds THIS product's own SAW code; drop foreign plates.
+ if (own && sawKey(g) === own) push(g);
+ }
return imgs;
}
← 4e55a40 auto-data-snapshot: 2026-09-01T08:28:16 (2 data files) — scr
·
back to Designerwallcoverings
·
Sanderson importer: repair X&Y pattern/color split for color 572262d →