← 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}`);
})();