← back to Dw Unbuyable Recovery Pilot
tk11041-innovations-reconcile/lib/provenance.mjs
61 lines
// TK-11041 — catalog-source provenance guard (the narrow scraper/test hardening).
//
// PURPOSE: encode the TK-10981 lesson as an executable gate. Before any *_catalog
// staging row is TRUSTED as an authoritative recovery SOURCE (to backfill a real
// mfr item# or a net price onto a customer-facing product), it must pass this
// guard. A plausible code SHAPE is NOT proof of provenance — the innovations_catalog
// scrape produced 81 rows whose mfr_sku is just the *tail of the pattern name*
// (Sum`atra` -> `atra-1`, Maz`arin` -> `arin-1`) and 236 rows with no product_url
// at all. Backfilling from those would fabricate identity/price data.
//
// Pure functions, no I/O — unit-testable in isolation (see ../test/provenance.test.mjs).
// A real Innovations-style vendor code: 2–4 uppercase letters + digits (ZIO-88, SOA008).
const CODE_SHAPE = /^[A-Z]{2,4}-?\d{1,5}$/;
// The truncation signature: an all-lowercase letter run + digits (arin-1, atra-3).
const LOWERCASE_TAIL = /^[a-z]{2,6}-\d+$/;
// Bare placeholders that leaked in from boolean/null scrapes.
const PLACEHOLDER = /^(TRUE|FALSE|NULL|N\/?A|-|\?)$/i;
/** Does the code's letter run appear as the TAIL of any word of the pattern name? */
function codeIsPatternTail(code, patternName) {
const letters = (code || '').replace(/[^a-z]/gi, '').toLowerCase();
if (letters.length < 3) return false;
const words = (patternName || '')
.toLowerCase()
.split(/[^a-z]+/)
.filter(w => w && w !== 'wallcovering' && w !== 'wallpaper');
// truncation match: a pattern word ENDS WITH the code letters but is not equal
// to them (i.e. the code is a suffix chopped off the real name).
return words.some(w => w.length > letters.length && w.endsWith(letters));
}
/**
* Classify a staging-catalog row as a recovery source.
* @param {{mfr_sku?:string, product_url?:string, pattern_name?:string, price_trade?:number|string}} row
* @returns {{code:string, trustworthy:boolean, reasons:string[]}}
* trustworthy === true => the row may be used to backfill identity/price.
* reasons lists every disqualifier found (empty when trustworthy).
*/
export function classifyCatalogSource(row = {}) {
const code = (row.mfr_sku ?? '').trim();
const reasons = [];
if ((row.product_url ?? '').trim() === '') reasons.push('no_provenance_url');
if (PLACEHOLDER.test(code)) reasons.push('placeholder_code');
if (LOWERCASE_TAIL.test(code)) reasons.push('corrupted_lowercase_tail');
if (codeIsPatternTail(code, row.pattern_name)) reasons.push('code_is_pattern_tail');
if (!CODE_SHAPE.test(code) && !LOWERCASE_TAIL.test(code) && !PLACEHOLDER.test(code)) {
reasons.push('unexpected_shape');
}
// de-dupe while preserving order
const unique = [...new Set(reasons)];
return { code, trustworthy: unique.length === 0, reasons: unique };
}
/** Convenience boolean. */
export function isTrustworthySource(row) {
return classifyCatalogSource(row).trustworthy;
}