Skip to main content

fhiratwill

PyPI Downloads

The Python library behind FHIR at Will. Safety-focused, framework-neutral tools for converting narrative text and audio to FHIR R4, de-identifying narrative, validating resources, and planning writes. Terminology binding is an optional companion package, fhiratwill.terminology_binder.

This repository is the processor. The self-hostable HTTP API, Docker image, Railway deploy, API keys, and validator sidecar live in FHIR-At-Will.

Alpha software. Generated resources remain untrusted until independently validated and reviewed for the intended clinical workflow.

This library vs the production API

These two repositories are the same product, split for different jobs:

You want Use
pip install and call text2fhir / validate from your own code This repo — PyPI package fhiratwill
A self-hostable HTTP API, Docker/Railway images, API keys, HL7 validator sidecar FHIR-It-Will

The production API depends on this package and wraps it with FastAPI, auth, PostgreSQL RLS, BYOK policy headers, sidecar transport, and delivery submission. Algorithm questions belong here. Deploy, auth, and HTTP contract questions belong in the API README.

Name What it is
FHIR at Will Public product
This repository (fhirbridge) Source for the installable library
fhiratwill PyPI package and import name (from fhiratwill import ...)
FHIR-It-Will Production HTTP API repository
fhirbridge The API service's package, container, and API title — not this library

Install

pip install fhiratwill
# Optional terminology binding, LiteLLM text, speech, or all of them:
pip install fhiratwill.terminology_binder
pip install "fhiratwill[binding]"
pip install "fhiratwill[llm]"
pip install "fhiratwill[voice]"
pip install "fhiratwill[all]"

Python 3.11 or newer is required. The base install performs no network I/O and includes no web framework, database, EHR client, or model provider SDK.

How processing works

Validation cascade

validate always returns all eight layers. A check that could not run is marked skipped or not_applicable; it is never allowed to look like a pass.

Layer Check Local mode (no adapters)
L1 Structural FHIR R4 parsing and resource allowlist Runs here
L2 Declared and requested profile conformance skipped unless you inject a ValidatorClient
L3 Code validity and ValueSet bindings skipped unless you inject a TerminologyClient
L4 FHIRPath invariants skipped unless you inject a ValidatorClient
L5 Physiological, temporal, and dose plausibility Runs here
L6 Source-to-output fidelity not_applicable until a source document exists
L7 Omitted clinical mention coverage not_applicable until a source document exists
L8 Auto-accept, review, or reject routing Runs from available signals; never auto-accepts while a required layer was skipped

conformant is true when no blocking issue was found by a layer that ran. status is auto, needs_review, or reject. A structurally valid heart rate of 44000 /min is still rejected by L5 as physiologically impossible.

The production API injects the HL7 validator sidecar and a terminology-server adapter so L2–L4 can run over HTTP. This library does not host those services.

Conversion pipeline

text2fhir and voice2fhir are explicit-adapter pipelines:

  1. Minimize identifiers in the narrative (unless DeidPolicy(mode=DeidMode.OFF)).
  2. Classify which catalog resource types the narrative documents. This call sees the FHIR inheritance model (Resource → DomainResource → type-specific elements) and the extraction rule pack. Skip it by passing resource_type.
  3. Fill in the blanks for only the classified type(s). The model returns JSON matching that resource's required and optional catalog fields. Pass subject_id to wire subject/patient as a reference and omit those fields from the schema.
  4. Restore minimized values only after the fill JSON is parsed.
  5. Assemble typed FHIR deterministically (assemble_bundle).
  6. Optionally bind terminology with nearest-neighbor search over dictionaries (fhiratwill.terminology_binder.bind_bundle; ICD-10-CM by default).
  7. Run the validation cascade on the result.

Assembly refuses rather than approximates: "62-year-old" does not become a birthDate, and "128/82 mmHg" does not become a Quantity of 128. Coded concepts leave assembly as text; a separate binding stage adds a catalog code when a unique nearest neighbor is found.

The classification prompt in this package is the per-type FHIR model guide and the local rule pack in extraction_rules.py. The fill prompt is only the classified resource's required and optional catalog fields. The production API can still compose an additional reviewed pack onto classification; see fhirbridge.llm.extraction_rules in FHIR-It-Will.

De-identify narrative

from fhiratwill import DeclaredIdentifier, IdentifierClass, deidentify

result = deidentify(
    "Synthetic patient Ada Example has MRN TEST-123.",
    known_identifiers=[
        DeclaredIdentifier(IdentifierClass.NAME, "Ada Example"),
        DeclaredIdentifier(IdentifierClass.MRN, "TEST-123"),
    ],
)
safe_text = result.text

Enforced HIPAA Safe Harbor-style minimization is the default. Detection is deterministic, known identifiers can be supplied by the caller, and the reversible in-memory vault is cleared before the function returns. The output is still sensitive clinical data and this feature does not by itself establish HIPAA or other regulatory compliance.

Validate a FHIR resource

from fhiratwill import validate

report = await validate(bundle)

Local mode runs structural and deterministic plausibility checks. It still returns all eight layers: checks that require terminology or validator adapters are marked skipped, source-evidence checks are not_applicable, and routing never claims automatic acceptance while a required layer was skipped. Inject adapters for profile, terminology, and FHIRPath validation; their outages propagate instead of becoming passes.

For the HTTP equivalent (POST /v1/validate) and the HL7 validator sidecar, use FHIR-It-Will.

Convert text to FHIR

text2fhir accepts any implementation of the LlmClient protocol. Provider, model, key, cost bound, and PHI acknowledgement are explicit request values; the library never reads them from environment variables or HTTP headers.

from decimal import Decimal

from pydantic import SecretStr

from fhiratwill import LlmInvocation, text2fhir

result = await text2fhir(
    "Synthetic patient reports a temperature of 38.2 C.",
    llm=my_llm_client,
    invocation=LlmInvocation(
        provider="local",
        model="reviewed-model",
        api_key=SecretStr("synthetic-key"),
        base_url="http://127.0.0.1:4000",
        max_cost_usd=Decimal("0.10"),
    ),
    seed="conversion-123",
    resource_type="Observation",  # omit to classify first
    subject_id="Patient/123",  # omit to let the model fill subject when stated
    terminology=my_terminology_client,  # omit to return explicit unbound evidence
)
bundle = result.bundle  # fhir.resources.R4B.bundle.Bundle
observation = result.resources[0]  # fhir.resources.R4B.observation.Observation
print(observation.valueQuantity.value, observation.valueQuantity.unit)
payload = result.bundle_dict  # the same Bundle as FHIR JSON
report = result.validation

Typed results and fhir.resources

fhiratwill speaks fhir.resources R4B end to end. A conversion returns typed model objects, and every element fhiratwill knows about is read from those same models rather than from a private table:

  • which elements a resource has, and their datatypes and cardinality;
  • which elements are required, and which belong to a value[x] choice;
  • what a Reference may point at, which is how subject and encounter links are wired during assembly;
  • whether a narrative string can be placed into an element at all, which decides the blanks the model is asked to fill.

fhiratwill.terminology_binder.bind_bundle, validate, rebind_bundle, run_preflight, and compile_write_plan accept either a typed Bundle or FHIR JSON, so result.bundle can be passed straight through. Use fhiratwill.element_specs, fill_elements, and resolve_datatype to inspect the same facts yourself.

The pipeline minimizes identifiers before model egress, classifies resource types (or uses a caller-supplied resource_type), fills the blanks of that resource's R4B schema, restores values only after parsing, assembles deterministically into typed models, optionally binds terminology, and runs validation. The result retains evidence for every stage, including classified_types and the optional classification call. Use classify_narrative when you only need the type list (ClinNarr-FHIR Test 1). An explicit DeidPolicy(mode=DeidMode.OFF) is required to disable minimization.

For the HTTP equivalent (POST /v1/NAR2FHIR) with BYOK headers and policy gates, use FHIR-It-Will.

Convert voice to FHIR

from fhiratwill import SpeechInvocation, voice2fhir

result = await voice2fhir(
    audio_bytes,
    media_type="audio/wav",
    speech=my_speech_client,
    speech_invocation=SpeechInvocation(
        provider="local",
        model="reviewed-stt-model",
        api_key=SecretStr("synthetic-key"),
        base_url="http://127.0.0.1:4000",
    ),
    llm=my_llm_client,
    llm_invocation=text_invocation,
    seed="conversion-124",
)

Audio is size-limited and external raw-audio egress is denied by default because audio cannot be de-identified before transcription. External speech requires explicit permission at both the high-level call and adapter policy. The transcript then follows the exact text pipeline.

The optional LiteLLM adapter is imported explicitly:

from fhiratwill.adapters.litellm import LiteLlmClient, LiteLlmPolicy

llm = LiteLlmClient(
    LiteLlmPolicy(
        local_only=False,
        egress_allowlist=frozenset({"api.openai.com"}),
    )
)

External invocations must also set phi_egress_acknowledged=True. Provider failures are normalized into PHI-safe exceptions; prompts, transcripts, provider bodies, and keys are never included in those exceptions.

For the HTTP equivalent (POST /v1/VOICE2FHIR) with X-STT-* headers, use FHIR-It-Will.

Assemble deterministically

from fhiratwill import assemble_bundle

entities = [
    {"resourceType": "Patient", "instance": "subject", "keyword": "gender", "value": "female"},
    {"resourceType": "Observation", "instance": "pulse", "keyword": "code", "value": "heart rate"},
    {
        "resourceType": "Observation",
        "instance": "pulse",
        "keyword": "valueQuantity",
        "value": "72/min",
    },
]
result = assemble_bundle(entities, seed="conversion-123")
bundle = result.bundle

Assembly keeps coded concepts as text, refuses unsafe coercions, records PHI-free notes, and produces stable UUIDs for a stable seed. It does not validate clinical truth.

Bind using nearest-neighbor dictionaries

Install the companion package (pip install "fhiratwill.terminology_binder[nn]"). ICD-10-CM is the default dictionary. Add more JSON catalogs as needed.

from fhiratwill.terminology_binder import Dictionary, TerminologyIndex, bind_bundle

index = TerminologyIndex.load()  # ICD-10-CM
index.add_dictionary(Dictionary.from_path("loinc.json"))
result = await bind_bundle(bundle, index=index)

Each catalog display and alias is embedded with SapBERT; a phrase is bound to the nearest catalog row by cosine similarity. Original CodeableConcept.text is retained. Pass verify=True and a TerminologyClient if you also want the hit checked against a terminology server.

Rebind and compile a write plan

from fhiratwill import (
    GENERIC_FHIR_TARGET,
    SubjectContext,
    compile_write_plan,
    rebind_bundle,
)

context = SubjectContext(patient_ref="Patient/123", encounter_ref="Encounter/456")
rebound, notes = rebind_bundle(bundle, context)
plan = compile_write_plan(
    bundle=bundle,
    context=context,
    conversion_id="conversion-123",
    tenant_id="tenant-7",
    descriptor=GENERIC_FHIR_TARGET,
)

Generated Patient and Encounter resources are always excluded. References are rebound only to caller-supplied context. The optional preflight adapter supports direct reads only—never identity search. Write plans contain deterministic idempotency keys and, when supported by the descriptor, a FHIR transaction Bundle.

This library stops at the plan. Submitting a transaction to an EHR is the production API (POST /v1/deliver).

Scope

This library:

  • de-identifies narrative using deterministic, reviewed rules;
  • validates FHIR locally and can orchestrate caller-provided validation adapters;
  • converts text or transcribed audio through explicit model adapters;
  • assembles grounded entity mappings into deterministic FHIR resources;
  • optionally binds terminology through fhiratwill.terminology_binder (nearest-neighbor dictionaries, ICD-10-CM by default);
  • checks and rebinds caller-supplied subject context; and
  • compiles destination-neutral write plans.

This library does not expose HTTP endpoints, authenticate callers, persist tenants, host a terminology or validator service, submit data to an EHR, implement SMART authentication, guarantee that model output is clinically correct, provide clinical decision support, or establish HIPAA, GDPR, medical-device, or other regulatory compliance. Those production concerns are the FHIR-It-Will service, which depends on this package. Full profile, terminology, and invariant validation still requires authoritative adapters and deployment-specific implementation guides.

Development

python -m pip install -e ".[dev]"
python -m pip install -e ./packages/terminology-binder
ruff check .
mypy
pytest
python -m build
python -m build packages/terminology-binder
twine check dist/* packages/terminology-binder/dist/*

No publishing command is part of the development workflow. See CONTRIBUTING.md and SECURITY.md before contributing.

License

Apache License 2.0.

Metadata

Release files for fhiratwill 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fhiratwill 0.4.0
File Size Uploaded
fhiratwill-0.4.0.tar.gz 76.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fhiratwill 0.4.0
File Interpreter ABI Platform
fhiratwill-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 156.8 kB

Release files / fhiratwill-0.4.0.tar.gz

Download URL fhiratwill-0.4.0.tar.gz
Size 76.5 kB
Tags Source
SHA-256 checksum
How to use checksums
9e07bdef722b50fdeed09a19463689e18366d52d00169c6621397f14fbbab318
BLAKE2b-256 checksum
How to use checksums
8dfd4caef9c5b379bc84613d1e36b6ea277c53d09fce405409e3a16681c64696
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / fhiratwill-0.4.0-py3-none-any.whl

Download URL fhiratwill-0.4.0-py3-none-any.whl
Size 80.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
38b189ab796a14bde2d1ba982a8de6d7ad83160e632870764c5fcde55a72beb6
BLAKE2b-256 checksum
How to use checksums
363a73b01c5fb602008c98fa1f76b4d2cc5ff4ababd71fd1a275988d763677a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page