← back to Dwjs Consolidation 2026 04 23
phase2_prepare.js
155 lines
#!/usr/bin/env node
/**
* PHASE 2 — FINAL MAPPING (NO WRITES).
*
* Inputs:
* audit_raw.json — Phase 1 audit output (every legacy variant + existing DWJS)
* mfr_resolved.json — legacy SKU → resolved mfr (from 13MB fmpro-master CSV)
*
* Outputs:
* mapping_final.csv — reviewable, includes real mfr + resolved series
* mapping_final.json — phase 3 input
* summary_phase2.txt — counts, flags
*
* Classification rules (authoritative):
* vendor_code YOR (York) → Series: York
* vendor_code BRE (Brewster) → Series: Brewster
* prefix DWBR → Series: Brewster (confirmed)
* existing DWJS: leave series assignment for Phase 3 decision
*
* Renumber range: DWJS-90000 + counter (one per legacy product).
*/
const fs = require('fs');
const path = require('path');
const OUT = __dirname;
const NEW_SKU_START = 90000;
const audit = JSON.parse(fs.readFileSync(path.join(OUT, 'audit_raw.json'), 'utf8'));
const resolved = JSON.parse(fs.readFileSync(path.join(OUT, 'mfr_resolved.json'), 'utf8'));
// Group audit rows by product_id so we assign a single new DWJS number per product
const byProduct = new Map();
for (const r of audit) {
if (!byProduct.has(r.product_id)) byProduct.set(r.product_id, []);
byProduct.get(r.product_id).push(r);
}
// We only renumber legacy (non-DWJS) products. Existing DWJS keeps its number.
let counter = NEW_SKU_START;
const productAssignments = new Map(); // product_id → new base number
for (const [pid, variants] of byProduct) {
const any = variants[0];
if (any.flags && any.flags.includes('EXISTING_DWJS_NO_RENAME')) continue;
productAssignments.set(pid, counter++);
}
console.log(`legacy products requiring renumber: ${productAssignments.size} (DWJS-${NEW_SKU_START}..DWJS-${counter-1})`);
// Build final rows
const cols = ['product_id','variant_id','current_sku','new_sku','sample_or_bolt','product_status','vendor','title',
'current_mfr_in_shopify','resolved_mfr','resolved_vendor_code','mfr_source','series_final','series_reason','price','flags'];
const lines = [cols.join(',')];
const jsonRows = [];
function csvEsc(s) { if (s==null) return ''; const str=String(s); return /[",\n]/.test(str) ? '"'+str.replace(/"/g,'""')+'"' : str; }
function classifyFinal(row, res) {
if (res && res.resolved_vendor_code) {
const v = res.resolved_vendor_code.toUpperCase();
if (v === 'YOR') return { series: 'York', reason: '13MB-csv vendor_code=YOR' };
if (v === 'BRE') return { series: 'Brewster', reason: '13MB-csv vendor_code=BRE' };
}
// fall back to the Phase 1 series_guess
if (row.series_guess === 'York' || row.series_guess === 'Brewster') {
return { series: row.series_guess, reason: row.series_reason };
}
return { series: 'Unknown', reason: 'no vendor_code + unclassified' };
}
const outSummary = { legacyByPrefix:{}, bySeriesFinal:{}, byStatus:{}, unresolvedMfr: [], flagCounts: {} };
for (const [pid, variants] of byProduct) {
const isLegacy = productAssignments.has(pid);
const newBase = productAssignments.get(pid);
const any = variants[0];
// resolved data is keyed by hyphenless dw sku (per-product, bolt)
const boltSku = (variants.find(v => v.sample_or_bolt === 'bolt') || any).current_sku;
const key = boltSku.replace('-sample','').replace('-Sample','').replace('-','');
const res = resolved[key] || null;
for (const v of variants) {
const { series, reason } = classifyFinal(v, res);
const newSku = isLegacy
? `DWJS-${newBase}${/sample/i.test(v.current_sku+v.title)?'-Sample':''}`
: v.current_sku;
const resolvedMfr = res?.resolved_mfr || v.mfr_sku || '';
const mfrSource = res?.source || (v.mfr_sku ? 'shopify-only' : 'NONE');
const flags = (v.flags || '').split('|').filter(Boolean);
if (isLegacy && !resolvedMfr) flags.push('NO_RESOLVED_MFR');
if (series === 'Unknown') flags.push('UNKNOWN_SERIES');
const row = {
product_id: v.product_id,
variant_id: v.variant_id,
current_sku: v.current_sku,
new_sku: newSku,
sample_or_bolt: v.sample_or_bolt,
product_status: v.product_status,
vendor: v.vendor,
title: v.title,
current_mfr_in_shopify: v.mfr_sku || '',
resolved_mfr: resolvedMfr,
resolved_vendor_code: res?.resolved_vendor_code || '',
mfr_source: mfrSource,
series_final: series,
series_reason: reason,
price: v.price,
flags: flags.join('|'),
};
jsonRows.push(row);
lines.push(cols.map(c => csvEsc(row[c])).join(','));
// summaries
const prefix = v.current_sku.split('-')[0] || '';
if (isLegacy) outSummary.legacyByPrefix[prefix] = (outSummary.legacyByPrefix[prefix]||0)+1;
outSummary.bySeriesFinal[series] = (outSummary.bySeriesFinal[series]||0)+1;
outSummary.byStatus[v.product_status] = (outSummary.byStatus[v.product_status]||0)+1;
for (const f of flags) outSummary.flagCounts[f] = (outSummary.flagCounts[f]||0)+1;
if (isLegacy && !resolvedMfr) outSummary.unresolvedMfr.push({sku: v.current_sku, title: v.title});
}
}
fs.writeFileSync(path.join(OUT, 'mapping_final.csv'), lines.join('\n'));
fs.writeFileSync(path.join(OUT, 'mapping_final.json'), JSON.stringify(jsonRows, null, 2));
const summary = [
'=== PHASE 2 FINAL MAPPING SUMMARY ===',
`Generated: ${new Date().toISOString()}`,
`Total variants: ${jsonRows.length}`,
`Unique products: ${byProduct.size}`,
`Legacy products renumbered: ${productAssignments.size}`,
`Range: DWJS-${NEW_SKU_START}..DWJS-${counter-1}`,
'',
'--- Legacy variants by prefix ---',
...Object.entries(outSummary.legacyByPrefix).sort((a,b)=>b[1]-a[1]).map(([k,v])=>` ${v.toString().padStart(5)} ${k}`),
'',
'--- Series final (all variants) ---',
...Object.entries(outSummary.bySeriesFinal).sort((a,b)=>b[1]-a[1]).map(([k,v])=>` ${v.toString().padStart(5)} ${k}`),
'',
'--- Status ---',
...Object.entries(outSummary.byStatus).sort((a,b)=>b[1]-a[1]).map(([k,v])=>` ${v.toString().padStart(5)} ${k}`),
'',
'--- Flags ---',
...Object.entries(outSummary.flagCounts).sort((a,b)=>b[1]-a[1]).map(([k,v])=>` ${v.toString().padStart(5)} ${k}`),
'',
`Unresolved mfr (legacy only): ${outSummary.unresolvedMfr.length}`,
...outSummary.unresolvedMfr.slice(0, 20).map(u => ` ${u.sku.padEnd(20)} ${u.title.slice(0,55)}`),
].join('\n');
fs.writeFileSync(path.join(OUT, 'summary_phase2.txt'), summary);
console.log('\n' + summary);
console.log('\nFiles written:');
console.log(' mapping_final.csv — reviewable CSV');
console.log(' mapping_final.json — Phase 3 input');
console.log(' summary_phase2.txt — counts');