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

# PF-100: Platform Ambient Transcription & AI Note Generation — Integration Contract

> Owner: PF (Platform Foundation) Status: 📋 Specification Spec: specs/pf/specs/PF-100-platform-ambient-transcription-and-note-generation.md Last Updated: 2026-0…

# Platform Ambient Transcription & AI Note Generation — Integration Contract

**Owner:** PF (Platform Foundation)
**Status:** 📋 Specification
**Spec:** [`specs/pf/specs/PF-100-platform-ambient-transcription-and-note-generation.md`](../../../specs/pf/specs/PF-100-platform-ambient-transcription-and-note-generation.md)
**Last Updated:** 2026-04-22

> Cross-core integration contract for PF-100. Captures the boundary between PF (capture, STT, LLM, template engine, evidence map, lifecycle) and consuming cores (CL, PM, HR, GR, CE, RH). All cross-core consumption is via `@/platform/transcription` — no direct module imports.

***

## 1. Pattern

This is a **Pattern 1 (Platform Integration Layer) + Pattern 2 (Event Contracts)** integration.

* **Layer:** `@/platform/transcription` exposes hooks, components, types.
* **Events:** `pf.transcription.*` event family published; cores subscribe via PF-66 realtime + edge-function subscriptions.
* **Tables:** `pf_transcription_*` owned by PF; cores own their domain tables (e.g. `cl_progress_notes`, `hr_meeting_summaries`) and link via `session_id` UUID column (no cross-core FK except CL → PM encounter per ADR-002).

***

## 2. Scope of contract

| Owned by PF (PF-100)                                              | Owned by consumer core                                                                                                            |
| ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Capture (browser/mobile/bot/external/upload/dictation)            | Choosing the `recording_purpose` and template                                                                                     |
| Consent gate + revocation cascade                                 | Subject-of-record CRUD                                                                                                            |
| STT vendor routing + adapters                                     | —                                                                                                                                 |
| LLM vendor routing + adapters                                     | —                                                                                                                                 |
| Template engine + evidence mapping                                | Per-core templates (registered under platform builtins, or per-tenant custom)                                                     |
| Audio storage + retention + lifecycle                             | Domain-record retention (clinical record vs HR record vs GR record)                                                               |
| `pf_transcription_*` tables                                       | Domain tables (`cl_progress_notes`, `hr_meeting_summaries`, `gr_meeting_minutes`, `ce_call_outcomes`, `rh_resident_interactions`) |
| BAA matrix + vendor-call audit                                    | —                                                                                                                                 |
| PF-91 evidence emission for Part 2 / consent / retention controls | Domain-specific compliance (e.g. CL-04-EN-66 Policy 940)                                                                          |

***

## 3. Public API surface (`@/platform/transcription`)

See PF-100 spec §8.1 for full hook list. Core consumer pattern:

```ts theme={null}
import {
  useStartTranscriptionSession,
  useReviewQueue,
  useAttestDraft,
} from '@/platform/transcription';

// CL example (encounter-linked)
const start = useStartTranscriptionSession();
await start.mutateAsync({
  module_key: 'cl',
  recording_purpose: 'cl_individual',
  encounter_id,
  subject_of_record_type: 'patient',
  subject_of_record_id: patient_id,
  source: 'browser',
});

// HR example
await start.mutateAsync({
  module_key: 'hr',
  recording_purpose: 'hr_one_on_one',
  subject_of_record_type: 'employee',
  subject_of_record_id: employee_id,
  source: 'browser',
});
```

Cores **MUST NOT** import from `@/platform/ai` directly for transcription concerns; the PF-100 layer wraps PF-27/PF-59 internally.

***

## 4. Event Contracts

All events published on the platform realtime + outbox pattern. Subscribers register via PF-66 (frontend) or via a database-trigger-driven edge subscription (backend).

| Event                                       | Publisher | Subscribers                                                            | Payload                                                                                                         |
| ------------------------------------------- | --------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `pf.transcription.session.created`          | PF-100    | CL-36 (link to encounter), PM-08 (charge prep), PF-91 (evidence)       | `{ session_id, organization_id, module_key, recording_purpose, source, subject_of_record_type, encounter_id? }` |
| `pf.transcription.session.consent_verified` | PF-100    | CL-11 (audit), PF-91                                                   | `{ session_id, consent_record_id, jurisdiction_profile_id, is_part2_program }`                                  |
| `pf.transcription.session.consent_revoked`  | PF-100    | All consumers — STOP downstream processing                             | `{ session_id, revoked_by, revoked_at }`                                                                        |
| `pf.transcription.session.cancelled`        | PF-100    | All consumers                                                          | `{ session_id, reason }`                                                                                        |
| `pf.transcription.draft.ready`              | PF-100    | CL-36 (review queue surface), HR/GR/CE/RH UIs, PF-10 (notify reviewer) | `{ session_id, draft_id, template_id, generation_latency_ms }`                                                  |
| `pf.transcription.draft.attested`           | PF-100    | Consumer core (write domain record), PF-91 (evidence), PF-04 (audit)   | `{ session_id, draft_id, reviewer_id, attested_at, attested_content_sha256, module_attest_payload }`            |
| `pf.transcription.audio.deleted`            | PF-100    | PF-91, PF-04, subject-of-record portal (notify subject)                | `{ session_id, deleted_at, retention_policy }`                                                                  |
| `pf.transcription.deletion_requested`       | PF-100    | Operator alert, PF-91                                                  | `{ session_id, requested_by, requested_at, scope }`                                                             |
| `pf.transcription.deletion_completed`       | PF-100    | PF-91, subject portal                                                  | `{ session_id, completed_at, scope }`                                                                           |
| `pf.transcription.policy.violation`         | PF-100    | PF-91 (evidence), PF-10 (notify admin)                                 | `{ session_id, violation_type, blocked_at }`                                                                    |

### Consumed events

* `pf.consent.cl_consent.revoked` (CL-11) — triggers session-revocation cascade for any active CL session referencing the consent
* `pf.encounter.created` (PM) — optional auto-link encounter for a clinical session in pre-record state
* `pf.encounter.cancelled` (PM) — cancels any in-progress capture for that encounter

***

## 5. Cross-core FK rules (ADR-002 compliance)

Per **ADR-002** and constitution §5.2.7:

* `pf_transcription_sessions.encounter_id` — UUID column with **no FK constraint** to `pm_encounters.id` at the database level (cross-core FK exception is reserved for CL→PM, not PF→PM).
  * Validation is enforced at the **application layer** (edge function `pf-transcription-start-session` rejects sessions whose `encounter_id` does not resolve to an existing `pm_encounters.id` in the same `organization_id` at session start).
  * No trigger-enforced FK is created; an earlier draft of spec §7.1 implied a trigger — that has been corrected.
* `pf_transcription_sessions.subject_of_record_id` — polymorphic UUID; no FK.
* CL projection layer (Phase 3): `cl_ambient_sessions` becomes a VIEW over `pf_transcription_sessions WHERE module_key='cl'`. The CL→PM FK from CL-36-EN-01's existing schema is preserved **independently** on the CL side (via a separate `cl_ambient_session_encounters` materialization or a CL-owned trigger that enforces the FK only when `module_key='cl' AND encounter_id IS NOT NULL`). PF-100 itself does not own the CL→PM FK; the CL DX team retains that responsibility per ADR-002.

***

## 6. Vendor adapter contract

Each STT or LLM adapter implements a strict TypeScript interface in `supabase/functions/_shared/transcription-vendors/`. PF-100 spec §FR-2.3 defines the STT contract; the LLM adapter contract mirrors PF-27/PF-59.

| Vendor                                    | Tier                       | BAA                   | Adapter file                                              |
| ----------------------------------------- | -------------------------- | --------------------- | --------------------------------------------------------- |
| Deepgram Nova-3 Medical                   | primary STT                | required (enterprise) | `_shared/transcription-vendors/deepgram-nova3-medical.ts` |
| AssemblyAI Universal-3 Pro + Medical Mode | failover STT, EU residency | self-serve            | `_shared/transcription-vendors/assemblyai-u3.ts`          |
| AWS Transcribe Medical                    | alternate STT              | AWS BAA               | `_shared/transcription-vendors/aws-transcribe-medical.ts` |
| AWS HealthScribe (bundled STT+LLM)        | alternate end-to-end       | AWS BAA               | `_shared/transcription-vendors/aws-healthscribe.ts`       |
| Azure AI Speech                           | alternate STT              | Microsoft BAA         | `_shared/transcription-vendors/azure-speech.ts`           |
| OpenAI Whisper / gpt-4o-transcribe        | alternate STT (ZDR)        | OpenAI BAA            | `_shared/transcription-vendors/openai-whisper.ts`         |
| Self-hosted WhisperX                      | high-sensitivity STT       | n/a (on-prem)         | `_shared/transcription-vendors/whisperx-self-hosted.ts`   |
| Anthropic Claude (Bedrock)                | primary LLM                | AWS BAA               | `_shared/transcription-vendors/claude-bedrock.ts`         |
| Azure OpenAI GPT-4o                       | alternate LLM              | Microsoft BAA         | `_shared/transcription-vendors/azure-openai.ts`           |
| Google Vertex Gemini 2.5 Pro              | alternate LLM              | Google BAA            | `_shared/transcription-vendors/vertex-gemini.ts`          |
| Self-hosted Llama 3.3 70B                 | high-sensitivity LLM       | n/a                   | `_shared/transcription-vendors/llama-self-hosted.ts`      |
| Recall.ai                                 | meeting bot                | Recall.ai BAA         | `_shared/transcription-vendors/recall-bot.ts`             |
| Plaud Developer Platform                  | external recorder          | Plaud BAA             | `_shared/transcription-vendors/plaud-device.ts`           |

Vendor BAA expiry monitor (`pf-transcription-vendor-baa-monitor` cron) emits PF-91 evidence + alerts at 30 / 14 / 7 day thresholds.

***

## 7. CL-36 migration contract (Phase 3)

Non-breaking. Sequence:

1. `cl_ambient_sessions` continues writing through CL-36 hooks; PF-100 hooks are added in parallel and wrap the same vendor calls.
2. CL backend swap: `cl_ambient_sessions` becomes a VIEW over `pf_transcription_sessions WHERE module_key='cl'`; `cl_ambient_transcripts` similarly over `pf_transcription_segments`.
3. CL-36 attribution overlay (`cl_ai_note_attributions`) consumes `pf.transcription.draft.attested` event with `module_attest_payload.cl_progress_note_id`.
4. CL-36 hooks become deprecated shims; deletion in next major release per constitution §8.4 deprecation policy.

The CL-36-EN-01 deferred Phase 2 (group sessions, multi-language) is delivered through PF-100 Phase 2 (group de-identification) and Phase 2/3 (multi-language templates).

***

## 8. PF-96 jurisdiction profile extension contract (PF-96-EN-RECORDING)

PF-100 requires PF-96 enhancement adding a new top-level `recording_rules JSONB DEFAULT '{}' NOT NULL` column to `pf_jurisdiction_profiles` (the `policy_json` column referenced by an earlier draft does **not** exist; the actual JSONB columns on the table are `clinical_rules`, `billing_rules`, `compliance_rules`, `metadata`). All recording-policy keys live under `recording_rules.*`.

Keys (see PF-100 spec §7.2 for the authoritative list):

* `recording_consent_model`
* `wiretap_class_severity`
* `recording_max_retention_days_part2`
* `recording_max_retention_days_general_clinical`
* `recording_max_retention_days_hr`
* `recording_max_retention_days_gr`
* `external_recorder_allowed`
* `external_recorder_forbidden_for_part2`
* `mental_health_record_retention_years`
* `requires_visible_recording_indicator`
* `requires_per_participant_verbal_affirmation`
* `recording_pause_resume_consent_threshold_seconds`

Tracked as a PF-96 enhancement in `specs/cross-cutting/MULTI-STATE-COMPLIANCE-ENHANCEMENT-CATALOG.md` and authored at `specs/pf/enhancements/PF-96-EN-RECORDING-recording-consent-jurisdiction-fields.md`. PF-100 Phase 0 includes the corresponding migration.

***

## 8A. PF-71 dependency contract (patient-name list lookup)

Spec §FR-3.4 (co-attendee de-identification) requires a server-side patient-name list lookup to verify that AI-generated draft text does not leak names of patients who are NOT the subject of record (e.g. group-session co-attendees).

**Expected API (PF-71 to provide; consumed by `pf-transcription-generate-note` edge function):**

```ts theme={null}
// supabase/functions/_shared/pf-71/patient-name-list.ts (PF-71 owned)
export async function pf71PatientNameList(args: {
  organization_id: string;
  encounter_id?: string;          // when present, returns names of all patients linked to the encounter
  group_session_id?: string;      // when present, returns names of all attendees of the group session
  context: 'draft_redaction';
}): Promise<{ names: string[]; phonetic_variants: string[]; }>;
```

**Behavior:**

* Returns first/last/preferred names + phonetic/common-misspelling variants for every patient in scope.
* Excludes the subject-of-record (caller passes `subject_of_record_id` separately).
* Server-side only — never invoked from the browser.
* Names are used solely for redaction matching; PF-100 MUST NOT log returned names. Edge function logs only counts (`{ matched_count: N }`).

**Status:** PF-71 currently exposes patient-context primitives but not this exact list-lookup. PF-100 Phase 0 includes a sub-task to either (a) confirm an existing PF-71 RPC is sufficient, or (b) scope a small PF-71 enhancement to add `pf_71_patient_name_list()`. Tracked in PF-100-TASKS.md Phase 0 prerequisites.

***

## 9. PF-91 evidence emission contract

For every attested session, PF-100 emits structured evidence:

* **Part 2 control evidence** (`is_part2_program=true` only): consent record id + version, segregation hash (Part 2 KMS alias confirmation), retention proof, deletion proof.
* **All-party consent control evidence**: per-participant consent acknowledgment timestamps, jurisdiction profile id, recording-indicator-shown proof.
* **Retention control evidence**: applied retention policy, scheduled deletion date, actual deletion timestamp.
* **BAA-coverage control evidence**: vendor id, BAA evidence id, model version, call counts.

Evidence rows are written to `pf_compliance_evidence` (PF-91) with `source='pf-100-transcription'` and `evidence_kind` per row above.

***

## 10. Audit & enforcement

* `npm run audit:integration-contracts` — verifies this file is referenced from PF-100 spec
* `npm run audit:permissions` — verifies all keys in PF-100 §6 are seeded
* `npm run audit:routes-navigation` — verifies any new routes (e.g. `/platform/transcription/sessions`, `/platform/transcription/review`) are registered

***

## 11. Backlinks

* Spec: [PF-100](../../../specs/pf/specs/PF-100-platform-ambient-transcription-and-note-generation.md)
* Predecessor: [CL-36](../../../specs/cl/specs/CL-36-ai-assisted-clinical-documentation.md), [CL-36-EN-01](../../../specs/cl/specs/CL-36-EN-01-ai-ambient-listening-auto-documentation.md)
* Predecessor integration: [`CL-36-ai-documentation-INTEGRATION.md`](./ai-documentation-integration.md)
* Jurisdiction: [`PF-96-medicaid-state-compliance-configuration-INTEGRATION.md`](./medicaid-state-compliance-configuration-integration.md)
* Cross-core matrix: [`CROSS_CORE_INTEGRATIONS.md`](./CROSS_CORE_INTEGRATIONS.md)
* Architecture decisions: [`ADR-002-cl-pm-cross-core-foreign-keys.md`](../decisions/ADR-002-cl-pm-cross-core-foreign-keys.md)

***

## Activating a Vendor (Runbook)

> Until these four steps are completed for at least one STT vendor, every call to `pf-transcription-stt-stream` returns `409 vendor_baa_inactive`. LLM note generation works today via Lovable AI Gateway (no BAA gate).

1. **Sign the BAA** — paper artifact between the org and the vendor (Deepgram / AssemblyAI / AWS). Cannot be code-completed.
2. **Register the BAA row** in `pf_transcription_vendor_baas`:
   ```sql theme={null}
   INSERT INTO pf_transcription_vendor_baas
     (organization_id, vendor_id, status, baa_signed_at, baa_expires_at)
   VALUES ('<org>', '<vendor_id>', 'active', now(), now() + interval '1 year');
   ```
   Use the **Settings → Transcription → Vendors** UI (`VendorBaaTable`) instead of raw SQL when possible.
3. **Add the API key to PF-76 vault** under the credential name expected by the adapter (e.g. `deepgram_api_key`). The router calls `pf_retrieve_credential(org_id, name)`; missing keys surface as `vendor_baa_inactive` with `reason: 'missing_credential'`.
4. **(Optional) Add a routing rule** in `pf_transcription_jurisdiction_routing` so a specific `(jurisdiction_code, recording_channel)` resolves to this vendor instead of the org default.

The `JurisdictionRoutingDialog` shows a live resolver preview so admins can confirm routing before saving. The `VendorBaaTable` displays a green/amber/red status dot per BAA so live status is visible at a glance.
