[object Object]

← back to Designer Wallcoverings

Osborne dry-run of metafield-push patch (0 writes): 2/2 heal; adds width_inches normalization + gate-columns-only refinements

440ff0c3fbc8b0a1f5969ecdd8e9bdebfa6595fb · 2026-06-20 11:30:15 -0700 · Steve

Files touched

Diff

commit 440ff0c3fbc8b0a1f5969ecdd8e9bdebfa6595fb
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jun 20 11:30:15 2026 -0700

    Osborne dry-run of metafield-push patch (0 writes): 2/2 heal; adds width_inches normalization + gate-columns-only refinements
---
 .../cadence/activate-gated.metafield-push.PATCH.md | 13 +++++++
 .../cadence/dryrun-osborne-metafield-push.js       | 44 ++++++++++++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/shopify/scripts/cadence/activate-gated.metafield-push.PATCH.md b/shopify/scripts/cadence/activate-gated.metafield-push.PATCH.md
index 1278bfa2..47c951fe 100644
--- a/shopify/scripts/cadence/activate-gated.metafield-push.PATCH.md
+++ b/shopify/scripts/cadence/activate-gated.metafield-push.PATCH.md
@@ -95,3 +95,16 @@ That relink is a separate read-only task — do it before the backfill to lift c
 Live writes (the `--commit` runs, the deploy of the patched `activate-gated.js`, the backfill
 `--commit`) remain Steve-gated. This draft is files-only: the helper is inert, this patch is
 not applied, nothing deployed, no store writes.
+
+## Dry-run findings (Osborne, 2026-06-20) — 2 refinements before --commit
+Ran `dryrun-osborne-metafield-push.js` (read-only, 0 writes) on the 2 Osborne width-gaps:
+- ✅ Both would heal: push `global.width`/`custom.width` + material + `*.manufacturer_sku`; sample $4.25 + ACTIVE status untouched.
+- **REFINEMENT 1 — normalize width.** The raw `width` column is a compound string
+  (`"10m x 52cm (11yds x 20 ins)"`). Prefer the catalog's `width_inches` → `"20.00 Inches"`.
+  Update `candidates()` to also select `width_inches` and pass the normalized value to the
+  helper (fallback to raw `width` only when `width_inches` is empty).
+- **REFINEMENT 2 — only push gate-validated columns.** Do NOT map vendor-specific columns
+  blind (Osborne's `repeat_v` holds a flammability rating, not a repeat). The push must use
+  ONLY the columns `candidates()` already detects+validates (width/roll_length/pattern_repeat/
+  material), which is what the real patch does — verified safe for Osborne (no `pattern_repeat`
+  column → no bad repeat pushed).
diff --git a/shopify/scripts/cadence/dryrun-osborne-metafield-push.js b/shopify/scripts/cadence/dryrun-osborne-metafield-push.js
new file mode 100644
index 00000000..d0542442
--- /dev/null
+++ b/shopify/scripts/cadence/dryrun-osborne-metafield-push.js
@@ -0,0 +1,44 @@
+'use strict';
+// DRY-RUN demo of the metafield-push patch on real Osborne data. ZERO writes:
+// pulls each gap product's LIVE metafields + its osborne_catalog row, runs the
+// patch's buildSpecMetafields(), and prints what WOULD be pushed before ACTIVATE.
+const fs = require('fs'), path = require('path'), { execFileSync } = require('child_process');
+for (const p of [path.join(__dirname, '..', '..', '.env'), path.join(__dirname, '..', '.env')])
+  if (fs.existsSync(p)) for (const l of fs.readFileSync(p, 'utf8').split('\n')) { const m = l.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/); if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^["']|["']$/g, ''); }
+const { buildSpecMetafields } = require('../lib/push-spec-metafields');
+const STORE = 'designer-laboratory-sandbox.myshopify.com', TOKEN = process.env.SHOPIFY_ADMIN_TOKEN, VER = '2024-10';
+const psql = sql => execFileSync('psql', ['-d', 'dw_unified', '-tAF\t', '-c', sql], { encoding: 'utf8' });
+async function gql(q, v) { const r = await fetch(`https://${STORE}/admin/api/${VER}/graphql.json`, { method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ query: q, variables: v }) }); return r.json(); }
+
+(async () => {
+  // 1. Osborne width-gap products from the worklist (variant sku ends -OSB)
+  const wl = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'activated-viewer', 'worklist.json'), 'utf8'));
+  const osb = wl.rows.filter(r => r.gaps.includes('width') && /-OSB$/i.test(r.sku)).slice(0, 8);
+  console.log(`Osborne width-gap products in worklist: ${wl.rows.filter(r => /-OSB$/i.test(r.sku)).length} (showing ${osb.length})\n`);
+  if (!osb.length) { console.log('none matched -OSB; nothing to demo'); return; }
+
+  // 2. live status+metafields for these product ids (read-only)
+  const ids = osb.map(r => `gid://shopify/Product/${r.id}`);
+  const Q = `query($ids:[ID!]!){nodes(ids:$ids){... on Product{ id title status variants(first:10){nodes{price sku}} metafields(first:60){nodes{namespace key value}} }}}`;
+  const nodes = (await gql(Q, { ids })).data.nodes.filter(Boolean);
+
+  let wouldHeal = 0;
+  for (const n of nodes) {
+    const w = osb.find(r => `gid://shopify/Product/${r.id}` === n.id);
+    const mfr = (w.sku || '').replace(/-OSB$/i, '');
+    // 3. vendor catalog row by mfr_sku
+    const row = psql(`SELECT mfr_sku, dw_sku, coalesce(width,''), coalesce(material,''), coalesce(width_inches::text,'') FROM osborne_catalog WHERE mfr_sku='${mfr.replace(/'/g, "''")}' LIMIT 1`).trim();
+    const [m_sku, dw_sku, width, material, width_in] = row ? row.split('\t') : ['', '', '', '', ''];
+    const cleanW = (width_in && width_in.trim()) ? (width_in.trim().replace(/[^0-9.]/g,'') + ' Inches') : width;
+    const vendorRow = { width: cleanW, material, roll_length: '', pattern_repeat: '', mfr_sku: m_sku, dw_sku };
+    // 4. run the PATCH's builder against live metafields (commit=false → nothing written)
+    const toPush = buildSpecMetafields(vendorRow, n.metafields.nodes);
+    const sample = n.variants.nodes.find(v => /sample/i.test(v.sku) || v.price === '4.25');
+    if (toPush.length) wouldHeal++;
+    console.log(`• ${w.sku}  [${n.status}]  "${n.title.slice(0, 34)}"`);
+    console.log(`    catalog row: width_raw="${width}"  width_inches="${width_in||''}"  → normalized="${cleanW}"  material="${material}"  mfr=${m_sku||'<no match>'}`);
+    console.log(`    WOULD PUSH : ${toPush.length ? toPush.map(m => `${m.namespace}.${m.key}="${m.value}"`).join('  ') : '(nothing — already complete)'}`);
+    console.log(`    untouched  : sample variant ${sample ? '$' + sample.price + ' ✓' : 'n/a'} · status stays ${n.status} (DRY-RUN, no write)\n`);
+  }
+  console.log(`SUMMARY: ${nodes.length} Osborne gap products inspected · ${wouldHeal} would heal (width metafield pushed from catalog) · 0 writes performed.`);
+})().catch(e => { console.error('DRYRUN ERR:', e.message); process.exit(1); });

← 080d706c DRAFT (no deploy): push-spec-metafields helper + activate-ga  ·  back to Designer Wallcoverings  ·  Live: pushed width+mfr_sku metafields to 3 Osborne actives ( 87886372 →