> ## 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 Event Wiring Integration Contract

> Contract Version: 1.0 Created: 2026-03-24 Owning Cores: CL (Clinical & EHR), PM (Practice Management) Status: Draft ADR Reference: ADR-004: CL-FW Event Patterns

**Contract Version:** 1.0
**Created:** 2026-03-24
**Owning Cores:** CL (Clinical & EHR), PM (Practice Management)
**Status:** Draft
**ADR Reference:** [ADR-004: CL-FW Event Patterns](../decisions/ADR-004-cl-fw-event-patterns.md)

> **Purpose:** Define the event contracts for asynchronous communication between CL and PM cores, focusing on clinical documentation → charge capture, referral acceptance → care coordination, and financial program cost → outcomes reporting.

***

## Event Contracts

### Event 1: `clinical_note_finalized`

**Summary:** When a clinical progress note is signed/finalized in CL-04, PM-07 creates a charge capture suggestion.

| Field       | Value                                                                            |
| ----------- | -------------------------------------------------------------------------------- |
| Event Name  | `progress_note_signed`                                                           |
| Channel     | `cl_pm_events`                                                                   |
| Publisher   | CL (CL-04: Progress Notes & Session Documentation)                               |
| Subscribers | PM (PM-07: Charge Capture), PF-10 (Notifications), FW (Audit)                    |
| Trigger     | CL-04 note status transitions to `finalized` (co-signature complete if required) |
| Frequency   | \~50-200/day per organization (based on clinician volume)                        |
| Latency SLA | \< 5 seconds from finalization to PM charge suggestion                           |

**Payload Schema:**

```typescript theme={null}
interface ProgressNoteSignedEvent {
  event: 'progress_note_signed';
  publisher: 'CL';
  subscriber: ['PM', 'PF', 'FW'];
  payload: {
    entity_id: string;          // cl_progress_notes.id (UUID)
    entity_type: 'progress_note';
    chart_id: string;           // cl_patient_charts.id
    encounter_id: string;       // pm_encounters.id (FK per ADR-002)
    rendering_provider_id: string; // pf_profiles.id of signing clinician
    service_date: string;       // ISO date (YYYY-MM-DD)
    billing_codes: string[];    // CPT codes derived from note type (e.g., ['90837', '90785'])
    modifiers?: string[];       // Modifier codes (e.g., ['GT'] for telehealth)
    duration_minutes: number;   // Actual session duration
    note_type: string;          // 'individual' | 'group' | 'family' | 'crisis'
    requires_cosignature: boolean;
    cosigned: boolean;
  };
  metadata: {
    organization_id: string;
    site_id?: string;
    user_id: string;            // Signing clinician
    timestamp: string;
    correlation_id: string;
    event_id: string;
  };
}
```

**PM Consumer Action:**

1. Validate `encounter_id` exists in `pm_encounters`
2. Check for duplicate `event_id` in processed events (idempotency)
3. Create charge suggestion in `pm_charge_suggestions` with billing codes, modifiers, duration, and provider
4. **High-risk billing codes (compliance guard):** Before auto-posting to `pm_charges`, evaluate CPT/HCPCS in `event.payload.billing_codes` against a curated high-risk list. **Default list** (same list MUST be used everywhere this guard is implemented; extend only via org config): `90839`, `90840`, `90785`, `H0038`, **`H2035`**, **`H0004`**, **`T1017`**, **`96127`**. **Org customization:** `pm_module_settings.auto_post_high_risk_codes_list` (text\[] or JSON array of codes) **replaces** the default when set; empty array = use default. **Definition — “high-risk”:** codes that (a) require strong clinical documentation / medical necessity evidence, (b) draw payer or regulatory audit scrutiny, or (c) commonly require prior authorization — orgs SHOULD align overrides with their compliance policy.
   * Add boolean `auto_post_high_risk_codes` to `pm_module_settings` (default `false`).
   * **Auto-post to `pm_charges` only when:** `orgSettings.auto_post_enabled === true` **AND** (no high-risk code in payload **OR** `orgSettings.auto_post_high_risk_codes === true`).
   * Otherwise create or update `pm_charge_suggestions` for **manual review** (PF-10 notify billing as today).
5. If auto-post rules pass, create `pm_charges` directly
6. Send PF-10 notification to billing staff if manual review required

**PHI Safety:** No patient names, DOB, or narrative text. Only UUIDs and billing metadata.

***

### Event 2: `referral_accepted`

**Summary:** When PM accepts an inbound referral, CL-12 creates a care coordination transition record.

| Field       | Value                                                |
| ----------- | ---------------------------------------------------- |
| Event Name  | `pm_referral_accepted`                               |
| Channel     | `cl_pm_events`                                       |
| Publisher   | PM (PM-08: Referral Management)                      |
| Subscribers | CL (CL-12: Care Coordination), PF-10 (Notifications) |
| Trigger     | Referral status transitions to `accepted` in PM      |
| Frequency   | \~5-20/day per organization                          |
| Latency SLA | \< 30 seconds                                        |

**Payload Schema:**

```typescript theme={null}
interface ReferralAcceptedEvent {
  event: 'pm_referral_accepted';
  publisher: 'PM';
  subscriber: ['CL', 'PF'];
  payload: {
    entity_id: string;           // pm_referrals.id (UUID)
    entity_type: 'referral';
    patient_id: string;          // pm_patients.id
    chart_id?: string;           // cl_patient_charts.id (if chart already exists)
    referring_provider_id: string;
    accepting_provider_id: string;
    referral_type: string;       // 'inbound' | 'outbound' | 'internal'
    service_type: string;        // e.g., 'behavioral_health', 'substance_use', 'psychiatric'
    priority: string;            // 'routine' | 'urgent' | 'emergent'
    authorization_number?: string;
  };
  metadata: {
    organization_id: string;
    site_id?: string;
    user_id: string;
    timestamp: string;
    correlation_id: string;
    event_id: string;
  };
}
```

**CL Consumer Action:**

1. Check if `cl_patient_charts` exists for patient; if not, flag for chart creation
2. Create `cl_care_transitions` record with referral context
3. Assign care coordinator via CL-12 assignment rules
4. Send PF-10 notification to accepting provider and care coordinator

***

### Event 3: `clinical_program_cost_allocated`

**Summary:** When FA allocates program costs, CL-10 outcomes can correlate clinical outcomes with financial investment.

| Field       | Value                                                  |
| ----------- | ------------------------------------------------------ |
| Event Name  | `fa_program_cost_allocated`                            |
| Channel     | `fa_events`                                            |
| Publisher   | FA (FA-35: Program Cost Allocation)                    |
| Subscribers | CL (CL-10: Outcomes, CL-15: Reporting)                 |
| Trigger     | FA completes monthly/quarterly program cost allocation |
| Frequency   | \~1-4/month per organization                           |
| Latency SLA | \< 5 minutes (batch event, not time-critical)          |

**Payload Schema:**

```typescript theme={null}
interface ProgramCostAllocatedEvent {
  event: 'fa_program_cost_allocated';
  publisher: 'FA';
  subscriber: ['CL'];
  payload: {
    entity_id: string;            // fa_cost_allocations.id
    entity_type: 'cost_allocation';
    program_id: string;           // Program UUID
    program_name: string;         // e.g., 'IOP', 'PHP', 'Residential'
    period_start: string;         // ISO date
    period_end: string;
    total_cost: number;           // In cents (integer) to avoid floating point
    cost_per_patient_day?: number;
    patient_count: number;
    allocation_method: string;    // 'direct' | 'step_down' | 'activity_based'
  };
  metadata: {
    organization_id: string;
    user_id: string;
    timestamp: string;
    correlation_id: string;
    event_id: string;
  };
}
```

**CL Consumer Action:**

1. Store cost data in CL-10 outcomes context for cost-effectiveness reporting
2. Update CL-15 program quality dashboard with cost-per-outcome metrics
3. No direct database writes to FA tables (read-only consumption)

***

### Event 4: `cl_crisis_episode_resolved`

**Summary:** When a crisis episode is dispositioned with an assigned billing code (CL-13), PM-07 creates a crisis-specific charge suggestion and links it to the episode for audit.

| Field       | Value                                                   |
| ----------- | ------------------------------------------------------- |
| Event Name  | `cl_crisis_episode_resolved`                            |
| Channel     | `cl_pm_events`                                          |
| Publisher   | CL (CL-13: Crisis Intervention)                         |
| Subscriber  | PM (PM-07: Charge Capture)                              |
| Trigger     | Crisis disposition recorded and `billing_code` assigned |
| Frequency   | Variable (crisis volume)                                |
| Latency SLA | \< 5 seconds to charge suggestion                       |

**Payload Schema:**

```typescript theme={null}
interface CrisisEpisodeResolvedEvent {
  event: 'cl_crisis_episode_resolved';
  publisher: 'CL';
  subscriber: ['PM'];
  payload: {
    entity_id: string;              // cl_crisis_episodes.id
    entity_type: 'crisis_episode';
    chart_id: string;
    crisis_type: string;
    service_date: string;           // ISO date YYYY-MM-DD
    billing_code: string;           // e.g. S9484, S9485, H2011, 90839, 90840
    duration_minutes?: number;
    rendering_provider_id: string;
    encounter_id?: string;           // pm_encounters.id when linked
  };
  metadata: {
    organization_id: string;
    site_id?: string;
    user_id: string;
    timestamp: string;
    correlation_id: string;
    event_id: string;               // Idempotency key for PM consumer
  };
}
```

**PM Consumer Action:**

1. Idempotency: same pattern as `progress_note_signed` using `metadata.event_id` in `fw_domain_events`
2. Create crisis-specific `pm_charge_suggestion` with `billing_code`, `service_date`, `rendering_provider_id`
3. For **S9484 / H2011** and similar time-based codes, derive **units** from `duration_minutes` per org/payer rules in `pm_module_settings` (or PM-07 configuration)
4. Link suggestion to `cl_crisis_episodes` via `entity_id` on suggestion metadata / extension table (implementation-specific) for audit
5. Apply the same **high-risk auto-post guard** as Event 1 when codes overlap the high-risk list

**Retry / idempotency:** Treat `event_id` as the dedupe key; duplicates must not create a second suggestion.

***

## Idempotency

All event handlers implement the following idempotency pattern:

```typescript theme={null}
async function handleEvent(event: DomainEvent): Promise<void> {
  // 1. Check for duplicate event_id — maybeSingle() so 0 rows returns null (first-time OK)
  const { data: existing } = await supabase
    .from('fw_domain_events')
    .select('id')
    .eq('event_id', event.metadata.event_id)
    .maybeSingle();

  if (existing) {
    logger.info(`Duplicate event ${event.metadata.event_id} — skipping`);
    return;
  }

  // 2. Process event
  await processEvent(event);

  // 3. Record processed event (store full payload for audit / replay)
  await supabase.from('fw_domain_events').insert({
    event_id: event.metadata.event_id,
    event_name: event.event,
    correlation_id: event.metadata.correlation_id,
    status: 'processed',
    processed_at: new Date().toISOString(),
    event_payload: event as unknown as Record<string, unknown>,
  });
}
```

***

## Dead-Letter Queue

Failed events follow the DLQ pattern from ADR-004:

1. On failure: write to `fw_domain_events` with `status = 'failed'`, error message, retry count
2. Cron edge function retries failed events (exponential backoff: 1s, 2s, 4s; max 3 retries)
3. After final failure: PF-10 notification to system admin
4. No retry on 4xx errors (except 429 rate limit)

***

## E2E Test Plan

| Test Scenario                          | Steps                                                                                     | Expected Outcome                                                                                                                                                                                          |
| -------------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Note finalization → charge created     | 1. Create encounter in PM; 2. Create progress note in CL-04; 3. Finalize note (sign)      | PM charge suggestion exists with correct `billing_codes`, `encounter_id`, **`duration_minutes`**, and **`modifiers`** (when present in event payload)                                                     |
| Referral acceptance → care transition  | 1. Create referral in PM; 2. Accept referral                                              | CL-12 care transition record created; care coordinator assigned                                                                                                                                           |
| Cost allocation → outcomes context     | 1. Run FA cost allocation for program                                                     | CL-10 cost-per-outcome metrics updated                                                                                                                                                                    |
| Duplicate event → no double processing | 1. Publish `progress_note_signed` twice with identical `metadata.event_id`                | Exactly **one** charge suggestion; handler logs a message equivalent to **"Duplicate event — skipping"** (or `Duplicate event ${event_id} — skipping`); **no uncaught exception**                         |
| Failed event → DLQ                     | 1. Publish `progress_note_signed` with invalid `encounter_id` until max retries exhausted | Row in `fw_domain_events` with **`status` = `'failed'`**, **`retry_count` = `3`**, **`error_message`** non-null/non-empty, **`processed_at` IS NULL** after final failure; PF-10 admin notification fired |

***

## Related Documents

* [EVENT\_CONTRACTS.md](EVENT_CONTRACTS.md) — Canonical event schema and channel registry
* [ADR-004: CL-FW Event Patterns](../decisions/ADR-004-cl-fw-event-patterns.md) — Channel routing decision
* [ADR-002: CL-PM Cross-Core Foreign Keys](../decisions/ADR-002-cl-pm-cross-core-foreign-keys.md) — FK exception for `pm_encounters`
* [CL-PM-ENCOUNTER-TO-BILLING.md](CL-PM-ENCOUNTER-TO-BILLING.md) — Existing encounter-to-billing contract
* [CL-PM-EPISODE-OF-CARE-INTEGRATION.md](CL-PM-EPISODE-OF-CARE-INTEGRATION.md) — Episode of care lifecycle
* [CL-FA-VBP-QUALITY-DATA-PIPELINE.md](CL-FA-VBP-QUALITY-DATA-PIPELINE.md) — CL-FA quality data flow
* [constitution.md](../../../constitution.md) §1.3 — No direct core-to-core imports; use Platform Integration Layer
