← back to Nationalrealestate
db/backfill-broker-of-record.ts
70 lines
/**
* TK-10139: seed broker_of_record_history with ONE initial observation per address
* from the current `listing` table (every listing that has a broker_id or firm_id).
*
* Dedupe: for each normalized address_key we keep the MOST-RECENT listing (by last_seen,
* then first_seen, then id) and insert a single history row observed at that listing's
* last_seen. Re-runnable: skips any address_key already present in the history table, so
* running it twice does not double-seed.
*
* npx tsx db/backfill-broker-of-record.ts
*/
import 'dotenv/config';
import { pool, query } from './pool.ts';
import { addressKeySql } from '../src/server/broker-of-record.ts';
// CANONICAL address_key — uses the SINGLE shared normalizer (addressKeySql), identical to
// computeAddressKey() in src/server/broker-of-record.ts, so backfilled keys match the recorder,
// the listings-ingest engine, and the commercial + listings API joins (state/zip stripped).
// NOTE: the `listing` table has NO separate city column — its `address` is already the
// full "street, city, state, zip" string — so address_key = normalized full address.
const ADDRESS_KEY_SQL = addressKeySql(`coalesce(l.address,'')`);
async function main() {
const before = await query<{ n: string }>(`SELECT count(*)::text n FROM broker_of_record_history`);
console.log(`[backfill] history rows before: ${before.rows[0].n}`);
const res = await query<{ id: number }>(
`WITH ranked AS (
SELECT
l.*,
${ADDRESS_KEY_SQL} AS address_key,
rg.state_code AS rg_state,
f.name AS firm_name,
ROW_NUMBER() OVER (
PARTITION BY ${ADDRESS_KEY_SQL}
ORDER BY l.last_seen DESC NULLS LAST, l.first_seen DESC NULLS LAST, l.id DESC
) AS rn
FROM listing l
LEFT JOIN firm f ON f.id = l.firm_id
LEFT JOIN region rg ON rg.id = l.region_id
WHERE (l.broker_id IS NOT NULL OR l.firm_id IS NOT NULL)
AND btrim(coalesce(l.address,'')) <> ''
)
INSERT INTO broker_of_record_history
(address_key, parcel_key, county_fips, ain, address, city, state_code,
broker_id, broker_name, firm_id, firm_name, source, source_url, observed_at)
SELECT
r.address_key, NULL, NULL, NULL, r.address, NULL, r.rg_state,
r.broker_id, NULL, r.firm_id, r.firm_name, r.source, r.url,
COALESCE(r.last_seen, r.first_seen, now())
FROM ranked r
WHERE r.rn = 1
AND NOT EXISTS (
SELECT 1 FROM broker_of_record_history h WHERE h.address_key = r.address_key
)
RETURNING id`,
);
const after = await query<{ n: string }>(`SELECT count(*)::text n FROM broker_of_record_history`);
console.log(`[backfill] inserted: ${res.rows.length}`);
console.log(`[backfill] history rows after: ${after.rows[0].n}`);
await pool.end();
}
main().catch((e) => {
console.error(e);
process.exit(1);
});