[object Object]

← 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

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 →