← 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}`);
}