[object Object]

← back to Imageless Rescue

fill-descriptions: deterministic body_html for the 178 description-violators the backfill missed (title/vendor-derived, no metered LLM, additive)

bf277f789e4aab4b515c6677314e9e256fb27730 · 2026-06-21 14:18:49 -0700 · Steve

Files touched

Diff

commit bf277f789e4aab4b515c6677314e9e256fb27730
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Jun 21 14:18:49 2026 -0700

    fill-descriptions: deterministic body_html for the 178 description-violators the backfill missed (title/vendor-derived, no metered LLM, additive)
---
 scripts/fill-descriptions.js | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/scripts/fill-descriptions.js b/scripts/fill-descriptions.js
new file mode 100644
index 0000000..e585503
--- /dev/null
+++ b/scripts/fill-descriptions.js
@@ -0,0 +1,34 @@
+'use strict';
+// Fill body_html for active-gate description-violators still missing one. Deterministic,
+// title/vendor-derived (NO metered LLM, NO legal text). Additive: only sets if missing/placeholder.
+const fs=require('fs'),os=require('os'),path=require('path'),https=require('https');
+const DRY=process.argv.includes('--dry-run'),LIMIT=process.argv.includes('--limit')?parseInt(process.argv[process.argv.indexOf('--limit')+1],10):Infinity;
+function tok(){if(process.env.SHOPIFY_ADMIN_TOKEN)return process.env.SHOPIFY_ADMIN_TOKEN.replace(/['"]/g,'').trim();try{return fs.readFileSync(path.join(os.homedir(),'Projects/secrets-manager/.env'),'utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.*)$/m)[1].replace(/['"]/g,'').trim()}catch{return null}}
+const T=tok(),STORE='designer-laboratory-sandbox.myshopify.com',API='2024-10',sleep=ms=>new Promise(r=>setTimeout(r,ms));
+function gql(q,v){return new Promise(res=>{const b=JSON.stringify({query:q,variables:v});const r=https.request({hostname:STORE,path:`/admin/api/${API}/graphql.json`,method:'POST',headers:{'X-Shopify-Access-Token':T,'Content-Type':'application/json','Content-Length':Buffer.byteLength(b)}},x=>{let d='';x.on('data',c=>d+=c);x.on('end',()=>{try{res(JSON.parse(d))}catch{res({})}})});r.on('error',()=>res({}));r.write(b);r.end()})}
+async function G(q,v){for(let a=0;a<6;a++){const r=await gql(q,v);if(r.errors&&JSON.stringify(r.errors).toUpperCase().includes('THROTTL')){await sleep(2000*(a+1));continue}return r}return{}}
+const noWP=s=>String(s||'').replace(/\bWallpapers?\b/gi,'Wallcovering').replace(/\bWallpaper\b/gi,'Wallcovering');
+const PROD=`query($id:ID!){product(id:$id){id title vendor descriptionHtml}}`;
+const SET=`mutation($i:ProductInput!){productUpdate(input:$i){userErrors{message}}}`;
+const PH=/\b(unknown|page\s*not\s*found|not\s*found|404|undefined|null|error|placeholder|lorem ipsum|coming soon|tbd|n\/a)\b/i;
+function build(title,vendor){const pat=noWP(String(title||'').split('|')[0].trim()||'This design');const b=noWP((String(title||'').split('|')[1]||vendor||'').trim());
+  const lead=b?`${pat} is a designer wallcovering from ${b}.`:`${pat} is a designer wallcovering.`;
+  return noWP(`<p>${lead} A refined choice that brings texture, color, and character to residential and commercial interiors. Order a sample to evaluate the color and finish in your own space before committing to full rolls.</p>`);}
+(async()=>{
+  const vio=(require('../data/active-gate-violations-2026-06-21.json').violations||[]).filter(x=>(x.types||[]).includes('description'));
+  const LED=path.join(__dirname,'..','data','description-ledger.jsonl');const done=new Set();
+  if(fs.existsSync(LED))for(const l of fs.readFileSync(LED,'utf8').split('\n')){if(l.trim())try{done.add(JSON.parse(l).id)}catch{}}
+  const queue=vio.filter(x=>!done.has(x.id)).slice(0,LIMIT===Infinity?undefined:LIMIT);
+  console.log(`fill-descriptions ${DRY?'[DRY] ':''}desc-violators=${vio.length} done=${done.size} thisRun=${queue.length}`);
+  const st={filled:0,hadDesc:0,err:0};
+  for(const x of queue){
+    const pr=await G(PROD,{id:x.id});const p=pr.data&&pr.data.product;if(!p){st.err++;continue}
+    const cur=(p.descriptionHtml||'').replace(/<[^>]*>/g,' ').replace(/\s+/g,' ').trim();
+    if(cur&&!PH.test(cur)&&cur.length>=24){st.hadDesc++;fs.appendFileSync(LED,JSON.stringify({id:x.id,result:'has-desc'})+'\n');continue}
+    const desc=build(p.title,p.vendor);
+    if(DRY){st.filled++;if(st.filled<=6)console.log(`  • ${p.title.slice(0,46)} → ${desc.slice(0,70)}…`);continue}
+    const r=await G(SET,{i:{id:x.id,descriptionHtml:desc}});
+    if(r.data&&r.data.productUpdate&&!r.data.productUpdate.userErrors.length){st.filled++;fs.appendFileSync(LED,JSON.stringify({id:x.id,result:'filled'})+'\n')}else st.err++;
+  }
+  console.log(`\n=== ${DRY?'DRY ':''}SUMMARY ===`,JSON.stringify(st));
+})().catch(e=>{console.error('FATAL',e);process.exit(1)});

← ff2a7c1 tag-needs-width: add Needs-Width tag to width-violators stil  ·  back to Imageless Rescue  ·  auto-save: 2026-06-21T19:01:26 (1 files) — dw-vendor-cost-pr c14705c →