← back to Fineartamerica Price Sync

canary.js

67 lines

#!/usr/bin/env node
// FAA loss-guard canary (READ-ONLY).
//
// The failure this guards against: a Shopify FAA variant price drifting at or
// below its FAA fulfillment cost — i.e. an order that loses money (exactly what
// happened on the Washington Top Hat 20"x24"). FAA raises base prices over time
// and static Shopify prices silently slide underwater; this catches that.
//
//   node canary.js --product 7549840293939          # check one product
//   node canary.js --variant 43235375022131         # check one variant (the canary)
//   node canary.js --all                             # sweep every FAA product
//
// Classifies each variant: LOSS (price <= cost), THIN (margin < floor, default
// 25%), OK. Emits data/canary-latest.json (heartbeat for the meta-watchdog) and
// exits non-zero if any LOSS is found. Alerting (CNCP card + George email) is a
// worsening-only transition — wired by the launchd wrapper, not here — so steady
// state stays silent.

const fs = require('fs');
const path = require('path');
const { parseSku, configKey, configLabel } = require('./lib/parse-sku');
const { grossMargin } = require('./lib/pricing');
const { getProduct, listFaaProducts } = require('./lib/shopify');
const { loadCache } = require('./lib/faa-cost');

const args = process.argv.slice(2);
const val = f => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : null; };
const FLOOR = Number(val('--floor') || 0.25);

(async () => {
  const costs = loadCache();
  let products;
  if (val('--product')) products = [await getProduct(val('--product'))];
  else if (val('--variant')) {
    // find the product owning this variant among FAA products
    const all = await listFaaProducts();
    products = all.filter(p => p.variants.some(v => String(v.id) === String(val('--variant'))));
  } else products = await listFaaProducts();

  const findings = [];
  for (const p of products) {
    for (const v of p.variants) {
      if (val('--variant') && String(v.id) !== String(val('--variant'))) continue;
      const cfg = parseSku(v.sku); if (!cfg) continue;
      const cost = costs[configKey(cfg)]; const cur = Number(v.price);
      if (cost == null) { findings.push({ v: v.id, variant: v.title, verdict: 'COST-UNKNOWN', current: cur }); continue; }
      const margin = grossMargin(cost, cur);
      const verdict = cur <= cost ? 'LOSS' : margin < FLOOR ? 'THIN' : 'OK';
      findings.push({ v: v.id, product: p.title, variant: v.title, config: configLabel(cfg),
        faaCost: cost, current: cur, margin: +(margin).toFixed(3), verdict });
    }
  }

  const loss = findings.filter(f => f.verdict === 'LOSS');
  const thin = findings.filter(f => f.verdict === 'THIN');
  const summary = { checked: findings.length, loss: loss.length, thin: thin.length,
    floor: FLOOR, worst: loss[0] || thin[0] || null };
  const latest = { ts: new Date().toISOString(), summary, findings };
  fs.writeFileSync(path.join(__dirname, 'data', 'canary-latest.json'), JSON.stringify(latest, null, 2));

  console.log(`FAA loss-guard: checked ${findings.length}  LOSS ${loss.length}  THIN ${thin.length}  (margin floor ${FLOOR*100}%)`);
  for (const f of [...loss, ...thin]) {
    console.log(`  ${f.verdict.padEnd(4)} ${f.variant.padEnd(30)} cost $${f.faaCost}  price $${f.current}  margin ${(f.margin*100).toFixed(0)}%`);
  }
  process.exit(loss.length ? 1 : 0);
})().catch(e => { console.error(e); process.exit(3); });