feat(ai): gate document, lab and medication extraction on their capabilities

Reading a stored document (suggest, summary, extract, index, chat,
reindex), scanning a lab report and turning medication text into a
schedule now answer under documentAi, labsOcr and medicationExtract. The
operator's master switch and the new reading-documents switch stop the
work and the egress; a module off for the record, a delegate inside
somebody else's record, and a missing receipt refuse with the capability
envelope. Existing no-provider codes keep their status.

The document pick and the labs pick re-check the capability for the
provider they picked, immediately before anything is sent, so a job
enqueued before a switch flipped finds no provider and falls back to the
provider-free text layer. Consent follows the document rule for all
three: any provider that leaves the machine needs an ai_extraction or
ai_full receipt, answered for the provider actually picked.

The fenced Coach chat and attachments answer under coach and documentAi.
Uploads are always stored and indexed; the background summary is only
enqueued while documentAi is open. The capability probes report the
capability beside the provider answer and keep a read offered when only
the receipt is missing. medications/extract keeps its 503 for no provider
and adds ai.provider.none, and its budget refusal gains the
coach.budget.exceeded code.
This commit is contained in:
Marc-André Bombeck committed 2026-09-24 08:44:57 +02:00
1 parent a19900c854
commit bd8ea9d86b
39 files changed
+1420 -376

No files matched your search

+8
View File
@@ -26,6 +26,8 @@
*/
import { z } from "zod/v4";
import type { AiCapabilityState } from "@/lib/ai/capabilities/types";
/** Max facts a single document may stage / confirm. A dense letter is ~40. */
export const INBOUND_MAX_FACTS = 120;
@@ -840,6 +842,12 @@ export interface DocumentAiCapabilityDto {
* - null: no read is available (see `reason`).
*/
egress: DocumentEgressClass | null;
/**
* The `documentAi` capability for this record. When it is closed by
* anything other than a missing provider or a missing consent receipt,
* `available` is false with a null `reason`, and this says why.
*/
ai: AiCapabilityState;
}
// ─── AI assist / summary / index (Document vault P2) ────────────────────────
+7
View File
@@ -16,6 +16,7 @@
* The DTO types the routes return + the UI consumes are derived here so the
* three layers agree on one shape.
*/
import type { AiCapabilityState } from "@/lib/ai/capabilities/types";
import { z } from "zod/v4";
import type { WrittenOutcome } from "@/lib/outcome/written-outcome";
@@ -258,6 +259,12 @@ export interface OcrCapabilityDto {
reason: "no-provider" | "enable-local-ocr" | null;
/** Whether PDF uploads are accepted (Anthropic vision provider only). */
pdfSupported: boolean;
/**
* The `labsOcr` capability for this record. When it is closed by anything
* other than a missing provider or a missing consent receipt, `available`
* is false with a null `reason`, and this says why.
*/
ai: AiCapabilityState;
}
/**