Technical Architecture

7 min read•docs/TECHNICAL-ARCHITECTURE.md•

Technical Architecture — CX Orbit MVP

1. Product boundary

CX Orbit is an internal analytics + light action system for Simiaroom teams (product, ops, support, matching, leadership).

It must answer:

  1. Where do clients enter and drop?
  2. After which session does continuation improve / churn peak?
  3. Which clients are at risk, and why?
  4. How good is client–therapist fit?
  5. What insights and follow-ups are open, and what was their outcome?

It must not become: full ops CRM, messaging platform, form builder, clinical decision engine, or multi-tenant SaaS in v1.

2. Logical architecture

flowchart LR
  subgraph sources [Data Sources]
    Browser[Browser SDK]
    Backend[Backend SDK]
    Prod[(Simiaroom Product DB / CDC)]
    GA[GA / Web Analytics]
    Porsline[Porsline Forms]
    OpsCSV[Periodic Ops Imports]
  end

  subgraph orbit [CX Orbit]
    Collector[Collector API]
    Validate[Auth + Schema + Privacy Validation]
    Quarantine[(Invalid Event Quarantine)]
    Raw[(Immutable Raw Events)]
    Ingest[Canonical Mapping]
    Identity[Identity Resolution]
    Store[(PostgreSQL Operational Store)]
    Analytics[(Analytical Store)]
    Derive[Derived Profile + Metrics]
    Risk[Rule-based Risk Engine]
    Insights[Insight Generator]
    API[Query API]
    UI[Internal Dashboards]
    Actions[Light Action Center]
  end

  Browser --> Collector
  Backend --> Collector
  Collector --> Validate
  Validate -->|valid| Raw
  Validate -->|invalid| Quarantine
  Prod --> Raw
  GA --> Raw
  Porsline --> Raw
  OpsCSV --> Raw
  Raw --> Ingest --> Identity
  Identity --> Store
  Identity --> Analytics
  Store --> Derive --> Risk
  Analytics --> Derive
  Derive --> Insights
  Risk --> API
  Insights --> API
  Store --> API
  API --> UI
  API --> Actions
  Actions --> Store

3. Layers

Layer Responsibility MVP approach
SDKs Convenient event production Thin browser/backend clients around a storage-neutral protocol; no risk/state logic
Collector Receive and protect the event boundary Authenticate, rate-limit, validate schema/privacy, timestamp, batch, and route
Raw event stream Replayable product/GA/system/internal events Immutable accepted events + restricted invalid-event quarantine
Identity Link anonymous → known user Deterministic rules + attribution window from simiaroom-cx-v5 (ID-01). Browser storage/consent: BROWSER-CONSENT-POLICY.md (T1.14). Merge job: T2.3.
Operational store Clients, therapists, sessions, payments, feedback, cases PostgreSQL normalized model for current state, actions, permissions, and configs
Analytical store Event history, funnels, cohorts, retention Partitioned PostgreSQL initially or ClickHouse when volume/latency justifies it
Derived profile Stage, cadence, pause/churn flags, risk signals Recompute on meaningful events (SYS-01)
Metrics Funnel, retention, fit, feedback, action KPIs SQL/materialized views or scheduled jobs
Risk Rule-based Healthy / Needs attention / High risk Versioned rule set, explainable reasons
Insights Pattern → actionable insight Semi-manual; templates + frequency thresholds
Actions Light follow-up tracking Create/assign/status/result; no full ticketing
UI Role-oriented pages 4 P0 decision-loop screens; remaining role views are P1. As-built lock: P0-WIREFRAMES.md (T1.8). Staff how-to: USAGE-GUIDE.md (T4.6). Role × screen × data-class: PRIVACY-ACCESS-MATRIX.md (T1.11; enforce T5.6)

4. Integration contracts

4.1 Product / status events

  • Source of truth for booking, payment, session completion, cancel, no-show, therapist change
  • Must attach: user_id / anonymous_id, therapist_id, session_id / booking_id, payment_id, timestamps, journey stage refs
  • Must originate from the authoritative backend or a reconciled source table, not browser confirmation UI
  • Prefer an outbox pattern for critical backend outcomes where the Simiaroom stack permits it

4.2 Behavioral (GA / product analytics)

  • Entry, assessment progress, therapist browse, booking abandon, return
  • Used for funnel friction; not sole source for status outcomes
  • Explicit tracking is preferred over broad autocapture because the product handles sensitive mental-health journeys

4.3 Porsline (external forms)

  • Forms live outside Orbit
  • Orbit defines trigger points and import schema
  • Responses join to client + session + therapist + journey stage

Catalog, join keys, and cadence: PORSLINE-FORMS.md (T1.5). Primary provider is Porsline; class is import until webhook (T1.9). T2.7 importer reuses T2.8 CSV audit. T2.6 live publish is still open.

Suggested trigger points (locked in the catalog; abandoned-payment send deferred to T2.13):

  • After session
  • After ~session 3 (sensitive continuation point)
  • After therapist change
  • After cancel
  • After long gap
  • After stop / non-continuation
  • After failed/abandoned payment (if operable)

4.4 Periodic imports

CSV/API batch for ops/support extras that are not yet evented:

  • Call logs, tickets, complaint status, manual follow-ups

Field-level class (evented / import / manual), latency per class, and feed owners: LIVE-VS-PERIODIC-FIELDS.md (T1.9). P0 booking / payment / session facts stay evented. Shipped P0 import feed: ops_support_ticket (CSV-IMPORT.md).

4.5 SDK and collector contract

  • The event protocol and collector API are the stable integration contract
  • Browser and backend SDKs are separate packages with separate credentials and guarantees
  • SDKs never connect directly to PostgreSQL, ClickHouse, or another warehouse
  • Delivery is at-least-once: stable event_id, bounded batching/retries, and downstream deduplication
  • Invalid events are rejected or quarantined; schema-invalid payloads are not retried automatically
  • HTTP success / partial / reject shapes, retry codes, quarantine fields, and resubmit dedup: SDK-AND-DATA-INGESTION.md §11 (T1.13)
  • Full protocol: SDK-AND-DATA-INGESTION.md

5. Non-functional constraints (MVP)

Concern MVP target
Delivery Design → build → pilot in 4 months
Explainability Every risk score shows reasons
Privacy Internal users only; no clinical AI claims
Freshness Near-daily acceptable for imports; product events preferably same-day
Complexity Prefer rules + SQL over ML
Extensibility Keep generic names in code (customer, provider, interaction) even if UI says client/therapist/session
Transaction isolation Tracking failures never fail booking/payment/session workflows
Event delivery At-least-once with idempotent event IDs; no exactly-once promise
Data minimization No therapy notes, recordings, or raw sensitive answers in behavioral events
Observability Accepted/rejected/dropped counts, queue depth, lag, freshness, and reconciliation are visible. Pilot numeric bar: DATA-QUALITY-BAR.md (T1.10), measured on T2.11 /dq.

6. Suggested service split (implementation-agnostic)

  1. Collector API — auth, rate limits, schema/privacy validation, accepted/rejected responses
  2. Ingest workers — durable buffering, raw storage, external payload → canonical mapping
  3. Identity service/job — resolve links, emit AUTH-04 / profile recompute
  4. Domain API — clients, sessions, risk, insights, actions, reports
  5. UI app — internal authenticated dashboard
  6. Batch jobs — reconciliation, metrics refresh, import parsers, insight candidates

Exact stack (Next.js, Django, etc.) is an open implementation choice; this doc stays stack-neutral.

7. Open decisions to lock in Month 1

  • Primary warehouse vs app DB for analytics
  • Measured daily event volume, retention period, dashboard concurrency, and latency target
  • PostgreSQL-only MVP vs managed ClickHouse analytical store
  • Auth for internal Simiaroom users (SSO / shared IdP)
  • Simiaroom implementation languages and first supported backend SDK
  • Live vs periodic field class (evented / import / manual) — LIVE-VS-PERIODIC-FIELDS.md (T1.9)
  • Data quality bar for pilot (completeness / lag / dup / quarantine / go-live) — DATA-QUALITY-BAR.md (T1.10)
  • Event transport mix (SDK, webhook, CDC, batch dump) and producer of record per event
  • Durable outbox/queue strategy for P0 authoritative events — ops/OUTBOX-RELAY.md (T2.14). Simiaroom same-TX Postgres insert is the product-repo follow-on.
  • Porsline join key strategy — PORSLINE-FORMS.md (T1.5). T2.7 importer on T2.8 audit path. T2.6 live send still open.
  • Gap thresholds for “dangerous pause” (days) — locked in RISK-RULES-V1.md: expire on expected_return_date, else 14 UTC days after pause
  • Risk rule weights / severity mapping — locked in risk_v1 as severity (attention / high) plus two-attention escalation; numeric weights deferred to risk_v1.1 (T4.3)
  • Internal role × screen × data-class access matrix (support 360 minimum necessary, export rules, payment-secrets policy) — PRIVACY-ACCESS-MATRIX.md (T1.11). Enforcement remains T5.6.
  • Browser consent, anonymous ID storage, and browser/collector minimization — BROWSER-CONSENT-POLICY.md (T1.14). Implementation remains T2.13 (P1).
  • Security review of authz / PII / secrets / consent / audit — SECURITY-REVIEW-T4.10.md (T4.10). Named human sign-off residual. Auth.js remains T5.5.
  • Reconciliation / event-loss review vs T1.10 completeness/dup — RECONCILIATION-REVIEW-T4.12.md (T4.12). Fixture/staging window; live T4.1 still open. T2.14 outbox: ops/OUTBOX-RELAY.md.
  • Production data residency (parked ADR 0001; confirm before T4.1).