[object Object]

← back to Designer Wallcoverings

LN colorway decollapse tool (image-hex → distinct names)

63611c9a40def3daa48592888f1948678ab5da3f · 2026-07-06 15:54:29 -0700 · Steve

Files touched

Diff

commit 63611c9a40def3daa48592888f1948678ab5da3f
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 6 15:54:29 2026 -0700

    LN colorway decollapse tool (image-hex → distinct names)
---
 scripts/decollapse_ln.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 90 insertions(+)

diff --git a/scripts/decollapse_ln.js b/scripts/decollapse_ln.js
new file mode 100644
index 00000000..447b60dd
--- /dev/null
+++ b/scripts/decollapse_ln.js
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+/**
+ * Decollapse live LN colorway collisions (Malibu Laurel - Pacific Mist ×20, Sage Strand - Dune Mist ×2).
+ * Fetch each product's Shopify image → dominant hex (magick) → distinct colorway name.
+ * DRY-RUN by default: prints dw_sku, current title, hex, proposed color. --apply updates PG + Shopify title.
+ */
+require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
+const { Client } = require('pg');
+const { execSync } = require('child_process');
+const fs = require('fs');
+const SHOP = process.env.SHOPIFY_STORE_DOMAIN;
+const TOKEN = process.env.SHOPIFY_ADMIN_ACCESS_TOKEN || process.env.SHOPIFY_ADMIN_TOKEN;
+const VER = process.env.SHOPIFY_ADMIN_API_VERSION || '2024-01';
+const REST = `https://${SHOP}/admin/api/${VER}`;
+const DBURL = process.env.DATABASE_URL || 'postgresql://stevestudio2@/dw_unified?host=/tmp';
+const APPLY = process.argv.includes('--apply');
+const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+const GROUPS = ['Malibu Laurel - Pacific Mist', 'Sage Strand - Dune Mist'];
+
+// small luxury lexicon: [name, r,g,b]
+const LEX = [
+  ['Onyx',20,20,22],['Charcoal',60,63,66],['Slate',80,90,100],['Pewter',128,128,124],['Dove Grey',160,162,160],
+  ['Silver',196,198,196],['Alabaster',232,230,222],['Ivory',238,232,208],['Oatmeal',214,208,180],['Linen',210,200,178],
+  ['Greige',198,192,180],['Sand',201,183,140],['Wheat',188,164,110],['Camel',183,150,110],['Taupe',140,128,110],
+  ['Mushroom',150,138,124],['Fog',200,206,206],['Mist',186,200,198],['Seafoam',176,206,196],['Celadon',150,178,160],
+  ['Sage',132,155,140],['Eucalyptus',120,150,140],['Juniper',70,95,80],['Pine',40,80,60],['Emerald',12,90,66],
+  ['Teal',25,110,120],['Lagoon',10,110,130],['Steel Blue',60,100,140],['Denim',55,95,130],['Indigo',30,45,80],
+  ['Navy',25,40,70],['Midnight',20,30,55],['Aegean',40,110,150],['Aqua',150,205,205],['Blush',235,205,192],
+  ['Terracotta',180,95,75],['Garnet',150,55,60],['Oxblood',110,45,45],['Cocoa',90,70,55],['Espresso',55,45,35],
+];
+const nameFor = (r, g, b) => { let best = LEX[0], bd = 1e9; for (const [n, R, G, B] of LEX) { const d = (r - R) ** 2 + (g - G) ** 2 + (b - B) ** 2; if (d < bd) { bd = d; best = [n]; } } return best[0]; };
+
+async function rest(method, path, body) {
+  for (let a = 0; a < 6; a++) {
+    const res = await fetch(`${REST}${path}`, { method, headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: body ? JSON.stringify(body) : undefined });
+    if (res.status === 429) { await sleep(2000); continue; }
+    if (!res.ok) throw new Error(`${res.status}: ${(await res.text()).slice(0, 120)}`);
+    return res.json();
+  }
+  throw new Error('429');
+}
+
+(async () => {
+  const db = new Client({ connectionString: DBURL }); await db.connect();
+  const { rows } = await db.query(
+    `SELECT dw_sku, shopify_product_id, pattern_name, color_name
+     FROM phillipe_romano_catalog
+     WHERE created_at::date='2026-07-06' AND mfr_sku LIKE 'LN%' AND shopify_product_id IS NOT NULL
+       AND (pattern_name||' - '||color_name) = ANY($1) ORDER BY dw_sku`, [GROUPS]);
+  fs.mkdirSync('/tmp/ln-dc', { recursive: true });
+  const used = {}; // per-pattern name uniqueness
+  for (const r of rows) {
+    const prod = (await rest('GET', `/products/${r.shopify_product_id}.json?fields=images,title`)).product;
+    const src = prod.images?.[0]?.src;
+    let hex = '?', nm = 'Mist';
+    if (src) {
+      const f = `/tmp/ln-dc/${r.dw_sku}.jpg`;
+      try {
+        execSync(`curl -s -A Mozilla/5.0 "${src}" -o "${f}"`, { timeout: 30000 });
+        hex = execSync(`magick "${f}" -resize 1x1! -format '%[hex:p{0,0}]' info:`, { timeout: 15000 }).toString().trim();
+        const r2 = parseInt(hex.slice(0, 2), 16), g2 = parseInt(hex.slice(2, 4), 16), b2 = parseInt(hex.slice(4, 6), 16);
+        nm = nameFor(r2, g2, b2);
+      } catch {}
+    }
+    // uniqueness within pattern: append qualifier if taken
+    const key = r.pattern_name; used[key] ||= new Set();
+    let final = nm; let i = 2;
+    while (used[key].has(final)) { final = `${nm} ${i++}`; }
+    used[key].add(final);
+    r.hex = hex; r.newColor = final; r.newTitle = `${r.pattern_name} - ${final}`;
+    await sleep(300);
+  }
+
+  console.log(`${APPLY ? '' : '[DRY-RUN] '}Decollapse ${rows.length} live LN products`);
+  console.log('DW_SKU        HEX      OLD → NEW COLOR');
+  for (const r of rows) console.log(`  ${r.dw_sku.padEnd(13)} #${r.hex.padEnd(7)} ${r.color_name} → ${r.newColor}`);
+
+  if (!APPLY) { console.log('\nNO WRITES. --apply to update dw_unified color_name + Shopify title.'); await db.end(); return; }
+  console.log('\nApplying…'); let ok = 0;
+  for (const r of rows) {
+    try {
+      await rest('PUT', `/products/${r.shopify_product_id}.json`, { product: { id: Number(r.shopify_product_id), title: `${r.newTitle} | Phillipe Romano` } });
+      await db.query(`UPDATE phillipe_romano_catalog SET color_name=$1, pl_updated_at=now() WHERE dw_sku=$2`, [r.newColor, r.dw_sku]);
+      ok++;
+    } catch (e) { console.log(`  ✗ ${r.dw_sku}: ${e.message.slice(0, 100)}`); }
+    await sleep(350);
+  }
+  console.log(`Decollapsed ${ok}/${rows.length}`);
+  await db.end();
+})().catch(e => { console.error('FATAL', e); process.exit(1); });

← d1369ad4 WallQuest->PR: Lillian August 140 live (material titles) + P  ·  back to Designer Wallcoverings  ·  Create Phillipe Romano 'Abruzzo Natural Collection' (368 Wal cd7214e5 →