← back to Commercialrealestate

scripts/build-deed-history.js

97 lines

// build-deed-history.js — Part (a) of the CRE deeds FREE-PASS.
// Extends the 13-record broker-history PILOT to ALL 1,407 listings using the FREE
// LA County assessor roll (assessor_parcel): attaches the last deed recording_date,
// AIN, and year_built to every listing via the same address match serve.js uses.
//
// FREE + LOCAL only (cre DB). No paid API. Broker (part b) stays null on the new
// records — deeds carry no agent name (Gov Code §6254.21); current-listing broker
// enrichment is the separate gated pass. The 13 pilot records (which already have
// current_broker + B-tier sale history) are MERGE-PRESERVED, never downgraded.
//
// Usage: node scripts/build-deed-history.js   (writes data/broker-history-full.json)

const fs = require('fs');
const path = require('path');
const { pool } = require('./db/brokers-db');

const ROOT = path.join(__dirname, '..');
const LISTINGS = path.join(ROOT, 'data', 'listings.json');
const PILOT = path.join(ROOT, 'data', 'broker-history-pilot.json');
const OUT = path.join(ROOT, 'data', 'broker-history-full.json');

// Same address parse serve.js /api/property-history uses (house number + first 3+ letter word).
const parse = (address) => ({
  num: (String(address).match(/^\s*(\d+)/) || [])[1] || '',
  word: (String(address).replace(/^\s*\d+\s*/, '').match(/[a-z]{3,}/i) || [])[0] || '',
});

(async () => {
  const listingsRaw = JSON.parse(fs.readFileSync(LISTINGS, 'utf8'));
  const listings = Array.isArray(listingsRaw) ? listingsRaw : (listingsRaw.listings || listingsRaw.properties || []);
  const pilot = JSON.parse(fs.readFileSync(PILOT, 'utf8'));
  const pilotById = new Map((pilot.properties || []).map((p) => [p.id, p]));

  let matched = 0, deed = 0, preserved = 0;
  const properties = [];

  for (const l of listings) {
    // Preserve the fully-enriched pilot record verbatim (has current_broker + tx history).
    if (pilotById.has(l.id)) { properties.push(pilotById.get(l.id)); preserved++; continue; }

    const { num, word } = parse(l.address);
    let row = null;
    if (num && word) {
      try {
        row = (await pool.query(
          `SELECT ain, year_built, units, recording_date
             FROM assessor_parcel WHERE situs_house_no=$1 AND situs_street ILIKE $2
             ORDER BY roll_year DESC NULLS LAST LIMIT 1`,
          [+num, '%' + word + '%'])).rows[0] || null;
      } catch (_) { /* table absent — degrade */ }
    }
    if (row) matched++;
    const deedDate = row && row.recording_date
      ? new Date(row.recording_date).toISOString().slice(0, 10) : null;
    if (deedDate) deed++;

    const tx = [];
    if (deedDate) tx.push({
      date: deedDate,
      event: 'Deed recorded (LA County assessor)',
      price: null,
      source: `LA County assessor_parcel, AIN ${row.ain}`,
      tier: 'A',
    });

    properties.push({
      id: l.id,
      address: l.address,
      city: l.city,
      ain: row ? String(row.ain) : null,
      assessor_year_built: (row && row.year_built) || l.year_built || null,
      assessor_units: (row && row.units) || l.units || null,
      last_deed_date: deedDate,
      sale_price_search: null,
      tx_history: tx,
      current_broker: null,           // part (b) — gated enrichment fills this later
      broker_flag: false,
    });
  }

  await pool.end();

  const full = {
    meta: {
      scope: `FULL — all ${listings.length} ranked listings. Deed date/AIN/year-built from the free LA County assessor roll (assessor_parcel). ${preserved} top records retain full broker + sale-history enrichment from the pilot; the rest are deed-only until the gated current-listing-broker pass runs.`,
      sources: (pilot.meta && pilot.meta.sources) || ['LA County Assessor (assessor_parcel — recording_date = last deed recording)'],
      broker_caveat: (pilot.meta && pilot.meta.broker_caveat) || 'Broker attribution requires the separate current-listing enrichment pass; deeds carry no agent name (Gov Code §6254.21).',
      counts: { listings: listings.length, assessor_matched: matched, with_deed_date: deed, pilot_preserved: preserved },
    },
    properties,
  };

  fs.writeFileSync(OUT, JSON.stringify(full, null, 1));
  console.log(`wrote ${OUT}`);
  console.log(`  listings=${listings.length}  assessor_matched=${matched}  with_deed_date=${deed}  pilot_preserved=${preserved}`);
})().catch((e) => { console.error('FAILED:', e.message); process.exit(1); });