← back to Dw Pairs Well
WS-1: junk-root denylist (SANCAR/INNOVATIONS/TRUE/placeholders → title fallback) + bulk mfr_sku pull tool
12d667726f82103b2ae0aeb526910752dac414f3 · 2026-06-26 09:12:47 -0700 · Steve
Files touched
M tools/pattern-id-backfill.jsA tools/pull-mfr-sku.js
Diff
commit 12d667726f82103b2ae0aeb526910752dac414f3
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jun 26 09:12:47 2026 -0700
WS-1: junk-root denylist (SANCAR/INNOVATIONS/TRUE/placeholders → title fallback) + bulk mfr_sku pull tool
---
tools/pattern-id-backfill.js | 15 +++++++++++++--
tools/pull-mfr-sku.js | 33 +++++++++++++++++++++++++++++++++
2 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/tools/pattern-id-backfill.js b/tools/pattern-id-backfill.js
index 697c9a9..90a7691 100644
--- a/tools/pattern-id-backfill.js
+++ b/tools/pattern-id-backfill.js
@@ -98,21 +98,32 @@ function stripColors(normalized) {
// W7008-01 -> W7008
// AT9604 -> AT9604 (no colorway → unchanged)
// 5004561 -> 5004561 (single pure-numeric token → keep, never empty out)
+// Junk roots: a few vendors stored a SUPPLIER NAME or boolean in the manufacturer_sku
+// metafield instead of a pattern code. These must NEVER become a pattern_id — demote to
+// the T2 title fallback. (DTD verdict A, 2026-06-26.)
+const DENY_ROOTS = new Set([
+ 'SANCAR','INNOVATIONS','TRUE','FALSE','NULL','NONE','NA','UNKNOWN','TBD','TEST',
+ 'SAMPLE','DEFAULT','N','Y','YES','NO','0','1','PENDING','TBA','VARIOUS','ASSORTED'
+]);
function mfrRoot(raw) {
if (!raw) return null;
let s = String(raw).trim().toUpperCase();
if (!s) return null;
// split on the common SKU separators
const tokens = s.split(/[-/_.\s]+/).filter(Boolean);
+ let root;
if (tokens.length >= 2) {
const last = tokens[tokens.length - 1];
// colorway suffix = 1-3 digits, optionally one trailing letter (01, 001, 01A)
if (/^\d{1,3}[A-Z]?$/.test(last)) {
- return tokens.slice(0, -1).join('-');
+ root = tokens.slice(0, -1).join('-');
}
}
// no separable colorway → root is the whole thing (separators normalized to '-')
- return tokens.join('-');
+ if (!root) root = tokens.join('-');
+ // junk-root guard: supplier names / booleans / placeholders → not a real pattern key
+ if (DENY_ROOTS.has(root)) return null;
+ return root;
}
function isTexture(tagsLower, titleLower) {
diff --git a/tools/pull-mfr-sku.js b/tools/pull-mfr-sku.js
new file mode 100644
index 0000000..2a4c38b
--- /dev/null
+++ b/tools/pull-mfr-sku.js
@@ -0,0 +1,33 @@
+// READ-ONLY: bulk-pull custom.manufacturer_sku for all ACTIVE products → tools/mfr-map.jsonl
+// Uses the full-access token (write_products implies read). No writes. $0 (Shopify API).
+const https=require('https');
+const STORE='designer-laboratory-sandbox.myshopify.com', API='2024-10';
+const fs=require('fs'), cp=require('child_process');
+const TOK=cp.execSync(`grep -rhoE 'shpat_[a-f0-9]{32}' ~/Projects/secrets-manager/.env ~/Projects/Designer-Wallcoverings/DW-Agents ~/Projects/Designer-Wallcoverings/shopify 2>/dev/null | sort -u`).toString().split('\n').find(t=>t.startsWith('shpat_307')&&t.endsWith('a43b'));
+if(!TOK){console.error('no full-access token');process.exit(1);}
+function gql(q){return new Promise((res,rej)=>{const body=JSON.stringify({query:q});const r=https.request({host:STORE,path:`/admin/api/${API}/graphql.json`,method:'POST',headers:{'X-Shopify-Access-Token':TOK,'Content-Type':'application/json','Content-Length':Buffer.byteLength(body)}},resp=>{let d='';resp.on('data',c=>d+=c);resp.on('end',()=>{try{res(JSON.parse(d))}catch(e){rej(d.slice(0,300))}})});r.on('error',rej);r.write(body);r.end();});}
+(async()=>{
+ const start=await gql(`mutation{bulkOperationRunQuery(query:"""
+ { products(query:"status:active") { edges { node { id handle vendor
+ metafield(namespace:"custom",key:"manufacturer_sku"){ value }
+ variants(first:1){ edges { node { sku } } } } } } }
+ """){ bulkOperation{ id status } userErrors{ field message } } }`);
+ const ue=start.data?.bulkOperationRunQuery?.userErrors;
+ if(ue&&ue.length){console.error('userErrors',JSON.stringify(ue));process.exit(1);}
+ console.log('bulk op started:',start.data.bulkOperationRunQuery.bulkOperation.id);
+ // poll
+ let url=null, tries=0;
+ while(tries++<120){
+ await new Promise(r=>setTimeout(r,5000));
+ const cur=await gql(`{ currentBulkOperation{ status objectCount url errorCode } }`);
+ const o=cur.data.currentBulkOperation;
+ process.stdout.write(`\r ${o.status} objects=${o.objectCount||0} `);
+ if(o.status==='COMPLETED'){url=o.url;console.log('\n COMPLETED objects=',o.objectCount);break;}
+ if(o.status==='FAILED'){console.error('\n FAILED',o.errorCode);process.exit(1);}
+ }
+ if(!url){console.error('\n timed out');process.exit(1);}
+ // download JSONL
+ await new Promise((res,rej)=>{const f=fs.createWriteStream('tools/mfr-map.jsonl');https.get(url,r=>{r.pipe(f);f.on('finish',()=>{f.close();res();});}).on('error',rej);});
+ const n=fs.readFileSync('tools/mfr-map.jsonl','utf8').trim().split('\n').filter(Boolean).length;
+ console.log('saved tools/mfr-map.jsonl lines=',n);
+})().catch(e=>{console.error('ERR',e);process.exit(1);});
← de4b0bc auto-save: 2026-06-26T08:13:39 (3 files) — server.js tools/g
·
back to Dw Pairs Well
·
auto-save: 2026-06-26T09:14:14 (4 files) — tools/pattern-id- 9454dbf →