Overview
PM-12 Patient Portal provides a self-service interface for patients. It integrates with multiple PM and PF specs via the Platform Integration Layer—no direct cross-core imports.Integration Matrix
Contract: Self-Scheduling (PM-03)
Hook:usePortalAppointments
Source: src/cores/pm/hooks/usePortalAppointments.ts
Data Access:
- Table:
pm_appointments - Filter:
.eq('organization_id', orgId).eq('patient_id', patientId) - Scope:
patientIdfromusePortalPatientId()(enforces proxy scope)
Constraints:
- Cancellation only for
status = 'scheduled' - No direct booking of new slots (future enhancement)
- RLS enforces patient isolation at DB level
Contract: Intake/Consent Forms (PF-08)
Page:PortalFormsPage
Source: src/cores/pm/pages/portal/PortalFormsPage.tsx
Integration Pattern:
- Uses
FormEmbedfrom@/platform/forms(Platform Integration Layer) - Patient context injected from
usePortalPatientId() - Form submissions stored in
fw_form_submissionswith patient linkage
Contract: Demographic Updates (PM-01)
Hook:usePortalDemographicRequests
Source: src/cores/pm/hooks/usePortalDemographicRequests.ts
Data Access:
- Read:
pm_patients(current demographics, read-only display) - Write:
fw_form_submissions(update request submission)
Contract: Billing & Payments (PM-09)
Hook:usePortalBilling
Source: src/cores/pm/hooks/usePortalBilling.ts
Data Access:
- Tables:
pm_charges,pm_payments - Filter:
.eq('organization_id', orgId).eq('patient_id', patientId)
Constraints:
- No direct card data handling (PCI compliance via existing PM-09 flow)
- Balance is calculated client-side from total_amount columns
Contract: Secure Messaging (PM-14)
Hook:usePortalMessaging
Source: src/cores/pm/hooks/usePortalMessaging.ts
Integration Pattern:
- Thin wrapper around existing PM-14 hooks
- Enforces
senderType: 'patient'on all outbound messages - Conversations filtered by patient_id from
usePortalPatientId()
Contract: Clinical Record Access (CL-*) — ✅ Implemented
Hook:usePortalClinicalSummary
Source: src/cores/pm/hooks/usePortalClinicalSummary.ts
Page: PortalClinicalPage
Source: src/cores/pm/pages/portal/PortalClinicalPage.tsx
Integration Pattern:
- Read-only access to clinical chart, medications, and treatment goals
- Consent enforcement via
useConsentCheckfrom@/platform/clinical(Platform Integration Layer) - SUD-indicated data gated by CL-11
cl_check_sud_consentRPC - Non-SUD data always accessible (21st Century Cures Act compliance)
- Patient context from
usePortalPatientId()(proxy-aware)
- Tables:
cl_patient_charts,cl_medications,cl_treatment_goals - Filter:
.eq('patient_id', patientId)fromusePortalPatientId()
Consent Enforcement:
- If
cl_patient_charts.flags.sud_indicatedis true, callscl_check_sud_consentRPC - Without consent: diagnosis-level data suppressed, medications and goals still visible (non-SUD)
- Consent banner displayed when data is restricted
Security Architecture
Authentication
- Supabase Auth (email/password + TOTP MFA)
- Portal sessions audited in
pm_portal_sessions(append-only) - Account lockout after 5 failed attempts
Data Isolation
- RLS Layer:
pm_portal_has_org_access()andpm_portal_visible_patient_ids()(SECURITY DEFINER) - Application Layer:
usePortalPatientId()hook enforces scope on all queries - Proxy Support: Active, non-expired proxy relationships only
Tables with FORCE ROW LEVEL SECURITY
pm_portal_userspm_portal_sessions