cavell-prism-client 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ site/
2
+ __pycache__/
3
+ *.pyc
4
+ .venv/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ .cache/
11
+ .env
12
+ .env.*
13
+ CLAUDE.md
14
+
15
+ # Claude Code local settings (machine-specific)
16
+ .claude/settings.local.json
@@ -0,0 +1,154 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
5
+ adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.2.0] - 2026-07-31
8
+
9
+ First public release, targeting the Prism API. The package is published as
10
+ **`cavell-prism-client`**; the import name stays `cavell_client`.
11
+
12
+ ### Breaking
13
+
14
+ - **Bearer auth replaces HTTP Basic.** `CavellAPI(base_url, api_key)` and
15
+ `CavellClient(api_url, api_key, fhir_base_url, ...)` take an LLM Gateway
16
+ key sent as `Authorization: Bearer <key>` on every request. Passing the
17
+ removed `username=`/`password=` keyword arguments raises a `TypeError`
18
+ with migration guidance. Positional callers of the old signature fail at
19
+ FHIR-client construction — switch to keyword arguments.
20
+ - **Base URLs moved** to `https://{qa,stg,prd}.prism.cavell.app/api`. A bare
21
+ host without a path gets `/api` appended automatically.
22
+ - **`CavellClient(...)` now validates both endpoints at construction**, so a
23
+ misconfiguration raises there instead of deep inside `seed()`/`extract()`,
24
+ and the exception type says which half is wrong: `GET /key/info`
25
+ pre-flights the LLM Gateway key without spending tokens
26
+ (`CavellAuthError` / `CavellGatewayUnavailableError` / `CavellAPIError`),
27
+ then `GET /metadata` checks the FHIR server (`FHIRAuthError` /
28
+ `FHIRConnectionError`). Calling `check_connection()` afterwards is no
29
+ longer necessary; constructing a client now requires network access to
30
+ both. `extract()` still pre-flights the key once per run.
31
+ - **`FHIRConnectionError`** — FHIR failures surface as a library exception
32
+ instead of a raw `httpx.HTTPStatusError`/`ConnectError` escaping from
33
+ `check_connection()`.
34
+ - **Run-global failures raise.** A 401 (`CavellAuthError`) or a 503 that
35
+ survives in-place retries (`CavellGatewayUnavailableError`) aborts
36
+ `IngestionPipeline.extract()` with an exception instead of producing
37
+ per-document failure outcomes. Re-running with `skip_processed=True`
38
+ (the default) resumes safely.
39
+ - **`extract(batch_size=...)` is now `extract(limit=...)`.** The old name
40
+ implied chunking it never did: it caps a single pass, and the documents
41
+ past the cap were left unprocessed. Passing `batch_size=` to `extract()`
42
+ raises `TypeError` with migration guidance rather than being silently
43
+ ignored — silently ignoring it would extract every document passed.
44
+ Use `extract_all()` to process a whole dataset in chunks. `limit` must
45
+ be `>= 1` if set.
46
+
47
+ ### Added
48
+
49
+ - `CavellAuthError` (401) and `CavellGatewayUnavailableError` (503), both
50
+ subclassing `CavellAPIError`.
51
+ - `Retry-After` is honored in the 429 retry loop (capped at 300s).
52
+ - **Chronology check**: documents older than the patient's newest
53
+ already-persisted document are **refused**. `extract()` and `extract_all()`
54
+ raise `OutOfOrderDocumentError` before anything in the call is extracted,
55
+ so no tokens are spent and nothing is persisted — including the in-order
56
+ documents in the same call. `OutOfOrderDocumentError.violations` lists every
57
+ offender as an `OutOfOrderDocument` (`patient_identifier`, `document_id`,
58
+ `document_index`, `date`, `watermark`). Equal dates are not violations
59
+ (dates are day-resolution), already-processed documents are filtered out
60
+ before the check, and the check fails open per patient if the watermark
61
+ query errors.
62
+
63
+ Extraction is context-aware: each note is read against the patient's
64
+ *current* resources with no date filtering, so an older note would be
65
+ interpreted against a clinical picture from its own future. Refusing is the
66
+ conservative position while that is true.
67
+
68
+ This supersedes an earlier design in which the older document was extracted
69
+ anyway behind an **update guard** that dropped updates to resources sourced
70
+ from newer documents. That code is retained but disabled
71
+ (`_apply_update_guard` and the commented-out block in
72
+ `_process_single_document`, plus one skipped test) in case the policy is
73
+ revisited.
74
+ - `mark_validated(resource_type, id)` removes the Prism `unvalidated` meta
75
+ tag via FHIR `$meta-delete`; `list_unvalidated_resources(patient, type)`
76
+ lists the clinician review queue.
77
+ - `list_processed_document_ids(patient_id=...)` for patient-scoped resume.
78
+ - **Active CarePlans are sent as extraction context**, activating the
79
+ server's plan versioning: continuing plans are updated/ended instead of
80
+ re-created on every note (previously ~10 duplicate plans per patient).
81
+ - Local dev tooling: `scripts/start_fhir.sh [--fresh]` and
82
+ `scripts/fhir_summary.py`; ruff security (S) rules; hardened pre-commit
83
+ (detect-secrets, uv-lock, lockfile-pinned ruff/ty).
84
+ - The `notebook` extra now declares version floors —
85
+ `pip install "cavell-prism-client[notebook]"` pulls `notebook>=7.6.1`,
86
+ `tqdm>=4.70.0`, and `ipywidgets>=8.0` (the demo notebooks need the
87
+ ipywidgets 8 progress-bar API).
88
+ - Python 3.14 is tested in CI and declared in the classifiers.
89
+ - **`IngestionPipeline.extract_all(documents, batch_size=N)`** — processes a
90
+ whole dataset, which `extract()` never did. Sorts every document by
91
+ ascending date across the **entire** dataset before splitting it into
92
+ batches, so each patient's documents are extracted oldest-first even when
93
+ they span batches. Batching an unsorted list splits it by input order,
94
+ which lets a later batch carry documents older than what an earlier batch
95
+ persisted; those trip the chronology guard and lose their updates.
96
+ Batches are cut by index, so the walk always terminates and works with
97
+ `skip_processed=False` and with documents that have no `document_id`. All
98
+ documents are validated before the first batch spends, which is also the
99
+ only way `document_id` uniqueness is enforced across batch boundaries. An
100
+ optional `on_batch` callback reports progress as each batch finishes.
101
+
102
+ ### Changed
103
+
104
+ - `skip_processed` queries `DocumentReference`s per patient instead of
105
+ scanning the whole server.
106
+ - Outbound extraction context is deflated: server bookkeeping (`meta`) and
107
+ generated narrative (`text`) are stripped from context resources.
108
+ - Version is single-sourced from `cavell_client.__version__` (hatch dynamic
109
+ versioning).
110
+
111
+ ### Migration
112
+
113
+ ```python
114
+ # before (0.1.x)
115
+ client = CavellClient(
116
+ api_url="https://<old-deployment-url>/api",
117
+ username="user",
118
+ password="pass",
119
+ fhir_base_url="http://localhost:8090",
120
+ )
121
+
122
+ # after (0.2.0)
123
+ client = CavellClient(
124
+ api_url="https://prd.prism.cavell.app/api",
125
+ api_key="<your LLM Gateway key>",
126
+ fhir_base_url="http://localhost:8090",
127
+ )
128
+ ```
129
+
130
+ Gateway keys are issued by the Cavell LLM Gateway, not Cavell accounts —
131
+ contact your Cavell representative if you don't have one.
132
+
133
+ Batched extraction moves to `extract_all()`:
134
+
135
+ ```python
136
+ # before (0.1.x) — processed only the first 500 documents; the rest needed
137
+ # further extract() calls, and batch membership followed input order
138
+ for outcome in pipeline.extract(documents, batch_size=500):
139
+ ...
140
+
141
+ # after (0.2.0) — processes every document, globally date-ordered
142
+ for outcome in pipeline.extract_all(documents, batch_size=500):
143
+ ...
144
+
145
+ # to cap a single pass instead (e.g. a sanity check before a full run)
146
+ for outcome in pipeline.extract(documents, limit=10):
147
+ ...
148
+ ```
149
+
150
+ ## [0.1.0]
151
+
152
+ Internal release: ingestion pipeline (seed → extract → persist), CSV
153
+ helpers, practitioner matching, observation deduplication, transient-failure
154
+ retries with a deferred pass.
@@ -0,0 +1,59 @@
1
+ # Contributing
2
+
3
+ ## Development setup
4
+
5
+ Requires [uv](https://docs.astral.sh/uv/) and Python ≥ 3.11.
6
+
7
+ ```bash
8
+ git clone https://github.com/polaris-health/cavell-prism-client
9
+ cd cavell-prism-client
10
+ uv sync # installs the package + dev dependencies
11
+ uv run pytest # 250+ hermetic tests, sub-second
12
+ uv run ruff check && uv run ruff format --check
13
+ uv run ty check
14
+ uv run pre-commit install # ruff, ty, detect-secrets, commitlint on every commit
15
+ ```
16
+
17
+ The test suite is fully hermetic (no network) and guarded by
18
+ `pytest --timeout=60` — a test that accidentally makes a real HTTP call
19
+ fails loudly.
20
+
21
+ For end-to-end experiments, `docker compose up -d` starts a local HAPI FHIR
22
+ server on `http://localhost:8090`.
23
+
24
+ ## Commits and branches
25
+
26
+ - Conventional commits (`feat:`, `fix:`, `docs:`, `chore:`, ...) — enforced
27
+ by commitlint via pre-commit.
28
+ - `develop` is the default branch; all PRs target it.
29
+ - `main` is release-only: promotion PRs `develop → main` merge with a merge
30
+ commit (enforced by ruleset + the `branch-guard` check).
31
+ - Every PR (develop and main) requires one **code-owner** approval
32
+ (`.github/CODEOWNERS`), and authors cannot approve their own PRs. Repo
33
+ admins can bypass the approval when merging a PR (`gh pr merge --admin`) —
34
+ the bypass is recorded on the PR; direct pushes and force pushes stay
35
+ impossible for everyone, and `v*` release tags can only be created by
36
+ admins.
37
+
38
+ ## Releases
39
+
40
+ The version lives in exactly one place: `src/cavell_client/__init__.py`
41
+ (`__version__`). `pyproject.toml` reads it via hatch dynamic versioning.
42
+
43
+ - **Nightlies** publish automatically from `develop` to PyPI as
44
+ `<next-version>.devNNN` (skipped when develop hasn't changed). Install
45
+ with `pip install --pre cavell-prism-client`.
46
+ - **Stable release**:
47
+ 1. Ensure `__version__` on develop is the version to release and
48
+ `CHANGELOG.md` has its section.
49
+ 2. Open the promotion PR `develop → main`, merge it (merge commit).
50
+ 3. Tag the merge commit: `git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin vX.Y.Z`.
51
+ The publish workflow verifies tag == version == on-main, builds, and
52
+ publishes to PyPI via trusted publishing, then creates the GitHub
53
+ release.
54
+ 4. **Immediately after**: open a PR to develop bumping `__version__` to
55
+ the next minor — otherwise the next nightlies would sort below the
56
+ just-published stable.
57
+
58
+ Package name `cavell-prism-client`, import name `cavell_client` — this is
59
+ intentional; don't "fix" one to match the other.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cavell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: cavell-prism-client
3
+ Version: 0.2.0
4
+ Summary: Python client for Cavell FHIR extraction API
5
+ Project-URL: Homepage, https://cavell.ai
6
+ Project-URL: Documentation, https://polaris-health.github.io/cavell-prism-client
7
+ Project-URL: Repository, https://github.com/polaris-health/cavell-prism-client
8
+ Project-URL: Changelog, https://github.com/polaris-health/cavell-prism-client/blob/main/CHANGELOG.md
9
+ Author-email: Cavell <support@cavell.ai>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: clinical,ehr,extraction,fhir,healthcare,nlp
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Healthcare Industry
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: httpx>=0.27
25
+ Provides-Extra: notebook
26
+ Requires-Dist: ipywidgets>=8.0; extra == 'notebook'
27
+ Requires-Dist: notebook>=7.6.1; extra == 'notebook'
28
+ Requires-Dist: tqdm>=4.70.0; extra == 'notebook'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # Cavell Prism Client
32
+
33
+ [![CI](https://github.com/polaris-health/cavell-prism-client/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/polaris-health/cavell-prism-client/actions/workflows/ci.yml)
34
+ [![PyPI](https://img.shields.io/pypi/v/cavell-prism-client)](https://pypi.org/project/cavell-prism-client/)
35
+ [![Python](https://img.shields.io/pypi/pyversions/cavell-prism-client)](https://pypi.org/project/cavell-prism-client/)
36
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
37
+
38
+ Python client for [Cavell Prism](https://cavell.ai) — extract structured
39
+ FHIR resources from clinical notes and persist them to your own FHIR server.
40
+
41
+ **Cavell never connects to your FHIR server.** Your system sends clinical
42
+ text to the Prism API, receives extracted resources back, and persists them
43
+ locally with your own credentials. Cavell has no access to your database and
44
+ stores no credentials: every request carries your own LLM Gateway key.
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ pip install cavell-prism-client
50
+ ```
51
+
52
+ Or with [uv](https://docs.astral.sh/uv/):
53
+
54
+ ```bash
55
+ uv add cavell-prism-client
56
+ ```
57
+
58
+ The import name is `cavell_client`.
59
+
60
+ ## Quickstart
61
+
62
+ You need a **Prism API URL** (`https://prd.prism.cavell.app/api`) and an
63
+ **LLM Gateway key** — contact your Cavell representative for a key. For a
64
+ local FHIR server, `docker compose up -d` in this repo starts HAPI on
65
+ `http://localhost:8090`.
66
+
67
+ ```python
68
+ from cavell_client import CavellClient, IngestionPipeline
69
+ from cavell_client import Organization, Patient, Document
70
+
71
+ with CavellClient(
72
+ api_url="https://prd.prism.cavell.app/api",
73
+ api_key="<your LLM Gateway key>",
74
+ fhir_base_url="http://localhost:8090",
75
+ ) as client:
76
+ pipeline = IngestionPipeline(client, default_organization="CGH-001")
77
+
78
+ # 1. Seed reference data and patients
79
+ pipeline.seed(
80
+ organizations=[Organization(identifier="CGH-001", name="City General")],
81
+ patients=[Patient(identifier="MRN-1", managing_organization="CGH-001")],
82
+ )
83
+
84
+ # 2. Extract clinical notes (per patient, in date order) and persist
85
+ outcomes = pipeline.extract(
86
+ [
87
+ Document(
88
+ text="Patient diagnosed with type 2 diabetes...",
89
+ patient_identifier="MRN-1",
90
+ date="2024-01-15",
91
+ document_id="note-001",
92
+ ),
93
+ ]
94
+ )
95
+ for outcome in outcomes:
96
+ print(outcome)
97
+ ```
98
+
99
+ Extraction is resume-safe (`skip_processed=True` by default), retries
100
+ transient failures, and aborts cleanly on auth/gateway outages. A document
101
+ older than the patient's newest already-extracted note is refused up front
102
+ with `OutOfOrderDocumentError` — extraction is context-aware and only moves
103
+ forward in time.
104
+
105
+ For a whole dataset, use `pipeline.extract_all(documents, batch_size=500)`: it
106
+ sorts every document by ascending date before splitting it into batches, so
107
+ each patient's notes are extracted oldest-first even when they span batches.
108
+ `extract()` makes a single pass and its `limit` caps that pass rather than
109
+ chunking it.
110
+
111
+ ## Clinical validation
112
+
113
+ Every extracted resource carries an `unvalidated` meta tag. When a clinician
114
+ has reviewed a resource, remove the tag; any later update re-adds it:
115
+
116
+ ```python
117
+ client.list_unvalidated_resources(patient_fhir_id, "Condition") # review queue
118
+ client.mark_validated("Condition", condition_id) # $meta-delete
119
+ ```
120
+
121
+ ## Documentation
122
+
123
+ Setup guides, the pipeline walkthrough, and demo notebooks (synthetic data):
124
+ [polaris-health.github.io/cavell-prism-client](https://polaris-health.github.io/cavell-prism-client)
125
+
126
+ ## Contributing & security
127
+
128
+ See [CONTRIBUTING.md](https://github.com/polaris-health/cavell-prism-client/blob/main/CONTRIBUTING.md) for development setup and the release
129
+ process, and [SECURITY.md](https://github.com/polaris-health/cavell-prism-client/blob/main/SECURITY.md) for how to report vulnerabilities.
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,103 @@
1
+ # Cavell Prism Client
2
+
3
+ [![CI](https://github.com/polaris-health/cavell-prism-client/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/polaris-health/cavell-prism-client/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/cavell-prism-client)](https://pypi.org/project/cavell-prism-client/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/cavell-prism-client)](https://pypi.org/project/cavell-prism-client/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ Python client for [Cavell Prism](https://cavell.ai) — extract structured
9
+ FHIR resources from clinical notes and persist them to your own FHIR server.
10
+
11
+ **Cavell never connects to your FHIR server.** Your system sends clinical
12
+ text to the Prism API, receives extracted resources back, and persists them
13
+ locally with your own credentials. Cavell has no access to your database and
14
+ stores no credentials: every request carries your own LLM Gateway key.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install cavell-prism-client
20
+ ```
21
+
22
+ Or with [uv](https://docs.astral.sh/uv/):
23
+
24
+ ```bash
25
+ uv add cavell-prism-client
26
+ ```
27
+
28
+ The import name is `cavell_client`.
29
+
30
+ ## Quickstart
31
+
32
+ You need a **Prism API URL** (`https://prd.prism.cavell.app/api`) and an
33
+ **LLM Gateway key** — contact your Cavell representative for a key. For a
34
+ local FHIR server, `docker compose up -d` in this repo starts HAPI on
35
+ `http://localhost:8090`.
36
+
37
+ ```python
38
+ from cavell_client import CavellClient, IngestionPipeline
39
+ from cavell_client import Organization, Patient, Document
40
+
41
+ with CavellClient(
42
+ api_url="https://prd.prism.cavell.app/api",
43
+ api_key="<your LLM Gateway key>",
44
+ fhir_base_url="http://localhost:8090",
45
+ ) as client:
46
+ pipeline = IngestionPipeline(client, default_organization="CGH-001")
47
+
48
+ # 1. Seed reference data and patients
49
+ pipeline.seed(
50
+ organizations=[Organization(identifier="CGH-001", name="City General")],
51
+ patients=[Patient(identifier="MRN-1", managing_organization="CGH-001")],
52
+ )
53
+
54
+ # 2. Extract clinical notes (per patient, in date order) and persist
55
+ outcomes = pipeline.extract(
56
+ [
57
+ Document(
58
+ text="Patient diagnosed with type 2 diabetes...",
59
+ patient_identifier="MRN-1",
60
+ date="2024-01-15",
61
+ document_id="note-001",
62
+ ),
63
+ ]
64
+ )
65
+ for outcome in outcomes:
66
+ print(outcome)
67
+ ```
68
+
69
+ Extraction is resume-safe (`skip_processed=True` by default), retries
70
+ transient failures, and aborts cleanly on auth/gateway outages. A document
71
+ older than the patient's newest already-extracted note is refused up front
72
+ with `OutOfOrderDocumentError` — extraction is context-aware and only moves
73
+ forward in time.
74
+
75
+ For a whole dataset, use `pipeline.extract_all(documents, batch_size=500)`: it
76
+ sorts every document by ascending date before splitting it into batches, so
77
+ each patient's notes are extracted oldest-first even when they span batches.
78
+ `extract()` makes a single pass and its `limit` caps that pass rather than
79
+ chunking it.
80
+
81
+ ## Clinical validation
82
+
83
+ Every extracted resource carries an `unvalidated` meta tag. When a clinician
84
+ has reviewed a resource, remove the tag; any later update re-adds it:
85
+
86
+ ```python
87
+ client.list_unvalidated_resources(patient_fhir_id, "Condition") # review queue
88
+ client.mark_validated("Condition", condition_id) # $meta-delete
89
+ ```
90
+
91
+ ## Documentation
92
+
93
+ Setup guides, the pipeline walkthrough, and demo notebooks (synthetic data):
94
+ [polaris-health.github.io/cavell-prism-client](https://polaris-health.github.io/cavell-prism-client)
95
+
96
+ ## Contributing & security
97
+
98
+ See [CONTRIBUTING.md](https://github.com/polaris-health/cavell-prism-client/blob/main/CONTRIBUTING.md) for development setup and the release
99
+ process, and [SECURITY.md](https://github.com/polaris-health/cavell-prism-client/blob/main/SECURITY.md) for how to report vulnerabilities.
100
+
101
+ ## License
102
+
103
+ MIT
@@ -0,0 +1,25 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ | Version | Supported |
6
+ |---------|-----------|
7
+ | latest 0.x release | ✅ |
8
+ | older releases | ❌ |
9
+
10
+ ## Reporting a vulnerability
11
+
12
+ Please do **not** open a public issue for security problems.
13
+
14
+ Report vulnerabilities privately via
15
+ [GitHub Security Advisories](https://github.com/polaris-health/cavell-prism-client/security/advisories/new)
16
+ or email <info@cavell.ai>
17
+
18
+ We aim to acknowledge reports within 5 business days.
19
+
20
+ ## Scope notes
21
+
22
+ - This SDK never stores credentials: the LLM Gateway key is held in memory
23
+ and sent per request; FHIR credentials go only to your own FHIR server.
24
+ - The demo datasets in `docs/notebooks/` are fully synthetic — reports about
25
+ "patient data" in them are appreciated but not vulnerabilities.
@@ -0,0 +1,94 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "cavell-prism-client"
7
+ dynamic = ["version"]
8
+ description = "Python client for Cavell FHIR extraction API"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.11"
12
+ authors = [{ name = "Cavell", email = "support@cavell.ai" }]
13
+ keywords = ["fhir", "healthcare", "clinical", "nlp", "extraction", "ehr"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Healthcare Industry",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Scientific/Engineering :: Medical Science Apps.",
24
+ "Typing :: Typed",
25
+ ]
26
+ dependencies = [
27
+ "httpx>=0.27",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ notebook = [
32
+ "notebook>=7.6.1",
33
+ "tqdm>=4.70.0",
34
+ "ipywidgets>=8.0",
35
+ ]
36
+
37
+
38
+ [project.urls]
39
+ Homepage = "https://cavell.ai"
40
+ Documentation = "https://polaris-health.github.io/cavell-prism-client"
41
+ Repository = "https://github.com/polaris-health/cavell-prism-client"
42
+ Changelog = "https://github.com/polaris-health/cavell-prism-client/blob/main/CHANGELOG.md"
43
+
44
+ [tool.hatch.version]
45
+ path = "src/cavell_client/__init__.py"
46
+
47
+ [tool.hatch.build.targets.wheel]
48
+ packages = ["src/cavell_client"]
49
+
50
+ [tool.hatch.build.targets.sdist]
51
+ # Demo data, docs, and CI never belong in the published artifact.
52
+ exclude = [
53
+ "docs/",
54
+ "scripts/",
55
+ ".github/",
56
+ ".claude/",
57
+ "docker-compose.yml",
58
+ "mkdocs.yml",
59
+ ".pre-commit-config.yaml",
60
+ "commitlint.config.js",
61
+ "uv.lock",
62
+ ".secrets.baseline",
63
+ ]
64
+
65
+ [tool.ruff]
66
+ line-length = 88
67
+ target-version = "py311"
68
+
69
+ [tool.ruff.lint]
70
+ select = ["E", "W", "F", "I", "B", "UP", "S"]
71
+
72
+ [tool.ruff.lint.per-file-ignores]
73
+ # pytest asserts and deliberately fake credentials
74
+ "tests/**" = ["S101", "S105", "S106"]
75
+ # demo notebooks and the study script use assert as a readable guard
76
+ "docs/notebooks/*.ipynb" = ["S101"]
77
+ "scripts/**" = ["S101"]
78
+
79
+ [tool.pytest.ini_options]
80
+ testpaths = ["tests"]
81
+ # Hermeticity guard: a test that accidentally makes a real network call fails
82
+ # loudly instead of sitting in retry backoff.
83
+ addopts = "--timeout=60"
84
+
85
+ [dependency-groups]
86
+ dev = [
87
+ "mkdocs-jupyter>=0.26.3",
88
+ "pre-commit>=4.0",
89
+ "mkdocs-material[imaging]>=9.7.7",
90
+ "pytest>=9.1.1",
91
+ "pytest-timeout>=2.3",
92
+ "ruff>=0.11",
93
+ "ty>=0.0.1",
94
+ ]
@@ -0,0 +1,49 @@
1
+ """Python client for Cavell FHIR extraction API."""
2
+
3
+ from cavell_client.client import CavellClient
4
+ from cavell_client.ingestion import (
5
+ Document,
6
+ IngestionOutcome,
7
+ IngestionPipeline,
8
+ Organization,
9
+ Patient,
10
+ Practitioner,
11
+ )
12
+ from cavell_client.models import (
13
+ CavellAPIError,
14
+ CavellAuthError,
15
+ CavellError,
16
+ CavellGatewayUnavailableError,
17
+ ExtractResult,
18
+ FHIRAuthError,
19
+ FHIRConnectionError,
20
+ OutOfOrderDocument,
21
+ OutOfOrderDocumentError,
22
+ PatientNotFoundError,
23
+ PersistResult,
24
+ UsageStats,
25
+ )
26
+
27
+ __all__ = [
28
+ "CavellClient",
29
+ "CavellError",
30
+ "CavellAPIError",
31
+ "CavellAuthError",
32
+ "CavellGatewayUnavailableError",
33
+ "Document",
34
+ "FHIRAuthError",
35
+ "FHIRConnectionError",
36
+ "IngestionOutcome",
37
+ "IngestionPipeline",
38
+ "Organization",
39
+ "OutOfOrderDocument",
40
+ "OutOfOrderDocumentError",
41
+ "Patient",
42
+ "Practitioner",
43
+ "PatientNotFoundError",
44
+ "ExtractResult",
45
+ "PersistResult",
46
+ "UsageStats",
47
+ ]
48
+
49
+ __version__ = "0.2.0"