← back to 1980swallpaper

scripts/pull-hybrid.js

87 lines

// Hybrid pull: dw_unified PG (rich pattern_name match) + live /products.json (current images).
// Prototype for the 1980swallpaper niche. If this produces >= 5 hydrated rows, the pattern works.
const https = require('https');
const fs = require('fs');
const path = require('path');
const { Pool } = require('pg');

const pool = new Pool({ connectionString: process.env.DW_UNIFIED_URL || 'postgresql://dw_admin@127.0.0.1:15432/dw_unified' });

const PATTERN_REGEX = '(neon kiss|triangle land|terrazzo|checkerboard|hicks hexagon|prism multi|feather fan|memphis|geometry pastel|arch deco|alice confetti|prisma pastel|neon city|your own world|eyes and circles|tiami|metromod|riviera linen)';

function fetchPage(page) {
  return new Promise((resolve, reject) => {
    https.get(`https://designerwallcoverings.com/products.json?limit=250&page=${page}`, {
      headers: { 'User-Agent': 'Mozilla/5.0 1980swallpaper-hybrid' }
    }, (res) => {
      let data = '';
      res.on('data', c => data += c);
      res.on('end', () => { try { resolve(JSON.parse(data).products || []); } catch(e) { reject(e); } });
    }).on('error', reject);
  });
}

(async () => {
  // Step 1: pull niche candidates from dw_unified
  const { rows } = await pool.query(
    `SELECT lower(sku) AS sku, pattern_name, vendor_id FROM products WHERE pattern_name ~* $1`,
    [PATTERN_REGEX]
  );
  console.log(`dw_unified candidates: ${rows.length}`);

  const skuSet = new Set(rows.map(r => r.sku));
  const skuMeta = Object.fromEntries(rows.map(r => [r.sku, r]));

  // Step 2: pull all live products
  const live = [];
  for (let p = 1; p <= 30; p++) {
    const products = await fetchPage(p);
    if (!products.length) break;
    live.push(...products);
    if (products.length < 250) break;
  }
  console.log(`live products fetched: ${live.length}`);

  // Step 3: JOIN by pattern_name segment matching live title (live title format: "Pattern Name | Vendor")
  // Build a regex from the unique pattern names; require all distinctive words present in title.
  const patternNames = [...new Set(rows.map(r => r.pattern_name))];
  const matched = [];
  for (const lp of live) {
    if (!lp.product_type || !/wallcovering/i.test(lp.product_type)) continue;
    if (!lp.images || !lp.images.length) continue;
    const titleLower = (lp.title || '').toLowerCase();
    for (const pn of patternNames) {
      // Take first 3 distinctive words from pattern_name (skip generic "Wallpaper", "Wallcovering")
      const words = pn.toLowerCase().replace(/wallpaper|wallcovering|metallic/g, '').split(/\s+/).filter(w => w.length > 2);
      if (words.length < 2) continue;
      // Require first 2 distinctive words to appear in live title
      if (words.slice(0, 2).every(w => titleLower.includes(w))) {
        matched.push({
          sku: lp.variants?.[0]?.sku || lp.handle,
          handle: lp.handle,
          title: lp.title,
          vendor: (lp.vendor || '').trim(),
          product_type: lp.product_type,
          image_url: lp.images[0].src,
          tags: lp.tags || [],
          aesthetic: '1980s',
          pattern_name_dw: pn,
          product_url: `https://designerwallcoverings.com/products/${lp.handle}`,
        });
        break;
      }
    }
  }
  console.log(`hydrated matches: ${matched.length}`);
  if (matched.length) console.log('sample:', matched[0]);

  // Step 4: atomic write
  const tmp = path.join(__dirname, '..', 'data', 'products.json.tmp');
  const final = path.join(__dirname, '..', 'data', 'products.json');
  fs.mkdirSync(path.dirname(final), { recursive: true });
  fs.writeFileSync(tmp, JSON.stringify(matched, null, 2));
  fs.renameSync(tmp, final);
  console.log(`wrote ${final}`);
  await pool.end();
})().catch(e => { console.error(e); process.exit(1); });