← back to Commercialrealestate

scripts/export-residential-snapshots.js

48 lines

// export-residential-snapshots.js — dump BOTH residential snapshots (data/sfr-redfin.json +
// data/condos-redfin.json) from the cre DB AFTER mark-off-market.js has run, so the prod (no-DB)
// snapshots carry the lifecycle fields (status / off_market_at / disposition / sold_price / sold_date).
//
// Supersedes export-sfr-snapshot.js in the refresh flow (that one is SFR-only and pre-lifecycle).
// Includes every active listing + off-market listings pulled within the last OFFMKT_DAYS (so the
// "pulled / sold" view has recent history without the file growing unbounded).
'use strict';
const fs = require('fs');
const path = require('path');
const { Pool } = require('pg');

const ROOT = path.join(__dirname, '..');
const pool = new Pool({ host: '/tmp', port: 5432, database: 'cre', user: process.env.USER || 'stevestudio2' });
const OFFMKT_DAYS = +(process.env.OFFMKT_DAYS || 180);
const WHERE = `WHERE price > 0 AND (status='active' OR off_market_at > now() - interval '${OFFMKT_DAYS} days')`;

(async () => {
  // SFR
  const sfr = (await pool.query(`
    SELECT id, address, city, zip, price, beds, baths, sqft, year_built, firm_name, broker_name,
           source, lat, lng, status, off_market_at, disposition, sold_price, sold_date, last_seen, created_at,
           days_on_market, listed_date, market_status, prev_market_status, prev_price, price_changed_at
      FROM sfr ${WHERE} ORDER BY (status='active') DESC, created_at DESC, price DESC`)).rows;
  fs.writeFileSync(path.join(ROOT, 'data', 'sfr-redfin.json'), JSON.stringify({
    meta: { source: 'Redfin LA County single-family — feed-first (gis-csv), local Chrome',
            note: 'status=off_market means the listing left Redfin\'s active feed; disposition=sold cross-referenced against closed_sale, else withdrawn.',
            offmarket_window_days: OFFMKT_DAYS, fetched_at: new Date().toISOString(), count: sfr.length },
    sfr }, null, 2));

  // Condos (via condo_card view — carries warrantability + lifecycle columns)
  const condos = (await pool.query(`
    SELECT id, address, city, zip, price, hoa, beds, baths, sqft, year_built, project_name,
           warrantable_status, warrant_source, warrant_signals, broker_name, firm_name, source, created_at,
           status, off_market_at, disposition, sold_price, sold_date, last_seen,
           days_on_market, listed_date, market_status, prev_market_status, prev_price, price_changed_at
      FROM condo_card ${WHERE} ORDER BY (status='active') DESC, created_at DESC, price DESC`)).rows;
  fs.writeFileSync(path.join(ROOT, 'data', 'condos-redfin.json'), JSON.stringify({
    meta: { source: 'Redfin LA County condos — feed-first (gis-csv), local Chrome',
            offmarket_window_days: OFFMKT_DAYS, fetched_at: new Date().toISOString(), count: condos.length },
    condos }, null, 2));

  const byStatus = (rows) => rows.reduce((a, r) => (a[r.status] = (a[r.status] || 0) + 1, a), {});
  console.log(JSON.stringify({ sfr: { count: sfr.length, byStatus: byStatus(sfr) },
                               condos: { count: condos.length, byStatus: byStatus(condos) } }));
  await pool.end();
})().catch(e => { console.error(e.message); process.exit(1); });