[object Object]

← back to Dw Sku Integrity

TK-10900/A: fix cross-class wrong-write (contrarian Defect B) + harden base-strip

e47fdb55badbc0bedcce1ec04a130b6b2288e6d3 · 2026-08-31 02:19:07 -0700 · codex-10896

Contrarian caught it: the product_type bucket-guard only ran on MULTI-candidate
keys, so a single wall-only catalog code (e.g. Abbey 61 -> 7394A61 'Upholstered
Walls/Panels') was stamped blindly onto an Upholstery Shopify row. Blast radius
1,523 wall-only single-base keys. Fix: bucket-compatibility is now a hard
precondition on the single-candidate path too -> cross_class_mismatch -> review,
never apply. Also harden baseCode to strip only trailing -<alpha> suffixes so a
numeric-hyphenated real code (91026-10) is preserved, not truncated.
Carnegie honest rate: 87.7% -> 70.0% matched (2,745), 694 cross-class quarantined.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit e47fdb55badbc0bedcce1ec04a130b6b2288e6d3
Author: codex-10896 <steve@designerwallcoverings.com>
Date:   Mon Aug 31 02:19:07 2026 -0700

    TK-10900/A: fix cross-class wrong-write (contrarian Defect B) + harden base-strip
    
    Contrarian caught it: the product_type bucket-guard only ran on MULTI-candidate
    keys, so a single wall-only catalog code (e.g. Abbey 61 -> 7394A61 'Upholstered
    Walls/Panels') was stamped blindly onto an Upholstery Shopify row. Blast radius
    1,523 wall-only single-base keys. Fix: bucket-compatibility is now a hard
    precondition on the single-candidate path too -> cross_class_mismatch -> review,
    never apply. Also harden baseCode to strip only trailing -<alpha> suffixes so a
    numeric-hyphenated real code (91026-10) is preserved, not truncated.
    Carnegie honest rate: 87.7% -> 70.0% matched (2,745), 694 cross-class quarantined.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 content-match-gen.mjs | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/content-match-gen.mjs b/content-match-gen.mjs
index 11d24ca..c04a4c9 100644
--- a/content-match-gen.mjs
+++ b/content-match-gen.mjs
@@ -41,9 +41,10 @@ const MINT_CATALOG = new Set(['carnegie', 'maharam', 'cmo paris', 'cmo_paris', '
 
 const sqlEscape = (v) => String(v).replace(/'/g, "''");
 const norm = (s) => (s || '').trim().toLowerCase().replace(/\s+/g, ' ');
-// Base code = token before the first DW type-suffix ('-panels', '-wallcoverings', ...). Real codes with
-// no such suffix pass through unchanged. Only applied to mint-catalog mfr_sku (dw_sku is used verbatim).
-const baseCode = (c) => (c || '').trim().split('-')[0].toUpperCase();
+// Base code = strip trailing DW type-suffix groups ('-panels', '-panels-museums', '-dividers', ...).
+// Strips ONLY trailing '-<alpha>' tokens, so a genuinely hyphenated real code with a numeric segment
+// ('91026-10') is preserved rather than truncated. Only applied to mint-catalog mfr_sku (dw_sku verbatim).
+const baseCode = (c) => (c || '').trim().replace(/(-[A-Za-z][A-Za-z]*)+$/, '').toUpperCase();
 // product_type semantic bucket so Shopify 'Upholstery' can match catalog 'Upholstery'/'Fabric' etc.
 const bucket = (pt) => {
   const s = norm(pt);
@@ -100,7 +101,7 @@ const shopRows = runPsql(KAM,
 
 const vname = norm(VENDOR);
 const entries = [], review = [];
-const stat = { shopify_blank: shopRows.length, no_shopify_id: 0, parse_miss: 0, no_map: 0, ambiguous: 0, matched: 0 };
+const stat = { shopify_blank: shopRows.length, no_shopify_id: 0, parse_miss: 0, no_map: 0, ambiguous: 0, cross_class: 0, matched: 0 };
 for (const [sid, title, ptype] of shopRows) {
   if (!sid) { stat.no_shopify_id++; continue; }
   let body = norm(title);
@@ -117,6 +118,15 @@ for (const [sid, title, ptype] of shopRows) {
     if (filtered.length === 1) cand = filtered;
   }
   if (cand.length !== 1) { stat.ambiguous++; review.push({ shopify_id: sid, title, key, candidates: [...codes.keys()], reason: 'ambiguous_multi_code' }); continue; }
+  // Bucket-compatibility precondition — applies to SINGLE-candidate keys too (the Abbey-61 class):
+  // never stamp a wall-only code onto a fabric row or vice versa. Shopify/catalog 'other' -> allow.
+  const shopBucket = bucket(ptype);
+  const catBuckets = codes.get(cand[0]);
+  if (shopBucket !== 'other' && !catBuckets.has(shopBucket) && !catBuckets.has('other')) {
+    stat.cross_class++;
+    review.push({ shopify_id: sid, title, key, candidate: cand[0], catalog_buckets: [...catBuckets], shopify_bucket: shopBucket, reason: 'cross_class_mismatch' });
+    continue;
+  }
   entries.push({ shopify_id: sid, title, candidate: cand[0] });
   stat.matched++;
 }

← 0aa462b TK-10900/A: content-match-gen.mjs — schema-adaptive catalog  ·  back to Dw Sku Integrity  ·  TK-10900: plan evidence updated — content-match SHIPPED + co e5896e1 →