← back to Dw Yolo Loop

scripts/kravet-2026-coverage-audit.mjs

42 lines

#!/usr/bin/env node
/* READ-ONLY: do we have a 2026 price for every live Kravet-family item?
   For each Kravet-family vendor, pull active products + their manufacturer_sku metafield,
   check membership in the 2026-priced SKU set (auth_pricing.new_map>0 → /tmp/auth2026_skus.txt).
   Reports per-vendor coverage + writes /tmp/kravet_2026_gaps.csv (items with NO 2026 price). */
import fs from 'node:fs';
import { rest, restAll } from './lib/shopify.mjs';

const SET = new Set(fs.readFileSync('/tmp/auth2026_skus.txt','utf8').trim().split('\n').map(s=>s.trim().toUpperCase()));
const VENDORS = ['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 getAll = vendor => restAll(`/products.json?vendor=${encodeURIComponent(vendor)}&status=active&limit=250&fields=id,title,handle,variants`, 'products');
const isSample = v => (v.option1||'').toLowerCase()==='sample' || /-sample$/i.test(v.sku||'');

(async()=>{
  const summary=[];
  const csv=['vendor,mfr_sku,dw_sku,title,handle'];
  for(const vendor of VENDORS){
    let prods; try{ prods=await getAll(vendor); }catch(e){ console.log(`  ${vendor}: ERR`); continue; }
    if(!prods.length) continue;
    let have=0, miss=0, noSku=0, n=0;
    for(const p of prods){
      n++;
      const mf=await(await rest(`/products/${p.id}/metafields.json`)).json();
      const code=norm(((mf.metafields||[]).find(x=>x.namespace==='custom'&&x.key==='manufacturer_sku')||{}).value||'');
      const yard=p.variants.find(v=>!isSample(v))||p.variants[0];
      if(!code){ noSku++; csv.push([vendor,'(no mfr_sku)',yard&&yard.sku||'',JSON.stringify(p.title),p.handle].join(',')); continue; }
      if(SET.has(code)) have++;
      else { miss++; csv.push([vendor,code,yard&&yard.sku||'',JSON.stringify(p.title),p.handle].join(',')); }
    }
    const pct=(100*have/prods.length).toFixed(1);
    summary.push({vendor,total:prods.length,have,miss,noSku,pct});
    console.log(`  ${vendor.padEnd(20)} ${prods.length} total | 2026:${have} (${pct}%) | missing:${miss} | no-mfr-sku:${noSku}`);
  }
  fs.writeFileSync('/tmp/kravet_2026_gaps.csv', csv.join('\n'));
  const T=summary.reduce((a,s)=>({total:a.total+s.total,have:a.have+s.have,miss:a.miss+s.miss,noSku:a.noSku+s.noSku}),{total:0,have:0,miss:0,noSku:0});
  console.log(`\n=== TOTAL: ${T.total} live Kravet items | have 2026 price: ${T.have} (${(100*T.have/T.total).toFixed(1)}%) | missing: ${T.miss} | no mfr_sku: ${T.noSku} ===`);
  console.log(`gap list → /tmp/kravet_2026_gaps.csv (${T.miss+T.noSku} rows)`);
})().catch(e=>{console.error(e);process.exit(1);});