← back to Dwjs Consolidation 2026 04 23

yb_analyze.js

86 lines

#!/usr/bin/env node
// READ-ONLY classifier over the saved bulk jsonl (no network). Corrected ROLL logic.
const fs=require('fs');
const DIR='/Users/macstudio3/Projects/dwjs-consolidation-2026-04-23/yb-audit';
const CONFIRMED=new Set(['Jeffrey Stevens','Malibu Wallpaper']); // DTD-committed York/Brewster identity
// York/Brewster LINEAGE vendors surfaced live but NOT in the committed scope -> report, do not gate.
const LINEAGE=new Set(['LA Walls','York','York Contract']);

const products=new Map();
for(const f of ['raw_skuDWJS.jsonl','raw_skuDWQW.jsonl']){
  for(const line of fs.readFileSync(`${DIR}/${f}`,'utf8').split('\n').filter(Boolean)){
    const o=JSON.parse(line);
    if(o.id?.includes('/Product/')) products.set(o.id,{id:o.id,title:o.title||'',vendor:o.vendor||'',productType:o.productType||'',tags:o.tags||'',status:o.status||'',m1:o.m1?.value??null,m2:o.m2?.value??null,m3:o.m3?.value??null,collections:[],variants:[]});
    else if(o.id?.includes('/Collection/')&&o.__parentId){const p=products.get(o.__parentId);if(p)p.collections.push({handle:o.handle||'',title:o.title||''});}
    else if(o.id?.includes('/ProductVariant/')&&o.__parentId){const p=products.get(o.__parentId);if(p)p.variants.push({id:o.id,sku:o.sku||'',title:o.title||'',v1:o.v1?.value??null,v2:o.v2?.value??null});}
  }
}
// TIGHT mural detection. DW tag taxonomy uses style tags like "Henna Wallcovering
// Mural" (a pattern descriptor, NOT a real mural) so a raw substring match over tags
// over-fires. Use: word "mural" in the customer-facing TITLE or productType, a
// mural COLLECTION, or a WHOLE tag that is exactly a mural marker.
const MURAL_TAGS=new Set(['mural','murals','wall mural','wall murals','mural collection']);
function muralReason(p){
  if(/\bmurals?\b/i.test(p.title)) return 'title';
  if(/\bmurals?\b/i.test(p.productType)) return 'type';
  const tags=Array.isArray(p.tags)?p.tags:String(p.tags||'').split(',');
  for(const t of tags){ if(MURAL_TAGS.has(String(t).trim().toLowerCase())) return 'tag:'+String(t).trim(); }
  for(const c of p.collections){ if(/\bmurals?\b/i.test(`${c.handle} ${c.title}`)) return 'collection:'+c.handle; }
  return null;
}
function mainSku(p){const m=p.variants.find(v=>!/sample/i.test(v.sku+v.title))||p.variants[0]||{sku:''};return m.sku;}

function classify(vendorSet){
  const r={total:0,nonRoll:[],mural:[],ok:0,drifted:[],byStatus:{},driftByStatus:{},driftByUnit:{}};
  for(const p of products.values()){
    if(!vendorSet.has(p.vendor))continue;
    r.total++; r.byStatus[p.status]=(r.byStatus[p.status]||0)+1;
    const dw=mainSku(p);
    const unit=(p.m3||'').toLowerCase();
    if(unit && !/roll/.test(unit)){r.nonRoll.push({dw,vendor:p.vendor,unit:p.m3,status:p.status});continue;}
    const mr=muralReason(p);
    if(mr){r.mural.push({dw,vendor:p.vendor,reason:mr,status:p.status,title:p.title});continue;}
    if(p.m1==='2'&&p.m2==='2'){r.ok++;continue;}
    const reason=(p.m1==null&&p.m2==null)?'missing-both':(p.m1!=='2'?`min=${p.m1}`:`units=${p.m2}`);
    const rec={dw,vendor:p.vendor,status:p.status,m1:p.m1,m2:p.m2,unit:p.m3||'(unset)',reason,product_id:p.id};
    r.drifted.push(rec);
    r.driftByStatus[p.status]=(r.driftByStatus[p.status]||0)+1;
    const uk=p.m3||'(unset)'; r.driftByUnit[uk]=(r.driftByUnit[uk]||0)+1;
  }
  return r;
}

const conf=classify(CONFIRMED);
fs.writeFileSync(`${DIR}/drifted_skus.json`,JSON.stringify(conf.drifted,null,2));
fs.writeFileSync(`${DIR}/mural_exempt.json`,JSON.stringify(conf.mural,null,2));
fs.writeFileSync(`${DIR}/fabric_nonroll_exempt.json`,JSON.stringify(conf.nonRoll,null,2));

console.log('================ CONFIRMED YORK/BREWSTER SCOPE (Jeffrey Stevens + Malibu Wallpaper) ================');
console.log('total products (all statuses) pulled under DWJS*/DWQW* glob & matching vendor:',conf.total);
console.log('  by status:',JSON.stringify(conf.byStatus));
console.log('  non-ROLL exempt (unit set & !contains roll):',conf.nonRoll.length, conf.nonRoll.length?JSON.stringify(conf.nonRoll.slice(0,3)):'');
console.log('  mural exempt:',conf.mural.length);
console.log('  OK already 2/2:',conf.ok);
console.log('  DRIFTED (single-roll-orderable):',conf.drifted.length);
console.log('    drift by status:',JSON.stringify(conf.driftByStatus));
console.log('    drift by unit  :',JSON.stringify(conf.driftByUnit));
console.log('    drift reasons  :',JSON.stringify(conf.drifted.reduce((a,d)=>{a[d.reason]=(a[d.reason]||0)+1;return a;},{})));

// ACTIVE-only drift (the live customer-facing harm)
const activeDrift=conf.drifted.filter(d=>d.status==='ACTIVE');
console.log('\n  ACTIVE drifted (live single-roll-orderable):',activeDrift.length);
console.log('  sample ACTIVE drifted:',JSON.stringify(activeDrift.slice(0,8).map(d=>({dw:d.dw,v:d.vendor,m1:d.m1,m2:d.m2,unit:d.unit})),null,1));

// Lineage-only report (NOT gated) — York/Brewster family present live but outside committed scope
const lin=classify(LINEAGE);
console.log('\n================ LINEAGE-ONLY CATCHES (report for Steve; NOT in this gated write) ================');
console.log('LA Walls / York / York Contract under DWJS*/DWQW* glob:');
console.log('  total:',lin.total,' by status:',JSON.stringify(lin.byStatus));
const linByVendor={};for(const d of [...lin.drifted,...lin.nonRoll,...lin.mural]) {}
const allLin={};for(const p of products.values()){if(LINEAGE.has(p.vendor)){allLin[p.vendor]=(allLin[p.vendor]||0)+1;}}
console.log('  by vendor:',JSON.stringify(allLin));
console.log('  non-ROLL exempt:',lin.nonRoll.length,' mural:',lin.mural.length,' OK 2/2:',lin.ok,' DRIFTED:',lin.drifted.length);
console.log('    lineage drift by vendor+status:',JSON.stringify(lin.driftByStatus));
fs.writeFileSync(`${DIR}/lineage_only_drifted.json`,JSON.stringify(lin.drifted,null,2));
console.log('\nfiles ->',DIR);