← back to Dw Yolo Loop

scripts/jsonld-425-scan/jsonld-425-scan.mjs

82 lines

// jsonld-425-scan — READ-ONLY, $0. Wider stratified follow-up to c61. Sizes the
// confirmed $4.25-JSON-LD leak against the MULTI-VARIANT population specifically.
// For each sampled live PDP: parse Product JSON-LD offers.price + fetch variant
// prices. Classify:
//   - single-variant (not at risk by definition)
//   - multi-variant + $4.25 default in JSON-LD while real variant exists = LEAK
//   - multi-variant + JSON-LD shows real price = OK
// Honest denominator = the MULTI-VARIANT population (the only set that can leak).
// EXCLUDES Phillip Jeffries (phillip-jeffries-display-excluded rule).
const WWW='https://www.designerwallcoverings.com';
const UA='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36';
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
async function get(u){for(let a=0;a<4;a++){try{const r=await fetch(u,{headers:{'User-Agent':UA}});if(r.status===429){await sleep(1500*(a+1));continue;}return {status:r.status,text:await r.text()};}catch(e){await sleep(800*(a+1));}}return {status:0,text:''};}
const PJ=/phillip[- ]?jeffries|phillip-jeffries/i;

// stratified handle pool: pull from ALL 30 US product sitemaps, a few from each
const idx=await get(`${WWW}/sitemap.xml`);
const prodMaps=[...idx.text.matchAll(/<loc>([^<]+sitemap_products_\d+\.xml[^<]*)<\/loc>/g)].map(m=>m[1].replace(/&amp;/g,'&')).filter(u=>!/\/en-ca\//.test(u));
let handles=[];
for(const sm of prodMaps){
  const x=await get(sm);
  const hs=[...x.text.matchAll(/<loc>[^<]*\/products\/([^<\/?]+)/g)].map(m=>m[1]);
  // take ~12 spread across each sitemap for vendor/age diversity
  const step=Math.max(1,Math.floor(hs.length/12));
  for(let i=0;i<hs.length && handles.length<400;i+=step) handles.push(hs[i]);
  await sleep(100);
}
handles=[...new Set(handles)].slice(0,360);
console.log(`=== jsonld-425-scan (READ-ONLY, $0) ===\nstratified sample: ${handles.length} live PDPs across ${prodMaps.length} sitemaps (PJ excluded)\n`);

function ldPrice(html){
  const blocks=[...html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)].map(m=>m[1]);
  for(const b of blocks){ let j; try{j=JSON.parse(b);}catch{continue;}
    const cands=j['@graph']?j['@graph']:[j];
    for(const c of cands){ if(c['@type']==='Product'){ const o=Array.isArray(c.offers)?c.offers[0]:c.offers; if(o&&o.price!=null) return parseFloat(o.price); } } }
  return null;
}

let single=0, multiOk=0, multiLeak=0, pjSkip=0, errs=0, noProduct=0;
const leaks=[];
let done=0;
for(const h of handles){
  const pj=await get(`${WWW}/products/${h}.json`);
  let prod; try{ prod=JSON.parse(pj.text).product; }catch{ errs++; done++; continue; }
  if(!prod){ errs++; done++; continue; }
  if(PJ.test(prod.vendor||'')||PJ.test(h)){ pjSkip++; done++; continue; }
  const prices=(prod.variants||[]).map(v=>parseFloat(v.price)).filter(n=>!isNaN(n));
  const maxV=prices.length?Math.max(...prices):null;
  const isMulti=(prod.variants||[]).length>1;
  const pdp=await get(`${WWW}/products/${h}`);
  const ld=pdp.status===200?ldPrice(pdp.text):null;
  if(ld===null){ noProduct++; done++; await sleep(120); continue; }
  if(!isMulti){ single++; }
  else if(ld<=4.25 && maxV!=null && maxV>4.25){ multiLeak++; if(leaks.length<25)leaks.push({h,vendor:prod.vendor,ld,maxV}); }
  else { multiOk++; }
  done++;
  if(done%50===0) console.log(`  ${done}/${handles.length} (multi-leak so far: ${multiLeak})`);
  await sleep(140);
}

const multiTotal=multiOk+multiLeak;
const leakPctMulti=multiTotal?(100*multiLeak/multiTotal):0;
const evaluated=single+multiOk+multiLeak;
console.log(`\n=== RESULTS ===`);
console.log(`evaluated: ${evaluated} | PJ-skipped: ${pjSkip} | json-err: ${errs} | no-JSONLD-product: ${noProduct}`);
console.log(`single-variant (not at risk): ${single}`);
console.log(`MULTI-variant total: ${multiTotal}`);
console.log(`  multi OK (JSON-LD shows real price): ${multiOk}`);
console.log(`  multi LEAK ($4.25 in JSON-LD, real variant exists): ${multiLeak}`);
console.log(`\n*** LEAK RATE among MULTI-variant products: ${multiLeak}/${multiTotal} = ${leakPctMulti.toFixed(1)}% ***`);
console.log(`LEAK RATE among ALL evaluated: ${multiLeak}/${evaluated} = ${evaluated?(100*multiLeak/evaluated).toFixed(1):0}%`);
// Wilson-ish honest bound note
if(multiTotal>0){
  const p=multiLeak/multiTotal, se=Math.sqrt(p*(1-p)/multiTotal), lo=Math.max(0,p-1.96*se)*100, hi=Math.min(1,p+1.96*se)*100;
  console.log(`95% CI on multi-variant leak rate: ${lo.toFixed(1)}% – ${hi.toFixed(1)}% (n=${multiTotal})`);
}
if(leaks.length){ console.log('\nsample leaks:'); leaks.slice(0,15).forEach(x=>console.log(`  ${x.h.slice(0,46)} [${x.vendor}] ld=$${x.ld} max=$${x.maxV}`)); }

import fs from 'fs';
fs.writeFileSync('/tmp/jsonld-425-scan.json',JSON.stringify({ts:new Date().toISOString(),sample:handles.length,evaluated,single,multiOk,multiLeak,multiTotal,leakPctMulti,pjSkip,errs,noProduct,leaks},null,2));
console.log('\nwrote /tmp/jsonld-425-scan.json');