← back to Harlequin Sample Price Analysis

scripts/integrity.mjs

65 lines

// integrity.mjs — PURE, dependency-free catalog data-integrity detectors.
// Generalises the TK-10870 Harlequin findings into reusable checks for ANY vendor:
//   1. product-id collisions   (>1 catalog row sharing one shopify_product_id)
//   2. mfr_sku mis-stamps       (one mfr_sku across >1 distinct Shopify sku)
//   3. junk mfr_sku values      (booleans / null-ish leaked into mfr_sku)
//   4. cost gaps                (on-shopify rows with no net cost)
// No DB, no I/O — feed it rows, get findings. Unit-tested by tests/integrity.test.mjs.
import { isJunkMfrSku } from './pricing.mjs';

/** Rows (with a shopify_product_id) that share that id with another row. */
export function findProductIdCollisions(catalogRows) {
  const byId = new Map();
  for (const r of catalogRows) {
    const id = r.shopify_product_id;
    if (id == null || id === '') continue;
    if (!byId.has(id)) byId.set(id, []);
    byId.get(id).push(r);
  }
  const groups = [];
  for (const [id, rows] of byId) if (rows.length > 1) groups.push({ shopify_product_id: id, count: rows.length, rows });
  return groups;
}

/** mfr_sku values stamped across >1 distinct Shopify sku (excludes junk values). */
export function findMfrMisStamps(shopifyRows) {
  const bySku = new Map();
  for (const r of shopifyRows) {
    const m = r.mfr_sku;
    if (m == null || m === '' || isJunkMfrSku(m)) continue;
    if (!bySku.has(m)) bySku.set(m, new Set());
    bySku.get(m).add(r.sku);
  }
  const groups = [];
  for (const [mfr_sku, skus] of bySku) if (skus.size > 1) groups.push({ mfr_sku, distinct_skus: [...skus].sort() });
  return groups;
}

/** Shopify rows whose mfr_sku is a junk/leaked value. */
export function findJunkMfrSku(shopifyRows) {
  return shopifyRows.filter((r) => isJunkMfrSku(r.mfr_sku));
}

/** On-shopify catalog rows with no usable net cost. */
export function findCostGaps(catalogRows) {
  return catalogRows.filter((r) => r.on_shopify && (r.price_trade == null || Number(r.price_trade) <= 0));
}

/** One combined integrity summary for a vendor. */
export function integrityReport({ catalogRows = [], shopifyRows = [] }) {
  const collisions = findProductIdCollisions(catalogRows);
  const misStamps = findMfrMisStamps(shopifyRows);
  const junk = findJunkMfrSku(shopifyRows);
  const costGaps = findCostGaps(catalogRows);
  const verdict = (collisions.length || misStamps.length || costGaps.length) ? 'FAIL'
    : junk.length ? 'WARN' : 'PASS';
  return {
    verdict,
    productId_collisions: collisions.length,
    mfr_misstamps: misStamps.length,
    junk_mfr_sku: junk.length,
    cost_gaps: costGaps.length,
    detail: { collisions, misStamps, junkSkus: junk.map((r) => r.sku), costGaps: costGaps.map((r) => r.mfr_sku) },
  };
}