[object Object]

← back to Butlr

butlr: sequential Ventura-Blvd hours sweep dialer (one call at a time, up to 40)

11722b0becaef9895a485ee84a6c81dbabb3455a · 2026-05-26 11:01:00 -0700 · SteveStudio2

Files touched

Diff

commit 11722b0becaef9895a485ee84a6c81dbabb3455a
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Tue May 26 11:01:00 2026 -0700

    butlr: sequential Ventura-Blvd hours sweep dialer (one call at a time, up to 40)
---
 scripts/ventura-hours-sweep.js | 86 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)

diff --git a/scripts/ventura-hours-sweep.js b/scripts/ventura-hours-sweep.js
new file mode 100644
index 0000000..0bd2fad
--- /dev/null
+++ b/scripts/ventura-hours-sweep.js
@@ -0,0 +1,86 @@
+// Sequential Ventura-Blvd "what are your hours" sweep.
+// Places up to 40 calls ONE AT A TIME — each call must reach a terminal
+// status (done/failed/canceled) before the next is placed. Live audio
+// broadcasts on live.agentabrams.com as each call runs.
+//
+// Run on the Kamatera butlr host:
+//   cd /root/public-projects/butlr && nohup node scripts/ventura-hours-sweep.js > /tmp/ventura_sweep.log 2>&1 &
+//
+// Stop it any time:  pkill -f ventura-hours-sweep
+
+require('dotenv').config({ path: '/root/public-projects/butlr/.env' });
+const fs = require('fs');
+const data = require('../lib/data');
+const twilio = require('../lib/twilio');
+
+const USER_ID = 'rcA9SnnO';
+const MAX_CALLS = 40;
+const PER_CALL_TIMEOUT_MS = 130000;  // give a call up to ~2min to finish
+const GAP_MS = 4000;                 // breath between calls
+const TERMINAL = new Set(['done', 'failed', 'canceled', 'cancelled']);
+const GOAL = 'Ask: what are your hours today? Wait for their answer, then thank them warmly and end the call. Ask nothing else; keep it under two sentences.';
+
+const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
+function log(...a) { console.log(new Date().toISOString(), ...a); }
+
+function shortLocality(addr) {
+  // OSM display_name: "Name, number, street, neighborhood, city, county, state, zip, country"
+  const parts = String(addr || '').split(',').map((s) => s.trim()).filter(Boolean);
+  // drop the leading name part, keep number+street + city-ish
+  const tail = parts.slice(1, 4).join(', ');
+  return tail.slice(0, 60);
+}
+
+async function waitTerminal(id) {
+  const start = Date.now();
+  while (Date.now() - start < PER_CALL_TIMEOUT_MS) {
+    const c = data.getCallUnscoped(id);
+    if (c && TERMINAL.has(c.status)) return c.status;
+    await sleep(3000);
+  }
+  return 'timeout';
+}
+
+(async () => {
+  let list;
+  try { list = JSON.parse(fs.readFileSync('/tmp/vall.json', 'utf8')); }
+  catch (e) { log('FATAL: cannot read /tmp/vall.json —', e.message); process.exit(1); }
+  list = list.slice(0, MAX_CALLS);
+  log(`=== Ventura hours sweep: ${list.length} businesses, sequential ===`);
+
+  let placed = 0, skipped = 0;
+  for (let i = 0; i < list.length; i++) {
+    const b = list[i];
+    const loc = shortLocality(b.addr);
+    const row = {
+      business_name: (b.name + (loc ? ' — ' + loc : '')).slice(0, 120),
+      business_phone: b.e164,
+      goal: GOAL,
+      callback_phone: '+16067130489',
+      callback_name: 'Steve Adams',
+      category: 'other',
+      category_label: 'Ventura hours sweep',
+      max_hold_minutes: 2,
+      max_spend_cents: 50,
+      consent_recording: 'on',
+      consent_terms: 'on',
+      notes: 'ventura 40 hours sweep ' + (i + 1) + '/' + list.length,
+      allow_repeat: true,
+    };
+    const r = data.addCall(row, USER_ID);
+    if (!r.ok) { skipped++; log(`SKIP ${i + 1}/${list.length} ${b.e164} ${b.name} — ${(r.errors || []).join('; ')}`); continue; }
+    try {
+      await twilio.dialCall(r.call);
+      placed++;
+      log(`PLACED ${i + 1}/${list.length} id=${r.call.id} ${b.e164} | ${b.name}`);
+    } catch (e) {
+      log(`DIAL-ERR ${i + 1}/${list.length} id=${r.call.id} — ${e.message}`);
+      continue;
+    }
+    const status = await waitTerminal(r.call.id);
+    log(`  └─ ${r.call.id} ended: ${status}`);
+    await sleep(GAP_MS);
+  }
+  log(`=== sweep done: placed=${placed} skipped=${skipped} ===`);
+  process.exit(0);
+})();

← 4997af2 live page: harden audio — auto-listen on appear, resume ctx  ·  back to Butlr  ·  live page: show recording length as m:ss badge + preload=met c415746 →