← back to Hollywood Import

momentum-feed/price-backfill.mjs

52 lines

#!/usr/bin/env node
// Backfill list_price + hw_price for momentum_colorways rows missing hw_price, from the
// Meilisearch feed keyed on momentum_sku (feed `number`) — the stable join the name-based
// refresh upsert missed. hw_price = list_price × 1.448 (verified existing Hollywood markup).
// Reversible, staging-only, guarded (WHERE hw_price IS NULL). Run: node price-backfill.mjs [--commit]
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const { Pool } = require('pg');
const HOST = 'https://ms-e886719d86e7-4256.sfo.meilisearch.io';
const KEY = process.env.MOMENTUM_MS_KEY || '95fe8376edc78d49e787db49edda68426b21649f6271f49e2f1412118612fbd6';
const MARKUP = 1.448;
const COMMIT = process.argv.includes('--commit');
const pool = new Pool({ connectionString: 'postgresql://dw_admin:DW2024!@127.0.0.1:5432/dw_unified' });

async function main() {
  // 1. Build number → price map from the feed.
  const priceOf = new Map();
  for (let off = 0; off < 20000; off += 200) {
    const r = await fetch(`${HOST}/indexes/redesign-colors/search`, {
      method: 'POST', headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({ q: '', limit: 200, offset: off, attributesToRetrieve: ['number', 'price'] }) });
    const hits = (await r.json()).hits || [];
    if (!hits.length) break;
    for (const h of hits) if (h.number && h.price != null && Number(h.price) > 0) priceOf.set(String(h.number), Number(h.price));
    process.stdout.write(`\r  feed offset ${off}  priced skus ${priceOf.size}   `);
  }
  console.log(`\nFeed price map: ${priceOf.size} SKUs with a price.`);

  // 2. Find unpriced rows that HAVE a momentum_sku present in the map.
  const { rows } = await pool.query(
    `SELECT id, momentum_sku FROM momentum_colorways WHERE hw_price IS NULL AND momentum_sku IS NOT NULL AND momentum_sku <> ''`);
  const hits = rows.filter(r => priceOf.has(String(r.momentum_sku)));
  console.log(`Unpriced rows: ${rows.length} | recoverable from feed: ${hits.length} | still-unpriced after: ${rows.length - hits.length}`);
  if (hits.length) { const s = hits[0]; console.log(`  e.g. sku ${s.momentum_sku} → list ${priceOf.get(String(s.momentum_sku))} → hw ${(priceOf.get(String(s.momentum_sku))*MARKUP).toFixed(2)}`); }

  if (!COMMIT) { console.log('DRY-RUN — re-run with --commit to write.'); await pool.end(); return; }
  const c = await pool.connect(); let done = 0;
  try {
    await c.query('BEGIN');
    for (const r of hits) {
      const lp = priceOf.get(String(r.momentum_sku));
      await c.query(`UPDATE momentum_colorways SET list_price=$1, hw_price=$2, updated_at=now() WHERE id=$3 AND hw_price IS NULL`,
        [lp, +(lp * MARKUP).toFixed(2), r.id]);
      done++;
    }
    await c.query('COMMIT');
    console.log(`COMMITTED — backfilled ${done} rows (list_price + hw_price = list×${MARKUP}).`);
  } catch (e) { await c.query('ROLLBACK'); console.error('ROLLBACK:', e.message); process.exitCode = 1; }
  finally { c.release(); await pool.end(); }
}
main().catch(e => { console.error(e); process.exit(1); });