← back to Nationalrealestate
usre: firm-phone coverage canary + incremental enrichment cron (TK-10687)
a05bd0f2e4339bcc190c093b0f18f6ac7f7f167d · 2026-08-18 13:28:14 -0700 · Steve Abrams
firm-phone-coverage.mjs (READ-ONLY): tracks 'every firm has a phone / every broker
callable' — % of CA active-broker firms with a phone + % of active brokers with a
resolvable phone (own/firm/firm_contacts fallback); emits PASS/WARN/FAIL + writes
data/firm-phone-coverage.json. Baseline: brokers 20.1% callable, 21,720 firms
phoneless. Cron (com.steve.firm-phone-enrich, 02:30/14:30, limit 400/run, $0 local,
skips if Ollama down) chips the queue highest-leverage-first + logs coverage each run.
Local usre writes only; Kamatera sync stays gated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M .gitignoreA deploy/launchd/com.steve.firm-phone-enrich.plistA scripts/firm-phone-coverage.mjsA scripts/run-firm-enrich.sh
Diff
commit a05bd0f2e4339bcc190c093b0f18f6ac7f7f167d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 18 13:28:14 2026 -0700
usre: firm-phone coverage canary + incremental enrichment cron (TK-10687)
firm-phone-coverage.mjs (READ-ONLY): tracks 'every firm has a phone / every broker
callable' — % of CA active-broker firms with a phone + % of active brokers with a
resolvable phone (own/firm/firm_contacts fallback); emits PASS/WARN/FAIL + writes
data/firm-phone-coverage.json. Baseline: brokers 20.1% callable, 21,720 firms
phoneless. Cron (com.steve.firm-phone-enrich, 02:30/14:30, limit 400/run, $0 local,
skips if Ollama down) chips the queue highest-leverage-first + logs coverage each run.
Local usre writes only; Kamatera sync stays gated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
.gitignore | 4 ++
deploy/launchd/com.steve.firm-phone-enrich.plist | 26 +++++++++
scripts/firm-phone-coverage.mjs | 67 ++++++++++++++++++++++++
scripts/run-firm-enrich.sh | 15 ++++++
4 files changed, 112 insertions(+)
diff --git a/.gitignore b/.gitignore
index b608ec2..7532879 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,7 @@ data/screenshots/
# editor/tooling backups
*.bak
+
+data/firm-enrich.log
+data/firm-enrich.out.log
+data/firm-enrich.err.log
diff --git a/deploy/launchd/com.steve.firm-phone-enrich.plist b/deploy/launchd/com.steve.firm-phone-enrich.plist
new file mode 100644
index 0000000..2bb66cb
--- /dev/null
+++ b/deploy/launchd/com.steve.firm-phone-enrich.plist
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>com.steve.firm-phone-enrich</string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>/bin/zsh</string>
+ <string>/Users/macstudio3/Projects/nationalrealestate/scripts/run-firm-enrich.sh</string>
+ </array>
+ <key>StartCalendarInterval</key>
+ <array>
+ <dict><key>Hour</key><integer>2</integer><key>Minute</key><integer>30</integer></dict>
+ <dict><key>Hour</key><integer>14</integer><key>Minute</key><integer>30</integer></dict>
+ </array>
+ <key>StandardOutPath</key>
+ <string>/Users/macstudio3/Projects/nationalrealestate/data/firm-enrich.out.log</string>
+ <key>StandardErrorPath</key>
+ <string>/Users/macstudio3/Projects/nationalrealestate/data/firm-enrich.err.log</string>
+ <key>RunAtLoad</key>
+ <false/>
+ <key>ProcessType</key>
+ <string>Background</string>
+</dict>
+</plist>
diff --git a/scripts/firm-phone-coverage.mjs b/scripts/firm-phone-coverage.mjs
new file mode 100644
index 0000000..b4d72a0
--- /dev/null
+++ b/scripts/firm-phone-coverage.mjs
@@ -0,0 +1,67 @@
+#!/usr/bin/env node
+// firm-phone-coverage.mjs — TK-10687 "every firm has a phone / every broker callable" tracker.
+// READ-ONLY. Measures how close we are to Steve's bar and surfaces the shortfall = the work-queue
+// the enrich-firm-contacts driver drives to zero. Emits PASS/WARN/FAIL (fleet-health vocabulary) so
+// it can plug into fleet-health-rollup, and writes data/firm-phone-coverage.json each run.
+//
+// Two headline numbers (CA, active-license scope):
+// 1. FIRM coverage — % of firms-with-an-active-broker that now have a phone.
+// 2. BROKER callable — % of active brokers with a resolvable phone (own OR firm fallback OR
+// firm_contacts) — the thing the RE directory actually shows.
+//
+// Usage: node scripts/firm-phone-coverage.mjs (prints report + writes json)
+'use strict';
+import pg from 'pg';
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const DB = process.env.DATABASE_URL || 'postgresql:///usre?host=/tmp';
+const ACTIVE = `(license_status ILIKE 'active%' OR license_status ILIKE 'licensed%' OR license_status ILIKE 'current / active%')`;
+const __dir = path.dirname(fileURLToPath(import.meta.url));
+const OUT = path.join(__dir, '..', 'data', 'firm-phone-coverage.json');
+
+async function main() {
+ const pool = new pg.Pool({ connectionString: DB });
+ const q = (s) => pool.query(s).then(r => r.rows[0]);
+
+ const firm = await q(`
+ WITH af AS (SELECT DISTINCT firm_id FROM broker WHERE firm_id IS NOT NULL AND license_state='CA' AND ${ACTIVE})
+ SELECT count(*)::int total,
+ count(*) FILTER (WHERE coalesce(nullif(f.phone,''),'')<>'')::int with_phone
+ FROM firm f JOIN af ON af.firm_id=f.id`);
+
+ // A broker is "callable" if it has its own phone, OR its firm has a phone, OR the firm has a
+ // crawled firm_contacts phone — exactly the /api/brokers COALESCE resolution.
+ const broker = await q(`
+ SELECT count(*)::int total,
+ count(*) FILTER (WHERE
+ coalesce(nullif(b.phone,''),'')<>''
+ OR coalesce(nullif(f.phone,''),'')<>''
+ OR EXISTS (SELECT 1 FROM firm_contacts fc WHERE fc.firm_id=f.id AND fc.kind='phone')
+ )::int callable
+ FROM broker b LEFT JOIN firm f ON f.id=b.firm_id
+ WHERE b.license_state='CA' AND b.firm_id IS NOT NULL AND ${ACTIVE}`);
+
+ const firmPct = firm.total ? +(100 * firm.with_phone / firm.total).toFixed(1) : 0;
+ const brokerPct = broker.total ? +(100 * broker.callable / broker.total).toFixed(1) : 0;
+ const firmMissing = firm.total - firm.with_phone;
+ const brokerMissing = broker.total - broker.callable;
+
+ // verdict on the BROKER-callable number (the customer-facing goal). PASS ≥99%, WARN ≥80%, else FAIL.
+ const verdict = brokerPct >= 99 ? 'PASS' : brokerPct >= 80 ? 'WARN' : 'FAIL';
+
+ const rec = {
+ verdict, ts: new Date().toISOString(), scope: 'CA active-license',
+ firms: { total: firm.total, with_phone: firm.with_phone, missing: firmMissing, pct: firmPct },
+ brokers: { total: broker.total, callable: broker.callable, missing: brokerMissing, pct: brokerPct },
+ };
+ try { fs.mkdirSync(path.dirname(OUT), { recursive: true }); fs.writeFileSync(OUT, JSON.stringify(rec, null, 2)); } catch {}
+
+ console.log(`[${verdict}] firm-phone coverage (CA active-license)`);
+ console.log(` FIRMS w/ active broker : ${firm.with_phone.toLocaleString()}/${firm.total.toLocaleString()} have a phone (${firmPct}%) · ${firmMissing.toLocaleString()} still missing`);
+ console.log(` ACTIVE BROKERS callable: ${broker.callable.toLocaleString()}/${broker.total.toLocaleString()} (${brokerPct}%) · ${brokerMissing.toLocaleString()} not yet callable`);
+ console.log(` → work-queue: enrich the ${firmMissing.toLocaleString()} phoneless firms (firm-phone-first) to drive both toward 100%.`);
+ await pool.end();
+}
+main().catch(e => { console.error('FATAL', e); process.exit(1); });
diff --git a/scripts/run-firm-enrich.sh b/scripts/run-firm-enrich.sh
new file mode 100755
index 0000000..4d986cd
--- /dev/null
+++ b/scripts/run-firm-enrich.sh
@@ -0,0 +1,15 @@
+#!/bin/zsh
+# TK-10687 incremental firm-phone enrichment (firm-phone-first, $0 local). Chips away at the ~21.7k
+# CA phoneless firms (highest active-broker count first) so every affiliated active broker becomes
+# callable via the firm-phone fallback. Bounded per run (--limit ≤ 500). Writes to the LOCAL usre DB
+# (staging); the Kamatera prod sync stays Steve-gated. Then records coverage for the tracker.
+export PATH="/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin"
+cd "$HOME/Projects/nationalrealestate" || exit 1
+LOG="$HOME/Projects/nationalrealestate/data/firm-enrich.log"
+LIMIT="${FIRM_ENRICH_LIMIT:-400}"
+echo "[$(date '+%F %T')] enrich start (limit $LIMIT)" >> "$LOG"
+# Ollama (local model) must be up; skip cleanly if not (never spend, never error the cron).
+if ! curl -s -o /dev/null http://localhost:11434/api/tags; then echo "[$(date '+%F %T')] SKIP — Ollama down" >> "$LOG"; exit 0; fi
+/opt/homebrew/bin/node scripts/enrich-firm-contacts.mjs --limit "$LIMIT" --apply >> "$LOG" 2>&1
+/opt/homebrew/bin/node scripts/firm-phone-coverage.mjs >> "$LOG" 2>&1
+echo "[$(date '+%F %T')] enrich done" >> "$LOG"
← 4cd353f usre: firm-phone-first contact enrichment driver (agents+loc
·
back to Nationalrealestate
·
auto-data-snapshot: 2026-08-18T13:28:17 (1 data files) — dat b3eef68 →