Client API
The lower-level client, for work the pipelines do not cover. For ordinary ingestion start with the clinical notes pipeline or the lab results pipeline.
Setup
from cavell_client import CavellClient, IngestionPipeline
client = CavellClient(
api_url="https://prd.prism.cavell.app/api",
api_key="your-llm-gateway-key",
fhir_base_url="http://localhost:8090",
fhir_client_id="...", # optional; provide together with fhir_client_secret
fhir_client_secret="...", # optional; omit both for unauthenticated FHIR servers
fhir_api_path="/fhir", # use the path exposed by your FHIR server
)
Use the client as a context manager so both underlying HTTP clients are closed cleanly:
with CavellClient(...) as client:
pipeline = IngestionPipeline(client, default_organization="ORG-1")
...
Methods
List tiers
Returns a list[dict] with the keys name and default.
Get patient resources
# All resources for a patient
resources = client.get_patient_resources("pat-123")
# Filter by type
conditions = client.get_patient_resources("pat-123", "Condition")
Delete patient resources
Cascade-deletes the patient and any resources that reference that patient. This depends on server-side cascading delete support. The repository's local HAPI configuration enables it with allow_cascading_deletes=true.
Count resources
Find patient ID
List processed document IDs
processed = client.list_processed_document_ids()
# or scoped to one patient (preferred on shared servers):
processed = client.list_processed_document_ids(patient_id=fhir_id)
Returns a set[str] of DocumentReference.identifier values used by the pipeline for resume-safe extraction. Without patient_id the whole server is scanned.
Mark a resource validated
The extraction API stamps every resource it emits with an unvalidated meta
tag (system <deployment-url>/fhir/CodeSystem/validation-status). The
consumer contract for "a clinician has reviewed this resource" is removing
that tag:
Any later extraction that updates the resource re-adds the tag.
List unvalidated resources
Returns the patient's resources still carrying the unvalidated tag —
the review queue for clinical validation.
Direct extraction API
The pipeline is the supported path, but CavellAPI can be used directly for
one-off extraction without FHIR persistence:
from cavell_client.api import CavellAPI
api = CavellAPI("https://prd.prism.cavell.app/api", api_key="...")
bundle, count, usage = api.extract(
text=note_text,
document_date="2024-01-15", # ISO YYYY-MM-DD; its own field, not meta prose
meta="Department: Cardiology",
tier="high",
allowed_resources=["Condition", "MedicationRequest"], # restrict output types
encounter_identifier="V-2024-0001", # the visit this note belongs to
)
allowed_resources restricts extraction to the listed FHIR resource types.
It is not available through the pipeline, which assumes full extraction for
its context and deduplication behavior.
encounter_identifier makes the API produce exactly one Encounter for the
visit: it carries the identifier as urn:cavell:encounter|<value>, and every
resource in the bundle references it. Pass the Encounter an earlier note
created inside context and the API updates it in place — same id, status
and period moved on — instead of creating a second one; the pipeline does that
lookup for you (FHIRClient.find_encounter). Leave the field out and no
Encounter is created.
Extracting a document out of chronological order
If you are extracting a note that predates data already on record, pass the
record as it stood on that note's date as context, everything newer as
future_context, and set out_of_order:
from cavell_client.fhir import FHIRClient
fhir = FHIRClient(base_url="...", client_id="...", client_secret="...")
past, future = fhir.fetch_split_patient_context(
patient_fhir_id, reference_date="2023-09-12"
)
bundle, count, usage = api.extract(
text=note_text,
document_date="2023-09-12",
patient_id=patient_fhir_id,
context=past,
future_context=future,
out_of_order=True,
)
fetch_split_patient_context sorts each resource by provenance — the newest
already-processed document that created or updated it — so past is what was
genuinely on record on that date, not merely what carries an older clinical date.
Without this, the note is read against a clinical picture from its own future and anything it creates carries that contamination backwards. Both fields are omitted from the payload when unset, so ordinary forward extraction is unchanged. The pipeline does all of this for you — see Out-of-order documents.
Requires matching API support
An extraction API that predates these fields ignores them silently, which would leave the note extracting against past-only context with nothing to reconcile against.
Direct lab-ingestion API
Structured lab results have their own endpoint, which spends no gateway
tokens. It expects FHIR ids that you have already resolved and verified, which
is exactly what LabIngestionPipeline does for you — use the raw
call only when you are doing that resolution yourself:
response = api.ingest_lab_results(
patient_id="1042", # a FHIR Patient.id, not an MRN
rows=[
{
"lab_result_id": "LAB-0001",
"test_name": "C-reactive protein",
"value": "212",
"unit": "mg/L",
"loinc_code": "1988-5",
"reference_high": 5.0,
"collected_datetime": "2024-01-15T21:40:00+01:00",
"status": "final",
},
],
)
# {"bundle": {...}, "count": 1, "rejected": []}
The response bundle's entries are conditional creates, so posting it twice
creates nothing twice. Bad rows come back in rejected with a request-relative
index — the pipeline is what maps those back onto your input positions. A 404
means the Prism deployment predates lab ingestion.
Response Types
ExtractResult
Returned on successful pipeline outcomes.
| Field | Type | Description |
|---|---|---|
bundle |
dict |
FHIR transaction bundle |
resources |
list[dict] |
Extracted FHIR resources (shorthand for bundle entries) |
count |
int |
Number of extracted resources |
patient_id |
str |
Resolved patient ID |
usage |
UsageStats or None |
Token usage and cost |
persistence |
PersistResult or None |
Persistence result |
PersistResult
| Field | Type | Description |
|---|---|---|
status |
str |
"success", "partial_failure", or "failed" |
created |
int |
Resources created |
updated |
int |
Resources updated |
errors |
list[dict] |
Error details for failed resources |
UsageStats
| Field | Type | Description |
|---|---|---|
input_tokens |
int |
Input tokens consumed |
output_tokens |
int |
Output tokens generated |
total_tokens |
int |
Total tokens |
requests |
int |
Number of API requests |
estimated_cost |
float |
Estimated cost in USD |
Timeouts
| Client | Default | Notes |
|---|---|---|
| Cavell API | 800s | Extraction involves many LLM calls; the API caps each call at ~300s |
| FHIR server | 30s | Per-request timeout for all FHIR operations |
Exceptions
| Exception | When |
|---|---|
CavellAPIError |
Cavell API returns an error |
CavellAuthError |
The API returns 401 — the LLM Gateway key is missing or rejected (subclass of CavellAPIError, non-retryable) |
CavellGatewayUnavailableError |
The API returns 503 — the LLM Gateway is unreachable from the server (subclass of CavellAPIError) |
PatientNotFoundError |
A patient_id was provided but does not exist on the FHIR server |
FHIRAuthError |
FHIR OAuth2 authentication fails |
FHIRConnectionError |
The FHIR server is unreachable or fhir_base_url is wrong |
CavellError |
Base class for library-specific exceptions |