← back to Carnegie Internal
scripts/build-carnegie.mjs
71 lines
#!/usr/bin/env node
// Build data/products.json (shaped snapshot: {vendor, facets, products}) for the Carnegie
// internal line viewer, straight from dw_unified.carnegie_catalog. $0 local PG read.
import pg from 'pg';
import { writeFileSync } from 'node:fs';
const pool = new pg.Pool({ connectionString: 'postgresql:///dw_unified?host=/tmp' });
// hex -> coarse color bucket (drives the Colors facet + color sort)
function bucket(hex){
const m=/#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i.exec(hex||''); if(!m) return 'Neutral';
const [r,g,b]=[1,2,3].map(i=>parseInt(m[i],16)); const L=0.2126*r+0.7152*g+0.0722*b;
if(L>210) return 'White'; if(L<55) return 'Black';
const mx=Math.max(r,g,b),mn=Math.min(r,g,b);
if(mx-mn<22) return L>150?'Light':'Gray';
if(r>=g&&r>=b) return (g>b)?'Warm':'Red';
if(g>=r&&g>=b) return 'Green';
return 'Blue';
}
const { rows } = await pool.query(`
SELECT dw_sku, mfr_sku, pattern_name, pattern_number, color_number, color_name, title,
price, content, product_type, dw_class, image_url,
(palette->0->>'hex') AS hex, array_to_string(color_tags,', ') AS colorway,
color_tags, width, finish, backing, origin, description_text
FROM carnegie_catalog WHERE dw_sku IS NOT NULL AND image_url IS NOT NULL
ORDER BY pattern_name, color_number, dw_sku`);
const products = rows.map(r => {
const cbucket = bucket(r.hex);
const colorName = (r.color_tags && r.color_tags[0]) || r.color_name || ('Color '+r.color_number);
return {
sku: r.dw_sku, dw_sku: r.dw_sku, mfr_sku: r.mfr_sku,
handle: r.dw_sku,
series: r.pattern_name, // "Patterns" facet
pattern_number: r.pattern_number,
color_number: r.color_number,
color: colorName, // "Colorways" facet
color_bucket: cbucket, // "Colors" facet
hex: r.hex || null,
title: r.title || ('Carnegie '+r.pattern_name+' '+r.color_number),
price: r.price != null ? Number(r.price) : null,
material: r.content || null, // "Content" facet (kept key 'material' for card reuse)
type: r.product_type || r.dw_class || null,
width: r.width || null,
finish: r.finish || null,
backing: r.backing || null,
origin: r.origin || null,
image: r.image_url,
swatch: r.image_url,
body: r.description_text || null,
};
});
// facets: field -> {value: count}
const F = ['series','color_bucket','color','material','type','width','finish','backing','origin'];
const facets = {}; for (const k of F) facets[k] = {};
for (const p of products) for (const k of F) { const v=p[k]; if(v) facets[k][v]=(facets[k][v]||0)+1; }
// vendor meta — Carnegie is a real customer-facing contract line (not private-label)
const vr = (await pool.query(
`SELECT vendor_name, vendor_discount_pct, sample_price FROM vendor_registry WHERE lower(vendor_name)='carnegie' LIMIT 1`)).rows[0] || {};
const vendor = { name: 'Carnegie', code: 'carnegie',
discount_pct: vr.vendor_discount_pct!=null?Number(vr.vendor_discount_pct):null,
sample_price: vr.sample_price!=null?Number(vr.sample_price):4.25, private_label:false };
const snapshot = { built_at:new Date().toISOString(), source:'carnegie_catalog',
count:products.length, vendor, facets, products };
writeFileSync(new URL('../data/products.json', import.meta.url), JSON.stringify(snapshot));
console.log(`carnegie snapshot: ${products.length} products, facets: ${F.map(k=>k+'='+Object.keys(facets[k]).length).join(' ')}`);
await pool.end();