Metadata-Version: 2.4
Name: kestrel-feature-healthcare
Version: 0.8.2
Summary: Patient-owned sovereign health records (FHIR/CCDA) as a Kestrel Sovereign feature package
Project-URL: Homepage, https://kestrelsovereign.com
Project-URL: Source, https://github.com/KestrelSovereignAI/kestrel-feature-healthcare
Project-URL: Issues, https://github.com/KestrelSovereignAI/kestrel-feature-healthcare/issues
Author: UncleSaurus
Maintainer: UncleSaurus
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: agents,ai,ccda,fhir,healthcare,kestrel,sovereign-records
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Requires-Python: <3.15,>=3.11
Requires-Dist: aiosqlite>=0.21.0
Requires-Dist: cryptography>=43
Requires-Dist: fhir-resources<9,>=8.0.0
Requires-Dist: kestrel-sovereign-sdk<1,>=0.14.1
Requires-Dist: kestrel-sovereign<1,>=0.14.1
Requires-Dist: lxml>=5
Provides-Extra: test
Requires-Dist: pytest-asyncio>=1.1.0; extra == 'test'
Requires-Dist: pytest-timeout>=2.3.1; extra == 'test'
Requires-Dist: pytest>=8.0.0; extra == 'test'
Description-Content-Type: text/markdown

# kestrel-feature-healthcare

Patient-owned sovereign health records (FHIR/CCDA) for Kestrel Sovereign.

An agent holds the owner's clinical records as part of its sovereign
memory. This is a reusable framework capability — a sibling package
alongside `kestrel-feature-visual` / `-reflection` / `-observability` —
not a host-product feature. Any healthcare host on Kestrel consumes it.

## Status

Multi-phase epic tracked in `KestrelSovereignAI/kestrel-sovereign`
([#1274](https://github.com/KestrelSovereignAI/kestrel-sovereign/issues/1274)).
All phases shipped:

- **Phase A** — typed FHIR R4B resource store, owner-DID-scoped,
  PHI encrypted at rest, audited reads.
- **Phase B** — CCDA document storage + header extraction (lxml),
  owner-DID-scoped, encrypted at rest, sharing one append-only PHI
  access log with Phase A.
- **Phase C** — pure-Python CCDA → FHIR R4B mapper (no third-party
  converter): Patient + Allergies / Medications / Problems / Results
  / Vital Signs / Immunizations / Procedures. Normalized resources
  land in the Phase A FHIR store, owner-scoped and audited.
- **Phase D1** — owner-consent-gated export/import bundle
  (`HealthcareBundle` + `HealthcareExporter` + `HealthcareImporter`).
  Composes the `DataAccessGrant` primitive from kestrel-sovereign
  ([#1273](https://github.com/KestrelSovereignAI/kestrel-sovereign/issues/1273)).
- **Phase D2** — owner-controlled in-process read-access surface
  (`AccessPolicy` + `AccessPolicyStore` + `ReadDenied`). Per-reader,
  per-resource-type, expirable, revocable.
- **D-CAR-native** — sovereignty-CAR sidecar (`HealthcareAssetCollector`
  + `HealthcareAssetRestorer`). The same bundle bytes ride inside a
  kestrel-sovereign CAR via the
  [`AssetRestorer`](https://github.com/KestrelSovereignAI/kestrel-sovereign/issues/1391)
  protocol.
- **Phase D3** — HIPAA Safe Harbor de-identification helpers
  (`Deidentifier` + `deidentify_bundle`). All 18 §164.514(b)(2)
  identifiers, configurable date-shift strategies, per-patient
  stable shifts that preserve clinical intervals.

## PHI handling (required)

Health records are PHI. The stores are **fail-closed**: resource
bodies are encrypted at rest with AES-256-GCM under a per-owner key
HKDF-derived from the host master key, and **no operation will
store or return PHI in the clear**. The host master key must be
configured (`KESTREL_DATA_KEY`); without it the FHIR tools fail
with a clear error rather than degrading to plaintext. Every owner
read/write/query — including denials — is recorded in an
append-only access log keyed by `(owner_did, reader_id?)`. Only the
resource *body* is encrypted — `resource_type` and `fhir_id` remain
queryable metadata.

## Installation

```bash
uv pip install kestrel-feature-healthcare
```

The package registers `HealthcareFeature` through the
`kestrel_sovereign.features` entry point group; it auto-discovers
when installed alongside `kestrel-sovereign>=0.14.1`.

## Consuming the package

Initialize the stores against a `DatabaseBackend` (the same concrete
the kestrel-sovereign host hands features):

```python
from kestrel_feature_healthcare import (
    FhirResourceStore, CcdaDocumentStore, PhiAccessLog,
)
from kestrel_sovereign.storage.db import get_backend

backend = await get_backend({"backend": "sqlite", "db_path": "kestrel.db"})
log = PhiAccessLog(backend)
fhir = FhirResourceStore(backend, log=log)
ccda = CcdaDocumentStore(backend, log=log)
await fhir.initialize()
await ccda.initialize()
```

### Consent-gated cross-agent transport (D1)

The patient mints an owner-signed `DataAccessGrant`; the exporter
builds a signed `HealthcareBundle`; the receiving agent verifies
both before any local-store mutation.

```python
from kestrel_feature_healthcare import HealthcareExporter, HealthcareImporter
from kestrel_sovereign.identity.access_grant import (
    DataAccessGrant, sign_owner, finalize,
)

grant = finalize(sign_owner(DataAccessGrant(
    owner_did=patient_did, source_did=source_agent_did,
    host_did=receiving_agent_did, issued_at="2026-05-29T00:00:00+00:00",
), [(patient_keypair, patient_kid)]))

bundle = await HealthcareExporter(fhir_src, ccda_src).build_bundle(
    owner_did=patient_did,
    source_did=source_agent_did,
    source_verification_methods=source_agent_vms,
    source_keypairs=[(source_keypair, source_kid)],
)

result = await HealthcareImporter(fhir_dst, ccda_dst).import_bundle(
    bundle.to_bytes(),
    grant=grant, host_did=receiving_agent_did,
)
```

### Read-access policy (D2)

Owner mints policies in-process; readers identify themselves to the
store; out-of-scope reads raise `ReadDenied` (distinct from
not-found, so denied reads can't probe for existence).

```python
from kestrel_feature_healthcare import AccessPolicyStore, ReadDenied

policy = AccessPolicyStore(backend, log=log)
await policy.initialize()
await policy.grant(patient_did, "staff:nurse", ["Patient", "Observation"])

p = await fhir.get_resource(
    patient_did, "Patient", "pat-1",
    reader_id="staff:nurse", access_policy=policy,
)
# get_resource(...) of MedicationRequest raises ReadDenied for this reader.
```

### Sovereignty-CAR-native transport (D-CAR-native)

Same bundle, transported inside a kestrel-sovereign CAR via the
`AssetRestorer` protocol:

```python
from kestrel_feature_healthcare import (
    HealthcareAssetCollector, HealthcareAssetRestorer,
)
from kestrel_sovereign.storage.sovereign_adapter import SovereignStorageAdapter

# Source side
adapter_src = SovereignStorageAdapter(storage_src.db, user_secret="…")
collector = HealthcareAssetCollector(
    HealthcareExporter(fhir_src, ccda_src),
    source_did=source_agent_did,
    source_verification_methods=source_agent_vms,
    source_keypairs=[(source_keypair, source_kid)],
    owners=[patient_did],
)
cid = await adapter_src.export_agent(
    source_agent_did, asset_collector=collector,
)

# Receive side
adapter_dst = SovereignStorageAdapter(storage_dst.db, user_secret="…")
restorer = HealthcareAssetRestorer(fhir_dst, ccda_dst)
result = await adapter_dst.import_agent(
    cid,
    grant=grant, host_did=receiving_agent_did,
    asset_restorers=[restorer],
)
```

### HIPAA Safe Harbor de-identification (D3)

```python
from kestrel_feature_healthcare import deidentify_bundle, DeidentifierConfig

deid = deidentify_bundle(
    bundle,
    source_keypairs=[(source_keypair, source_kid)],
    config=DeidentifierConfig(date_strategy="shift"),  # or "year_only" / "remove"
)
# deid.bundle is a new SIGNED bundle with all 18 Safe Harbor
# identifiers stripped/redacted. CCDA documents pass through
# untouched; deid.ccda_documents_skipped surfaces the count.
```

## Development

```bash
uv sync --extra test
uv run --extra test pytest -q                       # fast default suite
KESTREL_HEALTHCARE_E2E=1 uv run --extra test pytest -m e2e  # opt-in cross-feature
```

The default `pytest` run skips the cross-feature `e2e` suite. The
opt-in path exercises the full Phase A → D round-trip (seed → consent
grant → bundle / CAR transport → de-id → read-access gating) on real
stores in one continuous test. CI also exposes the e2e job via
`workflow_dispatch` from the Actions tab.
