← back to Sublease Agentabrams

scripts/import-crcp.js

57 lines

'use strict';
// Import CRCP real data → CRUnifiedDB: 2,537 brokers + 3,105 for-sale listings (honestly typed
// as listing_kind='sale', is_sublease=false), geo-matched from map-points by address. $0.
const fs = require('fs');
const { Pool } = require('pg');
const pool = new Pool({ host: '/tmp', database: 'crunified' });
const CR = process.env.HOME + '/Projects/commercialrealestate/data/';
const j = (f) => JSON.parse(fs.readFileSync(CR + f, 'utf8'));
const norm = (s) => (s || '').toLowerCase().replace(/[^a-z0-9]/g, '');
const SPACE = { 'Office': 'office', 'Retail': 'retail', 'Retail/NNN': 'retail', 'Mixed-use': 'flex', 'Multifamily': 'multifamily' };

(async () => {
  const brokers = j('brokers-snapshot.json').brokers;
  const listings = j('listings.json').listings;
  const points = j('map-points.json').points;

  // address+city → {lat,lng} from map-points (any kind) for geo matching
  const geo = new Map();
  for (const p of points) if (p.lat && p.lng) { const k = norm(p.address) + '|' + norm(p.city); if (!geo.has(k)) geo.set(k, { lat: p.lat, lng: p.lng }); }

  // --- brokers ---
  let b = 0;
  for (const r of brokers) {
    await pool.query(`INSERT INTO brokers (id,name,firm,agent_type,phone,email,website,linkedin,office_addr,total_assets,specialties)
      VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) ON CONFLICT (id) DO UPDATE SET
      name=EXCLUDED.name, firm=EXCLUDED.firm, phone=EXCLUDED.phone, email=EXCLUDED.email, website=EXCLUDED.website`,
      [r.id, r.name, r.firm, r.agent_type, r.phone, r.email, r.website, r.linkedin, r.office_addr, r.total_assets,
       Array.isArray(r.specialties) ? r.specialties.join(', ') : r.specialties]);
    b++;
  }

  // --- listings (for-sale base) ---
  let li = 0, withGeo = 0;
  for (const r of listings) {
    const g = geo.get(norm(r.address) + '|' + norm(r.city));
    if (g) withGeo++;
    await pool.query(`INSERT INTO listings
      (source_label, external_id, listing_kind, is_sublease, title, address, city, state, zip, lat, lng,
       space_type, asset_class, size_sf, units, cap_rate, year_built, price_amount, price_unit, status, source_url, raw, last_seen)
      VALUES ('crcp',$1,'sale',false,$2,$3,$4,'CA',$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,'sale price',$15,$16,$17, now())
      ON CONFLICT (source_label, external_id) WHERE source_label IS NOT NULL DO UPDATE SET price_amount=EXCLUDED.price_amount, cap_rate=EXCLUDED.cap_rate,
        lat=COALESCE(EXCLUDED.lat, listings.lat), lng=COALESCE(EXCLUDED.lng, listings.lng), last_seen=now()`,
      [String(r.id), (r.type ? r.type + ' — ' : '') + r.address, r.address, r.city, r.zip,
       g ? g.lat : null, g ? g.lng : null, SPACE[r.type] || 'other', r.type, r.sqft || null, r.units || null,
       r.cap_rate || null, r.year_built || null, r.price || null,
       (r.status || 'Active'), r.source, JSON.stringify({ cap_rate: r.cap_rate, upside: r.upside_note, rent_control: r.rent_control })]);
    li++;
  }

  const [{ n: total }] = (await pool.query(`SELECT COUNT(*)::int n FROM listings`)).rows;
  const [{ n: subs }] = (await pool.query(`SELECT COUNT(*)::int n FROM listings WHERE is_sublease`)).rows;
  console.log(`brokers: ${b} imported`);
  console.log(`CRCP listings: ${li} imported (${withGeo} geo-matched from map-points)`);
  console.log(`CRUnifiedDB now: ${total} listings total (${subs} sublease, ${total - subs} sale)`);
  await pool.end();
})();