← back to La Socrata Ingester
add rentv LinkedIn enricher via openclaw (dedup by firm, prioritized, resumable)
d2328d925bf3ec3d9cdb4fc3ea1119cceb7e3c84 · 2026-08-12 10:40:08 -0700 · Steve Abrams
Files touched
A scripts/enrich-rentv-linkedin.js
Diff
commit d2328d925bf3ec3d9cdb4fc3ea1119cceb7e3c84
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Aug 12 10:40:08 2026 -0700
add rentv LinkedIn enricher via openclaw (dedup by firm, prioritized, resumable)
---
scripts/enrich-rentv-linkedin.js | 48 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/scripts/enrich-rentv-linkedin.js b/scripts/enrich-rentv-linkedin.js
new file mode 100644
index 0000000..3523667
--- /dev/null
+++ b/scripts/enrich-rentv-linkedin.js
@@ -0,0 +1,48 @@
+// Enrich rentv_licensed_targets LinkedIn via openclaw ($0 — real logged-in Chrome, the only
+// reliable route since these RE pros are ~99% site-less so site-mining/curl-search can't reach
+// them). Reuses the proven searchLinkedin (Google in a real browser + precision guards).
+// PRIORITIZED: commercial + near-LA (within_300mi) first — that's RENTV's actual deal universe.
+// Resumable (contacts_enriched_at stamp). SINGLE worker (one openclaw browser, serialized).
+//
+// Usage: node scripts/enrich-rentv-linkedin.js [--loop] [--batch=N] [--limit=N]
+import { q, pool } from '../src/db.js';
+import { searchLinkedin } from './linkedin-openclaw.js';
+
+const flags = new Set(process.argv.slice(2).filter(a => a.startsWith('--') && !a.includes('=')));
+const kv = Object.fromEntries(process.argv.slice(2).filter(a => a.includes('=')).map(a => a.slice(2).split('=')));
+const BATCH = Number(kv.batch || 40);
+const LIMIT = kv.limit ? Number(kv.limit) : null;
+const DELAY = Number(process.env.RENTV_DELAY_MS || 400);
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+async function pick() {
+ // dedupe by firm: search each distinct entity_name ONCE, then propagate to all its rows
+ return (await q(`SELECT entity_name, max(city) city FROM rentv_licensed_targets
+ WHERE linkedin IS NULL AND contacts_enriched_at IS NULL AND entity_name IS NOT NULL AND entity_name <> ''
+ GROUP BY entity_name ORDER BY entity_name LIMIT ${BATCH}`)).rows;
+}
+
+async function main() {
+ const total = Number((await q(`SELECT count(DISTINCT entity_name) c FROM rentv_licensed_targets WHERE linkedin IS NULL AND contacts_enriched_at IS NULL AND entity_name IS NOT NULL AND entity_name<>''`)).rows[0].c);
+ console.log(`rentv LinkedIn enrichment via openclaw — ${total.toLocaleString()} distinct firms to process`);
+ let done = 0, li = 0;
+ for (;;) {
+ const batch = await pick();
+ if (!batch.length) { console.log('\n✔ rentv LinkedIn enrichment complete'); break; }
+ for (const r of batch) {
+ let link = null;
+ try { link = await searchLinkedin(r.entity_name, r.city); } catch {}
+ // propagate to EVERY row of this firm (dedupe)
+ const n = (await q(`UPDATE rentv_licensed_targets SET linkedin=$2::text,
+ linkedin_source=CASE WHEN $2::text IS NOT NULL THEN 'openclaw' ELSE linkedin_source END,
+ contacts_enriched_at=now() WHERE entity_name=$1 AND contacts_enriched_at IS NULL`, [r.entity_name, link])).rowCount;
+ done++; if (link) { li++; console.log(` [${done}] ${String(r.entity_name).slice(0, 34).padEnd(34)} → ${link}${n > 1 ? ` (×${n})` : ''}`); }
+ if (LIMIT && done >= LIMIT) { console.log(`\nreached --limit=${LIMIT}: ${done} processed, ${li} linkedin. $0.`); return; }
+ await sleep(DELAY);
+ }
+ console.log(`— ${done} processed · ${li} linkedin · ~${(total - done).toLocaleString()} left — $0`);
+ if (!flags.has('--loop')) break;
+ }
+ console.log(`\nTotal: ${done} processed, ${li} linkedin. $0 (openclaw real browser).`);
+}
+main().catch(e => { console.error('enrich-rentv-linkedin error:', e.message); process.exitCode = 1; }).finally(() => pool.end());
← f06221a chore: refactor (ERA_DAYS const, indent), minor version bump
·
back to La Socrata Ingester
·
rate-limit: prompt for token interactively when none supplie eb45025 →