← back to Designer Wallcoverings
Add Tres Tintas final completeness audit (desc/specs-by-type/room across all 592, roll-vs-mural spec shape aware)
f0d8c26f7535e1c8c2781e1377aaecadd7a1f783 · 2026-06-23 12:03:26 -0700 · Steve Abrams
Files touched
A shopify/scripts/tres-tintas-final-audit.js
Diff
commit f0d8c26f7535e1c8c2781e1377aaecadd7a1f783
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 23 12:03:26 2026 -0700
Add Tres Tintas final completeness audit (desc/specs-by-type/room across all 592, roll-vs-mural spec shape aware)
---
shopify/scripts/tres-tintas-final-audit.js | 136 +++++++++++++++++++++++++++++
1 file changed, 136 insertions(+)
diff --git a/shopify/scripts/tres-tintas-final-audit.js b/shopify/scripts/tres-tintas-final-audit.js
new file mode 100644
index 00000000..e8b0f989
--- /dev/null
+++ b/shopify/scripts/tres-tintas-final-audit.js
@@ -0,0 +1,136 @@
+#!/usr/bin/env node
+/**
+ * tres-tintas-final-audit.js
+ *
+ * Final completeness audit across ALL Tres Tintas products. For each product
+ * reports whether it has:
+ * - DESC : a real description (>120 chars), no old embedded spec table,
+ * no banned word "Wallpaper"
+ * - SPECS : correct shape for its type —
+ * ROLL (non-mural): width + length + repeat + weight +
+ * match + finish + material + origin
+ * MURAL (/mural/ ref, M-prefix): material/weight/finish/origin
+ * (width/repeat legitimately absent — NOT a defect)
+ * - ROOM : a 2nd image OR an image with a room/setting alt
+ *
+ * Classifies each product COMPLETE / INCOMPLETE and lists the misses.
+ * Read-only. Writes a JSON report to tres-tintas-room-backups/_final-audit.json.
+ *
+ * node tres-tintas-final-audit.js
+ */
+const fs = require('fs');
+const path = require('path');
+
+const STORE = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-01';
+function envval(key) {
+ const env = fs.readFileSync(path.join(process.env.HOME, 'Projects/secrets-manager/.env'), 'utf8');
+ const m = env.match(new RegExp(`^${key}=["']?([^"'\\n]+)`, 'm'));
+ return m ? m[1].trim() : null;
+}
+const TOKEN = envval('SHOPIFY_DRAFT_TOKEN');
+const H = { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' };
+const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+
+async function fetchRetry(url, opts, tries = 3, timeoutMs = 45000) {
+ let e; for (let i = 0; i < tries; i++) { try { return await fetch(url, { ...opts, signal: AbortSignal.timeout(timeoutMs) }); } catch (err) { e = err; await sleep(800 * (i + 1)); } } throw e;
+}
+async function gql(query, variables) {
+ const r = await fetchRetry(`https://${STORE}/admin/api/${API}/graphql.json`, { method: 'POST', headers: H, body: JSON.stringify({ query, variables }) });
+ const j = await r.json(); if (j.errors) throw new Error(JSON.stringify(j.errors)); return j.data;
+}
+
+// Is this a mural? mural refs are M-prefix (M2503-1) or have a /mural/ tag/ref.
+function isMural(title, tags) {
+ const refTag = (tags || []).find(t => /^pattern-ref-/i.test(t));
+ const ref = refTag ? refTag.replace(/^pattern-ref-/i, '') : '';
+ const titleSku = (/\b([A-Za-z]{1,3}\d{3,4}-\d+)\b/.exec(title || '') || [])[1] || '';
+ const sku = ref || titleSku;
+ return /^M\d/i.test(sku); // M-prefix = mural quality
+}
+
+async function main() {
+ if (!TOKEN) throw new Error('missing SHOPIFY_DRAFT_TOKEN');
+ const q = `query($cursor:String){ products(first:50, query:"vendor:'Tres Tintas'", after:$cursor){
+ pageInfo{ hasNextPage endCursor }
+ edges{ node{ id title status tags descriptionHtml
+ images(first:10){ edges{ node{ altText } } }
+ metafields(first:50){ edges{ node{ namespace key value } } } } } } }`;
+
+ let cursor = null;
+ const rows = [];
+ while (true) {
+ const conn = (await gql(q, { cursor })).products;
+ for (const e of conn.edges) {
+ const n = e.node;
+ const id = n.id.split('/').pop();
+ const tags = (n.tags || []).map(t => t.trim());
+ const mf = {};
+ for (const m of n.metafields.edges) mf[`${m.node.namespace}.${m.node.key}`] = m.node.value;
+ const has = (k) => mf[k] && String(mf[k]).trim() !== '';
+
+ // DESC
+ const desc = (n.descriptionHtml || '');
+ const descOk = desc.length > 120 && !/<table/i.test(desc) && !/\bwallpaper\b/i.test(desc);
+
+ // ROOM (2nd image or room-alt)
+ const alts = n.images.edges.map(x => (x.node.altText || ''));
+ const roomOk = n.images.edges.length > 1 || alts.some(a => /room|setting|interior|ambiente/i.test(a));
+
+ // SPECS by type
+ const mural = isMural(n.title, tags);
+ let specsOk, specMiss = [];
+ const need = mural
+ ? ['custom.product_weight', 'global.Finish', 'custom.material', 'custom.origin']
+ : ['custom.width', 'custom.length', 'custom.pattern_repeat', 'custom.product_weight', 'global.MATCH', 'global.Finish', 'custom.material', 'custom.origin'];
+ for (const k of need) if (!has(k)) specMiss.push(k);
+ specsOk = specMiss.length === 0;
+
+ const complete = descOk && roomOk && specsOk;
+ rows.push({ id, title: n.title, status: n.status, type: mural ? 'mural' : 'roll',
+ descOk, roomOk, specsOk, specMiss, complete });
+ }
+ if (conn.pageInfo.hasNextPage) cursor = conn.pageInfo.endCursor; else break;
+ }
+
+ const active = rows.filter(r => r.status === 'ACTIVE');
+ const archived = rows.filter(r => r.status === 'ARCHIVED');
+ const summarize = (set) => ({
+ total: set.length,
+ complete: set.filter(r => r.complete).length,
+ descMiss: set.filter(r => !r.descOk).length,
+ specMiss: set.filter(r => !r.specsOk).length,
+ roomMiss: set.filter(r => !r.roomOk).length,
+ });
+
+ const report = {
+ generatedAt: new Date().toISOString(),
+ overall: { total: rows.length, complete: rows.filter(r => r.complete).length },
+ active: summarize(active),
+ archived: summarize(archived),
+ rolls: summarize(rows.filter(r => r.type === 'roll')),
+ murals: summarize(rows.filter(r => r.type === 'mural')),
+ incompleteActive: active.filter(r => !r.complete).map(r => ({
+ id: r.id, title: r.title, type: r.type,
+ miss: [!r.descOk && 'desc', !r.roomOk && 'room', !r.specsOk && `specs(${r.specMiss.join(',')})`].filter(Boolean),
+ })),
+ };
+
+ const out = path.join(__dirname, '..', 'tres-tintas-room-backups', '_final-audit.json');
+ fs.writeFileSync(out, JSON.stringify(report, null, 2));
+
+ console.log('=== TRES TINTAS FINAL AUDIT ===');
+ console.log(`overall: ${report.overall.complete}/${report.overall.total} complete`);
+ console.log(`ACTIVE : ${active.length} total | complete ${report.active.complete} | desc-miss ${report.active.descMiss} | spec-miss ${report.active.specMiss} | room-miss ${report.active.roomMiss}`);
+ console.log(`ARCHIVED: ${archived.length} total | complete ${report.archived.complete} | (archived excluded from go-live gate)`);
+ console.log(` rolls : ${report.rolls.complete}/${report.rolls.total} complete`);
+ console.log(` murals : ${report.murals.complete}/${report.murals.total} complete`);
+ if (report.incompleteActive.length) {
+ console.log(`\nINCOMPLETE ACTIVE (${report.incompleteActive.length}):`);
+ for (const r of report.incompleteActive) console.log(` ${r.id} [${r.type}] ${r.title} -> ${r.miss.join(' | ')}`);
+ } else {
+ console.log('\nAll ACTIVE products COMPLETE.');
+ }
+ console.log(`\nreport -> ${out}`);
+}
+main().catch(e => { console.error(e); process.exit(1); });
← ea390dcd Spec recrawl: title-SKU fallback for the 22 active Tres Tint
·
back to Designer Wallcoverings
·
dw-vendor-collection-url: use collection.id existence test + caa2d35e →