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.

Version: 1.0.0 Last Updated: 2026-02-17 Constitution Reference: Section 1.2 (Core Independence), Section 1.3 (Integration Patterns) Source: CL-PM-SPEC-REVIEW Findings 2.1, 5.1

Overview

This contract defines how clinical documentation (CL core) triggers charge capture (PM core) without direct cross-core dependencies. Both CL and PM depend only on Platform Foundation (PF). Integration uses Pattern 2: Event-Based from the constitution.

Encounter Entity

Problem

No spec in CL or PM defines a formal encounter entity. The encounter is the central concept linking appointments, clinical notes, charges, and claims but currently exists only as scattered references:
  • pm_charges.encounter_id UUID (nullable, no FK)
  • CL-04 progress notes function as encounter records
  • PM-03 appointments transition to encounters implicitly

Solution: pm_encounters Table

The encounter entity lives in PM (owned by PM-03) because encounter lifecycle tracks the business/operational flow. CL adds clinical content to encounters via events.
CREATE TABLE pm_encounters (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  organization_id UUID NOT NULL REFERENCES pf_organizations(id),
  patient_id UUID NOT NULL,
  appointment_id UUID REFERENCES pm_appointments(id),
  encounter_date DATE NOT NULL,
  encounter_type TEXT NOT NULL CHECK (encounter_type IN (
    'outpatient', 'residential_daily', 'iop', 'php',
    'crisis', 'group', 'telehealth', 'evaluation'
  )),
  status TEXT NOT NULL DEFAULT 'scheduled' CHECK (status IN (
    'scheduled', 'checked_in', 'in_progress', 'documented',
    'charge_captured', 'billed', 'cancelled', 'no_show'
  )),
  rendering_provider_id UUID NOT NULL,
  supervising_provider_id UUID,
  service_location_id UUID,
  place_of_service TEXT NOT NULL DEFAULT '11',
  -- Populated by CL-04 via event
  clinical_note_id UUID,
  note_finalized_at TIMESTAMPTZ,
  -- Populated by PM-07 via charge capture
  charge_id UUID,
  charge_captured_at TIMESTAMPTZ,
  -- Populated by PM-08 via claim submission
  claim_id UUID,
  -- Standard columns
  custom_fields JSONB DEFAULT '{}',
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  created_by UUID REFERENCES pf_profiles(id),
  updated_by UUID REFERENCES pf_profiles(id),
  deleted_at TIMESTAMPTZ
);

-- Indexes
CREATE INDEX idx_pm_encounters_org ON pm_encounters(organization_id);
CREATE INDEX idx_pm_encounters_patient ON pm_encounters(organization_id, patient_id);
CREATE INDEX idx_pm_encounters_date ON pm_encounters(organization_id, encounter_date);
CREATE INDEX idx_pm_encounters_status ON pm_encounters(organization_id, status)
  WHERE deleted_at IS NULL;

-- RLS
ALTER TABLE pm_encounters ENABLE ROW LEVEL SECURITY;
ALTER TABLE pm_encounters FORCE ROW LEVEL SECURITY;

-- Uses pf_has_org_access SECURITY DEFINER helper (never query RLS-protected tables in policies)
CREATE POLICY pm_encounters_select ON pm_encounters
  FOR SELECT USING (pf_has_org_access(organization_id, auth.uid()));

CREATE POLICY pm_encounters_insert ON pm_encounters
  FOR INSERT WITH CHECK (pf_has_org_access(organization_id, auth.uid()));

CREATE POLICY pm_encounters_update ON pm_encounters
  FOR UPDATE
  USING (pf_has_org_access(organization_id, auth.uid()))
  WITH CHECK (pf_has_org_access(organization_id, auth.uid()));

CREATE POLICY pm_encounters_delete ON pm_encounters
  FOR DELETE USING (pf_is_org_admin(auth.uid(), organization_id));

Encounter Lifecycle

PM-03 Appointment created → encounter status = 'scheduled'
Patient checks in         → encounter status = 'checked_in'
Clinician starts session  → encounter status = 'in_progress'
CL-04 note finalized      → encounter status = 'documented'
                             (via clinical_note_finalized event)
PM-07 charge captured      → encounter status = 'charge_captured'
PM-08 claim submitted      → encounter status = 'billed'

Event Contract: clinical_note_finalized

Event: clinical_note_finalized Channel: cl_pm_events Publisher: CL (Clinical & EHR) — CL-04 Progress Notes Subscribers: PM (Practice Management) — PM-07 Charge Capture Status: 📝 Planned

Trigger

When a progress note transitions to signed or cosigned status in cl_progress_notes.

Payload Schema

interface ClinicalNoteFinalized {
  event: 'clinical_note_finalized';
  publisher: 'CL';
  subscriber: ['PM'];
  payload: {
    note_id: uuid;
    encounter_id: uuid;           // Links to pm_encounters
    patient_id: uuid;
    provider_id: uuid;            // Rendering provider
    supervising_provider_id?: uuid;
    service_date: string;         // ISO date
    begin_time: string;           // HH:MM:SS
    end_time: string;             // HH:MM:SS
    duration_minutes: number;
    note_type: string;            // 'individual' | 'group' | 'crisis' | 'family' etc.
    service_line: string;         // 'outpatient' | 'residential' | 'iop' | 'php' etc.
    cpt_code?: string;            // Suggested CPT from note type/duration
    diagnosis_codes: string[];    // ICD-10-CM codes
    place_of_service: string;     // POS code
    is_telehealth: boolean;
    telehealth_modality?: string; // 'audio_video' | 'audio_only'
    is_group: boolean;
    group_participant_count?: number;
    treatment_goal_ids: uuid[];   // Linked treatment goals
    modifiers_suggested: string[]; // Suggested modifiers (HQ, GT, 95, etc.)
  };
  metadata: {
    organization_id: uuid;
    site_id?: uuid;
    user_id: uuid;
    timestamp: string;            // ISO timestamp
    correlation_id: uuid;
    idempotency_key: uuid;        // Prevents duplicate charge creation
  };
}

Implementation

-- Trigger on CL-04 note finalization
CREATE OR REPLACE FUNCTION cl_publish_note_finalized()
RETURNS TRIGGER AS $$
BEGIN
  IF NEW.status IN ('signed', 'cosigned') AND OLD.status NOT IN ('signed', 'cosigned') THEN
    PERFORM pg_notify('cl_pm_events', json_build_object(
      'event', 'clinical_note_finalized',
      'publisher', 'CL',
      'payload', json_build_object(
        'note_id', NEW.id,
        'encounter_id', NEW.encounter_id,
        'patient_id', (SELECT patient_id FROM cl_patient_charts WHERE id = NEW.chart_id),
        'provider_id', NEW.clinician_id,
        'service_date', NEW.service_date,
        'begin_time', NEW.begin_time,
        'end_time', NEW.end_time,
        'duration_minutes', NEW.duration_minutes,
        'diagnosis_codes', ARRAY[NEW.diagnosis_code],
        'is_telehealth', COALESCE(NEW.is_telehealth, false),
        'is_group', (NEW.note_type = 'group')
      ),
      'metadata', json_build_object(
        'organization_id', NEW.organization_id,
        'user_id', NEW.clinician_id,
        'timestamp', now(),
        'correlation_id', gen_random_uuid(),
        'idempotency_key', NEW.id
      )
    )::text);
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER trigger_note_finalized
AFTER UPDATE ON cl_progress_notes
FOR EACH ROW
EXECUTE FUNCTION cl_publish_note_finalized();

Consumer: PM-07 Charge Capture

Edge Function: supabase/functions/cl-note-charge-capture/index.ts Consumer responsibilities:
  1. Receive clinical_note_finalized event
  2. Check idempotency_key — skip if charge already exists for this note
  3. Look up fee schedule for provider’s payer/plan
  4. Calculate AHCCCS timed units from duration_minutes
  5. Apply auto-modifiers (HQ for group, GT/95 for telehealth, etc.)
  6. Create pm_charges record with status pending
  7. Update pm_encounters.charge_captured_at and status
  8. Log to pf_audit_logs

Error Handling

ScenarioAction
Fee schedule not foundCreate charge with needs_review flag; alert billing team
Duplicate event (same idempotency_key)Skip silently, return success
Invalid CPT/diagnosis combinationCreate charge with needs_review flag
Consumer failureRetry 3x with exponential backoff; dead-letter after exhaustion
Encounter not foundCreate encounter from event payload (backfill)

Event Contract: charge_status_changed

Event: charge_status_changed Channel: pm_events Publisher: PM — PM-07 Charge Capture Subscribers: CL (for status visibility on chart) Status: 📝 Planned

Payload Schema

interface ChargeStatusChanged {
  event: 'charge_status_changed';
  publisher: 'PM';
  subscriber: ['CL'];
  payload: {
    charge_id: uuid;
    encounter_id: uuid;
    patient_id: uuid;
    old_status: string;
    new_status: string;  // 'pending' | 'reviewed' | 'approved' | 'billed' | 'void'
    cpt_code: string;
    total_charge: number;
  };
  metadata: {
    organization_id: uuid;
    user_id: uuid;
    timestamp: string;
    correlation_id: uuid;
  };
}

Cross-Reference

SpecRoleIntegration Point
PM-03Creates encounter when appointment transitionspm_encounters table owner
CL-04Publishes clinical_note_finalized on note sign-offEvent publisher
PM-07Consumes event, creates chargesEvent consumer
PM-08Creates claims from approved chargesDownstream consumer
PM-09Posts payments against claimsFurther downstream