Skip to main content

Anastomosis

Reconstruct, verify, and re-home clinical records.

anastomosis (n., medicine) — a surgical connection between two structures. This toolkit is that connection for electronic health records.

The problem

Every certified US EHR is legally required to export a practice's full Electronic Health Information (21st Century Cures Act, §170.315(b)(10)) — but the law doesn't say in what format. So practices that exercise their right get a pile of raw vendor tables, and the things that matter most — the clinical notes — routinely fail to survive migration to a new system: a systematic review of EHR-to-EHR transitions identifies data migration and continuity of the legacy record as persistent, under-addressed risk areas (Miake-Lye et al., JGIM 2023, doi:10.1007/s11606-023-08276-3), while commercial legacy-archive subscriptions put simple retention behind recurring fees; small practices get priced out or locked in.

Anastomosis is the missing last mile, free and open source:

  1. Ingest raw EHI exports — Practice Fusion/Tebra TSV, C-CDA/CCD, FHIR R4 (Bundle or Bulk-Data NDJSON), and Oracle Health/Cerner Millennium V500 dumps (experimental: the single-patient export's seven core tables, giving demographics, encounters, observations, conditions, allergies, and documents — no medications, procedures, or immunizations yet) — into a lossless canonical model: every unmapped field, and every unmapped table the export carries, is preserved verbatim in extensions; an export carrying data that cannot be placed is refused rather than silently dropped. Meet an export it doesn't recognize? Teach it that format from a single example, and it maps to the canonical model from then on.
  2. Reconstruct human-readable clinical documents from template packs: a neutral SOAP layout, a standard HL7 C-CDA stylesheet view, or a sample-learned vendor replica (the Practice Fusion pack reproduces that chart's layout and typography — or learn a new layout from your own sample PDFs).
  3. Verify every rendered document with a multi-layer QA engine (data-integrity, layout, and identity checks), and guard every upload with the L0–L6 verification ladder — on by default (--no-verify to skip; the run report names exactly which levels ran) — plus a live wrong-patient banner check that runs regardless. Active coverage depends on the destination AND the route. On the upload path (anast upload / the GUI console): L0 (file integrity) and L2/L4 (patient identity + live banner readback) run on every upload; L1 runs but only checks that the PDF opens, has ≥1 page, and clears the sub‑KiB floor — its exact expected‑page‑count check is not active here; L3 (pack‑driven header/DOS fields) is not active here either. Both are inactive because the upload manifest carries no pack name, per‑item expected page count, or encounter records to check against (wiring them is a tracked manifest‑schema follow‑up). L5/L6 (destination metadata + byte/identity round‑trip) run when the destination supports read‑back. Every level that does not apply skips with a reason recorded in the run report, so the claim never exceeds the coverage.
  4. Deliver by the shortest available path: a vendor API where one exists, C-CDA import where supported, or verified browser automation where neither does. migrate PREPARES a cross-EHR move — it writes verified charts, the structured C-CDA payload for destinations that import C-CDA, an upload manifest, and a verified route plan — but files nothing itself; anast upload EXECUTES delivery over either route: the destination's web UI (--to PACK --cdp URL) or its FHIR R4 API (--fhir URL), whichever the destination actually offers. The rendered PDF is the human-readable archive and upload fallback, never a forgery of another vendor's house style. Or build a searchable offline archive — plain folders, PDFs, and JSON readable for decades — in place of a paid legacy-archive subscription.

Local-first by design: the core pipeline makes zero network calls. Your records never leave your machine.

Status

v0.7.0 (alpha) — the seventh alpha, on PyPI and GitHub. See CHANGELOG.md for what shipped, DESIGN.md for the design record, and docs/PLAN.md for the roadmap.

Capability State
Foundational pipeline — ingest → reconstruct → QA → searchable archive ✅ v0.1.0
Migration mode — migrate from→to, verified delivery engine, destination registry + router ✅ v0.2.0
Learn a source format from one example; FHIR R4 ingest; standard C-CDA render ✅ v0.2.0
Pack-from-samples layout learner ✅ v0.2.0
Desktop GUI — pipeline dashboard, migration wizard, upload console ✅ v0.2.0
Windows desktop installer — bundles Chromium offline, installs WebView2 if absent, with an installed self-check ✅ v0.3.0
PHI-at-rest & log hardening — Windows NTFS ACLs on every output dir, redacting log handler wired at both entry points, CodeQL scanning, design/provenance record ✅ v0.4.0
Run-scoped log identifiers (no raw source GUIDs in logs), git-free PHI scanner, one-click release path shipping the installer ✅ v0.5.0
Claims-match-runtime pass — honest migrate PREPARED outcome, QA on every render mode, one upload verdict for CLI + GUI, flow-scoped GUI events, race-free pack trust ✅ v0.6.0
External-audit closure — one boundary-anchored identity predicate behind every wrong-patient gate, lossless refusals that name what they refuse, redirect-refusing FHIR client, verified Windows ACLs, budgeted delivered paths, licensed artifacts ✅ v0.7.0

Built and tested entirely against synthetic data; see docs/DISCLAIMER.md for production-readiness notes.

Install

Anastomosis is live on PyPI. The recommended install is pipx (or plain pip):

pipx install "anastomosis[render]"      # CLI + rendering
pip install "anastomosis[render,gui]"   # … or with the desktop GUI
playwright install chromium             # one-time: the rendering browser

For development, install from a clone:

git clone https://github.com/AzalDaniel/Anastomosis.git
cd Anastomosis
pip install -e ".[render]"        # add [dev] to run the test suite
playwright install chromium      # one-time: the rendering engine's browser

Windows app (no Python required)

Prefer a normal installer? Download Anastomosis-Setup-<version>.exe from the Releases page and run it. It bundles its own Python runtime and the Chromium render engine, so there is no separate pip or playwright step. The GUI renders through the Edge WebView2 runtime; if your machine lacks it the installer fetches and installs it silently (most Windows 10/11 machines already have it). It installs the desktop GUI (a Start-menu shortcut) and the anast command-line tool (an optional "add to PATH" task), and registers a normal uninstaller.

Offline/air-gapped machines: the WebView2 step downloads from Microsoft, so on a machine without internet and without WebView2 already present, install the Edge WebView2 Runtime (the Evergreen Standalone Installer) separately first. The anast CLI does not need WebView2; only the desktop GUI does.

The alpha installer is not yet code-signed, so Windows SmartScreen will show a "Windows protected your PC" warning. To proceed, click More info → Run anyway. (Code signing is planned for the general-availability release.) After installing, you can verify the install is intact with anast doctor.

Quickstart

One command takes a raw EHI export to verified chart PDFs and a searchable offline archive:

anast pipeline run ./my_ehi_export --out ./charts --archive ./my_archive

The source format is auto-detected (or pass --source pf-tebra/ccda/fhir-r4/ oracle-ehi, the last of which is experimental — see the ingest note above); --pack selects the document template; every rendered document is QA-verified by default; anast info lists every available source adapter and template pack.

Migrate from one EHR to another — emitting both the structured C-CDA payload for the target's C-CDA import (validated for round-trip fidelity through our own parser; full CDA-schema/Schematron conformance is on the M6 roadmap) and human-readable charts:

anast migrate ./my_ehi_export --from pf-tebra --to tebra --out ./migration

Meet an export format it doesn't recognize? Teach it once from a single example, then ingest that format like any built-in source:

anast source init ./example_export.csv --name acme_clinic

How it works — file-by-file

The pipeline is five conceptual stages — ingest → canonical model → reconstruct → verify → deliver — built as isolated, versioned modules behind defensive registries. One line per package in src/anastomosis/, each naming the property that was hardest to get right:

src/anastomosis/
├── core/
│   ├── model/        canonical, FHIR R4-aligned pydantic v2 records. LOSSLESSNESS:
│   │                 AnastBase carries an `extensions` dict so no unmapped source
│   │                 column is ever dropped; Encounter holds SOAP NoteSections +
│   │                 addenda; Observation covers vitals + social history.
│   ├── fhir/         PatientRecord ↔ FHIR R4 Bundle. EXACT ROUND-TRIP: to_bundle →
│   │                 from_bundle reproduces the record, extensions carried through
│   │                 urn:anastomosis namespaces (fhir.resources R4B available as an extra).
│   ├── timeutil/textutil/codes  sentinel-safe parsing (`\N`/`-1`/`1/1/0001` → None,
│   │                 never a fake value), 7-format dates, zoneinfo local time, LOINC vitals.
│   ├── logutil       RedactionFilter + exc_tag() + safe_log_id(): logs counts,
│   │                 run-scoped id surrogates, and exception
│   │                 TYPE names — never patient-derived values.
│   └── output        output dirs created 0o700 with a PHI-warning README.
├── sources/
│   ├── pf_tebra/     Practice Fusion/Tebra EHI v9 adapter. Joins the 29-table
│   │                 (KNOWN_TABLES) export graph; the mapper declares consumed
│   │                 columns and routes EVERY other column to `extensions` under a
│   │                 `pf_tebra:` key; escript resolves status by transaction priority.
│   ├── ccda/         C-CDA R2.1 / CCD ingest. HARDENED XML (resolve_entities=False,
│   │                 no_network=True, load_dtd=False, huge_tree=False); unmapped
│   │                 sections preserved under `ccda:section:<loinc>` extension keys.
│   ├── oracle_ehi/   Oracle Health/Cerner Millennium EHI adapter (V500 single-patient
│   │                 export) — EXPERIMENTAL, deliberately partial. Dependency-free
│   │                 MySQL INSERT-dump reader over `v500/{schema,activity,reference}`
│   │                 that reads the SEVEN core tables (plus the CODE_VALUE dictionary):
│   │                 PERSON/ENCOUNTER/CLINICAL_EVENT spine, CE_BLOB note text +
│   │                 CE_BLOB_RESULT remote refs (never fetched). Maps Patient,
│   │                 Encounter, Observation, Condition, AllergyIntolerance, and
│   │                 DocumentArtifact; medications, procedures, and immunizations are
│   │                 not mapped yet. Unmapped columns to `oracle_ehi:` extensions;
│   │                 undocumented CE_BLOB compression raises loudly rather than guessing.
│   ├── fhir_r4/      FHIR R4 / US Core ingest — a Bundle or a Bulk-Data `$export`
│   │                 NDJSON directory → canonical records; unmapped fields → `extensions`.
│   └── learned/      LEARN-A-SOURCE: a single generic adapter that runs a saved,
│                     validated mapping (DATA, no code) for a structured export taught
│                     from one example via `anast source init`; auto-detected by a
│                     column fingerprint; unmapped columns → `extensions`.
├── reconstruct/      Jinja2 + Chromium rendering engine + defensive pack registry.
│                     Renderer recycling, crash relaunch, deterministic
│                     collision-suffixing, idempotent skip; a broken pack is
│                     diagnosed and disabled WITHOUT taking the system down;
│                     `generic_soap` built-in with user-togglable section flags.
│                     `ccda_standard/` renders the structured C-CDA through HL7's
│                     vendored CDA.xsl to a neutral per-patient PDF — no network egress —
│                     for the `migrate --render ccda-standard` mode.
├── packgen/          pack-from-samples LAYOUT LEARNER. PyMuPDF-only, fully offline,
│                     no torch: font histogram → type scale, x-position bucketing →
│                     column grids (deliberately explainable greedy clustering, not a
│                     black box), get_drawings() fills → design tokens, bold spans
│                     recurring across ALL samples → section taxonomy + static-text
│                     intersection; emits a loadable draft pack with a same-patient caveat.
├── qa/               every rendered document is verified. CheckResult{pass|warn|fail}
│                     over data_integrity, layout_pagination, vitals_loinc, and
│                     date_staleness checks, with BOUNDARY-ANCHORED matching because
│                     naive substring matching false-passes missing content; a FAIL
│                     exits the pipeline nonzero.
├── deliver/
│   ├── archive/      ARCHIVIST output: static, zero-network, `file://`-openable
│   │                 archive — strict CSP, relative assets only, per-encounter HTML,
│   │                 PDFs, and a FHIR R4 Bundle per patient, readable for decades.
│   ├── bundle/       RESPONDER output: per-patient chart bundle (FHIR Bundle + PDFs)
│   │                 with the QA report SLICED to that one patient's documents.
│   ├── browser/      verified browser-automation upload engine. The 15-STATE LEDGER:
│   │                 a WAL-mode SQLite state machine (UploadState, LEGAL_TRANSITIONS)
│   │                 that survives a hard kill mid-upload and resumes; FakeDestination
│   │                 test double drives a kill-and-resume test with no real I/O.
│   ├── verify/       the L0–L6 verification ladder — the wrong-patient defense.
│   │                 L0 file integrity, L1 page/size, L2 identity fuzzy ≥0.88 + DOB
│   │                 hard-fail, L3 pack-driven header fields, L4 live banner readback,
│   │                 L5 destination metadata, L6 byte/identity round-trip.
│   ├── fhir_api/     FHIR R4 DocumentReference pusher over stdlib urllib (https, or
│   │                 http only for loopback); status codes + resource TYPE names in errors.
│   │                 attach.py: the construction seam the CLI upload command calls
│   │                 (`anast upload --fhir`), twin of deliver/browser/attach.py.
│   └── ccda_export/  PatientRecord → C-CDA R2.1, for destinations that import C-CDA;
│   │                 its contract is that THIS repo's own ccda parser reads it back.
│   └── router.py     SHORTEST-PATH router: vendor API → C-CDA import → browser
│                     automation; an `unverified` capability is never viable.
├── destinations/     EVIDENCE-OR-REFUSE capability registry (registry.yaml is DATA):
│                     every non-`none` capability MUST carry a source_url + verified
│                     date or validation fails loudly; the tebra browser pack ships
│                     with DISCOVER placeholder selectors (operator-derived via the
│                     wizard) so no vendor DOM is ever invented.
├── gui/              pywebview shell over a headless, fully testable controller and
│                     thin vanilla-JS pages: pipeline dashboard with live counters, a
│                     migration wizard (transit map + the full CLI lever set), a
│                     learn-a-source wizard, and an upload console that DRIVES the engine
│                     over the 15-state ledger (loopback-only; never closes your browser).
├── pipeline.py       the frontend-free pipeline core: emits PHI-safe StageEvents
│                     (detect → ingest → reconstruct → QA); CLI and GUI drive the SAME code.
└── cli.py            the `anast` (and `anastomosis`) CLI: `pipeline run`, `info`,
                      `doctor`, `gui`, `migrate`, `upload`, `archive`, `bundle`,
                      `destination {list,route,init}`, `pack init`, `source init`.

Two delivery routes, one engine

anast upload files a prepared output dir into a destination EHR over exactly one route per run. Both routes share everything that matters — the crash- resumable ledger, the retry budget, the skiplist, the L0–L6 verification ladder, the run report, and the exit-code verdict — because both hand the same Destination protocol to the same engine.

# Browser route: drive the destination's web UI in a browser YOU launched
# with a remote debug port and logged into yourself (loopback CDP only).
anast upload out/ --to tebra --cdp http://127.0.0.1:9222

# API route: file each chart as a FHIR R4 DocumentReference over HTTPS.
export ANAST_FHIR_TOKEN='...'          # the token never appears in argv
anast upload out/ --fhir https://ehr.example.com/fhir

Exactly one route must be selected: --to and --cdp together, or --fhir alone. Half a browser route, both routes at once, or neither is a usage error (exit 2) rather than a run that guesses. The GUI supports the browser route today; use the CLI for the FHIR route.

Bearer token handling. The API route never accepts a token on the command line — argv is visible to every process on the box (ps). It reads the token from an environment variable, ANAST_FHIR_TOKEN by default, or whichever variable --fhir-token-env VAR names when you keep several destinations apart. An unset (or blank) variable means unauthenticated, the normal case for a local HAPI server. The token is masked in every repr, so it cannot reach a log line or a traceback frame.

Patients that are not there yet. --create-patients (ON by default on this route) lets the resolver POST a new Patient — built by the same export code, so the identifier systems and the lossless extensions tail come along — when the destination holds none matching. A search that matches more than one patient is always refused: filing a chart against a guessed patient is the failure the whole subsystem exists to prevent. Pass --no-create-patients when the target is supposed to already hold every patient.

Verification coverage differs by route, and the report says so. On the API route L3 (pack-driven header fields) skips — there is no browser pack — while L5 (destination metadata) and L6 (byte/identity round-trip) run, because a FHIR server can be read back. The browser route is typically the reverse. Neither route claims more than it ran: the run report records each level's pass/fail/skip counts and the reason for every skip.

Design rationale

  • A five-stage pipeline over one canonical model. Ingest, canonical model, reconstruct, verify, deliver. Routing everything through a single lossless, FHIR R4-aligned model (core/model/) means every new source adapter and every new destination only has to speak to the model — not to each other — so the matrix of (sources × destinations) collapses to (sources + destinations).
  • Verification is the core product, not a test. A migration that puts the right notes in the wrong chart is worse than no migration. So the QA engine verifies every rendered document, and the delivery path runs the L0–L6 ladder around every upload by default (--no-verify to skip; a missing render extra refuses rather than files unverified) plus a live wrong-patient banner check — boundary-anchored and identity-based, because the naive matches (substring, whole-page similarity) demonstrably false-pass the exact failures these checks exist to catch. The truthful active coverage is destination- dependent (L0/L1/L2/L4 always; L3 with pack context; L5/L6 with destination read-back) and the run report names every level that skipped and why, so the product claim cannot drift wider than the runtime.
  • Packs and registries make a vendor change a one-module event. Source adapters, template packs, QA checks, and destinations are versioned modules behind defensive registries; the capability registry is data with cited evidence. A vendor changing an export format, a UI, or an API touches one module — never the system.
  • Local-first PHI posture. The core pipeline makes zero network calls; PHI never leaves the operator's machine. Logs carry counts, run-scoped id surrogates, and exception type names — never values or raw source identifiers — behind a redaction filter installed at both entry points, and output directories are locked down on both platforms (owner-only 0o700 on POSIX; on Windows NTFS, ACL inheritance is stripped and access restricted to the current user, SYSTEM, and Administrators).

The desktop GUI

anast gui opens the desktop app (the gui extra): a pipeline dashboard with live ingest/reconstruct/QA/deliver counters, a migration wizard that shows the destination transit map and the full set of run levers, a learn-a-source wizard, and an upload console that drives the delivery engine over its ledger. The GUI supports the browser route today; use the CLI for the FHIR route.

Privacy & safety

  • No PHI in this repository, ever. All fixtures are synthetic (Synthea-generated or hand-built with feedface- GUIDs). A hashed deny-list scanner (tools/phi_scan.py) runs on every commit and in CI.
  • You run this software on machines you control; you are responsible for HIPAA compliance in your environment. See SECURITY.md and docs/DISCLAIMER.md.

License

AGPL-3.0-or-later. Free for everyone to use, study, and improve — and anyone who offers it as a service must share their changes back. No CLA: contributors keep their copyright, which makes proprietary relicensing permanently impossible.

Metadata

Release files for anastomosis 0.7.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 anastomosis 0.7.0
File Size Uploaded
anastomosis-0.7.0.tar.gz 1.7 MB Details

Built distribution (wheel)

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

Total release size: 3.1 MB

Release files / anastomosis-0.7.0.tar.gz

Download URL anastomosis-0.7.0.tar.gz
Size 1.7 MB
Tags Source
SHA-256 checksum
How to use checksums
1d4336a3f6b1c8fd5eafc7e6376f00aabf951de8351cb369c53620da3aca5421
BLAKE2b-256 checksum
How to use checksums
ac6f01497fb8c380e2af90a3561aa8fa511c3dfd5855dcbc1ad3055d5a91adc3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 Aug 21, 2026.

Transparency log

Release files / anastomosis-0.7.0-py3-none-any.whl

Download URL anastomosis-0.7.0-py3-none-any.whl
Size 1.3 MB
Tags Python 3
SHA-256 checksum
How to use checksums
1ad83461906046afccd45abd31a9c7190b5a86de87eb3f9d129c8d029b7b32bd
BLAKE2b-256 checksum
How to use checksums
c89cc2fbb7c3af9f7c8906d3dd8545e1023a286aefabe26b5df946100ee3168b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 Aug 21, 2026.

Transparency log

Release history Release notifications | RSS feed

0.8.0

2 release files

This release

0.7.0 This release

2 release files

0.6.0

2 release files

0.5.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