← back to Harlequin Sample Price Analysis

scripts/integrity-report.mjs

36 lines

#!/usr/bin/env node
// integrity-report.mjs — READ-ONLY runner for the reusable catalog-integrity detector.
// Reads a vendor's catalog + shopify rows from dw_unified (forced read-only session) and
// prints the integrityReport verdict. NEVER writes.
//
// Usage:
//   node scripts/integrity-report.mjs <catalog_table> "<shopify_row_filter_sql>"
//   e.g. node scripts/integrity-report.mjs harlequin_catalog "vendor ILIKE '%harlequin%' OR sku LIKE 'DWHQ-%'"
import { execFileSync } from 'node:child_process';
import { integrityReport } from './integrity.mjs';

const table = process.argv[2] || 'harlequin_catalog';
const shopFilter = process.argv[3] || "vendor ILIKE '%harlequin%' OR sku LIKE 'DWHQ-%'";

// Guard: table name must be a bare identifier; filter must be SELECT-safe (no writes / no ';').
if (!/^[a-z_][a-z0-9_]*$/.test(table)) throw new Error('refusing: bad table name');
if (/;|\b(insert|update|delete|drop|alter|truncate|create|grant|copy)\b/i.test(shopFilter))
  throw new Error('refusing: unsafe filter');

const env = { ...process.env, PGHOST: process.env.PGHOST || '/tmp',
  PGDATABASE: process.env.PGDATABASE || 'dw_unified', PGOPTIONS: '-c default_transaction_read_only=on' };
const q = (sql) => JSON.parse(execFileSync('psql', ['-tAc', `SELECT coalesce(json_agg(t),'[]') FROM (${sql}) t`],
  { env, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }).trim() || '[]');

const catalogRows = q(`SELECT shopify_product_id, mfr_sku, on_shopify, price_trade FROM ${table}`);
const shopifyRows = q(`SELECT sku, mfr_sku FROM shopify_products WHERE ${shopFilter}`);

const report = integrityReport({ catalogRows, shopifyRows });
console.log(JSON.stringify({
  table, verdict: report.verdict,
  productId_collisions: report.productId_collisions,
  mfr_misstamps: report.mfr_misstamps,
  junk_mfr_sku: report.junk_mfr_sku,
  cost_gaps: report.cost_gaps,
}, null, 2));