← back to Dw Kravet Hires
scripts/build-batchD-final.mjs
112 lines
#!/usr/bin/env node
// TK-12097 Batch D (Gaston Y Daniela) — merge the two hi-res sources into the final
// apply-hires swap map. Mirrors build-batchA-final.mjs (READ + copy pattern).
// source 1: data/tk12097/batchD-swap-map.json (gaston_daniela_catalog Brandfolder, width=2048, MEASURED)
// source 2: data/tk12097/batchD-phase2-results.json (kravet.com Brandfolder hero, MEASURED long_edge)
// For each low-res product (<=500px), pick the LARGER genuine upgrade (> current AND >= 800 floor).
// Colorway safety: staging join is colorway-exact (always safe). A phase-2 kravet.com hero is trusted
// ONLY when its filename contains the product's SKU code; filename-color-matches-but-pattern-base-differs
// rows -> HELD to batchD-tier2-colorway-unverified.json, NEVER put in the map.
import fs from 'fs';
import path from 'path';
const HERE = path.dirname(new URL(import.meta.url).pathname);
const PROJ = path.resolve(HERE, '..');
const D = path.join(PROJ, 'data/tk12097');
const FLOOR = parseInt(process.env.MIN_UPGRADE || '800', 10); // Batch D genuine-upgrade / emit floor
const lowres = JSON.parse(fs.readFileSync(path.join(D, 'batchD-lowres-all.json'), 'utf8'));
const catMap = JSON.parse(fs.readFileSync(path.join(D, 'batchD-swap-map.json'), 'utf8'));
const p2 = JSON.parse(fs.readFileSync(path.join(D, 'batchD-phase2-results.json'), 'utf8'));
const catBy = new Map();
for (const r of catMap.rows) catBy.set(r.shopify_id, { url: r.proposed_hires_url, le: r.hires_le, src: 'gaston_daniela_catalog_brandfolder' });
const p2By = new Map();
for (const r of p2) {
if ((r.status === 'recovered' || r.status === 'below_threshold') && r.recovered_hires_url && r.long_edge > 400) {
p2By.set(r.shopify_id, { url: r.recovered_hires_url, le: r.long_edge, src: 'kravet_com_brandfolder_hero' });
}
}
// colorway safety (copied from build-batchA-final.mjs)
const codeOf = (mfr) => String(mfr).replace(/\.0$/, '').replace(/^[PT]/i, '').replace(/[^A-Za-z0-9]/g, '').toUpperCase();
const fnameAlnum = (url) => url.split('?')[0].split('/').pop().replace(/[^A-Za-z0-9]/g, '').toUpperCase();
const colorwaySafe = (r, mfr) => r.src === 'gaston_daniela_catalog_brandfolder' || fnameAlnum(r.url).includes(codeOf(mfr));
const rows = [];
const tier2 = [];
const noSource = [];
for (const lr of lowres) {
const cands = [];
if (catBy.has(lr.shopify_id)) cands.push(catBy.get(lr.shopify_id));
if (p2By.has(lr.shopify_id)) cands.push(p2By.get(lr.shopify_id));
const genuine = cands.filter((c) => c.le > (lr.cur_le || 400) && c.le >= FLOOR);
if (!genuine.length) { noSource.push(lr); continue; }
const safe = genuine.filter((c) => colorwaySafe(c, lr.mfr_sku));
if (!safe.length) {
const best2 = genuine.sort((a, b) => b.le - a.le)[0];
tier2.push({ shopify_id: lr.shopify_id, vendor: lr.vendor, mfr_sku: lr.mfr_sku, dw_sku: lr.dw_sku,
cur_width: lr.cur_le, rollback_url: lr.rollback_url, proposed_hires_url: best2.url, hires_le: best2.le,
hires_source: best2.src, reason: 'colorway_unverified_filename_pattern_mismatch' });
continue;
}
const best = safe.sort((a, b) => b.le - a.le)[0];
rows.push({
shopify_id: lr.shopify_id,
vendor: lr.vendor,
mfr_sku: lr.mfr_sku,
dw_sku: lr.dw_sku,
cur_width: lr.cur_le,
current_400px_url: lr.rollback_url,
rollback_url: lr.rollback_url,
proposed_hires_url: best.url,
hires_le: best.le,
hires_source: best.src,
swappable_from_local_staging: true,
});
}
// histogram + per-floor coverage
const bands = [[401, 500], [500, 800], [800, 1200], [1200, 2500], [2500, 99999]];
const hist = {};
for (const [lo, hi] of bands) hist[`${lo}-${hi === 99999 ? '+' : hi}`] = rows.filter((r) => r.hires_le >= lo && r.hires_le < hi).length;
const floors = [500, 600, 800, 1000, 1200];
const perFloor = {};
for (const f of floors) perFloor[`>=${f}`] = rows.filter((r) => r.hires_le >= f).length;
const byVendor = {};
for (const r of rows) byVendor[r.vendor] = (byVendor[r.vendor] || 0) + 1;
const bySource = {};
for (const r of rows) bySource[r.hires_source] = (bySource[r.hires_source] || 0) + 1;
const doc = {
ticket: 'TK-12097', batch: 'D',
parent: 'TK-12090 / TK-11494',
generated_at: new Date().toISOString(),
generated_by: 'vp-dw-commerce',
store: 'designer-laboratory-sandbox',
engine: 'reuse ~/Projects/dw-kravet-hires/scripts/apply-hires.mjs (--max-width 500)',
floor_applied: FLOOR,
scope: `Batch D = Gaston Y Daniela (Kravet-owned, 2148 ACTIVE). Live-measured low-res(<=500px) ACTIVE products. `
+ `Hi-res resolved from gaston_daniela_catalog Brandfolder (colorway-exact, width=2048) and kravet.com Brandfolder hero (phase-2), `
+ `LARGER genuine upgrade chosen per product (> current AND >= ${FLOOR}px), MEASURED. Old low-res media retained per product (rollback anchor).`,
counts: {
lowres_total: lowres.length,
colorway_safe_upgrades: rows.length,
tier2_held: tier2.length,
no_hires_source: noSource.length,
emitted_at_floor: rows.length, // all rows already satisfy >= FLOOR (800)
'emitted_at_floor>=800': rows.filter((r) => r.hires_le >= 800).length,
by_vendor: byVendor,
by_source: bySource,
width_histogram: hist,
coverage_per_floor: perFloor,
},
rows,
};
fs.writeFileSync(path.join(D, 'batchD-final-map.json'), JSON.stringify(doc, null, 1));
fs.writeFileSync(path.join(D, 'batchD-tier2-colorway-unverified.json'), JSON.stringify({ rows: tier2 }, null, 1));
fs.writeFileSync(path.join(D, 'batchD-no-hires-source.json'), JSON.stringify(noSource, null, 1));
console.log(JSON.stringify(doc.counts, null, 2));
console.log(`\nfloor applied = ${FLOOR}px -> ${rows.length} rows written to batchD-final-map.json`);