← 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) },
};
}