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_typevalues 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
- MVP-EVENT-SCOPE.md — P0 list and envelope/idempotency locks
- SOURCE-INVENTORY.md — access status per source system
- SOURCE-OWNERSHIP.md — producer-of-record matrix (T0.6)
- fixtures/events/contracts.yaml — T1.12 schema pack (12 slice events)
- simiaroom-cx-v5.md — event dictionary source of truth
- SDK-AND-DATA-INGESTION.md — collector ingress and reconciliation
- BROWSER-CONSENT-POLICY.md — T1.14 browser consent / ID storage; behavioral types remain T2.13
- PORSLINE-FORMS.md — T1.5 form catalog, triggers, join keys, cadence