← back to Nationalrealestate

src/ingest/brokers/ca_dre.ts

56 lines

/**
 * CA — Department of Real Estate current licensee master file. FREE bulk download
 * (secure.dre.ca.gov/datafile/CurrList.zip -> CurrList.csv, ~431k rows, refreshed
 * daily). Columns are snake_case already (lic_type, lastname_primary, ...).
 *
 *   lic_type='Corporation'          -> brokerage firm (name lives in lastname_primary)
 *   lic_type='Broker'|'Salesperson' -> individual licensee; employing firm via
 *                                      related_lic_number + related_lastname_primary
 *   lic_type='Officer'              -> a corporation's responsible-officer designation;
 *                                      skipped (that person also holds a Broker license
 *                                      listed on its own row) to avoid double-counting.
 *
 * Carries city (+ county_name) so the LA-region broker/firm surface can scope locally.
 */
import { execFileSync } from 'node:child_process';
import { join } from 'node:path';
import { download, DOWNLOADS } from '../run.ts';
import { parseCsvObjects } from './csv.ts';
import type { BrokerRow, FetchedFile, StateAdapter } from './types.ts';

const URL = 'https://secure.dre.ca.gov/datafile/CurrList.zip';

export const caDre: StateAdapter = {
  state: 'CA',
  source: 'ca_dre',
  url: URL,
  async fetch(): Promise<FetchedFile[]> {
    const { path } = await download(URL, 'ca_dre_CurrList.zip');
    execFileSync('unzip', ['-o', '-q', path, '-d', DOWNLOADS]);
    return [{ path: join(DOWNLOADS, 'CurrList.csv'), sha256: '' }];
  },
  *parse(files: FetchedFile[]): Generator<BrokerRow> {
    for (const o of parseCsvObjects(files[0].path)) {
      if (!o.lic_number) continue;
      const type = o.lic_type || '';
      const city = o.city || null;
      const st = (o.state || '').toUpperCase() || null;
      const status = o.lic_status || null;
      if (type === 'Corporation') {
        const name = o.lastname_primary; // company name lives in the primary-name column
        if (!name) continue;
        yield { kind: 'firm', name, license_no: o.lic_number, license_type: type, license_status: status, city, state_code: st };
      } else if (type === 'Broker' || type === 'Salesperson') {
        const name = [o.firstname_secondary, o.lastname_primary].filter(Boolean).join(' ');
        if (!name) continue;
        yield {
          kind: 'broker', name, license_no: o.lic_number, license_type: type, license_status: status,
          firm_name: o.related_lastname_primary || null, firm_license_no: o.related_lic_number || null,
          city, state_code: st,
        };
      }
      // Officer / Corporation-affiliate / other rows skipped
    }
  },
};