← back to Carnegie Reprice

carnegie-mfr-gate.mjs

138 lines

#!/usr/bin/env node
// carnegie-mfr-gate.mjs — TK-10792 stop-the-bleed guard
//
// Validates that a Carnegie product row has a REAL vendor mfr_sku before
// any activator is allowed to set status='active'. Products that fail are
// kept as draft and tagged Needs-Mfr-SKU.
//
// Usage as a library:
//   import { mfrSkuValid, assertActivatable, SKIP_TAG } from './carnegie-mfr-gate.mjs';
//   const { ok, reason } = assertActivatable(row);
//   if (!ok) { /* keep draft, add SKIP_TAG to tags */ }
//
// Usage as a CLI audit:
//   node carnegie-mfr-gate.mjs            # audit carnegie_catalog, print skip counts
//   node carnegie-mfr-gate.mjs --verbose  # print every skipped SKU

import { execFileSync } from 'node:child_process';

// ---- Gate logic ----------------------------------------------------------------

/** Tag applied to products that fail the gate */
export const SKIP_TAG = 'Needs-Mfr-SKU';

/**
 * Returns true iff `mfr` is a real vendor mfr_sku:
 *  - not null / empty / whitespace-only
 *  - does NOT match /^DWAG-/i  (DW-internal placeholder codes)
 *  - does NOT match /^DW[A-Z]{2}-/i in general (all DW-prefix internal codes)
 *    NOTE: only DWAG is confirmed as the bleed source; the regex is intentionally
 *    tight so legitimate vendor codes (DWSC-*, etc.) that happen to be known are
 *    not blocked. Adjust the regex if new internal prefixes are discovered.
 */
export function mfrSkuValid(mfr) {
  if (mfr === null || mfr === undefined) return false;
  const s = String(mfr).trim();
  if (!s) return false;
  if (/^DWAG-/i.test(s)) return false;
  return true;
}

/** Tag applied to products that fail the body_html gate */
export const NEEDS_DESC_TAG = 'Needs-Description';

/**
 * Returns true iff `body` is a real, non-blank product description.
 * Strips HTML tags,  , and whitespace before checking, so an empty
 * <p></p> shell or a bare wrapper counts as blank. This is the guard the
 * TK-10686 batch was missing — it shipped 57 products ACTIVE with blank
 * body_html because the activator gated only on mfr_sku.
 */
export function bodyHtmlValid(body) {
  if (body === null || body === undefined) return false;
  const text = String(body)
    .replace(/<[^>]*>/g, '')                 // strip tags
    .replace(/&(nbsp|thinsp|ensp|emsp|zwnj|zwj|#x?0*(a0|160|8203);?|#160|#8203);/gi, ' ') // whitespace-like entities
    .replace(/&[a-z]+;|&#x?[0-9a-f]+;/gi, '') // any remaining entity → drop (mdash/amp/etc. add no real words)
    .trim();
  return text.length > 0;
}

/**
 * Returns { ok: true } if the row is safe to activate, or
 * { ok: false, reason: string } if it must stay draft.
 * Fails on either a missing mfr_sku OR a blank body_html/description.
 *
 * @param {object} row - object with `mfr_sku` and (`body_html`|`description_text`)
 */
export function assertActivatable(row) {
  const mfr = row?.mfr_sku;
  if (!mfrSkuValid(mfr)) {
    const display = (mfr === null || mfr === undefined) ? 'null' : `"${String(mfr)}"`;
    return {
      ok: false,
      reason: `mfr_sku ${display} is null, empty, or a DWAG-* internal placeholder — product stays DRAFT (${SKIP_TAG})`,
    };
  }
  const body = row?.body_html ?? row?.description_text;
  if (!bodyHtmlValid(body)) {
    return {
      ok: false,
      reason: `body_html/description is blank — product stays DRAFT (${NEEDS_DESC_TAG})`,
    };
  }
  return { ok: true };
}

/**
 * Given a comma-separated tags string, ensure SKIP_TAG is present.
 * Returns the updated tags string.
 */
export function addSkipTag(tags) {
  const arr = (tags || '').split(',').map(t => t.trim()).filter(Boolean);
  if (!arr.includes(SKIP_TAG)) arr.push(SKIP_TAG);
  return arr.join(', ');
}

// ---- CLI audit (when run directly) --------------------------------------------

if (import.meta.url === `file://${process.argv[1]}`) {
  const VERBOSE = process.argv.includes('--verbose');
  const DB = 'postgresql:///dw_unified?host=/tmp';
  const PSQL = [
    '/opt/homebrew/opt/postgresql@14/bin/psql',
    '/usr/local/opt/postgresql@14/bin/psql',
    'psql',
  ].find(p => { try { execFileSync(p, ['--version'], { stdio: 'ignore' }); return true; } catch { return false; } }) || 'psql';

  const q = sql => execFileSync(PSQL, [DB, '-At', '-c', sql], { encoding: 'utf8' }).trim();

  console.log('=== Carnegie mfr_sku gate audit (carnegie_catalog) ===\n');

  const total = +q('SELECT COUNT(*) FROM carnegie_catalog');
  const nullCount = +q("SELECT COUNT(*) FROM carnegie_catalog WHERE mfr_sku IS NULL OR mfr_sku = ''");
  const dwagCount = +q("SELECT COUNT(*) FROM carnegie_catalog WHERE mfr_sku ILIKE 'DWAG-%'");
  const realCount = +q("SELECT COUNT(*) FROM carnegie_catalog WHERE mfr_sku IS NOT NULL AND mfr_sku <> '' AND mfr_sku NOT ILIKE 'DWAG-%'");

  console.log(`Total rows          : ${total}`);
  console.log(`Real mfr_sku (PASS) : ${realCount}  (${((realCount/total)*100).toFixed(1)}%)`);
  console.log(`DWAG-* codes (SKIP) : ${dwagCount}  (${((dwagCount/total)*100).toFixed(1)}%)`);
  console.log(`null/empty   (SKIP) : ${nullCount}  (${((nullCount/total)*100).toFixed(1)}%)`);
  console.log(`Total would SKIP    : ${dwagCount + nullCount}`);

  if (VERBOSE) {
    console.log('\n--- Skipped SKUs (sample up to 50) ---');
    const rows = q(
      "SELECT dw_sku, mfr_sku, pattern_name FROM carnegie_catalog " +
      "WHERE mfr_sku IS NULL OR mfr_sku = '' OR mfr_sku ILIKE 'DWAG-%' " +
      "ORDER BY pattern_name, mfr_sku LIMIT 50"
    );
    for (const line of rows.split('\n').filter(Boolean)) {
      console.log(' ', line);
    }
  }

  console.log('\nProducts in this set would be created as DRAFT with tag "Needs-Mfr-SKU".');
  console.log('They will NOT be activated until Steve provides the real vendor code.\n');
}