← back to Dw Yolo Loop

scripts/image-integrity-sweep/image-integrity-sweep.mjs

52 lines

// image-integrity-sweep — READ-ONLY, $0. Cycle 88. The unambiguous storefront defect: an ACTIVE
// product with NO image = a dead/blank PDP (direct conversion loss + poor feed quality). Exhaustive
// featuredImage-null check across all active products via Admin GraphQL (no HTTP fan-out for the core).
// Also: image-count distribution (0 / 1 / 2-5 / 6+), the single-image-PDP soft set, and a SAMPLE of
// image URLs written out for a separate small 404 spot-check (NOT a 71k full fan-out). EXCLUDES PJ.
// NEGATIVE CONTROL by construction: if the BULK have images, a no-image product is a true anomaly.
import fs from 'fs';
const ENV=fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8');
const TOK=((ENV.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1]||'').replace(/['"\r]/g,'').trim();
const GQL='https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/graphql.json';
const PJ=/phillip[- ]?jeffries/i;
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
async function gql(q,v){for(let a=0;a<6;a++){const r=await fetch(GQL,{method:'POST',headers:{'X-Shopify-Access-Token':TOK,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});if(r.status===429||r.status>=500){await sleep(2000*(a+1));continue;}const j=await r.json();if(j.errors&&JSON.stringify(j.errors).includes('Throttled')){await sleep(2500*(a+1));continue;}return j;}return null;}
const Q=`query($cursor:String){ products(first:80, after:$cursor, query:"status:active"){ pageInfo{hasNextPage endCursor}
  edges{node{ title vendor productType totalInventory
    featuredImage{ url }
    images(first:6){ edges{ node{ url } } } } } } }`;
let cursor=null,pages=0,scanned=0,complete=true;
const dist={0:0,1:0,'2-5':0,'6+':0};
let noFeatured=0, noImages=0, oneImage=0;
const noImgByVendor={}, noImgEx=[], oneImgEx=[], urlSample=[];
while(true){ const j=await gql(Q,{cursor}); if(!j?.data){complete=false;break;} const c=j.data.products;
  for(const e of c.edges){ const n=e.node; if(PJ.test(n.vendor||''))continue; scanned++;
    const imgs=(n.images?.edges||[]); const k=imgs.length;
    const hasFeatured=!!(n.featuredImage&&n.featuredImage.url);
    if(!hasFeatured) noFeatured++;
    if(k===0){ dist[0]++; noImages++; noImgByVendor[n.vendor]=(noImgByVendor[n.vendor]||0)+1; if(noImgEx.length<30) noImgEx.push({t:(n.title||'').slice(0,46),vendor:n.vendor,type:n.productType,inv:n.totalInventory,feat:hasFeatured}); }
    else if(k===1){ dist[1]++; oneImage++; if(oneImgEx.length<15) oneImgEx.push({t:(n.title||'').slice(0,40),vendor:n.vendor}); }
    else if(k<=5) dist['2-5']++; else dist['6+']++;
    // collect a spread-out URL sample for the 404 spot-check (every ~1800th product)
    if(hasFeatured && scanned%1800===0 && urlSample.length<40) urlSample.push(n.featuredImage.url);
  }
  pages++; if(!c.pageInfo.hasNextPage)break; cursor=c.pageInfo.endCursor;
  const cost=j.extensions?.cost?.throttleStatus; if(cost&&cost.currentlyAvailable<500) await sleep(1500); else await sleep(250);
  if(pages%100===0) console.log(`  ${pages}p scanned=${scanned} no-image=${noImages} no-featured=${noFeatured}`);
}
const pct=x=>scanned?(100*x/scanned).toFixed(2):'0';
console.log(`\n=== image-integrity-sweep (READ-ONLY, $0) ===`);
console.log(`active scanned: ${scanned} (complete=${complete}, ${pages}p) [PJ excluded]`);
console.log(`\nIMAGE-COUNT DISTRIBUTION:`);
console.log(`  0 images (DEAD PDP - the unambiguous defect): ${dist[0]} (${pct(dist[0])}%)`);
console.log(`  1 image (single-image PDP, soft):             ${dist[1]} (${pct(dist[1])}%)`);
console.log(`  2-5 images:                                   ${dist['2-5']} (${pct(dist['2-5'])}%)`);
console.log(`  6+ images:                                    ${dist['6+']} (${pct(dist['6+'])}%)`);
console.log(`\nNO featuredImage (storefront shows blank/placeholder): ${noFeatured} (${pct(noFeatured)}%)`);
console.log(`NEGATIVE CONTROL: with-images ${scanned-noImages} (${pct(scanned-noImages)}%) -> a 0-image product is ${dist[0]>0?'a true anomaly against a with-image bulk':'(none found)'}.`);
console.log(`\nNO-IMAGE by vendor (top 15):`); Object.entries(noImgByVendor).sort((a,b)=>b[1]-a[1]).slice(0,15).forEach(([v,n])=>console.log(`  ${v}: ${n}`));
console.log(`\nNO-IMAGE examples (feat=has-featuredImage inv=totalInventory):`); noImgEx.forEach(x=>console.log(`  "${x.t}" [${x.vendor}] type=${x.type} inv=${x.inv} feat=${x.feat}`));
console.log(`\nSINGLE-IMAGE examples:`); oneImgEx.forEach(x=>console.log(`  "${x.t}" [${x.vendor}]`));
fs.writeFileSync('/tmp/image-integrity-sweep.json',JSON.stringify({ts:new Date().toISOString(),scanned,complete,dist,noImages,noFeatured,oneImage,noImgByVendor,noImgEx,oneImgEx,urlSample},null,2));
console.log(`\nwrote /tmp/image-integrity-sweep.json (urlSample=${urlSample.length} for separate 404 spot-check)`);