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