[object Object]

← 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

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 →