← back to Designerwallcoverings

scripts/broken-image-pilot/broken-image-pilot.mjs

54 lines

// Cycle-44 broken-image pilot (read-only, $0). Samples live ACTIVE products across
// the catalog via /products.json and HEADs each product's featured image to measure
// real delivery health (200 + image/* content-type). Complements c26 (presence) and
// c29 (alt-text). No writes. Customer-facing + Google-feed quality signal.
const HOST = 'https://www.designerwallcoverings.com';
const PAGES = [1,8,16,24,32,40,48,56,64,72,80,88,96];
const PER_PAGE_SAMPLE = 24;
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36';

const sleep = ms => new Promise(r => setTimeout(r, ms));
function pick(arr, n){ // deterministic-ish stride sample, no RNG
  if (arr.length <= n) return arr;
  const out=[], stride=arr.length/n;
  for(let i=0;i<n;i++) out.push(arr[Math.floor(i*stride)]);
  return out;
}

const results = [];
let scanned=0, noImage=0, broken=0, ok=0, unknown=0;

for (const pg of PAGES) {
  let prods=[];
  try {
    const r = await fetch(`${HOST}/products.json?limit=250&page=${pg}`, {headers:{'User-Agent':UA}});
    if (!r.ok){ console.error(`page ${pg}: HTTP ${r.status}`); continue; }
    prods = (await r.json()).products || [];
  } catch(e){ console.error(`page ${pg}: ${e.message}`); continue; }
  if (!prods.length) continue;
  for (const p of pick(prods, PER_PAGE_SAMPLE)) {
    scanned++;
    const img = (p.images && p.images[0] && p.images[0].src) || null;
    if (!img){ noImage++; results.push({handle:p.handle, vendor:p.vendor, status:'NO_IMAGE', img:null}); continue; }
    try {
      const hr = await fetch(img, {method:'HEAD', headers:{'User-Agent':UA}});
      const ct = hr.headers.get('content-type')||'';
      if (hr.ok && ct.startsWith('image/')){ ok++; }
      else { broken++; results.push({handle:p.handle, vendor:p.vendor, status:`BROKEN_${hr.status}_${ct||'noct'}`, img}); }
    } catch(e){ unknown++; results.push({handle:p.handle, vendor:p.vendor, status:`ERR_${e.message.slice(0,30)}`, img}); }
    await sleep(40);
  }
  await sleep(150);
}

const pct = n => scanned? (100*n/scanned).toFixed(1)+'%' : 'n/a';
console.log(`\n=== Broken-image pilot — ${scanned} active products sampled across pages ${PAGES.join(',')} ===`);
console.log(`OK (200 + image/*):   ${ok}  (${pct(ok)})`);
console.log(`NO_IMAGE (0 images):  ${noImage}  (${pct(noImage)})`);
console.log(`BROKEN (non-200/non-image): ${broken}  (${pct(broken)})`);
console.log(`UNKNOWN (fetch error): ${unknown}  (${pct(unknown)})`);
if (results.length){
  console.log(`\n--- flagged (${results.length}) ---`);
  for (const r of results.slice(0,40)) console.log(`[${r.status}] ${r.vendor} :: ${r.handle}`);
}