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