Feature ID: CL-06Documentation Index
Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
Use this file to discover all available pages before exploring further.
Status: 🚧 In Progress
Spec: CL-06-e-prescribing-epcs.md
Last Updated: 2026-02-18
Last Verified: 2026-02-18
Table of Contents
- Overview
- Quick Reference
- Integration Points
- API / Data Contracts
- Event Contracts
- Edge Functions
- Decision Trees
- Pattern Library
- Security and RLS
- Common Mistakes
- Pre-Flight Checklist
- Related Docs
Overview
CL-06 defines electronic prescribing (e-prescribing) and EPCS (electronic prescribing for controlled substances): Surescripts integration, NCPDP SCRIPT 2023011, RTPB, and integration with medication list (CL-05), clinical decision support (CL-08), and Arizona CSPMP (CL-17). Prescriptions surface within the Medications tab of PatientChartPage (embedded inChartMedicationsSection) via a permission-gated “New Prescription” button. A standalone /cl/pharmacies route manages the org-level pharmacy directory.
Quick Reference
| Category | Name | Location |
|---|---|---|
| Table | cl_prescriptions | Business entity (PHI); org + chart scoped; site_id (nullable) |
| Table | cl_pharmacies | Reference data; org-scoped only (no site_id — deliberate, see Clarifications) |
| Event (outbound) | prescription_sent | Published by cl-transmit-prescription edge function after Surescripts acknowledgement |
| Edge Function | cl-transmit-prescription | verify_jwt: true; handles Surescripts stub transmit + EPCS audit |
| Permission (view) | cl.prescriptions.view | View prescription history for a chart |
| Permission (create) | cl.prescriptions.create | Create and send prescriptions |
| Permission (cancel) | cl.prescriptions.cancel | Cancel or void prescriptions |
| Permission (pharmacy view) | cl.pharmacies.view | View org pharmacy directory |
| Permission (pharmacy manage) | cl.pharmacies.manage | Add/edit pharmacies |
| RLS helper | cl_has_org_access_for_epcs(p_org_id) | Used for SELECT/INSERT/UPDATE/DELETE policies (calls pf_has_org_access) |
| Trigger | cl_pharmacies_set_updated_at() | Table-specific trigger on cl_pharmacies |
| Trigger | cl_prescriptions_set_updated_at() | Table-specific trigger on cl_prescriptions |
Integration Points
| Dependency | Pattern | Purpose |
|---|---|---|
| CL-01 (Patient Chart) | Direct | Prescriptions scoped to chart; chart_id FK |
| CL-05 (Medication Management) | Direct | medication_id FK links prescription to medication list entry; optional |
| CL-08 (Clinical Decision Support) | Direct | Drug interaction and allergy alerts during prescribing (future phase) |
| CL-17 (Arizona CSPMP) | Direct | PDMP query before controlled substance prescribing (CL-17 scope) |
| Surescripts | External | Prescription routing, RTPB, formulary (stub in MVP) |
| HTI-4 / NCPDP SCRIPT 2023011 | Standard | Message format, RTPB certification |
| PF-10 (Notifications) | Event | Receives prescription_sent event to notify prescriber of acknowledgement |
| PM-07 (Billing) | Event | Receives prescription_sent for charge-capture correlation (future phase) |
API / Data Contracts
cl_prescriptions
- Scope:
organization_id+chart_id; also hassite_id(nullable) for site-level context - PHI: Yes — full RLS enforcement + FORCE ROW LEVEL SECURITY
- Key FK columns:
chart_id → cl_patient_charts(id),prescriber_id → pf_profiles(id),pharmacy_id → cl_pharmacies(id) - Transmission status lifecycle:
pending → sent → acknowledged | error | cancelled - EPCS columns:
is_controlled,dea_schedule,epcs_token(TEXT — set when EPCS two-factor completed) - RTPB columns:
rtpb_checked_at(TIMESTAMPTZ),rtpb_formulary_status,rtpb_patient_cost - Other columns:
medication_name,ndc_code,strength,form,route,quantity,quantity_unit,days_supply,refills,sig,transmitted_at,error_message - Soft delete:
deleted_at TIMESTAMPTZ— all application queries must filterdeleted_at IS NULL - Custom fields:
custom_fields JSONB NOT NULL DEFAULT '{}'
cl_pharmacies
- Scope:
organization_idonly (nosite_id— org-wide pharmacy directory; see CL-06 Clarifications) - Address columns:
address_line_1,address_line_2,city,state,zip_code(no singleaddresscolumn) - EPCS flag:
is_surescripts_enabled BOOLEAN— picker filters this for controlled-substance prescriptions - NCPDP ID:
ncpdp_id(TEXT); application enforces uniqueness per org - Soft delete:
deleted_at TIMESTAMPTZ— pharmacy picker queries filterWHERE deleted_at IS NULL AND is_active = true - Custom fields:
custom_fields JSONB NOT NULL DEFAULT '{}'(migration includes it)
Privileged Operations (Edge Function Only)
- Prescription transmit to Surescripts: Edge Function
cl-transmit-prescription; service role client; validates auth before transmit - EPCS signing/token validation: Edge Function only (FIPS path delegated to certified vendor: DrFirst or DoseSpot)
- SECURITY DEFINER functions used only for DB operations that bypass RLS (audit writes, webhook callbacks)
Event Contracts
Outbound: prescription_sent
| Field | Value |
|---|---|
| Event name | prescription_sent |
| Owning core | cl |
| Category | operational |
| Publisher | cl-transmit-prescription edge function (service client after Surescripts acknowledgement) |
| Channel | cl_pm_events (domain events channel) |
| Subscribers | PM-07 (billing correlation), PF-10 (prescriber acknowledgement notification) |
| Seeded in migration | Yes — fw_workflow_events ON CONFLICT DO NOTHING |
KnownEventName registered | Yes — src/platform/events/types.ts |
Note:prescription_sentis published by the edge function usingcreateServiceClient(), not from React client code. The event fires only after Surescripts acknowledgement (status transitions toacknowledged) or on MVP stub success.
No Inbound Events
CL-06 does not subscribe to events from other cores for MVP. Future: CL-17 PDMP result may trigger a workflow event once CL-17 is implemented.Edge Functions
cl-transmit-prescription
| Attribute | Value |
|---|---|
| Function name | cl-transmit-prescription |
verify_jwt | true (user-authenticated prescriber action) |
| Auth pattern | validateAuth() from _shared/auth.ts |
| Client | createServiceClient() from _shared/supabase.ts |
| Logging | createLogger('cl-transmit-prescription') |
| CORS | getCorsHeaders(origin) from _shared/cors.ts |
| Error responses | createErrorResponse() / createValidationError() from _shared/errors.ts |
| Serve API | Deno.serve() (not deprecated serve()) |
| Supabase import | npm:@supabase/supabase-js@2 (not esm.sh) |
- Validate JWT + org access (
validateAuth,verifyOrgAccess) - Fetch prescription from
cl_prescriptions(verify ownership) - Call Surescripts stub (MVP) or real Surescripts API (production)
- Update
transmission_statusandsurescripts_message_idon success - Write audit log entry for controlled-substance transmissions (
pf_audit_logs) - Publish
prescription_sentevent viafw_domain_eventsinsert - Return
{ success: true, transmission_status: 'acknowledged', correlationId }
- Validate
epcs_tokenis set on the prescription record before transmitting (EPCS two-factor completed) - Delegate FIPS 140-2 / token validation to certified vendor (DrFirst/DoseSpot stub in MVP)
Decision Trees
When to Use Edge Function vs. Client Mutation
EPCS Flow (Controlled Substances)
Pharmacy Picker Logic
Pattern Library
Pharmacy Picker (Client Hook)
Surescripts MVP Stub Pattern
Edge Function: Publish prescription_sent
Security and RLS
RLS Policy Summary
| Table | Operation | Policy | Helper |
|---|---|---|---|
cl_prescriptions | SELECT | cl_has_org_access_for_epcs(organization_id) | cl_has_org_access_for_epcs |
cl_prescriptions | INSERT | cl_has_org_access_for_epcs(organization_id) | cl_has_org_access_for_epcs |
cl_prescriptions | UPDATE | USING + WITH CHECK: cl_has_org_access_for_epcs(organization_id) | cl_has_org_access_for_epcs |
cl_prescriptions | DELETE | cl_has_org_access_for_epcs(organization_id) | cl_has_org_access_for_epcs |
cl_pharmacies | SELECT | cl_has_org_access_for_epcs(organization_id) | cl_has_org_access_for_epcs |
cl_pharmacies | INSERT | cl_has_org_access_for_epcs(organization_id) | cl_has_org_access_for_epcs |
cl_pharmacies | UPDATE | USING + WITH CHECK: cl_has_org_access_for_epcs(organization_id) | cl_has_org_access_for_epcs |
cl_pharmacies | DELETE | cl_has_org_access_for_epcs(organization_id) | cl_has_org_access_for_epcs |
FORCE ROW LEVEL SECURITYon both tables (PHI)- All UPDATE policies include both
USINGandWITH CHECK(constitution §5.2.4) - Migration uses idempotent
DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_policies...) THEN CREATE POLICY... END IF; END $$pattern
Audit Requirements
- All controlled-substance prescription views/creates/updates must be logged to
pf_audit_logs(user_id, timestamp, action, prescription_id) epcs_token(TEXT) set on prescription record when EPCS two-factor completed- Edge function writes audit entry for EPCS transmissions
- PHI: no prescription data in console logs; no PHI in AI prompts or event payloads beyond stable identifiers
Common Mistakes
| Mistake | Fix |
|---|---|
Querying cl_prescriptions without deleted_at IS NULL | Always add .is('deleted_at', null) |
| Showing all pharmacies in picker for controlled substances | Filter is_surescripts_enabled = true when is_controlled = true |
| Calling Surescripts transmit from React client | Always use cl-transmit-prescription edge function |
Using serve() from deno std in edge function | Use Deno.serve() |
Importing Supabase in edge function from esm.sh | Use npm:@supabase/supabase-js@2 |
| Missing WITH CHECK on UPDATE policy | Both USING and WITH CHECK required (constitution §5.2.4) |
| Using FORCE RLS on service-role client in edge function | Service role bypasses RLS by design — validate org access in code |
Adding site_id to cl_pharmacies | Deliberate omission — pharmacy directory is org-scoped, not site-scoped (see Clarifications in spec) |
Publishing prescription_sent from React client | Event is published by the edge function using service client, not from React code |
Pre-Flight Checklist
Before running the Phase 1 migration:-
prescription_sentinKnownEventName—src/platform/events/types.ts✅ Done - 5 CL-06 permission constants in
CL_PERMISSIONS—src/platform/permissions/constants.ts✅ Done -
cl_has_org_access_for_epcsfunction exists in DB (CL-06 migration) - Migration uses idempotent
CREATE TABLE IF NOT EXISTSpattern - RLS policies use idempotent
DO $$ BEGIN IF NOT EXISTS...pattern (see follow-up migration) -
FORCE ROW LEVEL SECURITYon both tables - UPDATE policies include both USING and WITH CHECK
-
site_idoncl_prescriptions(nullable); absent fromcl_pharmacies(documented) -
deleted_aton both tables -
custom_fieldsoncl_prescriptions;custom_fieldsoncl_pharmacies(migration includes both) -
fw_workflow_eventsseed forprescription_sentwithON CONFLICT DO NOTHING -
pf_module_permissionsseed for 5 CL-06 keys withON CONFLICT DO NOTHING