← back to Dw Yolo Loop

scripts/kravet-reprice-dryrun.js

48 lines

#!/usr/bin/env node
/* DRY-RUN ONLY. For the 73 under-MAP Kravet-family products (from /tmp/kravet_offmap_paginated.csv),
   re-fetch variants, pinpoint the exact REAL variant to reprice (non-sample, price==measured cur),
   and emit the proposed price change cur->MAP. Writes /tmp/kravet_reprice_dryrun.csv. NO WRITES.
   Guards: sample variants ($4.25 / option Sample) are NEVER the target; unit was already matched
   upstream; only UNDER-MAP rows (delta<0) are repriced UP to MAP (MAP is a floor). */
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,200))}})});rq.write(JSON.stringify({query:q}));rq.end();});}
const isSample=opts=>(opts||[]).some(o=>String(o.value).toLowerCase()==='sample');

const rows=fs.readFileSync('/tmp/kravet_offmap_paginated.csv','utf8').trim().split('\n').slice(1)
  .map(l=>{const[vendor,sku,unit,cur,map,delta,gid]=l.split(',');return{vendor,sku,unit,cur:+cur,map:+map,delta:+delta,gid};})
  .filter(r=>r.delta<0);   // UNDER-MAP only

(async()=>{
  const out=['vendor,mfr_sku,unit,variant_id,current_price,new_price_MAP,uplift,product_title'];
  let ok=0, ambiguous=0, totalUplift=0;
  const warn=[];
  for(const r of rows){
    const q=`{ product(id:"${r.gid}"){ title status
      variants(first:25){ edges{ node{ id title price selectedOptions{ value } } } } } }`;
    let s; try{ s=await gql(q); }catch(e){ warn.push(`${r.sku} FETCH-ERR ${e}`); continue; }
    const p=s.data?.product; if(!p){ warn.push(`${r.sku} no-product`); continue; }
    if(p.status!=='ACTIVE'){ warn.push(`${r.sku} not-active(${p.status})-SKIP`); continue; }
    const vs=p.variants.edges.map(e=>e.node).filter(v=>!isSample(v.selectedOptions) && parseFloat(v.price)>5);
    // target = the real variant whose price matches the measured 'cur' (the under-MAP one)
    const cand=vs.filter(v=>Math.abs(parseFloat(v.price)-r.cur)<=0.01);
    if(cand.length!==1){ ambiguous++; warn.push(`${r.sku} AMBIGUOUS (${cand.length} variants @ $${r.cur}) — SKIP, needs eyes`); continue; }
    const v=cand[0];
    const uplift=+(r.map-parseFloat(v.price)).toFixed(2);
    totalUplift+=uplift; ok++;
    out.push(`${r.vendor},${r.sku},${r.unit},${v.id.split('/').pop()},${v.price},${r.map},${uplift.toFixed(2)},"${p.title.replace(/"/g,'')}"`);
    await sleep(250);
  }
  fs.writeFileSync('/tmp/kravet_reprice_dryrun.csv',out.join('\n'));
  console.log('=== KRAVET UNDER-MAP REPRICE — DRY RUN (no writes) ===');
  console.log(`under-MAP candidates: ${rows.length}`);
  console.log(`clean (1 unambiguous real variant): ${ok} | ambiguous/skipped: ${ambiguous}`);
  console.log(`total uplift to bring to MAP: +$${totalUplift.toFixed(2)}`);
  // per-vendor summary
  const per={}; out.slice(1).forEach(l=>{const c=l.split(','); per[c[0]]=(per[c[0]]||0)+1;});
  console.log('per-vendor:'); for(const [v,n] of Object.entries(per)) console.log(`  ${v.padEnd(20)} ${n}`);
  if(warn.length){ console.log('\nWARNINGS / skipped:'); warn.forEach(w=>console.log('  '+w)); }
  console.log(`\nfull proposed diff → /tmp/kravet_reprice_dryrun.csv`);
})().catch(e=>{console.error(e);process.exit(1);});