Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Feature ID: CL-06
Status: 🚧 In Progress
Spec: CL-06-e-prescribing-epcs.md
Last Updated: 2026-02-18
Last Verified: 2026-02-18

Table of Contents

  1. Overview
  2. Quick Reference
  3. Integration Points
  4. API / Data Contracts
  5. Event Contracts
  6. Edge Functions
  7. Decision Trees
  8. Pattern Library
  9. Security and RLS
  10. Common Mistakes
  11. Pre-Flight Checklist
  12. 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 in ChartMedicationsSection) via a permission-gated “New Prescription” button. A standalone /cl/pharmacies route manages the org-level pharmacy directory.

Quick Reference

CategoryNameLocation
Tablecl_prescriptionsBusiness entity (PHI); org + chart scoped; site_id (nullable)
Tablecl_pharmaciesReference data; org-scoped only (no site_id — deliberate, see Clarifications)
Event (outbound)prescription_sentPublished by cl-transmit-prescription edge function after Surescripts acknowledgement
Edge Functioncl-transmit-prescriptionverify_jwt: true; handles Surescripts stub transmit + EPCS audit
Permission (view)cl.prescriptions.viewView prescription history for a chart
Permission (create)cl.prescriptions.createCreate and send prescriptions
Permission (cancel)cl.prescriptions.cancelCancel or void prescriptions
Permission (pharmacy view)cl.pharmacies.viewView org pharmacy directory
Permission (pharmacy manage)cl.pharmacies.manageAdd/edit pharmacies
RLS helpercl_has_org_access_for_epcs(p_org_id)Used for SELECT/INSERT/UPDATE/DELETE policies (calls pf_has_org_access)
Triggercl_pharmacies_set_updated_at()Table-specific trigger on cl_pharmacies
Triggercl_prescriptions_set_updated_at()Table-specific trigger on cl_prescriptions

Integration Points

DependencyPatternPurpose
CL-01 (Patient Chart)DirectPrescriptions scoped to chart; chart_id FK
CL-05 (Medication Management)Directmedication_id FK links prescription to medication list entry; optional
CL-08 (Clinical Decision Support)DirectDrug interaction and allergy alerts during prescribing (future phase)
CL-17 (Arizona CSPMP)DirectPDMP query before controlled substance prescribing (CL-17 scope)
SurescriptsExternalPrescription routing, RTPB, formulary (stub in MVP)
HTI-4 / NCPDP SCRIPT 2023011StandardMessage format, RTPB certification
PF-10 (Notifications)EventReceives prescription_sent event to notify prescriber of acknowledgement
PM-07 (Billing)EventReceives prescription_sent for charge-capture correlation (future phase)

API / Data Contracts

cl_prescriptions

  • Scope: organization_id + chart_id; also has site_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 filter deleted_at IS NULL
  • Custom fields: custom_fields JSONB NOT NULL DEFAULT '{}'

cl_pharmacies

  • Scope: organization_id only (no site_id — org-wide pharmacy directory; see CL-06 Clarifications)
  • Address columns: address_line_1, address_line_2, city, state, zip_code (no single address column)
  • 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 filter WHERE 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

FieldValue
Event nameprescription_sent
Owning corecl
Categoryoperational
Publishercl-transmit-prescription edge function (service client after Surescripts acknowledgement)
Channelcl_pm_events (domain events channel)
SubscribersPM-07 (billing correlation), PF-10 (prescriber acknowledgement notification)
Seeded in migrationYes — fw_workflow_events ON CONFLICT DO NOTHING
KnownEventName registeredYes — src/platform/events/types.ts
Payload schema:
{
  "prescription_id": "string (UUID)",
  "chart_id": "string (UUID)",
  "organization_id": "string (UUID)",
  "prescriber_id": "string (UUID)",
  "pharmacy_id": "string (UUID or null)",
  "medication_name": "string",
  "is_controlled": "boolean",
  "schedule": "string or null (II/III/IV/V)",
  "sent_at": "string (ISO 8601)"
}
Note: prescription_sent is published by the edge function using createServiceClient(), not from React client code. The event fires only after Surescripts acknowledgement (status transitions to acknowledged) 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

AttributeValue
Function namecl-transmit-prescription
verify_jwttrue (user-authenticated prescriber action)
Auth patternvalidateAuth() from _shared/auth.ts
ClientcreateServiceClient() from _shared/supabase.ts
LoggingcreateLogger('cl-transmit-prescription')
CORSgetCorsHeaders(origin) from _shared/cors.ts
Error responsescreateErrorResponse() / createValidationError() from _shared/errors.ts
Serve APIDeno.serve() (not deprecated serve())
Supabase importnpm:@supabase/supabase-js@2 (not esm.sh)
Request body:
{
  "prescription_id": "string (UUID)",
  "organization_id": "string (UUID)"
}
Responsibilities:
  1. Validate JWT + org access (validateAuth, verifyOrgAccess)
  2. Fetch prescription from cl_prescriptions (verify ownership)
  3. Call Surescripts stub (MVP) or real Surescripts API (production)
  4. Update transmission_status and surescripts_message_id on success
  5. Write audit log entry for controlled-substance transmissions (pf_audit_logs)
  6. Publish prescription_sent event via fw_domain_events insert
  7. Return { success: true, transmission_status: 'acknowledged', correlationId }
EPCS path (controlled substances):
  • Validate epcs_token is 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

Prescription operation:

├── Is this a transmission to Surescripts?
│   └── YES → Use cl-transmit-prescription edge function

├── Is this an EPCS signature / 2FA token validation?
│   └── YES → Edge function (vendor stub in MVP)

├── Is this create/update/cancel (non-transmission)?
│   └── YES → Direct Supabase mutation from React hook
│       (still filter by organization_id for defense-in-depth)

└── Is this a PDMP check (CL-17)?
    └── YES → CL-17 scope; not CL-06 responsibility

EPCS Flow (Controlled Substances)

Prescriber creates Schedule II–V prescription:

├── Drug interaction / allergy check (CL-08 / CL-05 service)

├── RTPB check (if rtpb_checked_at not set)
│   └── Show cost / formulary alternatives panel

├── CSPMP/PDMP check (CL-17 linkage — out of scope for CL-06)

├── EPCSConfirmDialog:
│   └── "I acknowledge this is a controlled substance prescription"
│   └── 2FA / token input (vendor stub in MVP shows: "EPCS vendor not yet configured")
│   └── Sets epcs_token on prescription record when two-factor completed

└── Transmit via cl-transmit-prescription edge function
    └── On acknowledged: publish prescription_sent event

Pharmacy Picker Logic

User opens pharmacy picker:

├── Is prescription a controlled substance (is_controlled = true)?
│   └── Filter: WHERE is_surescripts_enabled = true AND deleted_at IS NULL AND is_active = true

└── Is prescription non-controlled?
    └── Filter: WHERE deleted_at IS NULL AND is_active = true

Pattern Library

Pharmacy Picker (Client Hook)

// usePrescriptionsByChart — filter active prescriptions
const { data } = await supabase
  .from('cl_prescriptions')
  .select('*')
  .eq('organization_id', organizationId)
  .eq('chart_id', chartId)
  .is('deleted_at', null)
  .order('created_at', { ascending: false });
// usePharmacyList — filtered pharmacy picker
const { data } = await supabase
  .from('cl_pharmacies')
  .select('id, name, ncpdp_id, is_surescripts_enabled, address_line_1, address_line_2, city, state, zip_code, phone, fax')
  .eq('organization_id', organizationId)
  .is('deleted_at', null)
  .eq('is_active', true)
  // For controlled substance prescriptions, add:
  // .eq('is_surescripts_enabled', true)
  .order('name');

Surescripts MVP Stub Pattern

// src/cores/cl/services/surescripts.ts
// Surescripts integration requires DrFirst/DoseSpot certification.
// This is a stub for MVP UI development.
export async function transmitPrescription(
  prescriptionId: string,
  _payload: SurescriptsPayload,
): Promise<SurescriptsResult> {
  // MVP stub — return mock acknowledgement
  return {
    success: true,
    messageId: `STUB-${Date.now()}-${prescriptionId.substring(0, 8)}`,
    status: 'acknowledged',
  };
}

Edge Function: Publish prescription_sent

// Within cl-transmit-prescription/index.ts (after Surescripts ack)
await serviceClient.from('fw_domain_events').insert({
  event_name: 'prescription_sent',
  organization_id: organizationId,
  payload: {
    prescription_id: prescriptionId,
    chart_id: prescription.chart_id,
    organization_id: organizationId,
    prescriber_id: prescription.prescriber_id,
    pharmacy_id: prescription.pharmacy_id,
    medication_name: prescription.medication_name,
    is_controlled: prescription.is_controlled,
    schedule: prescription.dea_schedule,
    sent_at: new Date().toISOString(),
  },
});

Security and RLS

RLS Policy Summary

TableOperationPolicyHelper
cl_prescriptionsSELECTcl_has_org_access_for_epcs(organization_id)cl_has_org_access_for_epcs
cl_prescriptionsINSERTcl_has_org_access_for_epcs(organization_id)cl_has_org_access_for_epcs
cl_prescriptionsUPDATEUSING + WITH CHECK: cl_has_org_access_for_epcs(organization_id)cl_has_org_access_for_epcs
cl_prescriptionsDELETEcl_has_org_access_for_epcs(organization_id)cl_has_org_access_for_epcs
cl_pharmaciesSELECTcl_has_org_access_for_epcs(organization_id)cl_has_org_access_for_epcs
cl_pharmaciesINSERTcl_has_org_access_for_epcs(organization_id)cl_has_org_access_for_epcs
cl_pharmaciesUPDATEUSING + WITH CHECK: cl_has_org_access_for_epcs(organization_id)cl_has_org_access_for_epcs
cl_pharmaciesDELETEcl_has_org_access_for_epcs(organization_id)cl_has_org_access_for_epcs
  • FORCE ROW LEVEL SECURITY on both tables (PHI)
  • All UPDATE policies include both USING and WITH 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

MistakeFix
Querying cl_prescriptions without deleted_at IS NULLAlways add .is('deleted_at', null)
Showing all pharmacies in picker for controlled substancesFilter is_surescripts_enabled = true when is_controlled = true
Calling Surescripts transmit from React clientAlways use cl-transmit-prescription edge function
Using serve() from deno std in edge functionUse Deno.serve()
Importing Supabase in edge function from esm.shUse npm:@supabase/supabase-js@2
Missing WITH CHECK on UPDATE policyBoth USING and WITH CHECK required (constitution §5.2.4)
Using FORCE RLS on service-role client in edge functionService role bypasses RLS by design — validate org access in code
Adding site_id to cl_pharmaciesDeliberate omission — pharmacy directory is org-scoped, not site-scoped (see Clarifications in spec)
Publishing prescription_sent from React clientEvent is published by the edge function using service client, not from React code

Pre-Flight Checklist

Before running the Phase 1 migration:
  • prescription_sent in KnownEventNamesrc/platform/events/types.ts ✅ Done
  • 5 CL-06 permission constants in CL_PERMISSIONSsrc/platform/permissions/constants.ts ✅ Done
  • cl_has_org_access_for_epcs function exists in DB (CL-06 migration)
  • Migration uses idempotent CREATE TABLE IF NOT EXISTS pattern
  • RLS policies use idempotent DO $$ BEGIN IF NOT EXISTS... pattern (see follow-up migration)
  • FORCE ROW LEVEL SECURITY on both tables
  • UPDATE policies include both USING and WITH CHECK
  • site_id on cl_prescriptions (nullable); absent from cl_pharmacies (documented)
  • deleted_at on both tables
  • custom_fields on cl_prescriptions; custom_fields on cl_pharmacies (migration includes both)
  • fw_workflow_events seed for prescription_sent with ON CONFLICT DO NOTHING
  • pf_module_permissions seed for 5 CL-06 keys with ON CONFLICT DO NOTHING