[object Object]

← back to Designerwallcoverings

Correct Thibaut source-gap evidence and stage specifications request

3e683ff02c6f08e2ca1e2dcf4163726826f3f850 · 2026-09-10 20:19:49 -0700 · Steve Abrams

Files touched

Diff

commit 3e683ff02c6f08e2ca1e2dcf4163726826f3f850
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 10 20:19:49 2026 -0700

    Correct Thibaut source-gap evidence and stage specifications request
---
 scripts/thibaut-wide-width-length-gap/diagnose.mjs | 29 +++++++++++++---------
 .../source-status.mjs                              |  9 +++++++
 .../source-status.test.mjs                         | 16 ++++++++++++
 3 files changed, 42 insertions(+), 12 deletions(-)

diff --git a/scripts/thibaut-wide-width-length-gap/diagnose.mjs b/scripts/thibaut-wide-width-length-gap/diagnose.mjs
index 9be4306..7153627 100644
--- a/scripts/thibaut-wide-width-length-gap/diagnose.mjs
+++ b/scripts/thibaut-wide-width-length-gap/diagnose.mjs
@@ -2,14 +2,14 @@
 /**
  * TK-11358 — Thibaut "Wide Width" (TWW-prefix) roll-length data-sourcing gap diagnostic.
  * READ-ONLY. Confirms live Shopify state + joins against the local dw_unified thibaut_catalog
- * staging table to show that the SOURCE has no roll-length data for these SKUs (not a
- * carry-through/backfill bug — a genuine vendor-PDP data gap, verified via the raw scraped
- * `specs.source_url` + `specs.pdp_single_roll_yd: 0` captured at scrape time).
+ * staging table. Blank/zero staging values and missing joins are separate evidence classes.
+ * This query does not verify current vendor PDPs or cover the full four-vendor canary.
  *
- * Usage: node diagnose.mjs > out/report.json
+ * Usage: node diagnose.mjs (writes out/report.json; prints a summary)
  */
 import { readFileSync, writeFileSync } from 'node:fs';
-import { execSync } from 'node:child_process';
+import { execFileSync } from 'node:child_process';
+import { sourceStatus } from './source-status.mjs';
 
 const envTxt = readFileSync('/Users/macstudio3/Projects/secrets-manager/.env', 'utf8');
 const TOKEN = envTxt.split('\n').find(l => l.startsWith('SHOPIFY_ADMIN_TOKEN='))?.split('=').slice(1).join('=').trim();
@@ -64,28 +64,32 @@ async function main() {
   let catalogRows = [];
   if (skus.length) {
     const sqlList = skus.map(s => `'${s.replace(/'/g,"''")}'`).join(',');
-    const sql = `SELECT dw_sku, mfr_sku, pattern_name, color_name, width, roll_length FROM thibaut_catalog WHERE dw_sku IN (${sqlList});`;
-    const out = execSync(`psql "host=/tmp dbname=dw_unified" -t -A -F'|' -c "${sql.replace(/"/g,'\\"')}"`, { encoding: 'utf8' });
-    catalogRows = out.trim().split('\n').filter(Boolean).map(l => {
-      const [dw_sku, mfr_sku, pattern_name, color_name, width, roll_length] = l.split('|');
-      return { dw_sku, mfr_sku, pattern_name, color_name, width, roll_length };
-    });
+    const sql = `SELECT COALESCE(json_agg(t), '[]'::json) FROM (SELECT dw_sku, mfr_sku, pattern_name, color_name, width, roll_length FROM thibaut_catalog WHERE dw_sku IN (${sqlList})) t;`;
+    const out = execFileSync('psql', ['host=/tmp dbname=dw_unified', '-X', '-t', '-A', '-v', 'ON_ERROR_STOP=1', '-c', sql], { encoding: 'utf8' });
+    catalogRows = JSON.parse(out);
   }
   const catalogBySku = Object.fromEntries(catalogRows.map(r => [r.dw_sku, r]));
   const emptyAtSource = lenLess.filter(p => {
     const c = catalogBySku[p.dwsku?.value];
-    return !c || !c.roll_length || /^0/.test(c.roll_length.trim()) || c.roll_length.trim() === '';
+    return ['blank', 'zero'].includes(sourceStatus(c));
   });
 
   const report = {
     generated_at: new Date().toISOString(),
+    scope: 'Thibaut active Wide Width title query; not the complete four-vendor canary population',
+    source_evidence: 'Local thibaut_catalog snapshot; current vendor PDPs not checked',
     active_wide_width_thibaut: active.length,
     draft_wide_width_thibaut_still_queued: draft.length,
     active_wide_width_length_less: lenLess.length,
     length_less_confirmed_empty_at_source: emptyAtSource.length,
     length_less_NOT_confirmed_empty_at_source: lenLess.length - emptyAtSource.length,
+    source_status_counts: Object.fromEntries(['blank', 'zero', 'unmatched', 'positive', 'unparsed'].map(status => [status,
+      lenLess.filter(p => sourceStatus(catalogBySku[p.dwsku?.value]) === status).length])),
     rows: lenLess.map(p => ({
+      product_id: p.id,
       handle: p.handle, dw_sku: p.dwsku?.value, title: p.title,
+      single_roll_length: p.sr?.value ?? null, double_roll_length: p.dr?.value ?? null,
+      source_status: sourceStatus(catalogBySku[p.dwsku?.value]),
       status: p.status, createdAt: p.createdAt, updatedAt: p.updatedAt,
       catalog: catalogBySku[p.dwsku?.value] || null,
     })),
@@ -97,6 +101,7 @@ async function main() {
     active_wide_width_length_less: report.active_wide_width_length_less,
     length_less_confirmed_empty_at_source: report.length_less_confirmed_empty_at_source,
     length_less_NOT_confirmed_empty_at_source: report.length_less_NOT_confirmed_empty_at_source,
+    source_status_counts: report.source_status_counts,
   }, null, 2));
 }
 main().catch(e => { console.error('ERROR', e); process.exit(1); });
diff --git a/scripts/thibaut-wide-width-length-gap/source-status.mjs b/scripts/thibaut-wide-width-length-gap/source-status.mjs
new file mode 100644
index 0000000..9a2e323
--- /dev/null
+++ b/scripts/thibaut-wide-width-length-gap/source-status.mjs
@@ -0,0 +1,9 @@
+// Missing joins are unknown; only actual staging values can be classified.
+export function sourceStatus(catalog) {
+  if (!catalog) return 'unmatched';
+  const length = String(catalog.roll_length ?? '').trim();
+  if (!length) return 'blank';
+  const number = length.match(/^(\d+(?:\.\d+)?)(?=\s|$)/);
+  if (!number) return 'unparsed';
+  return Number(number[1]) === 0 ? 'zero' : 'positive';
+}
diff --git a/scripts/thibaut-wide-width-length-gap/source-status.test.mjs b/scripts/thibaut-wide-width-length-gap/source-status.test.mjs
new file mode 100644
index 0000000..b6829da
--- /dev/null
+++ b/scripts/thibaut-wide-width-length-gap/source-status.test.mjs
@@ -0,0 +1,16 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { sourceStatus } from './source-status.mjs';
+
+test('unknown joins and invalid source text are not confirmed empty', () => {
+  assert.equal(sourceStatus(null), 'unmatched');
+  assert.equal(sourceStatus(undefined), 'unmatched');
+  assert.equal(sourceStatus({ roll_length: 'TBD' }), 'unparsed');
+  assert.equal(sourceStatus({ roll_length: '0 unknown label' }), 'zero');
+});
+
+test('blank and numeric zero differ from positive lengths with leading zero', () => {
+  for (const roll_length of ['', '  ', null]) assert.equal(sourceStatus({ roll_length }), 'blank');
+  for (const roll_length of ['0 yd (0.00 m)', '0.00 m', '0']) assert.equal(sourceStatus({ roll_length }), 'zero');
+  for (const roll_length of ['0.5 yd', '09 yd', '9 yd (8.23 m)']) assert.equal(sourceStatus({ roll_length }), 'positive');
+});

← 1a161c2 auto-data-snapshot: 2026-09-10T18:07:04 (15 data files) — da  ·  back to Designerwallcoverings  ·  auto-data-snapshot: 2026-09-10T21:50:55 (1 data files) — scr 768920f →