← back to Commercialrealestate
scripts/export-to-xlsx.js
56 lines
// export-to-xlsx.js — write the full CRE dataset to a local multi-tab .xlsx (no Drive API / gcloud
// needed). Steve uploads it to Google Drive → it becomes an editable Google Sheet he owns.
// Output: /tmp/la-cre-data.xlsx. $0 local. Run: node scripts/export-to-xlsx.js
'use strict';
const XLSX = require('xlsx');
const { Pool } = require('pg');
const fs = require('fs');
const path = require('path');
const pool = new Pool({ host: '/tmp', port: 5432, database: 'cre', user: process.env.USER || 'stevestudio2' });
const ROOT = path.join(__dirname, '..');
const OUT = process.env.OUT || '/tmp/la-cre-data.xlsx';
const d = v => v ? String(v).slice(0, 10) : '';
(async () => {
const q = s => pool.query(s).then(r => r.rows);
const webCol = (await pool.query(`SELECT 1 FROM information_schema.columns WHERE table_name='broker' AND column_name='website'`)).rowCount ? ', b.website' : ', NULL website';
const commercial = await q(`SELECT b.name "Broker", f.name "Firm",
(SELECT count(*) FROM broker_listing bl WHERE bl.broker_id=b.id)::int "Listings", b.total_assets "Total Crexi Book",
b.phone "Phone", b.email "Email" ${webCol.replace('b.website', 'b.website "Website"').replace('NULL website', 'NULL "Website"')}
FROM broker b LEFT JOIN firm f ON f.id=b.firm_id WHERE b.agent_type='commercial' OR b.agent_type IS NULL ORDER BY 3 DESC NULLS LAST`);
const agents = await q(`SELECT b.name "Agent", f.name "Firm",
(SELECT count(*) FROM broker_condo bc WHERE bc.broker_id=b.id)::int "Condos", b.phone "Phone", b.email "Email",
${webCol.includes('b.website') ? 'b.website' : 'NULL'} "Website", b.license "License"
FROM broker b LEFT JOIN firm f ON f.id=b.firm_id WHERE b.agent_type='residential' ORDER BY 3 DESC NULLS LAST`).catch(() => []);
const firms = await q(`SELECT f.name "Firm", count(DISTINCT bl.listing_id)::int "Listings", count(DISTINCT b.id)::int "Brokers"
FROM firm f JOIN broker b ON b.firm_id=f.id JOIN broker_listing bl ON bl.broker_id=b.id GROUP BY f.name ORDER BY 2 DESC`);
const sales = await q(`SELECT address "Address", city "City", zip "ZIP", sold_price "Sold Price", sold_date "Sold Date",
beds "Beds", baths "Baths", sqft "SqFt" FROM closed_sale WHERE sold_price>0 ORDER BY sold_date DESC NULLS LAST`).catch(() => []);
sales.forEach(s => s['Sold Date'] = d(s['Sold Date']));
let condos = [];
try {
condos = JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'fha-approved-condos.json'), 'utf8')).condos
.filter(c => c.warrant_signal === 'fha_approved')
.map(c => ({ Project: c.project_name, City: c.city, ZIP: c.zip, 'Status (proxy)': 'FHA-approved (proxy, not lender-verified)', Expiration: c.expiration_date }));
} catch (_) {}
const wb = XLSX.utils.book_new();
const add = (name, rows) => XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(rows.length ? rows : [{ note: 'no rows' }]), name.slice(0, 31));
add('Commercial Brokers', commercial);
add('Residential Agents', agents);
add('Firms', firms);
add('Warrantable Condos (FHA)', condos);
add('Closed Sales (Redfin ~5yr)', sales);
XLSX.writeFile(wb, OUT);
console.log(`wrote ${OUT}`);
console.log(` Commercial Brokers: ${commercial.length}`);
console.log(` Residential Agents: ${agents.length}`);
console.log(` Firms: ${firms.length}`);
console.log(` Warrantable Condos: ${condos.length}`);
console.log(` Closed Sales: ${sales.length}`);
await pool.end();
})().catch(e => { console.error('ERR', e.message); process.exit(1); });