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