← back to Designerwallcoverings
TK-11073 Wave1: Osborne color: facet from real colorway (title_core − pattern_name), drop AI-palette bare-tags
8950edffb04d4ba5f5bac4ea2decabc935545c99 · 2026-09-01 11:44:08 -0700 · Steve Abrams
Source the color: facet from the real scraped colorway (osborne_catalog.pattern_name
subtracted from title_core) instead of AI vision (e.backgroundColor). Demote AI palette
color-words (e.colors[].name) so they no longer emit as bare tags — palette hues stay in
custom.color_*/color_details metafields. Fall back to NO color tag when the colorway can't
be resolved, never a wrong one. Mirrors the TK-11070 Sanderson fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M scripts/osborne-onboard/build-payloads.mjs
Diff
commit 8950edffb04d4ba5f5bac4ea2decabc935545c99
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Sep 1 11:44:08 2026 -0700
TK-11073 Wave1: Osborne color: facet from real colorway (title_core − pattern_name), drop AI-palette bare-tags
Source the color: facet from the real scraped colorway (osborne_catalog.pattern_name
subtracted from title_core) instead of AI vision (e.backgroundColor). Demote AI palette
color-words (e.colors[].name) so they no longer emit as bare tags — palette hues stay in
custom.color_*/color_details metafields. Fall back to NO color tag when the colorway can't
be resolved, never a wrong one. Mirrors the TK-11070 Sanderson fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/osborne-onboard/build-payloads.mjs | 55 ++++++++++++++++++++++++++++--
1 file changed, 53 insertions(+), 2 deletions(-)
diff --git a/scripts/osborne-onboard/build-payloads.mjs b/scripts/osborne-onboard/build-payloads.mjs
index 87cc0c0..81040ac 100644
--- a/scripts/osborne-onboard/build-payloads.mjs
+++ b/scripts/osborne-onboard/build-payloads.mjs
@@ -19,8 +19,11 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
+import { execSync } from 'node:child_process';
import { specMetafields } from '../_spec-to-metafields.mjs';
+const DB = 'postgresql:///dw_unified?host=/tmp';
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
const OUT = path.join(HERE, 'out');
const SAMPLE_PRICE = '4.25';
@@ -50,12 +53,46 @@ function bodyHtml(r, e) {
return `<div class="osborne-product">\n${story}\n</div>`;
}
+// Colorway comes from the REAL scraped name, NEVER AI vision (TK-11073, mirrors TK-11070 Sanderson).
+// Osborne's title_core = "<PatternName> <Colorway>"; the real colorway is title_core with the
+// scraped pattern_name (r.pattern_name, or e.pattern_name) subtracted. Returns null when the
+// colorway can't be resolved confidently → caller emits NO color: facet (never a wrong one).
+function osborneColorway(r) {
+ const decode = s => String(s || '').replace(/&/g, '&').replace(/'/g, "'");
+ const norm = s => titleCase(decode(s)).toLowerCase().replace(/\s+/g, ' ').trim();
+ let tc = decode(r.title_core || '').replace(/^\(limited stock\)\s*/i, '').replace(/^s\s+/i, '').trim();
+ const pat = decode(r.pattern_name || '').trim();
+ if (!tc) return null;
+ if (!pat) return null; // no pattern to subtract → can't resolve → skip
+ const tcToks = tc.split(/\s+/), patToks = pat.split(/\s+/);
+ let colorway = null;
+ for (let i = 0; i < tcToks.length; i++) {
+ if (norm(tcToks[i]) === norm(patToks[0])) {
+ let j = 0;
+ while (j < patToks.length && i + j < tcToks.length && norm(tcToks[i + j]) === norm(patToks[j])) j++;
+ colorway = tcToks.slice(i + j).join(' ').trim();
+ break;
+ }
+ }
+ if (colorway == null && norm(tcToks[0]) === norm(patToks[0])) colorway = tcToks.slice(1).join(' ').trim();
+ if (!colorway) return null;
+ colorway = colorway.replace(/\s*\/\s*/g, '/').replace(/\s+/g, ' ').trim();
+ if (!colorway || colorway.length > 40) return null;
+ return colorway;
+}
+
function tags(r, e) {
const t = new Set(['Wallcovering', 'Wallcoverings', 'Osborne & Little', 'Osborne & Little Wallcovering', MATERIAL,
'Trending Wallcovering Collection 2026']); // → surfaces in New Arrivals (Steve directive)
if (r.collection) t.add(r.collection);
- if (e?.backgroundColor) t.add('color:' + titleCase(e.backgroundColor));
- for (const c of (e?.colors || [])) if (c?.name) t.add(titleCase(c.name));
+ // color: facet MUST come from the REAL scraped colorway, NOT AI vision (e.backgroundColor / e.colors).
+ // Fall back to NO color tag rather than a wrong one (TK-11073).
+ const cw = osborneColorway(r);
+ if (cw) t.add('color:' + titleCase(cw));
+ // NOTE (TK-11073): AI palette color-words (e.backgroundColor + e.colors[].name — Bone/Dove/Greige/Sky…)
+ // are DEMOTED. They produced stray-colorway pollution (a wrong color: facet + bare palette tags), so
+ // they are no longer emitted as tags. The color: facet above is the single authoritative colorway;
+ // the AI palette hues remain available via the custom.color_*/color_details metafields.
if (e?.style) t.add(titleCase(e.style));
if (e?.pattern) t.add(titleCase(e.pattern));
if (r.limited_stock) t.add('Limited Stock');
@@ -82,12 +119,26 @@ function metafields(r, e) {
return mf;
}
+// Real scraped pattern_name per mfr_sku from osborne_catalog (Mac2-canonical staging). Used by
+// tags()/osborneColorway() to subtract the pattern from title_core and recover the real colorway.
+function patternMap() {
+ const m = {};
+ try {
+ const raw = execSync(`psql "${DB}" -tAc "select coalesce(json_agg(json_build_object('sku',mfr_sku,'pat',pattern_name))::text,'[]') from osborne_catalog"`,
+ { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }).trim();
+ for (const r of JSON.parse(raw || '[]')) if (r.sku) m[String(r.sku).toUpperCase()] = r.pat || '';
+ } catch (e) { console.error('patternMap: DB read failed, colorway facet will be skipped —', e.message); }
+ return m;
+}
+
function main() {
const ready = load('build-ready.jsonl');
const enrByMfr = Object.fromEntries(load('enriched.jsonl').filter(x => !x.error).map(x => [x.mfr_sku, x]));
+ const patByMfr = patternMap();
const out = [];
let noEnrich = 0;
for (const r of ready) {
+ r.pattern_name = patByMfr[String(r.mfr_sku).toUpperCase()] || ''; // for osborneColorway()
const e = enrByMfr[r.mfr_sku];
if (!e) noEnrich++;
const sku = `${r.mfr_sku}-OSB`;
← 9b75f40 auto-data-snapshot: 2026-09-01T10:56:56 (8 data files) — scr
·
back to Designerwallcoverings
·
TK-11073 Wave1: Osborne LIVE remediation — 29 real-title pro b61983c →