← back to Tk 11331 Exec
d3b_heldreview_analyze.mjs
106 lines
#!/usr/bin/env node
// TK-11331 D3b-heldreview — READ-ONLY reconstruction + classification of the two HELD buckets.
// Replays d3c_stage.mjs classification logic exactly, then partitions the held sets.
import { createRequire } from 'module';
import fs from 'fs';
import path from 'path';
const require = createRequire(import.meta.url);
const { Pool } = require('pg');
const HERE = path.dirname(new URL(import.meta.url).pathname);
const TSV = path.join(HERE, 'data', 'momentum_feed_full.tsv');
const pool = new Pool({ connectionString: 'postgresql://dw_admin:DW2024!@127.0.0.1:5432/dw_unified' });
const norm = s => (s || '').replace(/\s+/g, ' ').trim().toLowerCase();
const KEEP = new Set(['Wallcovering', 'Acoustic']);
function readTSV() {
const lines = fs.readFileSync(TSV, 'utf8').split('\n').filter(Boolean);
const cols = lines[0].split('\t');
return lines.slice(1).map(l => { const p = l.split('\t'); const o = {}; cols.forEach((c, i) => o[c] = p[i]); return o; });
}
async function main() {
const { rows: pr } = await pool.query('SELECT pattern_name, color_name FROM momentum_colorways');
const stagedPatternNorm = new Set(pr.map(r => norm(r.pattern_name)));
const stagedPairNorm = new Set(pr.map(r => norm(r.pattern_name) + '' + norm(r.color_name)));
// map normalized pattern -> set of raw staged pattern spellings (for spelling-variant evidence)
const stagedPatternRaw = new Map();
for (const r of pr) {
const n = norm(r.pattern_name);
if (!stagedPatternRaw.has(n)) stagedPatternRaw.set(n, new Set());
stagedPatternRaw.get(n).add(r.pattern_name);
}
const feed = readTSV();
const inscope = feed.filter(r => KEEP.has(r.category_name) && (r.pattern_name || '').trim() && (r.preferred_color_name || '').trim());
// same raw dedup the stage script did
const seen = new Set(); const dedup = [];
for (const r of inscope) { const k = r.pattern_name + '' + r.preferred_color_name; if (seen.has(k)) continue; seen.add(k); dedup.push(r); }
// EXTEND-PATTERN bucket: norm(pattern) already staged
const extendRows = dedup.filter(r => stagedPatternNorm.has(norm(r.pattern_name)));
// NEW-PATTERN bucket (the 1,215 path): norm(pattern) not staged
const newRows = dedup.filter(r => !stagedPatternNorm.has(norm(r.pattern_name)));
// Classify extend rows: TRUE-DUP (norm pair already staged) vs GENUINELY-NEW
const extTrueDup = [], extNew = [];
for (const r of extendRows) {
const pairKey = norm(r.pattern_name) + '' + norm(r.preferred_color_name);
if (stagedPairNorm.has(pairKey)) extTrueDup.push(r); else extNew.push(r);
}
// 6 normalized-collision patterns named in the task
const COLLISION = ['Cavern II','Rectangular Hatch','Rectangular Linear','Rectangular Louvre','Rectangular Vault','Rectangular Vertex 9/16'];
console.log('== TOTALS ==');
console.log('inscope(dedup):', dedup.length, '| extendRows(pattern already staged):', extendRows.length, '| newRows:', newRows.length);
console.log('extend -> TRUE-DUP(norm pair staged):', extTrueDup.length, '| GENUINELY-NEW:', extNew.length);
// For each collision pattern, locate in feed + check staging
console.log('\n== COLLISION PATTERNS ==');
for (const cp of COLLISION) {
const cn = norm(cp);
const feedRows = dedup.filter(r => norm(r.pattern_name) === cn);
const patternStaged = stagedPatternNorm.has(cn);
const rawSpellings = stagedPatternRaw.get(cn);
// also: any staged pattern that shares the same normalized base? check near-collisions
console.log(`\n[${cp}] normalized="${cn}" feedColorways=${feedRows.length} patternAlreadyStaged=${patternStaged}` + (rawSpellings ? ` rawStaged=${JSON.stringify([...rawSpellings])}` : ''));
for (const fr of feedRows) {
const pairKey = cn + '' + norm(fr.preferred_color_name);
const pairStaged = stagedPairNorm.has(pairKey);
console.log(` cw="${fr.preferred_color_name}" num=${fr.number} cat=${fr.category_name} pairStaged=${pairStaged}`);
}
}
// Detect IN-SET collisions among newRows: two distinct raw patterns that normalize equal (spelling variants within the new set)
console.log('\n== IN-NEWSET NORMALIZED PATTERN COLLISIONS (distinct raw -> same norm) ==');
const byNorm = new Map();
for (const r of newRows) {
const n = norm(r.pattern_name);
if (!byNorm.has(n)) byNorm.set(n, new Set());
byNorm.get(n).add(r.pattern_name);
}
for (const [n, raws] of byNorm) { if (raws.size > 1) console.log(` norm="${n}" raws=${JSON.stringify([...raws])}`); }
// Detect pair-collisions within newRows: two rows same norm(pattern)+norm(color) but differing raw (ON CONFLICT would skip 2nd)
console.log('\n== IN-NEWSET PAIR COLLISIONS (same norm pattern+color, raw differs -> DB would skip one) ==');
const pairMap = new Map();
for (const r of newRows) {
const k = norm(r.pattern_name) + '' + norm(r.preferred_color_name);
if (!pairMap.has(k)) pairMap.set(k, []);
pairMap.get(k).push(r);
}
let collCount = 0;
for (const [k, arr] of pairMap) {
if (arr.length > 1) {
const raws = new Set(arr.map(x => x.pattern_name + '|' + x.preferred_color_name));
if (raws.size > 1) { collCount++; console.log(` ${k} => ${JSON.stringify([...raws])}`); }
}
}
console.log('pair-collision groups:', collCount);
// Dump the extend GENUINELY-NEW and TRUE-DUP sets for the memo
fs.writeFileSync(path.join(HERE,'data','d3b-held-extend-new.json'), JSON.stringify(extNew, null, 2));
fs.writeFileSync(path.join(HERE,'data','d3b-held-extend-truedup.json'), JSON.stringify(extTrueDup, null, 2));
await pool.end();
}
main().catch(e => { console.error(e); process.exit(1); });