← back to Dw Yolo Loop

scripts/kravet-price-vs-map.js

52 lines

#!/usr/bin/env node
/* Poll bulk op (products+variants+mfr_sku) → for each Kravet-family ACTIVE product, compare the
   live YARD-variant price to the current 2026 MAP (auth_pricing.new_map). Reports per-vendor how
   many are AT MAP vs OFF (under/over) — i.e. is the Cole & Son stale-price problem fleet-wide.
   READ-ONLY. Writes /tmp/kravet_offmap.csv (vendor,mfr_sku,cur,map,delta,product_gid). */
const https=require('https'); const fs=require('fs');
const TOKEN=process.env.T, STORE='designer-laboratory-sandbox.myshopify.com';
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
function gql(q){return new Promise(r=>{const rq=https.request(`https://${STORE}/admin/api/2024-10/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'}},x=>{let b='';x.on('data',d=>b+=d);x.on('end',()=>r(JSON.parse(b)))});rq.write(JSON.stringify({query:q}));rq.end();});}
function download(url){return new Promise((res,rej)=>{https.get(url,r=>{let b='';r.on('data',d=>b+=d);r.on('end',()=>res(b));}).on('error',rej);});}

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 KFAM=new Set(['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();
const isSampleOpt=o=>(o||[]).some(x=>String(x.value).toLowerCase()==='sample');

(async()=>{
  let url=null;
  for(let i=0;i<180;i++){ const s=await gql(`{ currentBulkOperation{ status objectCount url errorCode } }`); const op=s.data?.currentBulkOperation;
    if(i%4===0)console.log(`  poll ${i}: ${op?.status} obj=${op?.objectCount||0}`);
    if(op?.status==='COMPLETED'){url=op.url;break;} if(op?.status==='FAILED'){console.log('FAILED',op.errorCode);process.exit(1);} await sleep(5000); }
  if(!url){console.log('no url');process.exit(1);}
  console.log('downloading...'); const data=await download(url);
  const lines=data.trim().split('\n').filter(Boolean).map(l=>JSON.parse(l));
  // assemble: products carry vendor/status/metafield; variants carry __parentId + sku/price/options
  const prod={}, variants={};
  for(const n of lines){
    if(n.id && n.id.includes('/Product/')){ prod[n.id]={vendor:n.vendor,status:n.status,sku:norm(n.metafield?.value),vars:[]}; }
    else if(n.__parentId && n.price!==undefined){ (variants[n.__parentId]=variants[n.__parentId]||[]).push(n); }
  }
  const per={}; const off=['vendor,mfr_sku,cur,map,delta,product_gid'];
  let total=0,atmap=0,under=0,over=0,nomap=0;
  for(const [pid,p] of Object.entries(prod)){
    if(!KFAM.has(p.vendor)||p.status!=='ACTIVE') continue;
    const map=MAP[p.sku]; if(!map){ nomap++; continue; }
    const vs=variants[pid]||[]; const nonSample=vs.filter(v=>!isSampleOpt(v.selectedOptions)&&!/-sample$/i.test(v.sku||''));
    const yard=nonSample.sort((a,b)=>parseFloat(b.price)-parseFloat(a.price))[0]||vs[0]; if(!yard)continue;
    const cur=parseFloat(yard.price); total++;
    per[p.vendor]=per[p.vendor]||{t:0,at:0,under:0,over:0};
    per[p.vendor].t++;
    const d=cur-map;
    if(Math.abs(d)<=0.5){ atmap++; per[p.vendor].at++; }
    else { if(d<0){under++;per[p.vendor].under++;} else {over++;per[p.vendor].over++;}
      off.push(`${p.vendor},${p.sku},${cur},${map},${d.toFixed(2)},${pid}`); }
  }
  fs.writeFileSync('/tmp/kravet_offmap.csv', off.join('\n'));
  console.log('\n=== LIVE PRICE vs 2026 MAP (covered items only) ===');
  for(const [v,s] of Object.entries(per).sort((a,b)=>b[1].t-a[1].t)) console.log(`  ${v.padEnd(20)} ${s.t} | AT MAP:${s.at} (${(100*s.at/s.t).toFixed(0)}%) | UNDER:${s.under} | OVER:${s.over}`);
  console.log(`\nTOTAL covered: ${total} | AT MAP: ${atmap} (${(100*atmap/total).toFixed(1)}%) | UNDER(below MAP, urgent): ${under} | OVER: ${over}`);
  console.log(`off-MAP list → /tmp/kravet_offmap.csv (${off.length-1} rows)`);
})().catch(e=>{console.error(e);process.exit(1);});