← 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);});