← back to Dw Sku Integrity
classify.mjs
255 lines
// classify.mjs — deterministic, pure classifier for the canonical dw_sku backlog.
//
// DOCTRINE (Steve, 2026-08-26 — memory `dw-dwsku-keep-old-never-mint`):
// The canonical dw_sku must RECOVER an EXISTING code (self-copy from `sku`,
// or re-scrape the real mfr/DW code). It must NEVER mint a new sequential.
// This module therefore only ever proposes a candidate dw_sku that is
// ALREADY present on the row (in `sku`) — it can never fabricate one.
//
// Pure functions only: no DB, no I/O, no clock, no randomness. Fully testable.
// Parent ticket: TK-10896.
// The unit / variant suffix vocabulary observed on `sku` for blank-dw_sku rows.
// (Verified counts on the Mac2 dw_unified mirror 2026-08-30:
// Sample 7325, Yard 2104, Roll/roll 656, Per Yard 42, Panel 12, Bolt 9.)
// Extra common UoM tokens are included defensively so the strip stays correct
// if a vendor introduces them; adding a token here can only REMOVE a trailing
// unit word, never alter the code body.
export const UNIT_SUFFIXES = [
'Sample', 'Per Yard', 'Yard', 'Double Roll', 'Single Roll', 'Roll',
'Panel', 'Bolt', 'Each', 'Tile', 'SqFt', 'Sq Ft', 'Yд',
];
// Matches ONE trailing unit suffix, preceded by a '-' or whitespace separator.
// Case-insensitive. "Per Yard" contains a space, hence the \s in the alternatives.
const SUFFIX_RE = new RegExp(
'[\\s-]+(' +
UNIT_SUFFIXES
.slice() // longest-first so "Per Yard" wins over "Yard", "Double Roll" over "Roll"
.sort((a, b) => b.length - a.length)
.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\s+/g, '\\s+'))
.join('|') +
')$',
'i'
);
// PROVENANCE GUARD — Phase-4 "greenfield" mint prefixes (TK-10896, verified via
// the ticket event log + sku-integrity-phase2-phase4 decision aid, 2026-08-26).
// These vendor prefixes had ZERO coded products before the reverted Phase-4 mint,
// so ANY code in one of them now sitting in a blank row's `sku` is MINT RESIDUE,
// not a scraper-native code. Self-copying it would silently re-instate a number
// Steve explicitly reverted ("never mint"). Route these to re-scrape instead.
// Bucket A prefixes (registered==already-in-use: DWKN/DWTT/DWRW/DWJS/DWRO/DWCC)
// are intentionally NOT here — a code there may be scraper-native; disambiguating
// them needs the exact reverted-mint number list (follow-up), so they stay
// self-copy by default rather than over-blocking.
export const GREENFIELD_MINT_PREFIXES = new Set([
'DWAG', // Carnegie
'DWAX', // Maharam
'DWCX', // CMO Paris
'DWST', // Stout Textiles
'DWSC', // Scalamandre
'DWDX', // Designtex
'DWWG', // Wolf Gordon
]);
// Mixed-use Phase-4 prefixes. These prefixes predated the reverted mint, so a
// prefix match alone cannot say whether a row is scraper-native or mint residue.
// Fail closed into a dedicated provenance-review class until the retained
// canonical undo ledger can identify the exact minted numbers. This deliberately
// quarantines legitimate rows too; it never routes them to re-scrape or collision.
export const MIXED_USE_MINT_PREFIXES = new Set([
'DWKN', // Knoll
'DWTT', // Thibaut
'DWRW', // Rebel Walls
'DWJS', // Jeffrey Stevens / York legacy shared prefix
'DWRO', // Romo
'DWCC', // Novasuede
]);
// ---- mfr_sku PROVENANCE ALLOWLIST (DTD verdict A, 2026-09-02, TK-10900) -----
//
// The mfr_sku column is a REAL manufacturer code for only a verified set of
// vendors. For everyone else it is FABRICATED at import time (Maharam "MH-*",
// CMO Paris "CMO_*"), so recovering dw_sku from mfr_sku there would LAUNDER a
// fabricated code into the canonical identity field — the exact mistake this
// gate makes physically impossible.
//
// RULE: mfr_sku -> dw_sku recovery ("mfr self-copy") is permitted ONLY for a
// vendor on this allowlist. Any other vendor's mfr_sku self-copy is hard-routed
// to re-scrape (never written). Evidence: ~/Projects/dw-sku-integrity/evidence/.
// The allowlist is deliberately SMALL and grows only by Steve-ruled evidence.
export const MFR_SKU_REAL_ALLOWLIST = new Set(['carnegie', 'stout']);
// Normalize a vendor label to the allowlist key (lowercased, trimmed, and the
// vendor's leading token so "Stout Textiles" -> "stout"). Must stay in lockstep
// with how the allowlist keys are spelled.
export function vendorKey(vendor) {
const v = String(vendor || '').trim().toLowerCase();
if (!v) return '';
if (MFR_SKU_REAL_ALLOWLIST.has(v)) return v; // exact match wins
const first = v.split(/[\s/|,-]+/)[0]; // "stout textiles" -> "stout"
return MFR_SKU_REAL_ALLOWLIST.has(first) ? first : v;
}
// The single provenance predicate the whole pipeline consults. Everything that
// could write an mfr_sku-derived dw_sku MUST gate on this (classify + every plan
// generator), so the fabricated-mfr class can never reach a canonical write.
export function vendorAllowsMfrSelfCopy(vendor) {
return MFR_SKU_REAL_ALLOWLIST.has(vendorKey(vendor));
}
// Contract-fabric vendors (Carnegie et al.) append a product-type token to the
// real code, e.g. "47081-windows", "100795352-panels-museums". Strip ONLY a
// KNOWN product-type token (repeatedly) so the base colorway code is recovered
// while a genuine colorway word (e.g. "1234-natural") is preserved. Unknown
// tokens are left intact on purpose — a wrong strip could collapse two colorways
// into one identity; the generator's collapse-guard is the second line of defense.
export const MFR_PRODUCT_TYPE_TOKENS = new Set([
'windows', 'window', 'panels', 'panel', 'museums', 'museum', 'upholstery',
'wallcovering', 'wallcoverings', 'wall', 'drapery', 'dividers', 'divider',
'privacy', 'imo', 'vinyl', 'textile', 'textiles', 'ceiling', 'ceilings',
'wovens', 'woven', 'seating', 'cubicle', 'cubicles',
]);
export function normalizeMfrSku(mfr) {
if (mfr == null) return mfr;
let s = String(mfr).trim();
let prev;
do {
prev = s;
const m = /^(.*?)-([A-Za-z]{2,})$/.exec(s);
if (m && MFR_PRODUCT_TYPE_TOKENS.has(m[2].toLowerCase())) s = m[1];
} while (s !== prev && s.length > 0);
return s;
}
function greenfieldPrefixOf(code, prefixSet) {
const m = /^(DW[A-Z0-9]{1,6})-/i.exec(code || '');
if (!m) return null;
const pfx = m[1].toUpperCase();
return prefixSet.has(pfx) ? pfx : null;
}
// A canonical DW code prefix, e.g. DWAK-, DWCC-, DWEL-RM-...
const DW_CODE_RE = /^DW[A-Z0-9]{1,6}-/i;
// A Cork- source code (Greenland / Phillipe Romano cork line — self-copy its Cork-<num>).
const CORK_RE = /^cork-/i;
// A literal "null" placeholder sku (import defect, e.g. "null-Sample").
const NULL_RE = /^null([\s-]|$)/i;
// A code-shaped mfr candidate (no whitespace/parens/title junk, ≤40 chars).
// Mirrors apply-plan-gen.mjs CODE_SHAPE so a bad mfr never becomes a dw_sku.
const MFR_CAND_SHAPE = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,39}$/;
/**
* Strip trailing unit/variant suffix tokens from a sku, repeatedly, so that
* doubled suffixes ("DWKE-41415-Sample-Sample") collapse to the bare code.
* Returns the recovered code candidate (the code the row ALREADY holds).
*/
export function stripUnitSuffix(sku) {
if (sku == null) return sku;
let s = String(sku).trim();
let prev;
do {
prev = s;
s = s.replace(SUFFIX_RE, '');
} while (s !== prev && s.length > 0);
return s;
}
/**
* Classify one product row for the canonical-dw_sku backlog.
*
* @param {{dw_sku?:string, sku?:string, mfr_sku?:string, status?:string}} row
* @param {Set<string>} [activeCodeSet] set of dw_sku values already live on an
* ACTIVE product — used to flag COLLISION (candidate already taken).
* @param {{greenfieldPrefixes?:Set<string>,mixedUsePrefixes?:Set<string>}} [opts] provenance-guard config.
* @returns {{class:string, candidate:(string|null), collides:boolean, note?:string}}
*/
export function classifyRow(row, activeCodeSet, opts = {}) {
const greenfield = opts.greenfieldPrefixes || GREENFIELD_MINT_PREFIXES;
const mixedUse = opts.mixedUsePrefixes || MIXED_USE_MINT_PREFIXES;
const status = String(row.status || '').trim().toLowerCase();
const dw = String(row.dw_sku || '').trim();
if (dw) return { class: 'NOT_BLANK', candidate: null, collides: false };
if (status !== 'active') return { class: 'OUT_OF_SCOPE_STATUS', candidate: null, collides: false };
const sku = String(row.sku || '').trim();
const mfr = String(row.mfr_sku || '').trim();
const has = (c) => !!(activeCodeSet && c && activeCodeSet.has(c));
// HIGHEST-TRUST PATH — verified-real-mfr vendor (DTD verdict A, TK-10900):
// recover dw_sku directly from the NORMALIZED mfr_sku. This BEATS the `sku`
// path because these vendors' `sku` column carries reverted greenfield-mint
// residue (DWAG/DWAX...) that must never be self-copied. Gated by the machine
// allowlist — an off-allowlist vendor can NEVER reach this branch.
if (mfr && vendorAllowsMfrSelfCopy(row.vendor)) {
const cand = normalizeMfrSku(mfr);
if (cand && MFR_CAND_SHAPE.test(cand)) {
const collides = has(cand);
return { class: collides ? 'SELF_COPY_MFR_COLLISION' : 'SELF_COPY_MFR', candidate: cand, collides, note: `mfr_sku recovery (verified-real vendor '${vendorKey(row.vendor)}')` };
}
// Allowlisted vendor but the mfr_sku is not code-shaped -> re-scrape.
return { class: 'RESCRAPE', candidate: null, collides: false, note: 'verified-real vendor but mfr_sku is not code-shaped' };
}
// Literal "null" placeholder sku = scraper import defect (JOB 4).
if (sku && NULL_RE.test(sku)) {
// A non-allowlisted vendor's mfr_sku is potentially fabricated -> re-scrape,
// NEVER staging-link/self-copy (the fabricated-mfr laundering guard).
if (mfr) return { class: 'MFR_FABRICATED_RESCRAPE', candidate: null, collides: false, note: `null-literal sku; mfr_sku not verified-real for vendor '${String(row.vendor || '').trim()}'` };
return { class: 'IMPORT_DEFECT', candidate: null, collides: false };
}
if (sku) {
const cand = stripUnitSuffix(sku);
if (!cand) return { class: 'RESCRAPE', candidate: null, collides: false, note: 'sku was all-suffix' };
if (DW_CODE_RE.test(cand)) {
// Provenance guard FIRST: a greenfield-prefix code is reverted-mint residue,
// never scraper-native → must re-scrape, must NOT self-copy.
const gf = greenfieldPrefixOf(cand, greenfield);
if (gf) return { class: 'MINT_RESIDUE_RESCRAPE', candidate: null, collides: false, note: `greenfield mint prefix ${gf} (reverted Phase-4) — recover real mfr code, do not self-copy ${cand}` };
const collides = has(cand);
const mixed = greenfieldPrefixOf(cand, mixedUse);
// A code already owned by another active product is a definite collision;
// that stronger fact wins over the unresolved mixed-prefix provenance hold.
if (mixed && collides) return { class: 'SELF_COPY_DW_COLLISION', candidate: cand, collides: true };
if (mixed) return { class: 'PROVENANCE_REVIEW', candidate: null, collides: false, note: `mixed-use Phase-4 prefix ${mixed} — compare ${cand} with retained undo ledger before self-copy` };
return { class: collides ? 'SELF_COPY_DW_COLLISION' : 'SELF_COPY_DW', candidate: cand, collides };
}
if (CORK_RE.test(cand)) {
const collides = has(cand);
return { class: collides ? 'SELF_COPY_COLLISION' : 'SELF_COPY_CORK', candidate: cand, collides };
}
// Non-DW, non-cork existing source code — still a recover-existing-code fix.
const collides = has(cand);
return { class: collides ? 'SELF_COPY_COLLISION' : 'SELF_COPY_SOURCE', candidate: cand, collides };
}
// No usable sku. Reaching here with an mfr means the vendor is NOT on the
// verified-real allowlist (allowlisted vendors were handled at the top), so
// the mfr_sku is potentially fabricated -> re-scrape, never self-copy.
if (mfr) return { class: 'MFR_FABRICATED_RESCRAPE', candidate: null, collides: false, note: `mfr_sku not verified-real for vendor '${String(row.vendor || '').trim()}' — re-scrape, never self-copy` };
return { class: 'RESCRAPE', candidate: null, collides: false };
}
// Recovery-path grouping used for the backlog rollup / job routing.
export const RECOVERY_GROUP = {
SELF_COPY_DW: 'recoverable_now_self_copy',
SELF_COPY_CORK: 'recoverable_now_self_copy',
SELF_COPY_SOURCE: 'recoverable_now_self_copy',
// mfr_sku recovery — verified-real-mfr vendors ONLY (Carnegie, Stout).
SELF_COPY_MFR: 'recoverable_now_mfr',
SELF_COPY_MFR_COLLISION: 'dedup_gated_TK10649',
STAGING_LINK: 'recoverable_now_staging_link', // legacy — no longer produced by classify
SELF_COPY_DW_COLLISION: 'dedup_gated_TK10649',
SELF_COPY_COLLISION: 'dedup_gated_TK10649',
RESCRAPE: 'rescrape_program_TK10900',
IMPORT_DEFECT: 'rescrape_program_TK10900',
MINT_RESIDUE_RESCRAPE: 'rescrape_program_TK10900',
// Fabricated mfr_sku on a non-allowlisted vendor — must re-scrape, never self-copy.
MFR_FABRICATED_RESCRAPE: 'rescrape_program_TK10900',
PROVENANCE_REVIEW: 'provenance_review_TK10896',
NOT_BLANK: 'out_of_scope',
OUT_OF_SCOPE_STATUS: 'out_of_scope',
};