← back to Dw Width Fix

scan.mjs

85 lines

import fs from 'fs';
const env = Object.fromEntries(fs.readFileSync('.env','utf8').split('\n').filter(l=>l.includes('=')&&!l.startsWith('#')).map(l=>{const i=l.indexOf('=');return [l.slice(0,i).trim(), l.slice(i+1).trim()];}));
const TOKEN = env.SHOPIFY_ADMIN_TOKEN;
const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
const API = `https://${DOMAIN}/admin/api/2024-10/graphql.json`;

async function gql(query, variables={}) {
  const r = await fetch(API, {
    method:'POST',
    headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},
    body:JSON.stringify({query, variables})
  });
  const j = await r.json();
  if (j.errors) { console.error(JSON.stringify(j.errors)); throw new Error('gql'); }
  return j.data;
}

// broken classifier per memo
function classify(v) {
  if (v == null) return {broken:false, reason:'null'};
  const s = String(v).trim();
  if (s === '') return {broken:true, reason:'empty'};
  const low = s.toLowerCase();
  // NOT broken (verified legit non-numeric)
  if (low === 'hide') return {broken:false, reason:'leather-hide'};
  if (/untrimmed/.test(low)) return {broken:false, reason:'maya-untrimmed'};
  if (/sold per yard/.test(low)) return {broken:false, reason:'per-yard'};
  if (/pillow|cushion/.test(low)) return {broken:false, reason:'pillow'};
  // Elitis european decimal like "0,7m (27\")" -> has a real inch width in parens
  if (/^0[.,]\d+\s*m/.test(low) && /\d+\s*("|in)/.test(low)) return {broken:false, reason:'euro-decimal-with-inches'};
  // BROKEN classes
  if (s === '0') return {broken:true, reason:'literal-zero'};
  if (/^0\s*cm\s*\(0\.00/.test(low)) return {broken:true, reason:'zero-cm'};
  if (low === 'tbd') return {broken:true, reason:'placeholder-tbd'};
  if (/call to verify/.test(low)) return {broken:true, reason:'placeholder-call'};
  if (low === '(ft)') return {broken:true, reason:'unit-only-ft'};
  if (low === 'roll length') return {broken:true, reason:'label-only'};
  if (s === ':') return {broken:true, reason:'colon-only'};
  if (/wallpaper\.?$/.test(low) && !/\d/.test(s)) return {broken:true, reason:'desc-leaked'};
  // general: no digit at all AND not a known-legit non-numeric => suspicious (report, not auto)
  if (!/\d/.test(s)) return {broken:true, reason:'no-digit-nonlegit'};
  // has a digit but is it a real zero width? "0 cm" variants, or purely "0..."
  if (/^0(\.0+)?\s*(cm|in|"|mm|m)?$/.test(low)) return {broken:true, reason:'zero-width'};
  return {broken:false, reason:'ok'};
}

const out = [];
let cursor = null, page=0;
const q = `query($cursor:String){
  products(first:200, after:$cursor, query:"status:active") {
    pageInfo{hasNextPage endCursor}
    nodes{ id title status vendor handle
      metafield(namespace:"custom", key:"width"){ id value }
    }
  }
}`;
let total=0, withWidth=0;
while(true){
  const d = await gql(q,{cursor});
  const conn = d.products;
  for(const n of conn.nodes){
    total++;
    if(!n.metafield) continue;
    withWidth++;
    const c = classify(n.metafield.value);
    if(c.broken){
      out.push({id:n.id, title:n.title, vendor:n.vendor, handle:n.handle,
        mfId:n.metafield.id, value:n.metafield.value, reason:c.reason});
    }
  }
  page++;
  process.stderr.write(`page ${page} scanned=${total} withWidth=${withWidth} broken=${out.length}\r`);
  if(!conn.pageInfo.hasNextPage) break;
  cursor = conn.pageInfo.endCursor;
}
process.stderr.write('\n');
fs.writeFileSync('broken-live.json', JSON.stringify(out,null,2));
console.log(`\nTOTAL active scanned: ${total}`);
console.log(`with custom.width: ${withWidth}`);
console.log(`BROKEN now: ${out.length}`);
const byReason={}, byVendor={};
for(const o of out){ byReason[o.reason]=(byReason[o.reason]||0)+1; byVendor[o.vendor]=(byVendor[o.vendor]||0)+1; }
console.log('by reason:', JSON.stringify(byReason,null,2));
console.log('by vendor:', JSON.stringify(byVendor,null,2));