> ## 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.

# CL-PM Integration Contract: Encounter-to-Billing Pipeline

> 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 F…

**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.

```sql theme={null}
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

```typescript theme={null}
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

```sql theme={null}
-- 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

| Scenario                                | Action                                                          |
| --------------------------------------- | --------------------------------------------------------------- |
| Fee schedule not found                  | Create charge with `needs_review` flag; alert billing team      |
| Duplicate event (same idempotency\_key) | Skip silently, return success                                   |
| Invalid CPT/diagnosis combination       | Create charge with `needs_review` flag                          |
| Consumer failure                        | Retry 3x with exponential backoff; dead-letter after exhaustion |
| Encounter not found                     | Create 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

```typescript theme={null}
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

| Spec  | Role                                                 | Integration Point           |
| ----- | ---------------------------------------------------- | --------------------------- |
| PM-03 | Creates encounter when appointment transitions       | `pm_encounters` table owner |
| CL-04 | Publishes `clinical_note_finalized` on note sign-off | Event publisher             |
| PM-07 | Consumes event, creates charges                      | Event consumer              |
| PM-08 | Creates claims from approved charges                 | Downstream consumer         |
| PM-09 | Posts payments against claims                        | Further downstream          |
