← back to Designer Wallcoverings
fix(importer): fail-loud on missing per-color mfr sku instead of fabricating a counter
9d08204f6684ffff63651629518f2f4b78ba61db · 2026-09-01 08:02:27 -0700 · Steve Abrams
Root cause of the go-live-gate window growth (TK-11063): importer/scraper sites did
`sku: color.sku || ${pattern_number}-${color_number}` (and `VENDOR-${i+1}`),
minting a fabricated sequential code when the real per-color mfr code was not captured
— exactly how Carnegie Grain 6300 became 63001..630026. Adds shared fail-loud helper
lib/resolve-mfr-sku.ts: returns the real sku or HOLDS the row (never fabricates);
by-colour lines (Novasuede) exempt. Wired into fix-all-vendors-structure.ts,
fetch-vendor-products.ts (in-browser emits '' + Node-side holds), and the DW-Agents
Arte importer. Rule dw-dwsku-keep-old-never-mint. TK-11063.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M DW-Agents/scripts/import-products.tsA DW-Programming/ImportNewSkufromURL/__tests__/resolve-mfr-sku.test.tsM DW-Programming/ImportNewSkufromURL/fetch-vendor-products.tsM DW-Programming/ImportNewSkufromURL/fix-all-vendors-structure.tsA DW-Programming/ImportNewSkufromURL/lib/resolve-mfr-sku.ts
Diff
commit 9d08204f6684ffff63651629518f2f4b78ba61db
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Sep 1 08:02:27 2026 -0700
fix(importer): fail-loud on missing per-color mfr sku instead of fabricating a counter
Root cause of the go-live-gate window growth (TK-11063): importer/scraper sites did
`sku: color.sku || ${pattern_number}-${color_number}` (and `VENDOR-${i+1}`),
minting a fabricated sequential code when the real per-color mfr code was not captured
— exactly how Carnegie Grain 6300 became 63001..630026. Adds shared fail-loud helper
lib/resolve-mfr-sku.ts: returns the real sku or HOLDS the row (never fabricates);
by-colour lines (Novasuede) exempt. Wired into fix-all-vendors-structure.ts,
fetch-vendor-products.ts (in-browser emits '' + Node-side holds), and the DW-Agents
Arte importer. Rule dw-dwsku-keep-old-never-mint. TK-11063.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
DW-Agents/scripts/import-products.ts | 12 ++++-
.../__tests__/resolve-mfr-sku.test.ts | 33 +++++++++++++
.../ImportNewSkufromURL/fetch-vendor-products.ts | 20 ++++++--
.../fix-all-vendors-structure.ts | 19 ++++++--
.../ImportNewSkufromURL/lib/resolve-mfr-sku.ts | 54 ++++++++++++++++++++++
5 files changed, 131 insertions(+), 7 deletions(-)
diff --git a/DW-Agents/scripts/import-products.ts b/DW-Agents/scripts/import-products.ts
index 75f1a1d3..5870b949 100755
--- a/DW-Agents/scripts/import-products.ts
+++ b/DW-Agents/scripts/import-products.ts
@@ -76,11 +76,21 @@ async function importArteProducts(): Promise<ImportStats> {
if (pattern.colors) {
for (const color of pattern.colors) {
stats.total++;
+ // FAIL-LOUD (TK-11063): never fabricate `${pattern_number}-${color_number}`
+ // when the REAL per-color sku was not captured — that is exactly the
+ // Carnegie 6300→63001.. garbage-code class (rule dw-dwsku-keep-old-never-mint).
+ // HOLD the colorway (skip + count) so an incomplete scrape is visible.
+ const realSku = typeof color.sku === 'string' ? color.sku.trim() : '';
+ if (!realSku) {
+ stats.failed++;
+ stats.errors.push(`Arte HOLD ${pattern.name} - ${color.name}: missing-real-mfr-sku (no fabricated code minted)`);
+ continue;
+ }
try {
await prisma.products.create({
data: {
vendor_id: 'arte-international',
- sku: color.sku || `${pattern.pattern_number}-${color.color_number}`,
+ sku: realSku,
name: `${pattern.name} - ${color.name}`,
product_url: color.url || '',
pattern_name: pattern.name,
diff --git a/DW-Programming/ImportNewSkufromURL/__tests__/resolve-mfr-sku.test.ts b/DW-Programming/ImportNewSkufromURL/__tests__/resolve-mfr-sku.test.ts
new file mode 100644
index 00000000..0c391ef8
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/__tests__/resolve-mfr-sku.test.ts
@@ -0,0 +1,33 @@
+import { describe, it, expect } from '@jest/globals';
+import { resolveMfrSku } from '../lib/resolve-mfr-sku';
+
+describe('resolveMfrSku — fail-loud (TK-11063)', () => {
+ it('returns the real sku when present', () => {
+ expect(resolveMfrSku('24121-windows', 'carnegie')).toEqual({ ok: true, sku: '24121-windows' });
+ });
+
+ it('HOLDS (never fabricates) when the real sku is missing on a code-required line', () => {
+ for (const missing of ['', ' ', undefined, null]) {
+ const r = resolveMfrSku(missing as any, 'carnegie');
+ expect(r.ok).toBe(false);
+ expect(r.sku).toBeNull();
+ expect(r.reason).toBe('missing-real-mfr-sku');
+ }
+ });
+
+ it('treats literal "unknown" as missing', () => {
+ const r = resolveMfrSku('Unknown', 'arte-international');
+ expect(r.ok).toBe(false);
+ expect(r.sku).toBeNull();
+ });
+
+ it('exempts by-colour lines (Novasuede) — missing code is allowed, empty sku ok', () => {
+ const r = resolveMfrSku('', 'novasuede');
+ expect(r.ok).toBe(true);
+ expect(r.sku).toBe('');
+ });
+
+ it('trims whitespace on a real code', () => {
+ expect(resolveMfrSku(' T10958 ', 'thibaut')).toEqual({ ok: true, sku: 'T10958' });
+ });
+});
diff --git a/DW-Programming/ImportNewSkufromURL/fetch-vendor-products.ts b/DW-Programming/ImportNewSkufromURL/fetch-vendor-products.ts
index 880a266d..455ff2c6 100644
--- a/DW-Programming/ImportNewSkufromURL/fetch-vendor-products.ts
+++ b/DW-Programming/ImportNewSkufromURL/fetch-vendor-products.ts
@@ -6,6 +6,7 @@ import { chromium, Browser } from 'playwright';
import * as fs from 'fs';
import sqlite3 from 'better-sqlite3';
import * as dotenv from 'dotenv';
+import { resolveMfrSku } from './lib/resolve-mfr-sku';
dotenv.config({ path: '.env.local' });
@@ -93,8 +94,18 @@ class VendorProductFetcher {
await page.waitForTimeout(waitTime);
// Extract products based on vendor-specific selectors
- const products = await this.extractProducts(page, vendor.vendor_id);
-
+ const rawProducts = await this.extractProducts(page, vendor.vendor_id);
+
+ // FAIL-LOUD (TK-11063): HOLD rows with no REAL captured mfr sku instead of the
+ // in-browser code fabricating one. resolveMfrSku returns ok:false for a missing
+ // code on a code-required line → drop + count (visible incompleteness, never a
+ // fabricated `${VENDOR}-${i+1}` code entering the pipeline).
+ const products = rawProducts.filter((p: any) => resolveMfrSku(p.sku, vendor.vendor_id).ok);
+ const heldMissingMfr = rawProducts.length - products.length;
+ if (heldMissingMfr > 0) {
+ console.log(` ⚠️ Held ${heldMissingMfr} product(s) with no real mfr sku (not fabricated)`);
+ }
+
result.products_found = products.length;
result.products = products;
@@ -188,7 +199,10 @@ class VendorProductFetcher {
results.push({
title: title,
- sku: sku || `${vendorId.toUpperCase()}-${i + 1}`,
+ // FAIL-LOUD (TK-11063): NEVER fabricate a `${VENDOR}-${i+1}` sequential code
+ // in-browser. Emit '' when no real sku was captured; the Node side HOLDS it via
+ // resolveMfrSku so an incomplete scrape is visible, not silently poisoned.
+ sku: sku || '',
url: fullUrl,
image: image,
vendor: vendorId
diff --git a/DW-Programming/ImportNewSkufromURL/fix-all-vendors-structure.ts b/DW-Programming/ImportNewSkufromURL/fix-all-vendors-structure.ts
index 545d7592..b6039fd1 100644
--- a/DW-Programming/ImportNewSkufromURL/fix-all-vendors-structure.ts
+++ b/DW-Programming/ImportNewSkufromURL/fix-all-vendors-structure.ts
@@ -1,15 +1,17 @@
#!/usr/bin/env npx tsx
import * as fs from 'fs';
+import { resolveMfrSku } from './lib/resolve-mfr-sku';
function fixAllVendorsStructure() {
console.log('🚀 Fixing ALL vendors to follow Collection-Pattern-Colors structure...\n');
-
+
const resultsPath = '/root/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/public/vendor-test-results.json';
const data = JSON.parse(fs.readFileSync(resultsPath, 'utf-8'));
-
+
let fixedCount = 0;
let alreadyCorrectCount = 0;
+ let heldMissingMfr = 0; // rows dropped because no REAL mfr code was captured (fail-loud, TK-11063)
// Process each vendor
for (const vendorId in data.vendors) {
@@ -62,11 +64,21 @@ function fixAllVendorsStructure() {
});
}
+ // FAIL-LOUD (TK-11063): resolve the REAL captured sku — NEVER fabricate a
+ // `${VENDOR}-${n}` sequential counter (the Carnegie 6300→63001.. bug class,
+ // standing rule dw-dwsku-keep-old-never-mint). A row with no real code is
+ // HELD (skipped + counted) so the scrape is visibly incomplete, not poisoned.
+ const mfr = resolveMfrSku(item.sku, vendorId);
+ if (!mfr.ok) {
+ heldMissingMfr++;
+ console.warn(` ⚠ HOLD ${vendorId} "${item.title || patternName}" — ${mfr.reason} (no fabricated code minted)`);
+ return; // do not push a fabricated-code product
+ }
const pattern = patternMap.get(patternName);
pattern.products.push({
type: 'product',
title: item.title || `${patternName} - ${colorName}`,
- sku: item.sku || `${vendorId.toUpperCase()}-${pattern.products.length + 1}`,
+ sku: mfr.sku,
url: item.url || vendor.url || ''
});
pattern.colorCount = pattern.products.length;
@@ -97,6 +109,7 @@ function fixAllVendorsStructure() {
console.log(`✅ COMPLETED ALL VENDOR FIXES`);
console.log(` Fixed: ${fixedCount} vendors`);
console.log(` Already correct: ${alreadyCorrectCount} vendors`);
+ console.log(` Held (no real mfr sku, NOT fabricated): ${heldMissingMfr} product(s)`);
console.log(` Total vendors: ${Object.keys(data.vendors).length}`);
console.log('=================================\n');
diff --git a/DW-Programming/ImportNewSkufromURL/lib/resolve-mfr-sku.ts b/DW-Programming/ImportNewSkufromURL/lib/resolve-mfr-sku.ts
new file mode 100644
index 00000000..2e35af14
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/lib/resolve-mfr-sku.ts
@@ -0,0 +1,54 @@
+/*
+ * resolve-mfr-sku.ts — FAIL-LOUD manufacturer-SKU resolver for the import engine.
+ *
+ * Root cause of the go-live-gate FAILs (TK-11063): importer/scraper sites did
+ * sku: color.sku || `${pattern_number}-${color_number}` // or `VENDOR-${i+1}`
+ * — i.e. when the REAL per-color manufacturer SKU was NOT captured, they FABRICATED a
+ * sequential counter. That is exactly how Carnegie "Grain 6300" became the fake codes
+ * 63001..630026 (base pattern 6300 + ordinal color_number 1..N), which then flowed to
+ * staging and went live with a garbage mfr number. Standing rule [[dw-dwsku-keep-old-never-mint]]:
+ * never mint a new sequential code — preserve a REAL existing code or fail loud.
+ *
+ * This resolver replaces every "|| fabricate" fallback: it returns the REAL sku when
+ * present, otherwise NULL — and the caller MUST HOLD/SKIP the row (never insert), so a
+ * scrape that fails to capture real per-color codes is VISIBLY incomplete instead of
+ * silently poisoning the catalog. Per-vendor policy exempts legitimately code-less
+ * by-colour lines (e.g. Novasuede) — for those, missing is allowed (returns '' ok:true).
+ */
+
+// By-COLOUR private lines that legitimately have NO per-color manufacturer code
+// (kept in sync with the activator gate's NOMFR_EXEMPT_VENDORS + the canary).
+const NOMFR_EXEMPT_VENDORS = ['novasuede'];
+
+export interface ResolveMfrResult {
+ /** true = caller may proceed; false = caller MUST hold/skip (do not create the row). */
+ ok: boolean;
+ /** the real sku when ok && present; '' for an exempt code-less line; null on a hold. */
+ sku: string | null;
+ /** machine reason for a hold, for logging/alerting. */
+ reason?: string;
+}
+
+function isExemptVendor(vendorId?: string): boolean {
+ const v = String(vendorId || '').toLowerCase();
+ return NOMFR_EXEMPT_VENDORS.some((e) => v.includes(e));
+}
+
+/**
+ * Resolve the manufacturer SKU for a single colorway, FAIL-LOUD (never fabricate).
+ *
+ * @param realSku the sku actually captured by the scrape (color.sku / item.sku / vSku).
+ * Empty/whitespace/undefined ⇒ "not captured".
+ * @param vendorId the vendor id (for the by-colour exemption policy).
+ * @returns ok:false + reason when the real code is missing on a code-REQUIRED line —
+ * the caller must skip + log; ok:true + sku otherwise.
+ */
+export function resolveMfrSku(realSku: unknown, vendorId?: string): ResolveMfrResult {
+ const s = typeof realSku === 'string' ? realSku.trim() : (realSku == null ? '' : String(realSku).trim());
+ if (s && !/^unknown$/i.test(s)) return { ok: true, sku: s };
+ // Missing/blank real code.
+ if (isExemptVendor(vendorId)) return { ok: true, sku: '', reason: 'by-colour-line-no-mfr-code' };
+ return { ok: false, sku: null, reason: 'missing-real-mfr-sku' };
+}
+
+export { NOMFR_EXEMPT_VENDORS, isExemptVendor };
← 4428bcdb auto-data-snapshot: 2026-09-01T07:51:05 (3 data files) — dat
·
back to Designer Wallcoverings
·
TK-11047 verify 500 pending Carnegie specs b4aa396b →