| Layer | Current Build — Test Environment | Migration Target — Foundations Portal |
|---|---|---|
| Interface & Auth | ||
| Frontend |
Next.js 15 App Router · TypeScript · Tailwind CSS · Radix UI
D3.js for the architecture map. Lucide React icons. Playfair Display + DM Sans type.
⚠ All household data in localStorage — lost on a different machine.
|
Integrate into Foundations portal (React) NEW
Option A: Next.js micro-frontend as a sub-path (/crm). Option B: export components as npm package consumed by portal. Decision deferred.
|
| Auth |
Clerk (JWT with advisor_id claim)
JWT template "magfin". MAGFIN_ADMIN_ADVISOR_IDS env var for firm-admin workaround (JWT role claim pending). fiwealth.com + gmail both provisioned as firm-admin.
|
Portal SSO + Clerk bridge SWAP
Portal has existing SSO. ClerkIdentitySync adapts to portal identity provider. Multi-tenant: each advisor sees only their clients.
|
| State |
localStorage + React Context (HouseholdsProvider)
HouseholdsProvider merges seed data + localStorage patches. All edits are append-only diffs. Financials, notes, open loops all local.
⚠ Not multi-user — Matt and Will can't share live state.
|
PostgreSQL (multi-advisor, multi-tenant) NEW
All household, financial, and task state moves server-side. React Context becomes a read-through cache. HouseholdEdit audit log migrates to a SQL table.
|
| AI & Intelligence | ||
| LLM |
LiteLLM → OpenAI gpt-4o-mini LIVE
OPENAI_API_KEY set · LLM_MODEL=openai/gpt-4o-mini · MAGFIN_DEV_AUTH_FALLBACK=1. Single complete() entrypoint — all skills (client-brief, draft-email, query-client, generate-summary) go through this. Provider swap is one env-var change.
◆ MAGFIN_LLM_API_KEY (Anthropic) not set — not needed while OpenAI key is present. Switch to Claude: set LLM_MODEL=claude-sonnet-4-6 + MAGFIN_LLM_API_KEY=sk-ant-...
|
LiteLLM → Anthropic claude-sonnet-4-6 SWAP
No code changes. Set MAGFIN_LLM_API_KEY + LLM_MODEL in production secrets. Claude is the preferred production model (better citation compliance, lower hallucination rate on financial content).
|
| STT |
OpenAI Whisper API (via whisper_helper.py, port 9000)
Browser MediaRecorder → Next.js proxy → whisper_helper.py → OpenAI. Web Speech API fallback if helper is down. No local model — switched to API mode 2026-05-12 (local model + ffmpeg broke on Windows).
|
OpenAI Whisper API (same) SAME
Phase 2 (mobile): on-device Whisper for offline transcription (~25% battery/hr). Eliminates Twilio transcription fees for mobile advisors.
|
| TTS |
ElevenLabs (call disclosure audio) LIVE
ensure_unavailable_audio() fires at startup — generates the two-party consent disclosure clip played before Twilio recordings. ELEVENLABS_API_KEY set 2026-05-18 — clip will auto-generate on next backend startup.
|
ElevenLabs (same) SAME
Disclosure audio is compliance-critical — do not swap vendor without legal review. Key goes into production secrets manager.
|
| Vector DB |
ChromaDB local (SQLite-backed, port 8000)
Per-advisor-per-client partition isolation. ChromaIndexer in brain/indexer.py. QueryChunk → Citation roundtrip. Falls back gracefully on ChromaUnavailable (503 at route layer).
|
Managed Chroma / Pinecone / pgvector SWAP
ChromaIndexer is abstracted — swap the driver, keep the interface. pgvector on PostgreSQL is the lowest-friction option (one less service). Pinecone if query latency becomes a constraint.
|
| Integrations | ||
| Orion |
Orion Connect — live account balances LIVE
GET /api/integrations/orion/accounts — /v1/Portfolio/Accounts endpoint. Basic auth with per-advisor credentials. Surfaced in Settings > Integrations status tile.
|
Orion Wealth API (same) SAME
Credentials move to production secrets. Per-advisor mapping in advisor_aliases.py.
|
| Zoom |
Zoom OAuth + Webhook + AI Companion LIVE
Zoom app connected. HMAC-sig webhook → pending queue → pre-analyze → o3 deep analysis → email/fuzzy-name match → brain ingest. Backfill available.
|
Zoom AI Companion (same) SAME
No changes needed. Token DB at .local/zoom_tokens.sqlite moves to server secrets.
|
| Phone |
Twilio Voice SDK + TwiML + ElevenLabs LIVE
Inbound: two-party consent disclosure (TTS) + record. Outbound: REST dial + browser Voice SDK. Post-call: 3-pass AI analysis, sidecar JSON, advisor proposal review.
|
Twilio (same) → Phase 2: mobile Twilio Voice SDK SWAP
Phase 2: mobile app on Twilio Voice SDK (CallKit on iOS). Phase 3: LiveKit/Plivo only if economics force it.
|
| Storage & Data | ||
| Brain Files |
Markdown brain files (filesystem, AES-256-GCM encrypted)
BrainStore.write() is the only write path — auto-appends audit_log.md. Ed25519 Merkle manifests via Windows Task Scheduler. Per-advisor directory: brain/advisor_{id}/clients/{slug}/
⚠ Gitignored — not in git. Must be manually rsync'd to DO server after every new client add. Sequential startup sync (100ms gap) prevents 504 bursts.
|
Azure Blob Storage / S3 NEW
BrainStore write path stays the same — swap the filesystem driver for an object storage adapter. Encryption moves to server-side KMS. audit_log.md becomes a write-ahead log streamed to blob.
|
| Prod Server |
Digital Ocean droplet (147.182.245.224) · nginx · systemd LIVE
crm.projectopsai.com. nginx: all /api/* → FastAPI (8001), 12 Next.js exceptions → port 3000. systemd: crm-frontend + crm-backend (Restart=always). Deploy user: deploy@. Brain/recordings gitignored — manual rsync required.
⚠ Brain files + recordings not in git. Manual rsync to server on each new client add.
|
Managed hosting (same or scale-up) SAME
DO droplet sufficient for current scale. Brain files move to object storage (S3/Spaces) when advisor count exceeds single-server threshold.
|
| Documents |
SQLite WAL mode + FTS5 (brain/documents.db)
50 MiB cap. FTS5 full-text search with porter tokenizer. pypdf background extract → LiteLLM AI summary. File bytes stored in brain/advisor_{id}/documents/
|
PostgreSQL + object storage (Azure Blob / S3) NEW
SQLite becomes PostgreSQL. File bytes move to object storage. FTS5 → pg_trgm or pgvector semantic search. Same upload/list/download/search API surface.
|
| Recordings |
Sidecar JSON + .vtt files (filesystem)
recordings/advisor_{id}/{YYYY-MM}/*.json + *.vtt. Transcript-only policy: audio deleted from Twilio after transcription. No audio bytes ever served.
|
PostgreSQL metadata + blob .vtt storage NEW
Same transcript-only policy. .vtt files to object storage. JSON sidecars become DB rows. Recordings search endpoint stays identical.
|
| Communications & Integrations | ||
| Phone |
Twilio Voice SDK (browser) + Twilio REST LIVE
All three Twilio creds set (SID · Auth Token · From Number). TwiML inbound: two-party consent disclosure (14 states). Outbound dial + quick-brief prefetch. Phone lookup: Tier 0 override table → brain profile.md scan. IncomingCallBanner + twilioDevice singleton in browser.
◆ ELEVENLABS_API_KEY set 2026-05-18 — disclosure audio clip will auto-generate on next backend startup. Compliance gap resolved.
|
Twilio (same) + Phase 2: iOS/Android SDK SAME
Phase 2: Twilio Voice SDK on mobile with CallKit (iOS). On-device Whisper transcription. Eliminates browser dependency for calls. endConferenceOnExit=false stays — advisor leg bridged through conference. Eliminates Twilio recording/transcription fees.
|
| Meetings |
Zoom OAuth (per-advisor SQLite token store) + Webhook LIVE
HMAC-SHA256 webhook sig verify + 5-min replay window. transcript_completed → auto-pull .vtt. Auto-matcher scores transcripts against profile.md (0.88 auto-file / 0.45 suggest). 30s background loop.
◆ LIVE 2026-05-18: Zoom app connected, real meetings flowing into ZoomInbox. Suggestion panel now includes all households (prospects + clients), not just backend-scaffolded clients.
|
Zoom (same) SAME
Token store moves from SQLite → PostgreSQL. Auto-matcher and webhook pipeline stay identical. Zoom AI companion (Zocks replacement) routes through same .vtt pipeline.
|
|
Draft-only — AI email drafts, advisor sends manually
Hard Rule #1: CRM never sends. postDraftEmail() → LLM drafts subject + body → advisor copies and sends. Email tone setting (professional-warm / formal / conversational) persisted in localStorage.
|
Outlook Mail.Read (read-only context) → draft NEW
Phase 1: read-only inbox — pull recent client emails for context. Phase 2 (deferred): bidirectional, segmented campaigns (consult Matt on workflow). Azure AD app reg needed either way.
|
|
| Backend & Deployment | ||
| Backend |
FastAPI + uvicorn · Python 3.12
Port 8001. uvicorn magfin.main:app --host 127.0.0.1 --port 8001 --reload. Pydantic v2 models. LiteLLM in magfin/llm/client.py. CORS allowlist + 60/min rate limit (in-process).
⚠ Rate limiter in-process — needs PostgreSQL or Redis for multi-worker deployments.
|
FastAPI modules → portal backend integration SWAP
Extract magfin as an installable pip package. Portal mounts /api/skills, /api/clients, /api/calls routes. Rate limiting moves to API gateway / load balancer. Multi-worker safe with PostgreSQL session store.
|
| Deploy |
Digital Ocean droplet · nginx · systemd LIVE
crm.projectopsai.com (147.182.245.224). nginx: /api/* → FastAPI 8001, 12 Next.js exceptions → 3000. systemd crm-frontend + crm-backend (Restart=always). Deploy: git pull → npm run build → kill next-start process.
◆ Vercel retired 2026-06-01 — all traffic now on DO. ngrok still used for local dev Zoom OAuth callback only.
|
Cloud (Azure App Service / AWS ECS) NEW
No ngrok. TLS at load balancer. Backend auto-scales. Frontend served from CDN (Vercel or Cloudflare). CI/CD pipeline replaces manual alias command. Secrets in Azure Key Vault / AWS Secrets Manager.
|
| Monitoring |
None (console logs + uvicorn stdout)
PII log filter strips phone/email from uvicorn logs. Feedback widget POSTs brain/_feedback/{date}.md — Will reads during sessions. No error tracking, no uptime monitoring.
|
Sentry / Datadog + structured logging NEW
PII filter stays. Add structured JSON logging (correlation IDs per request). Error tracking with PII scrubbing. Compliance audit trail readable from dashboard, not raw files.
|