← back to Dw Yolo Loop

scripts/live-price-canary/live-price-pilot.mjs

58 lines

// live-price-pilot (c38) — READ-ONLY storefront crawl. The Mac2 mirror price
// column is unreliable (c37, ~30% synced), so measure TRUE customer-visible
// pricing by sampling ACTIVE handles and reading live /products/<handle>.json.
// Classifies each: NO_PRICE (blank/all 0), SAMPLE_ONLY (only $4.25, no real roll),
// HAS_REAL (a variant > $4.25). Sizes whether a standing live-price canary is
// warranted. $0 (storefront GETs only). Handles sampled from mirror (status lag
// OK — we verify live + drop 404s).
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
const N = parseInt(process.argv[2]||'150',10);
const PSQL='/opt/homebrew/opt/postgresql@14/bin/psql', DB='postgresql:///dw_unified?host=/tmp';
const OUT=`${process.env.HOME}/.claude/yolo-queue/live-price-pilot-2026-06-16.json`;
const MD =`${process.env.HOME}/.claude/yolo-queue/live-price-pilot-2026-06-16.md`;
const rows = execFileSync(PSQL,[DB,'-At','-F','\t','-c',
  `select handle, vendor from shopify_products where status='ACTIVE' and handle is not null and handle<>'' order by random() limit ${N}`],{encoding:'utf8'})
  .trim().split('\n').filter(Boolean).map(l=>l.split('\t'));
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
async function getJSON(h){
  const ctrl=new AbortController(); const t=setTimeout(()=>ctrl.abort(),12000);
  try{ const r=await fetch(`https://www.designerwallcoverings.com/products/${encodeURIComponent(h)}.json`,{signal:ctrl.signal});
    if(r.status!==200) return {status:r.status};
    const j=await r.json(); return {status:200, product:j.product}; }
  catch(e){ return {status:0, err:String(e.message).slice(0,30)}; }
  finally{ clearTimeout(t); }
}
const buckets={NO_PRICE:0, SAMPLE_ONLY:0, HAS_REAL:0, STALE_404:0, UNKNOWN:0};
const byVendor={}; const examples={NO_PRICE:[],SAMPLE_ONLY:[]};
let decided=0;
for(const [h,v] of rows){
  const r=await getJSON(h);
  if(r.status===404){buckets.STALE_404++; continue;}
  if(r.status!==200||!r.product){buckets.UNKNOWN++; continue;}
  const prices=(r.product.variants||[]).map(x=>parseFloat(x.price)).filter(n=>!isNaN(n));
  let cls;
  if(!prices.length || prices.every(p=>p===0)) cls='NO_PRICE';
  else if(prices.every(p=>p<=4.25)) cls='SAMPLE_ONLY';
  else cls='HAS_REAL';
  buckets[cls]++; decided++;
  byVendor[v]=byVendor[v]||{NO_PRICE:0,SAMPLE_ONLY:0,HAS_REAL:0}; byVendor[v][cls]++;
  if((cls==='NO_PRICE'||cls==='SAMPLE_ONLY') && examples[cls].length<5) examples[cls].push(`${h} [${v}] (${prices.join(',')||'none'})`);
  await sleep(120);
}
const pct=x=>decided?Math.round(1000*x/decided)/10:0;
const report={generated_at:new Date().toISOString(), sampled:rows.length, decided, buckets,
  pct:{NO_PRICE:pct(buckets.NO_PRICE),SAMPLE_ONLY:pct(buckets.SAMPLE_ONLY),HAS_REAL:pct(buckets.HAS_REAL)}, examples, byVendor};
fs.writeFileSync(OUT,JSON.stringify(report,null,2));
let md=`# Live-price pilot (storefront crawl) — ${new Date().toISOString().slice(0,16)}\n\n`;
md+=`**READ-ONLY (live /products/<handle>.json GETs), \$0.** Sampled ${rows.length} ACTIVE handles, ${decided} decided (live 200s). Mirror price column is unreliable (c37) — this measures TRUE displayed price.\n\n`;
md+=`| Live price class | count | % of decided |\n|---|--:|--:|\n`;
md+=`| HAS_REAL (a variant > \$4.25) | ${buckets.HAS_REAL} | ${report.pct.HAS_REAL}% |\n`;
md+=`| SAMPLE_ONLY (only \$4.25, no real roll) | ${buckets.SAMPLE_ONLY} | ${report.pct.SAMPLE_ONLY}% |\n`;
md+=`| NO_PRICE (blank / all 0) | ${buckets.NO_PRICE} | ${report.pct.NO_PRICE}% |\n`;
md+=`\n_STALE_404=${buckets.STALE_404} (mirror-ACTIVE but 404 live) · UNKNOWN=${buckets.UNKNOWN}._\n\n`;
if(examples.NO_PRICE.length){md+=`**NO_PRICE examples:**\n`; for(const e of examples.NO_PRICE) md+=`- ${e}\n`;}
fs.writeFileSync(MD,md);
console.log(`[live-price-pilot] sampled=${rows.length} decided=${decided} | HAS_REAL ${report.pct.HAS_REAL}% · SAMPLE_ONLY ${report.pct.SAMPLE_ONLY}% · NO_PRICE ${report.pct.NO_PRICE}% · 404=${buckets.STALE_404}`);
console.log(`Report: ${MD}`);