← back to Dw Yolo Loop

scripts/kravet-cost/currency-check.js

72 lines

#!/usr/bin/env node
/**
 * currency-check.js — per-brand "is this mfr_sku still CURRENT at Kravet?" verifier.
 * Reuses the proven exact-sku Algolia logic from kravet-disco-algolia-verify.js.
 * READ-ONLY (Algolia index queries + a DB read). Writes a per-brand result CSV only —
 * never archives/updates Shopify or the DB (archiving disco SKUs is a separate gated step).
 *
 * USAGE:  AKEY=<algolia-key> node currency-check.js "<vendor brand>"
 * Designed to run one brand per process so the whole family fans out in parallel
 * (see run-parallel.sh). Output: data/kravet-cost/currency-<brand-slug>.csv
 *
 * A SKU is:
 *   CURRENT       — exact sku present in the live Kravet US index
 *   DISCO_COLOR   — pattern still indexed, this colorway gone
 *   DISCO_PATTERN — whole pattern absent (line itself is indexed elsewhere)
 *   AMW_SKIP      — Andrew Martin (AMW*/AM1*) — not in this index, needs its own source
 */
const https = require('https');
const fs = require('fs');
const { execSync } = require('child_process');

const APP = 'M9TBUM1WAE', IDX = 'kravet_production_kravet_us_products', KEY = process.env.AKEY;
const BRAND = process.argv[2];
if (!BRAND) { console.error('usage: AKEY=… node currency-check.js "<brand>"'); process.exit(2); }
if (!KEY) { console.error('FATAL: AKEY (Algolia key) not set in env — cannot currency-check'); process.exit(3); }

const slug = BRAND.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
const OUT = `data/kravet-cost/currency-${slug}.csv`;
const sleep = ms => new Promise(r => setTimeout(r, ms));

// pull this brand's ACTIVE mfr_skus from the mirror (read-only)
const sql = `select distinct on (shopify_id) mfr_sku from shopify_products
  where status='ACTIVE' and vendor='${BRAND.replace(/'/g, "''")}' and mfr_sku is not null and mfr_sku<>'';`;
const rows = execSync(`psql -d dw_unified -tA -c "${sql}"`, { encoding: 'utf8' })
  .trim().split('\n').map(s => s.trim()).filter(Boolean);

function query(q) {
  return new Promise(res => {
    const body = JSON.stringify({ query: q, restrictSearchableAttributes: ['sku'], hitsPerPage: 200, attributesToRetrieve: ['sku'] });
    const req = https.request(`https://${APP}-dsn.algolia.net/1/indexes/${IDX}/query`,
      { method: 'POST', headers: { 'X-Algolia-Application-Id': APP, 'X-Algolia-API-Key': KEY, 'Content-Type': 'application/json' } },
      r => { let b = ''; r.on('data', d => b += d); r.on('end', () => { try { res(JSON.parse(b)); } catch (e) { res({ hits: [], _err: 1 }); } }); });
    req.on('error', () => res({ hits: [], _err: 1 }));
    req.setTimeout(20000, () => { req.destroy(); res({ hits: [], _err: 1 }); });
    req.write(body); req.end();
  });
}

(async () => {
  const patCache = {};
  const out = [];
  let current = 0, dColor = 0, dPat = 0, amw = 0, err = 0, i = 0;
  for (const sku of rows) {
    i++;
    const pat = sku.split('.')[0];
    if (/^AMW|^AM1/i.test(pat)) { out.push([sku, 'AMW_SKIP']); amw++; continue; }
    if (!(pat in patCache)) {
      const r = await query(pat);
      if (r._err) { out.push([sku, 'ERROR']); err++; continue; }
      patCache[pat] = new Set((r.hits || []).map(h => (h.sku || '').toUpperCase()).filter(s => s.startsWith(pat.toUpperCase())));
      await sleep(120);
    }
    const set = patCache[pat];
    if (set.has(sku.toUpperCase())) { out.push([sku, 'CURRENT']); current++; }
    else if (set.size > 0) { out.push([sku, 'DISCO_COLOR']); dColor++; }
    else { out.push([sku, 'DISCO_PATTERN']); dPat++; }
    if (i % 100 === 0) process.stderr.write(`  [${BRAND}] ${i}/${rows.length} cur:${current} discoC:${dColor} discoP:${dPat}\n`);
  }
  fs.writeFileSync(OUT, 'mfr_sku,status\n' + out.map(r => r.join(',')).join('\n') + '\n');
  console.log(`[${BRAND}] total:${rows.length} CURRENT:${current} DISCO_COLOR:${dColor} DISCO_PATTERN:${dPat} AMW:${amw} ERR:${err} -> ${OUT}`);
})();