← back to Nationalrealestate
resolver: confidence-tier matches — only set firm.website when host echoes the name; loose matches recorded as low_confidence leads (audited ~30% loose), don't overwrite website with a maybe-wrong site
7241fde27663949aa1c6c82a88dcc0dce2ad5b99 · 2026-07-30 19:18:24 -0700 · steve@designerwallcoverings.com
Files touched
M src/enrich/firm_website_resolve.ts
Diff
commit 7241fde27663949aa1c6c82a88dcc0dce2ad5b99
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Thu Jul 30 19:18:24 2026 -0700
resolver: confidence-tier matches — only set firm.website when host echoes the name; loose matches recorded as low_confidence leads (audited ~30% loose), don't overwrite website with a maybe-wrong site
---
src/enrich/firm_website_resolve.ts | 36 +++++++++++++++++++++++++-----------
1 file changed, 25 insertions(+), 11 deletions(-)
diff --git a/src/enrich/firm_website_resolve.ts b/src/enrich/firm_website_resolve.ts
index 50a9711..567a778 100644
--- a/src/enrich/firm_website_resolve.ts
+++ b/src/enrich/firm_website_resolve.ts
@@ -91,8 +91,14 @@ async function searchText(q: string): Promise<Array<{ name: string; website?: st
return (j.places || []).map((p: any) => ({ name: p.displayName?.text || '', website: p.websiteUri }));
}
-/** Best own-site pick: skip aggregators; prefer a host that echoes the firm name. */
-function pickWebsite(hits: Array<{ name: string; website?: string }>, firmName: string): string | null {
+/**
+ * Best own-site pick: skip aggregators; prefer a host that echoes the firm name.
+ * Returns `confident=true` only when the winning host echoes the name — Places
+ * sometimes returns an agent's personal page or a franchise-brand portal for a
+ * firm, so a non-echoing host is recorded as a LEAD (firm_site low_confidence)
+ * WITHOUT overwriting firm.website. ~30% of hits are loose (audited 2026-07-30).
+ */
+function pickWebsite(hits: Array<{ name: string; website?: string }>, firmName: string): { url: string; confident: boolean } | null {
const cands = hits
.map(h => h.website).filter((u): u is string => !!u)
.filter(u => { const h = hostOf(u); return h && !BLOCK.has(h); });
@@ -100,7 +106,8 @@ function pickWebsite(hits: Array<{ name: string; website?: string }>, firmName:
const named = cands.find(u => { const h = hostOf(u); return h && nameEchoesHost(firmName, h); });
const winner = named || cands[0];
const h = hostOf(winner);
- return h ? 'https://' + h : null;
+ if (!h) return null;
+ return { url: 'https://' + h, confident: !!named };
}
interface FirmRow { id: number; name: string; hq_city: string | null; license_state: string | null }
@@ -141,20 +148,27 @@ async function main() {
if (budget <= 0) { console.log(`[resolve] monthly cap ${CAP} reached — HARD STOP, $0 spent`); await pool.end(); return; }
const runId = await openRun(SOURCE, 'places-firm-resolve');
- let calls = 0, resolved = 0, noSite = 0, fatal: any = null;
+ let calls = 0, resolved = 0, lowConf = 0, noSite = 0, fatal: any = null;
try {
for (const f of queue) {
if (calls >= budget) { console.log('[resolve] cap reached mid-run — stopping'); break; }
const q = `${f.name} ${f.hq_city || ''} ${f.license_state || ''}`.replace(/\s+/g, ' ').trim();
const hits = await searchText(q);
calls++; await bumpQuota(1); logCost(1, `resolve "${f.name.slice(0, 40)}"`);
- const website = pickWebsite(hits, f.name);
- if (website) {
+ const pick = pickWebsite(hits, f.name);
+ if (pick && pick.confident) {
resolved++;
- await query(`UPDATE firm SET website = COALESCE(website, $2) WHERE id = $1`, [f.id, website]);
+ await query(`UPDATE firm SET website = COALESCE(website, $2) WHERE id = $1`, [f.id, pick.url]);
await query(
`INSERT INTO firm_site (firm_id, url, discovery_method) VALUES ($1,$2,'google_places_resolve')
- ON CONFLICT (firm_id) DO NOTHING`, [f.id, website]);
+ ON CONFLICT (firm_id) DO NOTHING`, [f.id, pick.url]);
+ } else if (pick) {
+ // Loose match (host doesn't echo the firm name) — record the LEAD for
+ // review/crawl but do NOT overwrite firm.website with a maybe-wrong site.
+ lowConf++;
+ await query(
+ `INSERT INTO firm_site (firm_id, url, discovery_method, crawl_status)
+ VALUES ($1,$2,'google_places_resolve','low_confidence') ON CONFLICT (firm_id) DO NOTHING`, [f.id, pick.url]);
} else {
noSite++;
await query(
@@ -170,10 +184,10 @@ async function main() {
// Fail-soft: always close the run (never leave a zombie 'running').
const status = calls > 0 ? 'ok' : 'failed';
await closeRun(runId, status, {
- upserted: resolved, skipped: noSite,
- notes: `${calls} Places calls, ${resolved} sites resolved, ${noSite} no-site${fatal ? ` · partial(${String(fatal?.message || fatal).slice(0, 40)})` : ''}`,
+ upserted: resolved, skipped: lowConf + noSite,
+ notes: `${calls} Places calls, ${resolved} confident sites, ${lowConf} low-confidence leads, ${noSite} no-site${fatal ? ` · partial(${String(fatal?.message || fatal).slice(0, 40)})` : ''}`,
});
- console.log(`[resolve] done: ${calls} calls · ${resolved} sites resolved · ${noSite} no-site · $0 (free-tier)${fatal ? ' (partial)' : ''}`);
+ console.log(`[resolve] done: ${calls} calls · ${resolved} confident · ${lowConf} low-confidence · ${noSite} no-site · $0 (free-tier)${fatal ? ' (partial)' : ''}`);
await pool.end();
}
← 0846541 Add Places-API firm->website resolver (resolve:firms): relia
·
back to Nationalrealestate
·
Add Weichert listings adapter (3rd homes source) + fix extra 1bf4cc4 →