← back to Hollywood Import

scrub-momentum.js

75 lines

'use strict';
// Momentum → Hollywood private-label scrub. "hollywood" vendor_code in dw_unified
// is the DW private label for Momentum Textiles & Walls. NEVER expose "Momentum"
// (or its sub-brand "Versa") in any customer-facing field. The catalog already
// carries the PL mapping in specs (pl_brand / pl_city_name); this turns a
// vendor_catalog hollywood row into a public-safe product object + a leak check.
//
// Public mapping:  vendor = specs.pl_brand ("Hollywood Wallcoverings")
//                  collection = specs.pl_city_name (CA cities)
//                  sku = dw_sku (DWHD-)
// Suppress:        real_vendor, installation_pdf (momentum URL), grade_collection
//                  ("VERSA …"), hw/list/cost prices; scrub "Versa's Second-Look".
// Image:           image_url passed only as transient push src → Shopify rehosts
//                  to cdn.shopify.com. Never stored in a text field.

const SOURCE_URL    = /https?:\/\/[^\s"']*momentum[^\s"']*/gi;
const VERSA_PROGRAM = /Versa'?s\s+Second[-\s]?Look\s*®?\s*Program/gi;
const SOURCE_BRANDS = /\b(momentum(?:\s+textiles(?:\s+and\s+walls)?)?|versa(?:'s)?(?:\s+designed\s+surfaces)?)\b/gi;
// Spec keys that are internal-only or carry a source-brand value — never push.
const DROP_KEYS = new Set(['real_vendor','installation_pdf','grade_collection','hw_price','list_price','cost']);

function scrubText(s) {
  if (s == null) return s;
  return String(s)
    .replace(SOURCE_URL, '')
    .replace(VERSA_PROGRAM, "the manufacturer's take-back program")
    .replace(SOURCE_BRANDS, (m) => /momentum/i.test(m) ? 'Hollywood' : '')
    .replace(/\s{2,}/g, ' ').replace(/\s+,/g, ',').trim();
}

function scrubMomentum(row) {
  const specs = typeof row.specs === 'string' ? JSON.parse(row.specs || '{}') : (row.specs || {});
  const pubSpecs = {};
  for (const [k, v] of Object.entries(specs)) {
    if (DROP_KEYS.has(k)) continue;
    pubSpecs[k] = (typeof v === 'string') ? scrubText(v) : v;
  }
  const tags = scrubText(specs.tags || '').split(',').map(t => t.trim()).filter(Boolean);
  // ALL images, not just primary (Steve: "always pull all data and images" —
  // every Hollywood/Momentum SKU must carry every image). Parse all_images
  // (JSON array or comma/newline list), prepend primary, dedupe.
  let imgs = [];
  if (row.all_images) {
    try { const a = JSON.parse(row.all_images); if (Array.isArray(a)) imgs = a; } catch { imgs = String(row.all_images).split(/[,\n]/); }
  }
  if (row.image_url) imgs.unshift(row.image_url);
  const images = [...new Set(imgs.map(s => String(s || '').trim()).filter(Boolean))];
  // completeness gate — importer MUST refuse rows that aren't fully crawled
  // (need all images + core data). false → re-crawl the full page first.
  const complete = images.length > 0 && !!row.pattern_name && !!row.mfr_sku && !!(specs && Object.keys(specs).length);
  return {
    vendor:     specs.pl_brand || 'Hollywood Wallcoverings',
    title:      scrubText(row.pattern_name) + (row.color_name ? ', ' + scrubText(row.color_name) : ''),
    collection: specs.pl_city_name ? scrubText(specs.pl_city_name) : (row.collection ? scrubText(row.collection) : null),
    sku:        row.dw_sku,
    mfr_sku:    row.mfr_sku,
    images,                       // ALL images — TRANSIENT push srcs, Shopify rehosts; excluded from leak check
    imageCount: images.length,
    complete,                     // false = NOT import-ready
    tags,
    specs:      pubSpecs,
  };
}

// Leak check: scan every customer-facing field EXCEPT the image URLs (their
// momentum CDN host is the sanctioned transient source Shopify launders). Leaks.
function leakCheck(pub) {
  const { images, imageSrc, ...rest } = pub;
  const hay = JSON.stringify(rest);
  const hits = hay.match(/momentum|versa/gi) || [];
  return hits;
}

module.exports = { scrubMomentum, scrubText, leakCheck };