← 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(/&/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');