Source Event Mapping

14 min read•docs/SOURCE-EVENT-MAPPING.md•

P0 Source → Canonical Event Mapping (T1.3)

Status: signed for test (implement agent attestation 2026-08-12)
Purpose: End-to-end mapping from Simiaroom / Orbit / form sources to canonical P0 events. Unblocks T2.2b backend instrumentation.
Scope: Every event in MVP-EVENT-SCOPE.md § Priority P0 target set plus treatment_completed (CHR-S04) from the initial 8–12 slice.

Related: SOURCE-INVENTORY.md, SOURCE-OWNERSHIP.md, LIVE-VS-PERIODIC-FIELDS.md (T1.9), PORSLINE-FORMS.md (T1.5 join keys), fixtures/events/contracts.yaml (T1.12), PRIVACY-ACCESS-MATRIX.md (T1.11), SDK-AND-DATA-INGESTION.md §7.


Legend

Column Meaning
Source Triggering source system, table/outcome row, or job; envelope source producer ID; authoritative vs behavioral
Transform Source field → canonical properties + entity + identity mapping
Idempotency source_event_id pattern within producer (project_id + source + source_event_id is the canonical key)
Privacy class Event-level class; property detail in contracts.yaml where schema exists
Phase slice = initial 12 (T2.2b first); incremental = P0 backend after slice; derived = Orbit job; behavioral = browser/product (non-authoritative)

Idempotency rules (locked T0.4):

  • Prefer source outcome row ID (auth_events.id, booking_outcomes.id, …).
  • Never reuse a bare entity PK across different canonical_type values on the same entity.
  • Fallback when one fact per entity: {canonical_type}:{entity.id}.
  • CDC / import / SDK paths for the same fact must share source + source_event_id.

Assumed source tables (logical names — map to actual DDL when Simiaroom repo access granted; see SOURCE-INVENTORY.md § Stack verification):

Logical table / trigger Service Inventory #
auth_sessions / login outcome simiaroom_auth 1
identity_links simiaroom_auth 1
assessments / progress simiaroom_assessment 2
therapist_selections / change requests simiaroom_matching 3
bookings simiaroom_booking 4
payments / gateway callbacks simiaroom_payments 5
sessions / attendance simiaroom_sessions 6
client_status / pause / completion / stop simiaroom_clinical 7
churn_reasons / offboarding churn workflow (#8) 8
Form webhook payloads porsline / myoutcomes 11–12
Orbit Actions / derived jobs cx_orbit_system 14

Master mapping table — P0 canonical types (39 rows)

Authentication & identity

Dict ID canonical_type Phase Source Transform Idempotency Privacy class
AUTH-01 authentication_succeeded slice simiaroom_auth — post-commit on successful login/OTP (auth_sessions outcome row). Transport: sdk + outbox. user_id ← session.user_id; entity = client / user_id; properties.auth_method ← session.method; optional device_id, anonymous_id from session; occurred_at ← session.authenticated_at (UTC). auth_success_{auth_session_id} operational (+ identifier props)
AUTH-04 identity_link_created incremental simiaroom_auth — post-commit when anonymous_id bound to user_id (identity_links row). entity = identity_link / link_id; identity.user_id + identity.anonymous_id; properties.link_method, link_confidence, attribution_window_days from link row. idlink_{identity_link_id} operational (+ identifier)

Assessment

Dict ID canonical_type Phase Source Transform Idempotency Privacy class
ASM-02 assessment_started behavioral Browser / web app (cx_orbit_browser, P1) — client starts assessment UI. Non-authoritative; diagnostic only. entity = assessment / assessment_id (client-generated or server-issued draft id); map dictionary props from UI state. identity.anonymous_id allowed until link. assessment_started:{assessment_id} operational
ASM-04 assessment_abandoned behavioral Browser / web app (cx_orbit_browser, P1) — exit without complete. Non-authoritative. Same shape as ASM-02; include step_index, progress_percent at abandon. assessment_abandoned:{assessment_id}:{step_index} operational
ASM-09 assessment_completed slice simiaroom_assessment — post-commit on assessment outcome (assessments.status = completed). Transport: sdk + outbox. Per contracts.yaml: assessment_method, step_index, progress_percent, assessment_context; entity = assessment / assessment_id. asm_complete_{assessment_id} operational

Matching / therapist selection

| Dict ID | canonical_type | Phase | Source | Transform | Idempotency | Privacy class | | --- | --- | --- | --- | --- | --- | | THR-01 | therapist_options_viewed | behavioral | Browser / web app (cx_orbit_browser, P1) — options list rendered. Non-authoritative. | properties.referral_id, option_rank, match_method, selection_context, option_context; entity optional therapist if single focus. | thr_options_view:{referral_id}:{session_id} | operational | | THR-02 | therapist_profile_viewed | behavioral | Browser / web app (cx_orbit_browser, P1). Non-authoritative. | entity = therapist / therapist_id; referral + rank props. | thr_profile_view:{therapist_id}:{referral_id} | operational | | THR-05 | therapist_selected | slice | simiaroom_matching — post-commit on selection outcome (therapist_selections row). | Per contracts: referral_id, option_rank, match_method, selection_context; entity = therapist / therapist_id. | thr_select_{selection_id} | operational | | CHG-01 | therapist_change_requested | incremental | simiaroom_matching — post-commit on change request (therapist_change_requests insert). | entity = client or change_case; properties.referral_id, therapist refs, selection_context=replacement. | chg_req_{change_request_id} | operational | | CHG-02 | therapist_change_reason_submitted | incremental | simiaroom_matching — reason captured on same or follow-up row. | Link to change request; reason enum/text in properties.change_reason. | chg_reason_{change_request_id} | operational |

Booking

| Dict ID | canonical_type | Phase | Source | Transform | Idempotency | Privacy class | | --- | --- | --- | --- | --- | --- | | BKG-01 | booking_started | behavioral | Browser / web app (cx_orbit_browser, P1). Non-authoritative. | entity = booking / draft booking_id; scheduling props. | booking_started:{booking_id} | operational | | BKG-05 | booking_submitted | behavioral | Browser / web app (cx_orbit_browser, P1). Non-authoritative. | Client submit intent before payment; not proof of confirm. | booking_submitted:{booking_id} | operational | | BKG-06 | booking_abandoned | behavioral | Browser / web app (cx_orbit_browser, P1). Non-authoritative. | Abandon step index in properties. | booking_abandoned:{booking_id}:{step} | operational | | BKG-S03 | booking_confirmed | slice | simiaroom_booking — post-commit when booking status = confirmed (bookings row). RET-06 return path: booking_context=return. | Per contracts: therapist_id, slot_id, session_number, booking_context, scheduled_start_utc, optional payment_id. | bkg_evt_confirm_{booking_outcome_id} | operational + scheduling | | BKG-S04 | booking_cancelled | slice | simiaroom_booking — post-commit on cancel outcome. | Per contracts: cancel_actor, cancel_reason, optional scheduled_start_utc. | bkg_evt_cancel_{booking_outcome_id} | operational + internal |

Payment

| Dict ID | canonical_type | Phase | Source | Transform | Idempotency | Privacy class | | --- | --- | --- | --- | --- | --- | | PAY-04 | payment_started | behavioral | Browser / checkout UI (cx_orbit_browser, P1). Non-authoritative. | entity = payment / payment_id; amount, gateway from checkout state. | payment_started:{payment_id} | financial | | PAY-06 | payment_succeeded | slice | simiaroom_payments — post-commit on verified success (payments row + gateway callback handler). | Per contracts: order_id, amount, currency, gateway, optional booking_id/package_id. | pay_ver_{payment_verification_id} | financial | | PAY-07 | payment_failed | slice | simiaroom_payments — post-commit on terminal failure. | Per contracts + failure_code from gateway/handler. | pay_fail_{payment_verification_id} | financial |

Session

| Dict ID | canonical_type | Phase | Source | Transform | Idempotency | Privacy class | | --- | --- | --- | --- | --- | --- | | SES-04 | session_joined | behavioral | Client app / session UI (cx_orbit_browser or session client, P1). Non-authoritative attendance signal. | entity = session / session_id; booking + therapist refs. | session_joined:{session_id} | operational | | SES-06 | session_completed | slice | simiaroom_sessions — post-commit on completion (sessions.status = completed or staff workflow). | Per contracts: booking_id, therapist_id, session_number, attendance_status=completed, actual_duration. | ses_complete_{session_outcome_id} | operational | | SES-08 | session_no_show_recorded | slice | simiaroom_sessions — post-commit on no-show outcome. | Per contracts: attendance_status = client/therapist no-show. | ses_noshow_{session_outcome_id} | operational | | SES-S01 | therapist_attendance_issue_recorded | incremental | simiaroom_sessions — staff/system records therapist late/absence (session_attendance_issues row). | properties.issue_type, late_minutes; entity = session / session_id. | ses_att_issue_{issue_id} | operational |

Continuation, pause, return

| Dict ID | canonical_type | Phase | Source | Transform | Idempotency | Privacy class | | --- | --- | --- | --- | --- | --- | | CON-04 | therapy_pause_declared | slice | simiaroom_clinical — post-commit on pause declaration (client_status / pause record). | Per contracts: session_count, established_cadence, current_pause_flag, optional dates/balance. | pause_decl_{pause_record_id} | operational + scheduling | | CON-S01 | unplanned_gap_detected | derived | cx_orbit_system — Orbit session continuum job (T2.4). SQL over session spine; not Simiaroom write path. | properties.days_since_last_session, established_cadence, flags from derived profile; entity = client / user_id. | unplanned_gap:{user_id}:{gap_period_start} | operational | | RET-01 | return_session_started | behavioral | Browser / web app (cx_orbit_browser, P1) — returning user enters app after pause/churn. Non-authoritative reactivation signal. | properties.previous_state, return_source, days_inactive, last_therapist_id. | return_start:{user_id}:{analytics_session_id} | operational | | CON-07 | therapy_resumed | incremental | simiaroom_booking — post-commit when first booking after pause is confirmed (paired with BKG-S03 booking_context=return and current_pause_flag cleared in clinical store). Emitter pinned: booking service, not session service. | Continuation props: session_count, established_cadence, current_pause_flag=false, optional expected_return_date. | therapy_resumed:{booking_id} | operational |

Churn & completion

| Dict ID | canonical_type | Phase | Source | Transform | Idempotency | Privacy class | | --- | --- | --- | --- | --- | --- | | CHR-02 | therapy_stop_declared | incremental | simiaroom_clinical — user/staff stop declaration (client_status / stop record). | Continuation props + stop context. | stop_decl_{stop_record_id} | operational | | CHR-06 | churn_reason_submitted | incremental | simiaroom_clinical or churn workflow (#8) — reason captured (form backend or staff). | properties.churn_reason / category enums; link to stop record. | churn_reason_{reason_submission_id} | operational | | CHR-S04 | treatment_completed | slice | simiaroom_clinical — post-commit on successful treatment completion. | Per contracts: completion_reason, completed_at, session_count. | treat_complete_{completion_record_id} | operational | | CHR-S01 | risk_state_entered | derived | cx_orbit_system — Orbit rule engine (T3.4 evaluateRisk / risk_v1). | properties.risk_rule_version, risk_signals, profile_snapshot_id. | risk_enter:{user_id}:{rule_version}:{snapshot_id} | internal | | CHR-S02 | churn_suspected | derived | cx_orbit_system — Orbit rule engine (risk_v1 §8). | properties.days_inactive, current_pause_flag, rule version. | churn_suspect:{user_id}:{evaluation_id} | internal | | CHR-S03 | churn_confirmed | derived | cx_orbit_system — Orbit rule engine (CHR-02 stop in risk_v1; not auto-confirmed from gap). | properties.churn_rule_version, churn_reason, confirmed_at. | churn_confirm:{user_id}:{confirmation_id} | internal |

Feedback (restricted)

| Dict ID | canonical_type | Phase | Source | Transform | Idempotency | Privacy class | | --- | --- | --- | --- | --- | --- | | FDB-02 | baseline_submitted | incremental | Porsline webhook → Simiaroom receipt handler → Collector (form transport). Primary: Porsline (#11). Until webhook: weekly porsline_form_export (T1.9). MyOutcomes (#12) alternate only. | Map form response → entity = feedback / feedback_id; properties.form_type, form_version, score, consent_status; join user_id via PORSLINE-FORMS.md keys (client_id = user_id). | fdb_baseline_{form_response_id} | restricted | | FDB-04 | session_feedback_submitted | incremental | Same form path; session-scoped ORS/SRS-style scores (session_feedback, session_feedback_deep). | Link session_id, therapist_id, scores in properties. Session join required. | fdb_session_{form_response_id} | restricted | | FDB-07 | session_dissatisfaction_reported | incremental | Form or in-app quality signal → receipt handler. Catalog: change/cancel/stop/gap/payment-fail forms + session problem flag. | Quality signal props; may trigger INT case. Join per form in T1.5. | fdb_dissat_{form_response_id} | restricted |

Internal action loop

| Dict ID | canonical_type | Phase | Source | Transform | Idempotency | Privacy class | | --- | --- | --- | --- | --- | --- | | INT-01 | internal_case_created | incremental | cx_orbit_system — Orbit Actions API post-commit (operational store). Transport: orbit. | entity = case / case_id; properties.case_type, priority, owner_id, trigger_event_id, sla_due_at. | case_create_{case_id} | internal | | INT-03 | client_contact_attempted | incremental | cx_orbit_system — Orbit Actions API on contact log commit. | Case refs + channel/result in properties. | case_contact_{case_id}:{attempt_seq} | internal | | INT-09 | internal_case_resolved | slice | cx_orbit_system — Orbit Actions API on case close. | Per contracts: case_type, priority, owner_id, resolution_code, optional trigger_event_id. | case_res_{case_id} | internal |

Derived profile

| Dict ID | canonical_type | Phase | Source | Transform | Idempotency | Privacy class | | --- | --- | --- | --- | --- | --- | | SYS-01 | user_profile_recomputed | derived | cx_orbit_system — Orbit profile job (T2.9) after meaningful authoritative events. | properties.profile_snapshot_id, changed_fields[], source_event_id (trigger). | profile_recompute:{user_id}:{snapshot_id} | internal |


Initial 12-event slice — instrumentation priority (T2.2b)

These rows are schema-ready (T1.12) and must be instrumented first in Month 2:

# canonical_type Producer (source) Schema Reconciliation query (CDC, when unblocked)
1 authentication_succeeded simiaroom_auth v1 ✓ auth_sessions where outcome = success
2 assessment_completed simiaroom_assessment v1 ✓ assessments where status = completed
3 therapist_selected simiaroom_matching v1 ✓ therapist_selections latest per user
4 booking_confirmed simiaroom_booking v1 ✓ bookings where status = confirmed
5 booking_cancelled simiaroom_booking v1 ✓ bookings where status = cancelled
6 payment_succeeded simiaroom_payments v1 ✓ payments where status = succeeded
7 payment_failed simiaroom_payments v1 ✓ payments where status = failed
8 session_completed simiaroom_sessions v1 ✓ sessions where status = completed
9 session_no_show_recorded simiaroom_sessions v1 ✓ sessions where attendance = no_show
10 therapy_pause_declared simiaroom_clinical v1 ✓ pause records active
11 treatment_completed simiaroom_clinical v1 ✓ completion records
12 internal_case_resolved cx_orbit_system v1 ✓ Orbit cases where status = resolved

Defer / mitigation register

Every P0 item has a mapped row above. Items not in the Month 2 backend instrumentation slice are explicitly deferred with mitigation:

Category Events Defer to Mitigation until live
Browser behavioral (non-authoritative) ASM-02, ASM-04, THR-01, THR-02, BKG-01, BKG-05, BKG-06, PAY-04, SES-04, RET-01 T2.13 @cx-orbit/browser Funnel friction metrics incomplete; authoritative backend outcomes (ASM-09, THR-05, BKG-S03, PAY-06/07, SES-06/08) cover conversion spine
Schema not yet in contracts.yaml AUTH-04, CHG-01/02, SES-S01, CHR-02/06, CON-07, FDB-02/04/07, INT-01/03 T2.2b expansion after slice Mapping + idempotency defined here; JSON Schema + fixtures added per event before SDK method ships
Orbit derived (no Simiaroom write) CON-S01, CHR-S01–S03, SYS-01 T2.4 CON-S01; T2.9 SYS-01 + risk stub; T3.4 CHR-S01–S03 per risk_v1 Emit after authoritative session/churn inputs exist; SQL/fixture-driven tests in Orbit repo
Form provider access FDB-02/04/07 T2.6 publish + T2.7 importer (join keys locked in PORSLINE-FORMS.md) Batch CSV import via Orbit ingest; Porsline primary (#11). MyOutcomes (#12) alternate, not a second SoT
CDC reconciliation All authoritative Simiaroom rows T2.2c (blocked #9) SDK + outbox only; completeness target deferred until read replica granted
Simiaroom repo / DDL unverified All simiaroom_* sources Before T2.2b PRs Logical table names in this doc; replace with actual deploy units + DDL at repo access (inventory gap row)
Gateway vendor TBD PAY-06/07 verification payments-team Payment backend remains producer; gateway feeds handler only (#10)

SYS-01 recompute triggers (P0)

Profile recompute (user_profile_recomputed) should run after these authoritative P0 types (initial list; refine in T2.9):

authentication_succeeded, identity_link_created, assessment_completed, therapist_selected, booking_confirmed, booking_cancelled, payment_succeeded, payment_failed, session_completed, session_no_show_recorded, therapy_pause_declared, therapy_resumed, therapy_stop_declared, treatment_completed, churn_reason_submitted, internal_case_resolved, and derived unplanned_gap_detected / churn state events.


Sign-off

Role Name Date Status
Data lead (cx-orbit-data) TBD — stakeholder sync — Pending named signatory
Eng lead (CX Orbit technical lead) TBD — stakeholder sync — Pending named signatory
Implement agent (T1.3 deliverable) cx-orbit-implement-task 2026-08-12 Mapping table complete; ready for Test Agent

Formal named signatures will be recorded after data + eng lead review. This does not block Test/Approve against mapping completeness and implementability.


References