← back to Dw Yolo Loop
Kravet off-MAP: read-only paginated measurer (no bulk-op, no Kamatera; joins live price to 2026 MAP via custom.manufacturer_sku)
f57943b8f5f5b28f7dee70ef332b022a9d3c1131 · 2026-06-15 10:10:28 -0700 · Steve Abrams
Files touched
A scripts/kravet-offmap-paginated.js
Diff
commit f57943b8f5f5b28f7dee70ef332b022a9d3c1131
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 15 10:10:28 2026 -0700
Kravet off-MAP: read-only paginated measurer (no bulk-op, no Kamatera; joins live price to 2026 MAP via custom.manufacturer_sku)
---
scripts/kravet-offmap-paginated.js | 73 ++++++++++++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/scripts/kravet-offmap-paginated.js b/scripts/kravet-offmap-paginated.js
new file mode 100644
index 0000000..8f3e7cc
--- /dev/null
+++ b/scripts/kravet-offmap-paginated.js
@@ -0,0 +1,73 @@
+#!/usr/bin/env node
+/* READ-ONLY off-MAP measurement WITHOUT a bulk op (avoids collision w/ scheduled catalog op).
+ Paginates products per Kravet-family vendor via GraphQL, joins live price to 2026 MAP via the
+ custom.manufacturer_sku metafield (== MAP-file key), unit-aware where /tmp/sku_unit.json knows it,
+ sample-trap guarded ($4.25 / option=Sample excluded). Writes /tmp/kravet_offmap_paginated.csv.
+ MAP source = /tmp/all_sku_newmap.txt (Jun-13 dump of auth_pricing.new_map). No writes anywhere. */
+const https=require('https'), fs=require('fs');
+const T=process.env.T, S='designer-laboratory-sandbox.myshopify.com';
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+function gql(q){return new Promise((res,rej)=>{const rq=https.request(`https://${S}/admin/api/2024-10/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':T,'Content-Type':'application/json'}},x=>{let b='';x.on('data',d=>b+=d);x.on('end',()=>{try{res(JSON.parse(b))}catch(e){rej(b.slice(0,300))}})});rq.write(JSON.stringify({query:q}));rq.end();});}
+
+const MAP={}; fs.readFileSync('/tmp/all_sku_newmap.txt','utf8').trim().split('\n').forEach(l=>{const[s,m]=l.split('|');if(s)MAP[s.trim().toUpperCase()]=parseFloat(m);});
+const UNIT=JSON.parse(fs.readFileSync('/tmp/sku_unit.json','utf8'));
+const KFAM=['Kravet','Kravet Couture','Kravet Design','Lee Jofa','Lee Jofa Modern','Brunschwig & Fils','Cole & Son','GP & J Baker','Clarke And Clarke','Mulberry','Threads','Baker Lifestyle','Gaston y Daniela','Andrew Martin'];
+const norm=s=>(s||'').toUpperCase().replace(/\s+/g,' ').trim();
+function unitOf(opt){const o=(opt||'').toLowerCase();
+ if(/sample/.test(o))return 'SAMPLE';
+ if(/yard/.test(o))return 'YARD';
+ if(/roll|bolt/.test(o))return 'ROLL';
+ if(/sq|square|foot|sf\b/.test(o))return 'SQUARE FOOT';
+ if(/panel|mural/.test(o))return 'PANEL';
+ if(/each|tile|set/.test(o))return 'EACH';
+ return 'OTHER';}
+
+(async()=>{
+ const out=['vendor,mfr_sku,unit,cur,map,delta,product_gid'];
+ let sampleOnly=0,noMap=0,unitMismatch=0,unitUnknown=0,atMap=0,under=0,over=0,underAmt=0,overAmt=0,seen=0;
+ const per={};
+ for(const vendor of KFAM){
+ let cursor=null, vendorCount=0, page=0;
+ const vq=vendor.replace(/"/g,'\\"');
+ while(true){
+ page++;
+ const after=cursor?`, after:"${cursor}"`:'';
+ const q=`{ products(first:100, query:"vendor:'${vq}' status:active"${after}){
+ pageInfo{ hasNextPage endCursor }
+ edges{ node{ id status
+ metafield(namespace:"custom",key:"manufacturer_sku"){ value }
+ variants(first:10){ edges{ node{ price selectedOptions{ value } } } } } } } }`;
+ let s; try{ s=await gql(q); }catch(e){ console.log(` ${vendor} p${page} ERR ${e}`); break; }
+ const pr=s.data?.products; if(!pr){ console.log(` ${vendor} no data: ${JSON.stringify(s).slice(0,160)}`); break; }
+ for(const e of pr.edges){
+ const n=e.node; seen++; vendorCount++;
+ const sku=norm(n.metafield?.value);
+ const vs=(n.variants?.edges||[]).map(x=>({p:parseFloat(x.node.price),u:unitOf((x.node.selectedOptions||[]).map(o=>o.value).join(' '))}));
+ const real=vs.filter(v=>v.u!=='SAMPLE' && v.p>5);
+ if(!real.length){ sampleOnly++; continue; }
+ const map=MAP[sku]; if(!map){ noMap++; continue; }
+ const mapUnit=UNIT[sku];
+ let v = mapUnit ? real.find(x=>x.u===mapUnit) : null;
+ if(!v){ if(mapUnit){ const top=real.sort((a,b)=>b.p-a.p)[0]; if(top.u!=='OTHER'){ unitMismatch++; continue; } v=top; }
+ else { v=real.sort((a,b)=>b.p-a.p)[0]; unitUnknown++; } }
+ per[vendor]=per[vendor]||{at:0,under:0,over:0};
+ const d=v.p-map;
+ if(Math.abs(d)<=0.5){ atMap++; per[vendor].at++; }
+ else { if(d<0){under++;per[vendor].under++;underAmt+=(map-v.p);}else{over++;per[vendor].over++;overAmt+=(v.p-map);}
+ out.push(`${vendor},${sku},${v.u},${v.p},${map},${d.toFixed(2)},${n.id}`); }
+ }
+ if(!pr.pageInfo.hasNextPage) break;
+ cursor=pr.pageInfo.endCursor; await sleep(350);
+ }
+ console.log(` ${vendor.padEnd(20)} active=${vendorCount}`);
+ }
+ fs.writeFileSync('/tmp/kravet_offmap_paginated.csv',out.join('\n'));
+ console.log('\n=== KRAVET-FAMILY off-MAP (READ-ONLY, vs Jun-13 auth_pricing.new_map snapshot) ===');
+ console.log(`active scanned: ${seen}`);
+ console.log(`sample-only(skip): ${sampleOnly} | no-MAP(skip): ${noMap} | unit-mismatch(skip): ${unitMismatch} | unit-unknown(compared): ${unitUnknown}`);
+ console.log(`COMPARED: AT-MAP ${atMap} | UNDER ${under} (+$${underAmt.toFixed(0)} to recover) | OVER ${over} (-$${overAmt.toFixed(0)})`);
+ console.log('per-vendor (compared only):');
+ for(const [v,s] of Object.entries(per).sort((a,b)=>(b[1].under+b[1].over)-(a[1].under+a[1].over)))
+ console.log(` ${v.padEnd(20)} at:${s.at} under:${s.under} over:${s.over}`);
+ console.log(`\noff-MAP rows → /tmp/kravet_offmap_paginated.csv (${out.length-1})`);
+})().catch(e=>{console.error(e);process.exit(1);});
← f9f091b brands: repoint push script to recreated page id 20801060869
·
back to Dw Yolo Loop
·
Kravet under-MAP reprice: 73 variants lifted to MAP (live, v 18fc285 →