← back to Re Flyer Aggregator

scripts/generate-spotlight.mjs

118 lines

#!/usr/bin/env node
// TK-10708  Tier-2 self-generated CLOSED-SALE RECAP one-sheet.
// Reads PUBLIC-RECORD deals from usre.recent_commercial_deals and renders our OWN
// one-sheet (RENTV brand tokens only -- NO third-party photos/maps/logos). This is a
// closed-sale recap, NOT an active-listing offering memorandum -> zero copyright
// exposure. Feeds RENTV's paid "Property Spotlight Eblast" product.
//
// Usage:
//   node scripts/generate-spotlight.mjs            # top 3 deals by price
//   node scripts/generate-spotlight.mjs --n 5      # top 5
//   node scripts/generate-spotlight.mjs --doc 34974-3025   # a specific deal
//
// Output: out/spotlight-*.html (open in a browser; print-to-PDF = US Letter one-sheet).

import { execFileSync } from 'node:child_process';
import { writeFileSync, mkdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const OUT = join(ROOT, 'out');
mkdirSync(OUT, { recursive: true });

const args = process.argv.slice(2);
const getArg = (f, d) => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : d; };
const N = parseInt(getArg('--n', '3'), 10);
const DOC = getArg('--doc', null);

const COLS = 'sale_date,sale_price,ctype,address,city,county_name,sqft,year_built,doc_number';
const where = DOC
  ? `WHERE doc_number = '${DOC.replace(/'/g, "''")}' AND address IS NOT NULL`
  : `WHERE address IS NOT NULL`;   // skip data-gap rows with no address
// row_to_json => one JSON object per line = unambiguous parsing (no delimiter guessing).
const sql = `SELECT row_to_json(t) FROM (SELECT ${COLS} FROM recent_commercial_deals ${where} ORDER BY sale_price DESC LIMIT ${DOC ? 1 : N}) t`;

let rows;
try {
  const raw = execFileSync('psql', ['usre', '-t', '-A', '-c', sql], { encoding: 'utf8' });
  rows = raw.trim().split('\n').filter(Boolean).map(line => {
    const d = JSON.parse(line);
    return { ...d, sale_price: +d.sale_price, sqft: +d.sqft || null, year_built: +d.year_built || null };
  });
} catch (e) {
  console.error('psql query failed:', e.message);
  process.exit(1);
}
if (!rows.length) { console.error('No deals matched.'); process.exit(1); }

const fmtMoney = n => '$' + Math.round(n).toLocaleString('en-US');
const titleCase = s => (s || '').toLowerCase().replace(/\b\w/g, c => c.toUpperCase());
const ppsf = d => (d.sqft && d.sqft > 0) ? '$' + Math.round(d.sale_price / d.sqft).toLocaleString('en-US') + '/sf' : '—';

const LOGO = `<svg viewBox="0 0 220 70" width="150" xmlns="http://www.w3.org/2000/svg" aria-label="RENTV">
  <ellipse cx="110" cy="35" rx="104" ry="30" fill="none" stroke="#E8A81C" stroke-width="4"/>
  <text x="110" y="44" text-anchor="middle" font-family="Archivo, Inter, Helvetica, sans-serif" font-weight="800" font-size="34" fill="#16357A" letter-spacing="1">RENTV</text>
</svg>`;

const page = d => `<section class="sheet">
  <header class="hdr">
    <div>${LOGO}</div>
    <div class="kicker">PROPERTY SPOTLIGHT<br><b>CLOSED-SALE RECAP</b></div>
  </header>
  <div class="addr">
    <h1>${titleCase(d.address)}</h1>
    <div class="sub">${titleCase(d.city)}${d.county_name ? ', ' + titleCase(d.county_name) + ' County' : ''}</div>
  </div>
  <div class="hero">
    <div class="price">${fmtMoney(d.sale_price)}</div>
    <div class="pill">${titleCase(d.ctype || 'Commercial')}</div>
  </div>
  <div class="stats">
    <div class="stat"><span>Sale date</span><b>${d.sale_date}</b></div>
    <div class="stat"><span>Building SF</span><b>${d.sqft ? d.sqft.toLocaleString('en-US') : '—'}</b></div>
    <div class="stat"><span>Price / SF</span><b>${ppsf(d)}</b></div>
    <div class="stat"><span>Year built</span><b>${d.year_built || '—'}</b></div>
  </div>
  <div class="prov">Source: public county recorder / assessor record · Doc ${d.doc_number}. Generated by RENTV from public-record data — a closed-sale recap, not a broker offering memorandum or active listing.</div>
  <footer class="ftr">
    <div><b>Steve Bloom</b> · President &amp; CEO, RENTV.com, Inc. · (310) 213-6409 · rentv.com</div>
    <div class="tag">Cutting-Edge Commercial Real Estate Media Coverage · ~50,000 CRE professionals</div>
  </footer>
</section>`;

const html = `<!doctype html><html lang="en"><head><meta charset="utf-8">
<title>RENTV Property Spotlight — Closed-Sale Recap</title>
<style>
  :root{--blue:#16357A;--navy:#0E2350;--red:#D0202E;--gold:#E8A81C;--ink:#141414;--gray:#6B7280;--panel:#F4F6FB}
  *{box-sizing:border-box}
  body{margin:0;background:#dfe3ec;font-family:Inter,Archivo,Helvetica,Arial,sans-serif;color:var(--ink)}
  .sheet{width:816px;min-height:1056px;margin:24px auto;background:#fff;padding:56px 60px;display:flex;flex-direction:column;box-shadow:0 6px 30px rgba(0,0,0,.18)}
  .hdr{display:flex;justify-content:space-between;align-items:center;border-bottom:3px solid var(--gold);padding-bottom:16px}
  .kicker{text-align:right;font-size:13px;letter-spacing:3px;color:var(--blue);line-height:1.2}
  .kicker b{font-size:20px}
  .addr{margin-top:34px}
  .addr h1{margin:0;font-family:'Playfair Display',Georgia,serif;font-size:42px;line-height:1.05;color:var(--navy)}
  .addr .sub{margin-top:8px;color:var(--gray);font-size:18px;letter-spacing:.5px}
  .hero{margin-top:30px;display:flex;align-items:center;gap:22px}
  .price{font-size:64px;font-weight:800;color:var(--blue);letter-spacing:-1px}
  .pill{background:var(--red);color:#fff;padding:8px 18px;border-radius:999px;font-weight:700;font-size:16px;text-transform:uppercase;letter-spacing:1px}
  .stats{margin-top:38px;display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:14px}
  .stat{background:var(--panel);border-left:4px solid var(--gold);padding:16px 18px;border-radius:6px}
  .stat span{display:block;color:var(--gray);font-size:12px;text-transform:uppercase;letter-spacing:1px}
  .stat b{font-size:24px;color:var(--navy)}
  .prov{margin-top:auto;padding-top:30px;color:var(--gray);font-size:12px;line-height:1.5;border-top:1px solid #e3e6ee}
  .ftr{margin-top:16px;background:var(--navy);color:#fff;padding:16px 20px;border-radius:8px;font-size:13px}
  .ftr .tag{color:var(--gold);margin-top:6px;font-size:11px;letter-spacing:.5px}
  @media print{body{background:#fff}.sheet{margin:0;box-shadow:none;width:auto;min-height:auto}@page{size:letter;margin:0}}
</style></head><body>
${rows.map(page).join('\n')}
</body></html>`;

const name = DOC ? `spotlight-${DOC.replace(/[^\w.-]/g, '_')}.html` : `spotlight-top${rows.length}.html`;
const path = join(OUT, name);
writeFileSync(path, html);
console.log(`Wrote ${path}`);
console.log(`Deals: ${rows.map(r => `${r.address} (${fmtMoney(r.sale_price)})`).join(' · ')}`);
console.log(`To PDF: open in Chrome -> Print -> Save as PDF (Letter, margins none).`);