← back to Designerwallcoverings
TK-11076: colorway-title-anchor PL-exclusion + Malibu run scripts
bfae207fc567cd5ce2e871b7d5cd803d45339ca6 · 2026-09-04 12:42:38 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STupyqBoLfTPHaTr1Gi42b
Files touched
A scripts/colorway-title-anchor/out/build-comma-memo.mjsA scripts/colorway-title-anchor/out/run-comma-malibu.shA scripts/colorway-title-anchor/out/run-remaining-malibu.shA scripts/colorway-title-anchor/out/set-one-colorway.mjsA scripts/colorway-title-anchor/run-remaining-missing.shA scripts/colorway-title-anchor/tk11076-pl-exclusion.mjs
Diff
commit bfae207fc567cd5ce2e871b7d5cd803d45339ca6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 4 12:42:38 2026 -0700
TK-11076: colorway-title-anchor PL-exclusion + Malibu run scripts
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STupyqBoLfTPHaTr1Gi42b
---
.../colorway-title-anchor/out/build-comma-memo.mjs | 27 +++++
.../colorway-title-anchor/out/run-comma-malibu.sh | 25 +++++
.../out/run-remaining-malibu.sh | 35 ++++++
.../colorway-title-anchor/out/set-one-colorway.mjs | 57 ++++++++++
.../colorway-title-anchor/run-remaining-missing.sh | 37 +++++++
.../colorway-title-anchor/tk11076-pl-exclusion.mjs | 120 +++++++++++++++++++++
6 files changed, 301 insertions(+)
diff --git a/scripts/colorway-title-anchor/out/build-comma-memo.mjs b/scripts/colorway-title-anchor/out/build-comma-memo.mjs
new file mode 100644
index 0000000..674bd30
--- /dev/null
+++ b/scripts/colorway-title-anchor/out/build-comma-memo.mjs
@@ -0,0 +1,27 @@
+import fs from 'node:fs';
+import os from 'node:os';
+const rows = fs.readFileSync(new URL('./malibu-comma-handles.txt', import.meta.url), 'utf8')
+ .trim().split('\n').map(l => { const [handle, colorway, ...t] = l.split('\t'); return { handle, colorway, title: t.join('\t') }; });
+const isMal = c => /,\s*and\b/i.test(c);
+const malformed = rows.filter(d => isMal(d.colorway));
+const lists = rows.filter(d => !isMal(d.colorway));
+const esc = s => (s || '').replace(/\|/g, '\\|');
+let m = `# TK-11107 — Malibu Wallpaper colorway retag: ${rows.length} COMMA-colorway products HELD for review\n\n`;
+m += `**Filed:** 2026-09-02 · **Agent:** tk11078-malibu-pilot · **Ticket:** TK-11107\n`;
+m += `**Status:** HELD — excluded from the Steve-approved full apply (they contain commas; the pilot gate excluded comma colorways). NOT written to Shopify.\n\n`;
+m += `## Context\nSteve approved the full Malibu Wallpaper colorway retag. Of 655 would-write products, **610 clean-parse were applied** (title-anchored \`color:\` tag + \`custom.real_color_name\`; titles/prices/status/SKUs/images untouched; reversible + ledgered). These **${rows.length}** have comma-containing parsed colorways and were held.\n\n`;
+m += `## Group A — ${malformed.length} MALFORMED parse artifacts (dangling ", and") — recommend FIX before applying\n`;
+m += `A colorway should not read e.g. \`Silver, And Crimson\` (real title colorway is "Metallic Gold, Silver, and Crimson"). The parser needs to handle the Oxford-comma/list grammar in these titles.\n\n`;
+m += `| handle | parsed_colorway | title |\n|---|---|---|\n`;
+for (const d of malformed) m += `| ${d.handle} | \`${d.colorway}\` | ${esc(d.title)} |\n`;
+m += `\n## Group B — ${lists.length} multi-color LISTS (probably legit) — your call to APPLY as-is\n`;
+m += `Plausibly-real multi-color colorways. If applied, the tag becomes \`color:A / B / C\` (commas→slashes); the metafield keeps the verbatim comma form.\n\n`;
+m += `| handle | parsed_colorway | title |\n|---|---|---|\n`;
+for (const d of lists) m += `| ${d.handle} | \`${d.colorway}\` | ${esc(d.title)} |\n`;
+m += `\n## APPROVE / REVISE / BLOCK\n`;
+m += `- [ ] APPLY GROUP B as-is (${lists.length} multi-color lists) — reversible, ledgered, same script via --only\n`;
+m += `- [ ] FIX GROUP A parser then apply (${malformed.length} malformed) — needs a parse-colorway.mjs tweak for list/Oxford-comma grammar\n`;
+m += `- [ ] BLOCK — leave all ${rows.length} as-is\n`;
+const out = os.homedir() + '/.claude/yolo-queue/pending-approval/TK-11107-malibu-comma-colorways-review.md';
+fs.writeFileSync(out, m);
+console.log(`wrote ${out}\n total=${rows.length} groupA(malformed)=${malformed.length} groupB(lists)=${lists.length}`);
diff --git a/scripts/colorway-title-anchor/out/run-comma-malibu.sh b/scripts/colorway-title-anchor/out/run-comma-malibu.sh
new file mode 100644
index 0000000..e08c64a
--- /dev/null
+++ b/scripts/colorway-title-anchor/out/run-comma-malibu.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# TK-11107 — Steve-APPROVED ("do it") apply of the 45 held comma-colorways.
+# - 44 multi-color colorways via the standard retag script (out/malibu-comma44.txt)
+# - 1 manual set for dwqw-56511 (no-delimiter title) -> "Metallic Gold, Silver, and Crimson"
+# Reversible + ledgered. TITLES/prices/status/SKUs/images untouched.
+set -uo pipefail
+cd "$(dirname "$0")/.." # -> colorway-title-anchor
+export SHOPIFY_ADMIN_TOKEN="$(grep -E '^SHOPIFY_ADMIN_TOKEN=' ~/Projects/secrets-manager/.env | head -1 | cut -d= -f2- | tr -d '"'"'"'"' )"
+[ -z "${SHOPIFY_ADMIN_TOKEN:-}" ] && { echo "FATAL: no SHOPIFY_ADMIN_TOKEN"; exit 1; }
+export TK_AGENT=tk11107-malibu-full
+
+echo "=== apply 44 multi-color comma colorways ==="
+ONLY=$(paste -sd, out/malibu-comma44.txt)
+OUT=$(node tk11076-colorway-retag.mjs --vendor='Malibu Wallpaper' --apply --only="$ONLY" 2>&1)
+echo "$OUT" | grep -E "^\[[0-9]+/|DONE|VERIFY-FAIL|userErrors" | tail -8
+
+echo ""
+echo "=== manual set: dwqw-56511 -> Metallic Gold, Silver, and Crimson ==="
+node out/set-one-colorway.mjs dwqw-56511-handle "Metallic Gold, Silver, and Crimson" 2>&1 | tail -3
+
+RB=~/.claude/yolo-queue/executed-reversible/TK-11076-Malibu_Wallpaper-rollback.json
+RBN=$(node -e "try{const j=require('$RB');console.log(Object.keys(j).filter(k=>k!=='_meta').length)}catch(e){console.log('?')}")
+echo ""
+echo "===== COMMA-SET COMPLETE. rollback json handle entries now: $RBN (expected ~674 = 629 + 45) ====="
+( cd ~/Projects/ticket-system && tk log TK-11107 "Applied 45 comma-colorways (44 via script + 1 manual dwqw-56511). rollback entries=$RBN." >/dev/null 2>&1 ) || true
diff --git a/scripts/colorway-title-anchor/out/run-remaining-malibu.sh b/scripts/colorway-title-anchor/out/run-remaining-malibu.sh
new file mode 100644
index 0000000..58c8426
--- /dev/null
+++ b/scripts/colorway-title-anchor/out/run-remaining-malibu.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/env bash
+# TK-11107 — Steve-APPROVED full Malibu Wallpaper colorway retag, REMAINING chunks.
+# Clean-parse handles ONLY (chunks ad,ae,af,ag). The 45 comma-colorway handles in
+# out/malibu-comma-handles.txt are DELIBERATELY EXCLUDED and held for Steve review.
+# Idempotent: already-correct products are skipped by the script. Reversible + ledgered.
+set -uo pipefail
+cd "$(dirname "$0")/.." # -> colorway-title-anchor
+export SHOPIFY_ADMIN_TOKEN="$(grep -E '^SHOPIFY_ADMIN_TOKEN=' ~/Projects/secrets-manager/.env | head -1 | cut -d= -f2- | tr -d '"'"'"'"' )"
+if [ -z "${SHOPIFY_ADMIN_TOKEN:-}" ]; then echo "FATAL: no SHOPIFY_ADMIN_TOKEN in secrets"; exit 1; fi
+
+export TK_AGENT=tk11107-malibu-full
+TOTAL_DONE=0; TOTAL_PASS=0; TOTAL_FAIL=0
+for c in ad ae af ag; do
+ f="out/malibu-chunk-$c"
+ [ -f "$f" ] || { echo "skip missing $f"; continue; }
+ ONLY=$(paste -sd, "$f")
+ n=$(echo "$ONLY" | tr ',' '\n' | grep -c .)
+ echo "=== chunk $c ($n handles) ==="
+ OUT=$(node tk11076-colorway-retag.mjs --vendor='Malibu Wallpaper' --apply --only="$ONLY" 2>&1)
+ echo "$OUT" | grep -E "^\[[0-9]+/|DONE|VERIFY-FAIL|userErrors" | tail -6
+ D=$(echo "$OUT" | grep -oE 'DONE Malibu Wallpaper: [0-9]+' | grep -oE '[0-9]+' | head -1); D=${D:-0}
+ P=$(echo "$OUT" | grep -oE 'verify-pass [0-9]+' | grep -oE '[0-9]+' | head -1); P=${P:-0}
+ Fl=$(echo "$OUT" | grep -oE 'verify-fail [0-9]+' | grep -oE '[0-9]+' | head -1); Fl=${Fl:-0}
+ TOTAL_DONE=$((TOTAL_DONE+D)); TOTAL_PASS=$((TOTAL_PASS+P)); TOTAL_FAIL=$((TOTAL_FAIL+Fl))
+ ( cd ~/Projects/ticket-system && tk log TK-11107 "chunk $c: done $D / pass $P / fail $Fl" >/dev/null 2>&1 ) || true
+done
+
+RB=~/.claude/yolo-queue/executed-reversible/TK-11076-Malibu_Wallpaper-rollback.json
+RBN=$(node -e "try{const j=require('$RB');console.log(Object.keys(j).filter(k=>k!=='_meta').length)}catch(e){console.log('?')}")
+LEDN=$(grep -c 'dwqw-56' ~/.claude/yolo-queue/executed-reversible/ledger.jsonl 2>/dev/null || echo '?')
+echo ""
+echo "===== REMAINING-CHUNKS TOTAL: done $TOTAL_DONE · verify-pass $TOTAL_PASS · verify-fail $TOTAL_FAIL ====="
+echo "rollback json handle entries: $RBN | ledger dwqw-56 lines: $LEDN"
+( cd ~/Projects/ticket-system && tk log TK-11107 "REMAINING done. total this run: done $TOTAL_DONE pass $TOTAL_PASS fail $TOTAL_FAIL. rollback entries=$RBN ledger dwqw-56=$LEDN. 45 comma-colorways still HELD for Steve." >/dev/null 2>&1 ) || true
+echo "DONE. If verify-fail>0 above, rollback: node tk11076-rollback.mjs --vendor='Malibu Wallpaper' --only=<bad-handles> --apply"
diff --git a/scripts/colorway-title-anchor/out/set-one-colorway.mjs b/scripts/colorway-title-anchor/out/set-one-colorway.mjs
new file mode 100644
index 0000000..da475fd
--- /dev/null
+++ b/scripts/colorway-title-anchor/out/set-one-colorway.mjs
@@ -0,0 +1,57 @@
+#!/usr/bin/env node
+/* Targeted single-product colorway set for a title that has NO deterministic delimiter
+ * (dwqw-56511 — "Confucius Tree Traditional Botanical Metallic Gold, Silver, and Crimson").
+ * Same write shape as tk11076-colorway-retag.mjs: strip color:* facets, add color:<facet>,
+ * set custom.real_color_name, record rollback BEFORE mutating, verify, ledger. TITLE UNTOUCHED.
+ * node set-one-colorway.mjs <handle> "<Verbatim Colorway>"
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+import { execSync } from 'node:child_process';
+const HANDLE = process.argv[2];
+const COLORWAY = process.argv[3];
+if (!HANDLE || !COLORWAY) { console.error('usage: set-one-colorway.mjs <handle> "<Colorway>"'); process.exit(1); }
+const STORE = process.env.SHOPIFY_STORE || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = process.env.SHOPIFY_ADMIN_TOKEN;
+const API = process.env.SHOPIFY_API_VERSION || '2024-10';
+if (!TOKEN) { console.error('FATAL: SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+const NS = 'custom', KEY = 'real_color_name';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+const norm = s => String(s || '').toLowerCase().replace(/\s+/g, ' ').trim();
+async function gql(query, variables) {
+ const res = await fetch(`https://${STORE}/admin/api/${API}/graphql.json`, {
+ method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query, variables }) });
+ return (await res.json()).data;
+}
+const facetVal = COLORWAY.replace(/\s*,\s*/g, ' / ');
+const facet = 'color:' + facetVal;
+const run = async () => {
+ const d = await gql(`query($q:String!){ products(first:1,query:$q){ nodes{ id handle title tags m:metafield(namespace:"${NS}",key:"${KEY}"){value} } } }`, { q: `handle:${HANDLE}` });
+ const p = d.products.nodes[0];
+ if (!p) { console.error('product not found:', HANDLE); process.exit(1); }
+ const oldTags = p.tags.slice();
+ const newTags = oldTags.filter(t => !/^color:/i.test(t) && norm(t) !== norm(facet));
+ newTags.push(facet);
+ // rollback record BEFORE mutation
+ const RB = path.join(process.env.HOME, '.claude/yolo-queue/executed-reversible/TK-11076-Malibu_Wallpaper-rollback.json');
+ const rb = fs.existsSync(RB) ? JSON.parse(fs.readFileSync(RB, 'utf8')) : {};
+ rb[p.handle] = { id: p.id, ts: new Date().toISOString(), old_tags: oldTags, new_tags: newTags,
+ old_metafield: (p.m && p.m.value) || '', new_metafield: COLORWAY, note: 'manual set — no-delimiter title' };
+ fs.writeFileSync(RB, JSON.stringify(rb, null, 2));
+ const u = await gql(`mutation($input:ProductInput!){ productUpdate(input:$input){ product{id} userErrors{field message} } }`,
+ { input: { id: p.id, tags: newTags, metafields: [{ namespace: NS, key: KEY, type: 'single_line_text_field', value: COLORWAY }] } });
+ const ue = u && u.productUpdate && u.productUpdate.userErrors;
+ if (ue && ue.length) { console.error('userErrors:', JSON.stringify(ue)); process.exit(1); }
+ await sleep(600);
+ const v = await gql(`query($id:ID!){ product(id:$id){ tags m:metafield(namespace:"${NS}",key:"${KEY}"){value} } }`, { id: p.id });
+ const facetOk = v.product.tags.some(t => norm(t) === norm(facet));
+ const metaOk = norm(v.product.m && v.product.m.value) === norm(COLORWAY);
+ console.log(`${p.handle} ${facetOk && metaOk ? '✓' : 'VERIFY-FAIL'} (facetOk=${facetOk} metaOk=${metaOk}) tag="${facet}" meta="${COLORWAY}"`);
+ try { execSync(`node "${path.join(process.env.HOME, '.claude/yolo-queue/executed-reversible/log-exec.mjs')}" ` +
+ `--agent tk11107-malibu-full --ticket TK-11076 --action "manual colorway set ${p.handle}: ${facet}" --blast 1 ` +
+ `--undo "cd ${path.dirname(process.argv[1])}/.. && node tk11076-rollback.mjs --vendor=\\"Malibu Wallpaper\\" --only=${p.handle} --apply" ` +
+ `--verify "GET ${p.id} real_color_name + color: facet == ${COLORWAY}"`, { stdio: 'ignore' }); } catch {}
+ console.log('DONE-ONE');
+};
+run();
diff --git a/scripts/colorway-title-anchor/run-remaining-missing.sh b/scripts/colorway-title-anchor/run-remaining-missing.sh
new file mode 100755
index 0000000..78bd98e
--- /dev/null
+++ b/scripts/colorway-title-anchor/run-remaining-missing.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# TK-11076 Phase B — run the 28 non-PL addressable vendors with no rollback file yet.
+# Serial (one at a time) so with Kravet still running we stay <= 2 vendors concurrently.
+# 90s gap BETWEEN vendors (bulk-push spacing rule). Each vendor's --apply has its own
+# per-vendor rollback file + per-item verify + ledger (built into the retag script).
+set -u
+cd /Users/macstudio3/Projects/designerwallcoverings/scripts/colorway-title-anchor
+export SHOPIFY_ADMIN_TOKEN=$(grep -E "^SHOPIFY_ADMIN_TOKEN=" ~/Projects/secrets-manager/.env | head -1 | cut -d= -f2- | tr -d '"'\''')
+export SHOPIFY_STORE=designer-laboratory-sandbox.myshopify.com
+export SHOPIFY_API_VERSION=2024-10
+
+VENDORS=(
+ "Pixels" "Donghia" "Daisy Bennett" "Mulberry" "Natural Paint" "Threads"
+ "Versace" "Groundworks" "Lee Jofa" "Steve Abrams Studios" "Thibaut"
+ "Brand McKenzie" "Missoni Wallpaper" "Madagascar Walls" "Ultrasuede"
+ "Retro Walls" "Koroseal" "Designers Guild" "Arte International" "Nobilis"
+ "Traditional Whimsy" "Hand Crafted" "Graduate Collection UK" "Osborne & Little"
+ "Graham & Brown" "Winfield Thybony" "Rebecca Moses" "Tres Tintas"
+)
+
+# Wait for Kravet to be the only other running vendor (never exceed 2). Kravet is expected
+# to be running; these small vendors run one at a time = 2 max. If a second non-Kravet
+# vendor were somehow running we'd wait, but this driver is the only other launcher.
+i=0
+total=${#VENDORS[@]}
+for V in "${VENDORS[@]}"; do
+ i=$((i+1))
+ slug=$(echo "$V" | sed 's/[^A-Za-z0-9]/_/g')
+ echo "=== [$i/$total] APPLY $V ($(date +%H:%M:%S)) ==="
+ node tk11076-colorway-retag.mjs --vendor="$V" --apply > "out/apply-${slug}.log" 2>&1
+ tail -1 "out/apply-${slug}.log"
+ if [ "$i" -lt "$total" ]; then
+ echo "--- 90s inter-vendor gap ---"
+ sleep 90
+ fi
+done
+echo "=== ALL 28 MISSING VENDORS DONE ($(date +%H:%M:%S)) ==="
diff --git a/scripts/colorway-title-anchor/tk11076-pl-exclusion.mjs b/scripts/colorway-title-anchor/tk11076-pl-exclusion.mjs
new file mode 100644
index 0000000..84499b0
--- /dev/null
+++ b/scripts/colorway-title-anchor/tk11076-pl-exclusion.mjs
@@ -0,0 +1,120 @@
+#!/usr/bin/env node
+/**
+ * TK-11076 STEP 1 — PRIVATE-LABEL EXCLUSION (authoritative, deterministic, no guessing).
+ *
+ * Steve's hard scope constraint: run the colorway retag ONLY on lines we do NOT private label.
+ * EXCLUDE every private-label line entirely. If a vendor's PL status is AMBIGUOUS -> EXCLUDE
+ * (conservative) and list it for Steve.
+ *
+ * Sources of truth (in priority order):
+ * 1. vendor_registry.is_private_label / private_label_name (the authoritative flag)
+ * 2. The customer-facing private_label_name values (Phillipe Romano, Malibu Wallpaper, Hollywood, etc.)
+ * — because shopify_products.vendor holds the CUSTOMER-FACING string.
+ * 3. Steve's explicit named directives that OVERRIDE registry drift:
+ * - Greenland (cork, sold as Phillipe Romano; "Greenland" must never surface) — registry has it
+ * flagged false = DRIFT, force-exclude.
+ * - The Phillipe Romano umbrella source lines (astek/command54/rigo/justindavid/greenland/
+ * relativity/twil/mdc) — all sold under "Phillipe Romano".
+ * 4. CONSERVATIVE catch: any addressable vendor whose live name is a DW house/private brand
+ * (not a real outside manufacturer) and whose PL status is not cleanly resolvable -> EXCLUDE + flag.
+ *
+ * Output: out/tk11076-exclusion.json { excluded:[{vendor,count,reason}], clean:[{vendor,write}], ... }
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { execSync } from 'node:child_process';
+
+const HERE = path.dirname(fileURLToPath(import.meta.url));
+const OUT = path.join(HERE, 'out');
+const DB = 'postgresql:///dw_unified?host=/tmp';
+const addr = JSON.parse(fs.readFileSync(path.join(OUT, 'tk11076-addressable-by-vendor.json'), 'utf8'));
+
+const norm = s => String(s || '').toLowerCase().replace(/&/g, '&').replace(/\s*&\s*/g, ' & ')
+ .replace(/[^a-z0-9& ]/g, ' ').replace(/\s+/g, ' ').trim();
+
+// ---- 1+2: registry-driven PL universe (source vendor_name UNION customer-facing private_label_name)
+const regRows = execSync(`psql "${DB}" -tAF '|' -c "` +
+ `SELECT vendor_name, COALESCE(private_label_name,''), is_private_label FROM vendor_registry"`,
+ { encoding: 'utf8' }).trim().split('\n').map(l => l.split('|'));
+
+const plNames = new Set(); // normalized names that are private-label
+const plReason = new Map(); // norm-name -> human reason
+function addPL(name, reason) { const n = norm(name); if (n) { plNames.add(n); if (!plReason.has(n)) plReason.set(n, reason); } }
+
+for (const [vname, plname, ispl] of regRows) {
+ if (ispl === 't') {
+ addPL(vname, `registry is_private_label=true (source line)`);
+ if (plname && plname.trim()) addPL(plname, `registry customer-facing PL brand "${plname}"`);
+ }
+}
+
+// ---- 3: explicit named overrides (Steve's directive + cheat sheet) — force-exclude regardless of registry flag
+const FORCE_EXCLUDE = [
+ ['Greenland', 'Steve directive: Greenland cork sold as Phillipe Romano; name must never surface (registry flag=false is DRIFT)'],
+ ['Greenland Printing', 'Steve directive: Greenland → Phillipe Romano'],
+ ['Los Angeles Fabrics', 'cheat sheet: Justin David fabrics, customer-facing "Los Angeles Fabrics", under Phillipe Romano umbrella (PL)'],
+ ['LA Fabrics', 'PL: Justin David / Phillipe Romano fabric line'],
+];
+for (const [n, r] of FORCE_EXCLUDE) addPL(n, r);
+
+// ---- 4: conservative DW-house-brand catch. Live vendor names that are DW OWN brands (not outside
+// manufacturers) get excluded even if not registry-flagged, and are FLAGGED as ambiguous for Steve.
+// These are matched against the addressable set below; a name qualifies if it is clearly a DW house/
+// private label per its wording. We keep this list explicit (no fuzzy manufacturer over-exclusion).
+const AMBIGUOUS_DW_HOUSE = new Map([
+ ['designer wallcoverings', 'DW house brand (our own label) — is_private_label context; exclude conservatively'],
+ ['dw home', 'DW house brand'],
+ ['dw bespoke studio', 'DW house brand (registry PL)'],
+ ['glass beaded', 'DW house-sourced brand (Glass Bead is registry PL) — exclude conservatively'],
+ ['glass bead', 'DW house-sourced brand (registry PL)'],
+ ['glambeads by dw', 'DW house brand ("by DW")'],
+ ['glitter walls', 'DW multi-source house brand (registry PL)'],
+ ['surface stick', 'DW house/sourced brand — PL status unresolved, exclude conservatively + flag'],
+ ['la walls', 'DW house brand (LA Walls) — PL status unresolved, exclude conservatively + flag'],
+ ['architectural fabrics', 'DW "Architectural" house rebrand line — exclude conservatively + flag'],
+ ['hollywood acoustical', 'Hollywood (registry PL: Momentum/Versa private-labeled as Hollywood)'],
+ ['hollywood wallcoverings', 'registry PL (Momentum/Versa → Hollywood)'],
+ ['los angeles fabrics', 'Justin David / Phillipe Romano (PL)'],
+ ['malibu', 'collapses to the excluded PL brand "Malibu Wallpaper" (WallQuest/Brewster private label) — exclude conservatively'],
+]);
+for (const [n, r] of AMBIGUOUS_DW_HOUSE) { if (!plNames.has(n)) { plNames.add(n); plReason.set(n, r + ' [AMBIGUOUS→EXCLUDE]'); } }
+
+// ---- intersect with the addressable set
+const excluded = [], clean = [], flaggedAmbiguous = [];
+for (const row of addr.per_vendor) {
+ if (row.write === 0) continue; // no confident writes anyway; not in the write set
+ const n = norm(row.vendor);
+ if (plNames.has(n)) {
+ const reason = plReason.get(n);
+ excluded.push({ vendor: row.vendor, write_excluded: row.write, reason });
+ if (/AMBIGUOUS/.test(reason)) flaggedAmbiguous.push({ vendor: row.vendor, write_excluded: row.write, reason });
+ } else {
+ clean.push({ vendor: row.vendor, write: row.write });
+ }
+}
+
+excluded.sort((a, b) => b.write_excluded - a.write_excluded);
+clean.sort((a, b) => b.write - a.write);
+
+const out = {
+ ts: new Date().toISOString(),
+ ticket: 'TK-11076',
+ addressable_write_total: addr.write,
+ excluded_private_label: excluded,
+ excluded_write_count: excluded.reduce((s, r) => s + r.write_excluded, 0),
+ flagged_ambiguous_for_steve: flaggedAmbiguous,
+ clean_non_private_label: clean,
+ clean_write_count: clean.reduce((s, r) => s + r.write, 0),
+};
+fs.writeFileSync(path.join(OUT, 'tk11076-exclusion.json'), JSON.stringify(out, null, 2));
+
+console.log(`\n=== TK-11076 STEP 1: PRIVATE-LABEL EXCLUSION ===`);
+console.log(`addressable write total: ${addr.write}`);
+console.log(`\n--- EXCLUDED (private-label) : ${excluded.length} vendors, ${out.excluded_write_count} writes ---`);
+for (const r of excluded) console.log(` EXCLUDE ${String(r.write_excluded).padStart(4)} ${r.vendor.padEnd(28)} — ${r.reason}`);
+console.log(`\n--- FLAGGED AMBIGUOUS (excluded conservatively, surface to Steve) : ${flaggedAmbiguous.length} ---`);
+for (const r of flaggedAmbiguous) console.log(` FLAG ${String(r.write_excluded).padStart(4)} ${r.vendor.padEnd(28)} — ${r.reason}`);
+console.log(`\n--- CLEAN (non-private-label, eligible to WRITE) : ${clean.length} vendors, ${out.clean_write_count} writes ---`);
+for (const r of clean) console.log(` WRITE ${String(r.write).padStart(4)} ${r.vendor}`);
+console.log(`\nexclusion JSON: ${path.join(OUT, 'tk11076-exclusion.json')}`);
← 7c24b65 TK-11076: Cole & Son color-facet remediation + rollback scri
·
back to Designerwallcoverings
·
TK-11076: Hollywood rollback artifact (no-op — batch judged 86ca8d4 →