Mojo Bridge

Status
operational
Tier
Tier 2 — Platform
Owner
Ryan Colston
Started
2026-05-25
Target launch
2026-05-26

Linear: RYR-236 — sync_log volume alerting (2026-07-23 vault→Linear audit) Verification correction (2026-07-24): sync_log volume alert is ALREADY LIVEalert_sync_log.sql + terraform data-transfer + monitoring policy ENABLED (verified 2026-07-24). (RYR-236 canceled)

Mojo Dialer → BigQuery sync. Captures setter dialing activity + the full owner/property/phone graph Mojo holds, so the setter-program deliverables can join Mojo data to FUB on normalized phone. Lives at ~/rylobasic/systems/setter-program/mojo-bridge/.

Why

Mojo has no public API but its SPA at app201.mojosells.com/v2/rest/* is stable and session-cookie authed. The 12,425 contacts on account 689102 carry MLS metadata, AVM history, multi-phone/multi-email arrays with per-channel DNC flags, and full MLS remarks — none of which surface in the Mojo↔FUB native integration. Without this bridge, every call our setters will make lives only inside Mojo's UI, with no programmatic way to track dial volume, talk time, or outcome attribution against FUB consults.

The bridge buys time. The long-term answer is Custom Dialer (Twilio + STT + Vertex, ~Sept 2026). Until then, BQ becomes the system of record while Mojo keeps doing the four things it does well: power dialing, skip tracing, FSBO/Expired feeds, MLS metadata enrichment.

Current state

2026-06-30: - Recording mirror was never actually working — fixed. The audio download URL omitted the trailing slash before ?record_id; Mojo 301-redirected to the slash form and requests dropped the session cookie across the redirect → every download 401'd. 0 of 71 recordings had ever mirrored. The old code masked it by aborting at the first 401 (looked like one dead recording, 85712249). Root-caused live (slash → 200 audio/mpeg; no-slash → 301 → 401), fixed in endpoints.py (MR !958), deployed (rev pos-mojo-bridge-00010), and verified: 71/71 backfilled on first run, skiplist empty. Also hardened mirror_recordings to stay loud — a total failure always raises session_expired, never silently skiplisted (MR !958). This was the real cause of the daily mojo-sync-log-alert. Docs corrected (MR !960). - Setter Call Reviews built (manual one-off, 2026-06-30). Per-setter Drive folders of every 1-min+ recorded call, each call its own sub-folder containing a transcript Doc + the recording mp3. Lives in ry@rycolston.com Drive under "Listing Legends — Setter Call Reviews" (parent folder 1OcOKAL7a4nkbHdGkWWUB-ePH7WlQTlxP) → sub-folders Viola Hajdini — Call Reviews (13 calls), Rafferty Ojeba — Call Reviews (3), Ry Colston — Call Reviews (2). ryan.colston@orchard.com has editor access on the parent (cascades to all sub-folders). Setters NOT yet shared (await Ry's OK; would be violahajdiniii@gmail.com / raffertyojeba@gmail.com, reader). Rafferty's 3 are hand-cleaned/named; the rest were auto-built via the transcription pipeline below. NOT a recurring system yet — manual build. - Transcription pipeline (proven this session, NOT productionized). Recording mp3 (8 kHz mono, gs://pospj-480915-mojo-bridge/recordings/{record_id}.mp3) → Google Speech-to-Text v1 longrunningRecognize: encoding=MP3, sampleRateHertz=8000, model=latest_long, enableAutomaticPunctuation=true, diarizationConfig{2 speakers}. Gotchas: (1) the phone_call enhanced model returns EMPTY — use latest_long; (2) user-cred REST calls need header x-goog-user-project: pospj-480915 or 403 quota-project; (3) gcloud ml speech doesn't expose punctuation/diarization flags → hit the REST API. speech.googleapis.com enabled on pospj-480915 (2026-06-30, MANUAL — not Terraform-managed, no IaC home; no drift). Auto-punctuation is reliable; diarization is hit-or-miss on 8 kHz (clean speaker split on ~9/15 longer calls, collapsed to 1 speaker on shorter → fall back to a punctuated paragraph, no fake labels). Speaker→name mapping is a pitch-phrase heuristic. Within STT free tier (60 min/mo) at this volume. For consistent speaker labels + full productionization (mirror_recordings → STT → LLM-clean → Drive doc, daily), the upgrade is an LLM cleanup step ("Lever 2"). Scripts were ad-hoc (scratchpad), not committed.

2026-06-18: - Native "Expired Data" subscription RETIRED (decision 2026-06-18). The feed has been silent since 2026-04-26 (posting_history vendor "Mojo Expired"); rather than fix it, Ry decided the manual NTREIS→Mojo import (below) replaces it — more control over which searches/cohorts load. Let the $50/mo subscription lapse/cancel; do NOT flag the silent feed as a problem going forward. - Interim — revived the manual NTREIS→Mojo import. Pulled the Alberto (1,306) + Brandon 0-30 (80) + Brandon 1yr+ (588) Cancelled/Expired/Withdrawn saved searches into Mojo-ready CSVs (with postal code) for hand-import to calling lists. Now repeatable: data-platforms/ntreis/skills/update-ntreis/export_search_to_mojo.py + runbook data-platforms/ntreis/docs/ntreis-to-mojo-manual-import.md (MR !649). A Ryan | Cancelled/Expireds saved search also exists, not yet pulled. - mojo-bridge ingest bugfix shippedsession_summary "Total" footer row was failing the BQ load and time_sheet was crashing the MERGE on a minute-granularity key collision; fixed + deployed (MR !647/!648, rev pos-mojo-bridge-00006).

2026-06-10 audit + additions: - Audit finding — no dialing ever captured. call_detail 0 rows across 17 green runs; roster has only Ry. Anthony's dials are NOT on account 689102. - Audit finding — Mojo Expired feed silent since 2026-04-26. Verify subscription. - Audit finding — account holds 1,655 contacts across 4 main lists (Mojo Training 50, Test 0, Sample|Alberto 1,356, Vacant Land 249); the 12,425 figure from 5/25 is stale. - Contacts ingestion live: POST /mojo-cold-load?limit=N resumable backfill + endpoint=contacts slice in daily_ingest (new ids ∪ ids called in window, cap 500/run). - Recording mirror live: daily 07:00 CT scheduler mojo-mirror-recordings; every unmirrored row → gs://pospj-480915-mojo-bridge/recordings/, URI written back. - BQ views mojo_raw.v_setter_calls + v_setter_productivity created (were repo-only).

What's live as of 2026-05-26: - Scaffold at ~/rylobasic/systems/setter-program/mojo-bridge/ (MR !198 merged) - Cloud Run service pos-mojo-bridge at https://pos-mojo-bridge-uvqirrk5la-uc.a.run.app — image :ab28bc12, revision 00001-sv6 (MR !200) - Cloud Scheduler pos-mojo-bridge-mojo-daily-ingest daily 06:00 CT → POST /mojo-daily-ingest - Tfvars + Terraform at infra/terraform/pos-mojo-bridge/ (MR !201) - Drift coveragecheck-tfvars-drift.sh now governs 8/8 services including mojo-bridge (MR !202) - Session-refresh runner — re-mints MOJO_SESSION_COOKIE when sync_log shows session_expired, driving Playwright through Mojo login. Originally the Mac-mini launchd agent com.rycolston.mojo-session-refresh (MR !203, creds from 1P item mojosells.com in POSPJ vault). Migrated to the mojo-session-refresh Cloud Run Job 2026-06-21 (image :e9a6ec79, fired by Cloud Scheduler mojo-session-refresh-sched every 15 min, self-gates on sync_log via _refresh_needed(), creds from Secret Manager MOJO_USERNAME/MOJO_PASSWORD). The mini launchd agent (com.rycolston.mojo-session-refresh) was deleted 2026-06-24 (Phase 3 cleanup; had been disabled cold-standby). See infra/cloud-run-jobs/mojo-session-refresh/README.md. - Cloud Monitoring alert policy mojo-bridge: non-ok rows in sync_log — scheduled query daily 06:45 CT writes to mojo_raw.sync_log_alerts, RAISEs on non-empty, alert fires email to ry@rycolston.com (MR !204, synthetic-row tested end-to-end) - B4 auto-apply detects hyphenated service names (MR !205 — bug surfaced by mojo-bridge being the first multi-word pos-* service) - GCS bucket gs://pospj-480915-mojo-bridge/ with 2-year lifecycle on raw/ - 12 mojo_raw.* BQ tables + mojo_raw.sync_log_alerts (alert dest) - Secret Manager MOJO_SESSION_COOKIE — auto-refreshed by the mojo-session-refresh Cloud Run Job when stale (was the Mac-mini runner pre-2026-06-21) - Deployed daily-ingest smoke-test 2026-05-26 08:34 CT: 5/5 endpoints OK, 1 time_sheet + 76 posting_history rows landed via the actual Cloud Run service (run_id 34484651-d268-4506-88a9-c783c8cfa0a4)

What's NOT live (parked, not blocking): - Schema drift detection (status=schema_drift rows in sync_log). - Volume-based alerting (green runs with 0 rows are invisible to the current alert). - Consumers (agent-newsletter, consults-dashboard, team-coaching, handbook) — deferred until dialing data exists.

Next 3 actions

  1. Get dialing onto account 689102 DONE — resolved by live evidence 2026-07-16: call_detail 2,679 rows across 4 setters (see Current state). Replaced by: wire the first consumer (agent-newsletter dials / flywheel scoreboard setter inputs, RYR-175/RYR-172).
  2. Verify the Mojo Expired feed subscription — note the 2026-06-18 decision above already RETIRED it (let it lapse); confirm the cancellation actually happened, then drop this line.
  3. Add a volume expectation to the sync_log alert (setter worked + 0 call_detail rows = alert) — now actionable since real dialing volume exists to baseline against.

Decisions log

Open issues

References