← back to Reidwitlin Landing
lib/relabel-colorways.js
70 lines
/**
* Colorway-label repair for the Reid Witlin catalog.
*
* Root cause (2026-07-07): a handful of whole series were stamped a SINGLE
* colorway name upstream in rwltd_catalog.color_name — e.g. all 13 "Dream
* Weaver" variants read "Off-white" even though their images span white →
* dark-brown → purple → red. mfr_sku for these series is a bare counter
* (dream-weaver-1..18), so the real colorway name is unrecoverable from text.
*
* The IMAGES are correct (each variant has its own unique swatch; the
* AI-vision hex matches the pixels). Only the LABEL lies. So a "Pairs well
* with" card shows the right picture next to the wrong word, and the product
* is mis-identified.
*
* Fix: when a series' color label is uniform across many divergent hues, the
* label is untrustworthy — replace it with the pixel-derived colour family
* (which we already compute in color_bucket) so the card's text agrees with
* its image. Handles / dw_sku / mfr_sku are never touched, so every link
* still resolves to the same real product — it's just now labelled honestly.
*/
// color_bucket -> human display word (matches the site's colour-filter vocabulary)
const COLOR_LABEL = {
white: 'White', grey: 'Grey', black: 'Black', pink: 'Pink', red: 'Red',
orange: 'Orange', brown: 'Brown', gold: 'Gold', green: 'Green',
teal: 'Teal', blue: 'Blue', purple: 'Purple',
};
const norm = s => (s || '').toLowerCase().replace(/[^a-z0-9]/g, '');
/**
* Mutates `products` in place: relabels colorways for series whose label is
* unreliable. Returns { seriesFixed, productsFixed, series:[names] } for logging.
*
* A series is "unreliable-labelled" when it has >= MIN_N products that all
* share one non-empty colour label yet carry >= MIN_HUES distinct hexes.
*/
function relabelUnreliableSeries(products, { MIN_N = 4, MIN_HUES = 4 } = {}) {
const bySeries = new Map();
for (const p of products) {
if (!p.series) continue;
if (!bySeries.has(p.series)) bySeries.set(p.series, []);
bySeries.get(p.series).push(p);
}
const fixedSeries = [];
let productsFixed = 0;
for (const [series, ps] of bySeries) {
const labels = new Set(ps.map(p => norm(p.color)).filter(Boolean));
const hexes = new Set(ps.map(p => p.hex?.toUpperCase()).filter(Boolean));
const unreliable = ps.length >= MIN_N && labels.size === 1 && hexes.size >= MIN_HUES;
if (!unreliable) continue;
fixedSeries.push(series);
for (const p of ps) {
const word = COLOR_LABEL[p.color_bucket];
if (!word) continue; // no pixel-derived family (rare) — leave the label as-is
p.color = word;
p.display_name = word;
p.title = `${series} ${word} | Reid Witlin`;
productsFixed++;
}
}
return { seriesFixed: fixedSeries.length, productsFixed, series: fixedSeries };
}
module.exports = { relabelUnreliableSeries, COLOR_LABEL };