← back to Commercialrealestate

scripts/export-license-directory.js

61 lines

#!/usr/bin/env node
// Export a static License Directory from the local `cre` DB → data/license-directory.json,
// so the CRCP CRM can render licensed RE agents + firms + title/escrow companies as
// contactable cards on PROD (which has no `cre` DB — same static-export pattern as
// brokers-snapshot.json). Scoped to SoCal luxury metros for a shippable v1.
//
// Usage: NODE_PATH=$HOME/.claude/skills/browserbase/node_modules node scripts/export-license-directory.js
const fs = require('fs');
const path = require('path');
const { Client } = require('pg');

const OUT = path.join(__dirname, '..', 'data', 'license-directory.json');
const METROS = ['beverly-hills', 'malibu', 'montecito', 'newport-beach'];
const slug = s => String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');

(async () => {
  const c = new Client({ host: '/tmp', database: 'cre' });
  await c.connect();
  const metroList = METROS.map((_, i) => `$${i + 1}`).join(',');

  // Agents (individual licensees) in the SoCal luxury metros.
  const agents = (await c.query(
    `SELECT name, firm, license_number, license_type, city, metro, expiration::text AS expiration, source
       FROM gov_licensed_agent
      WHERE state='CA' AND metro IN (${metroList})
      ORDER BY firm NULLS LAST, name`, METROS)).rows
    .map(r => ({
      id: 'agent:' + slug(r.name) + ':' + (r.license_number || slug(r.firm || '')),
      kind: 'agent', name: r.name, firm: r.firm || '', license_number: r.license_number || '',
      license_type: r.license_type || '', city: r.city || '', metro: r.metro || '',
      expiration: r.expiration ? String(r.expiration).slice(0, 10) : null, source: r.source || '',
    }));

  // Firms with their SoCal licensed-agent counts.
  const firms = (await c.query(
    `SELECT firm AS name, count(*)::int AS agent_count,
            mode() WITHIN GROUP (ORDER BY metro) AS metro
       FROM gov_licensed_agent
      WHERE state='CA' AND metro IN (${metroList}) AND firm IS NOT NULL AND firm <> ''
      GROUP BY firm ORDER BY count(*) DESC`, METROS)).rows
    .map(r => ({ id: 'firm:' + slug(r.name), kind: 'firm', name: r.name, agent_count: r.agent_count, metro: r.metro || '' }));

  // Title + escrow companies (deal-side).
  const companies = (await c.query(
    `SELECT name, kind, underwriter FROM transaction_company WHERE kind IN ('title','escrow') ORDER BY kind, name`)).rows
    .map(r => ({ id: r.kind + ':' + slug(r.name), kind: r.kind, name: r.name, underwriter: r.underwriter || '' }));

  await c.end();
  const out = {
    generated_at: new Date().toISOString(),
    scope: { state: 'CA', metros: METROS },
    counts: { agents: agents.length, firms: firms.length, title: companies.filter(x => x.kind === 'title').length, escrow: companies.filter(x => x.kind === 'escrow').length },
    agents, firms,
    title: companies.filter(x => x.kind === 'title'),
    escrow: companies.filter(x => x.kind === 'escrow'),
  };
  fs.writeFileSync(OUT, JSON.stringify(out));
  console.log('Wrote', OUT);
  console.log('counts:', JSON.stringify(out.counts));
})().catch(e => { console.error('FATAL', e.message); process.exit(1); });