← back to Designerwallcoverings
scripts/body-quality-scan/body-quality-scan.mjs
37 lines
import fs from 'fs';
const T=(fs.readFileSync('/Users/stevestudio2/Projects/secrets-manager/.env','utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)||[])[1];
const URL='https://designer-laboratory-sandbox.myshopify.com/admin/api/2024-10/graphql.json';
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(URL,{method:'POST',headers:{'X-Shopify-Access-Token':T,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v||{}})});if(r.status===429){await sleep(2500);continue;}const j=await r.json();if(j.errors&&JSON.stringify(j.errors).match(/THROTTLED/)){await sleep(2500);continue;}return j;}throw new Error('throttled');}
const txt=h=>(h||'').replace(/<[^>]+>/g,'').replace(/&[a-z]+;/g,' ').replace(/\s+/g,' ').trim();
// defect classifiers (segmented)
const cls={
empty: b=>txt(b).length<5,
preamble: b=>/^\s*(<p>\s*)?(okay,?\s*)?here('s|s| is| are)\s+a?\s*product\s+description/i.test(b),
codefence: b=>/```|<code>|^\s*\|.*\|.*\|/m.test(b) || /\*\*[A-Z]/.test(txt(b)),
ai_artifact: b=>/\b(as an ai|i'm sorry|i cannot|certainly[!,]|here is the|in summary,|note:|disclaimer:)\b/i.test(txt(b)),
truncated: b=>{const t=txt(b); return t.length>20 && t.length<400 && /[a-z,]$/.test(t) && !/[.!?)]$/.test(t);},
};
const counts={empty:0,preamble:0,codefence:0,ai_artifact:0,truncated:0}; const samples={empty:[],codefence:[],ai_artifact:[],truncated:[]};
let cur=null,total=0,pages=0; const t0=Date.now();
do{
const j=await gql(`query($c:String){products(first:100,after:$c,query:"status:active"){pageInfo{hasNextPage endCursor} nodes{handle vendor descriptionHtml}}}`,{c:cur});
const pg=j.data.products;
for(const n of pg.nodes){ total++; const b=n.descriptionHtml||'';
if(cls.empty(b)){counts.empty++; if(samples.empty.length<6)samples.empty.push(n.handle+' ['+n.vendor+']');}
else { // a body can only be one of the rest meaningfully; check in priority
if(cls.preamble(b)){counts.preamble++;}
else if(cls.codefence(b)){counts.codefence++; if(samples.codefence.length<6)samples.codefence.push(n.handle+' ['+n.vendor+']');}
else if(cls.ai_artifact(b)){counts.ai_artifact++; if(samples.ai_artifact.length<6)samples.ai_artifact.push(n.handle+' ['+n.vendor+']');}
else if(cls.truncated(b)){counts.truncated++; if(samples.truncated.length<6)samples.truncated.push(n.handle+' ['+n.vendor+'] :: '+txt(b).slice(-50));}
}
}
pages++; if(pages%150===0)process.stderr.write(` ...${pages}p ${total} prod\n`);
cur=pg.pageInfo.hasNextPage?pg.pageInfo.endCursor:null;
}while(cur);
const out={total, elapsed_s:((Date.now()-t0)/1000)|0, counts, samples};
fs.writeFileSync('/tmp/body-quality.json',JSON.stringify(out,null,2));
console.log(`scanned ${total} ACTIVE in ${out.elapsed_s}s`);
console.log('defect counts:',JSON.stringify(counts));
for(const k of Object.keys(samples)){ if(samples[k].length){console.log(`-- ${k} samples --`); samples[k].forEach(s=>console.log(' '+s)); } }