← back to Tk10895 GroupB
erica/live-by-pid.mjs
47 lines
import fs from 'node:fs';
const TOK = fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8')
.split('\n').find(l=>l.startsWith('SHOPIFY_ADMIN_TOKEN='))?.split('=').slice(1).join('=').trim();
const SHOP='designer-laboratory-sandbox.myshopify.com', API='2024-10';
const retail=c=>Math.round((c/0.65/0.85)*100)/100;
// Erica Nyarko, Quadrille, 2026-09-10: "First price is the fabric, the second is the wallpaper $Fabric/$WP."
const MILL={ // pattern-plane -> mill-stated net cost
'Wildflowers II|Wallcovering':142, 'Wildflowers II|Fabric':172,
'Silhouette Reverse|Fabric':172, 'Volpi|Wallcovering':198,
'Club Cane|Wallcovering':174,
};
const rows=fs.readFileSync('pids.txt','utf8').trim().split('\n').map(l=>{
const [mfr,pid,ptype,cost,our]=l.split('|'); return {mfr,pid,ptype,cost:+cost,our:+our}; });
const ids=rows.map(r=>`gid://shopify/Product/${r.pid}`);
const q=`query($ids:[ID!]!){ nodes(ids:$ids){ ... on Product { id legacyResourceId title status
variants(first:20){ edges{ node{ id title price position sku inventoryQuantity } } } } } }`;
const r=await fetch(`https://${SHOP}/admin/api/${API}/graphql.json`,{method:'POST',
headers:{'X-Shopify-Access-Token':TOK,'Content-Type':'application/json'},
body:JSON.stringify({query:q,variables:{ids}})});
const j=await r.json();
if(j.errors){console.error(JSON.stringify(j.errors));process.exit(1);}
const byPid=Object.fromEntries(j.data.nodes.filter(Boolean).map(n=>[n.legacyResourceId,n]));
const out=[];
for(const row of rows){
const n=byPid[row.pid];
if(!n){ console.log(`${row.mfr} :: PRODUCT NOT FOUND (pid ${row.pid})`); continue; }
const pat=n.title.replace(/ (Screen Printed|Hand Print).*$/,'').replace(/^(.*?II|.*?Reverse|Volpi|Club Cane).*$/,'$1');
const vs=n.variants.edges.map(e=>e.node).sort((a,b)=>a.position-b.position);
const sell=vs.filter(v=>parseFloat(v.price)>10);
const T=n.title.toLowerCase();
const key=Object.keys(MILL).find(k=>T.startsWith(k.split('|')[0].toLowerCase()) && k.split('|')[1]===row.ptype);
const millCost=MILL[key]; const exp=millCost?retail(millCost):null;
const livePrice=sell.length?parseFloat(sell[0].price):null;
let verdict;
if(livePrice===null) verdict='SAMPLE-ONLY (not sellable)';
else if(exp===null) verdict='no mill basis';
else if(Math.abs(livePrice-exp)<0.02) verdict='✅ MATCHES mill';
else if(livePrice>exp) verdict=`❌ OVERPRICED +$${(livePrice-exp).toFixed(2)}`;
else verdict=`❌ UNDERPRICED -$${(exp-livePrice).toFixed(2)}`;
out.push({mfr:row.mfr,pid:row.pid,title:n.title,status:n.status,ptype:row.ptype,
db_cost:row.cost,db_our:row.our,mill_cost:millCost??null,expected:exp,live:livePrice,verdict,
variants:vs.map(v=>({pos:v.position,title:v.title,price:v.price,sku:v.sku}))});
console.log(`${row.mfr} ${row.ptype.padEnd(12)} ${n.status.padEnd(7)} live=${livePrice===null?'—':'$'+livePrice} exp=${exp?'$'+exp:'—'} ${verdict} ${n.title.slice(0,52)}`);
}
fs.writeFileSync('erica-live-audit.json',JSON.stringify(out,null,2));
console.log('\nrows='+out.length+' -> erica-live-audit.json');