← back to Council 0809 Builds

rolls-ar-deeplink/audit-pathA-pg.js

67 lines

#!/usr/bin/env node
// READ-ONLY Path A audit (PG side only — no Shopify, no writes). Reuses the backfill's
// OWN toInches normalizer + supply logic, but instead of first-writer-wins it DETECTS
// cross-vendor SKU collisions + per-vendor value distribution + out-of-range (unit errors).
'use strict';
const { execFileSync } = require('child_process');
function psql(sql) { return execFileSync('psql', ['-h', '/tmp', '-d', 'dw_unified', '-tAF\t', '-X', '-c', sql], { encoding: 'utf8', maxBuffer: 1 << 28 }); }
function toInches(v) { if (v == null) return null; const m = String(v).match(/[0-9]+(\.[0-9]+)?/); if (!m) return null; const n = parseFloat(m[0]); return Number.isFinite(n) && n > 0 ? Math.round(n * 100) / 100 : null; }
const median = (a) => { if (!a.length) return null; const s = [...a].sort((x, y) => x - y); const m = s.length >> 1; return s.length % 2 ? s[m] : Math.round((s[m - 1] + s[m]) / 2 * 100) / 100; };

const tables = psql("select table_name from information_schema.columns where table_schema='public' and column_name='repeat_v' and table_name like '%_catalog' and table_name not like '%_bak%' and table_name not like '_bak%'").trim().split('\n').filter(Boolean).map(s => s.trim());

const perVendor = [];
const keyMap = new Map(); // upper(sku) -> [{vendor, val}]
let totalRows = 0, totalNumeric = 0;

for (const t of tables) {
  const cols = psql(`select column_name from information_schema.columns where table_schema='public' and table_name='${t}'`).trim().split('\n').map(s => s.trim());
  const skuCol = ['mfr_sku', 'manufacturer_sku', 'sku', 'pattern_number', 'product_code', 'style_number'].find(c => cols.includes(c));
  if (!skuCol) { perVendor.push({ t, skuCol: null, rows: 0, numeric: 0, note: 'NO sku column — contributes nothing' }); continue; }
  let rows = [];
  try { rows = psql(`select ${skuCol}::text, repeat_v::text from ${t} where repeat_v is not null`).trim().split('\n').filter(Boolean); } catch { perVendor.push({ t, skuCol, rows: 0, numeric: 0, note: 'query failed' }); continue; }
  const vals = []; let oor = 0;
  for (const line of rows) {
    const [sku, rv] = line.split('\t');
    const n = toInches(rv);
    if (!sku || n == null) continue;
    vals.push(n);
    if (n > 36) oor++;                    // >36in = likely a cm value stored as-is / unit error
    const key = sku.trim().toUpperCase();
    if (!keyMap.has(key)) keyMap.set(key, []);
    keyMap.get(key).push({ vendor: t, val: n });
  }
  totalRows += rows.length; totalNumeric += vals.length;
  perVendor.push({ t, skuCol, rows: rows.length, numeric: vals.length,
    min: vals.length ? Math.min(...vals) : null, med: median(vals), max: vals.length ? Math.max(...vals) : null,
    out_of_range_gt36: oor });
}

// cross-vendor collisions: same key in >=2 DISTINCT vendors
let collisionKeys = 0, divergentKeys = 0; const divergentExamples = [];
for (const [key, arr] of keyMap) {
  const vendors = new Set(arr.map(a => a.vendor));
  if (vendors.size < 2) continue;
  collisionKeys++;
  const distinctVals = new Set(arr.map(a => a.val));
  if (distinctVals.size > 1) {
    divergentKeys++;
    if (divergentExamples.length < 15) divergentExamples.push({ key, spread: [...arr.map(a => `${a.vendor.replace('_catalog','')}=${a.val}"`)] });
  }
}

const worstOOR = perVendor.filter(v => v.out_of_range_gt36 > 0).sort((a, b) => b.out_of_range_gt36 - a.out_of_range_gt36).slice(0, 12);
const noSku = perVendor.filter(v => v.skuCol === null).map(v => v.t);

console.log(JSON.stringify({
  catalogs: tables.length,
  supply_rows_with_repeat: totalRows,
  supply_numeric_parseable: totalNumeric,
  distinct_supply_keys: keyMap.size,
  cross_vendor_collision_keys: collisionKeys,
  DIVERGENT_collision_keys: divergentKeys,   // ← the hazard: first-writer-wins mis-prefills the losers
  divergent_examples: divergentExamples,
  vendors_no_sku_col: noSku,
  worst_out_of_range_vendors: worstOOR.map(v => ({ vendor: v.t, gt36: v.out_of_range_gt36, of_numeric: v.numeric, max: v.max })),
}, null, 2));