Healthcare AI · Agentic Pipeline · HIPAA-Aligned

EMR Agentic AI — An 11-Agent Pipeline
for HIPAA-Aligned Clinical Documentation

A reference implementation of the technology stack behind AI-assisted clinical documentation: a multi-agent pipeline that turns a doctor's raw, fragmented FHIR chart into a single, condition-centric pre-fill form — de-identified, evidence-grounded, hallucination-checked, and RBAC-gated, before a clinician ever sees it. Every component runs end to end against a real local Postgres database and either bundled synthetic FHIR data or a live HAPI FHIR Docker server — no mocked responses over fake data.

Project Type Reference Implementation
Core Pipeline 11-Agent LangGraph
Standards HL7 v2 · FHIR R4
Industry Healthcare / Clinical Documentation
EMR Agentic AI — Secure, Intelligent, Clinician-Driven Architecture

At a Glance

MetricValue
Agents11 (LangGraph StateGraph, RBAC-gated)
StandardsHL7 v2, FHIR R4
BackendFastAPI, PostgreSQL/SQLAlchemy
AIOpenAI GPT-4o
Vector DBQdrant (RAG)
Tests426 passing, offline by default
SecurityJWT (HS256), AES-256-GCM, sha256 audit hash-chain, RBAC
StatusReference implementation — not yet deployed to a real hospital

Architecture Highlights

11-Agent LangGraph Pipeline React Clinician UI FHIR R4 Native HL7 v2 Ingestion RAG (Qdrant) Human-in-the-Loop Tamper-Evident Audit Trail Verified JWT Auth AES-256-GCM Encryption Role-Based Access Control

Overview

EMR Agentic AI is a reference implementation of the technology stack behind AI-assisted clinical documentation: a multi-agent pipeline that turns a doctor's raw, fragmented FHIR chart into a single, condition-centric pre-fill form — de-identified, evidence-grounded, hallucination-checked, and RBAC-gated, before a clinician ever sees it. It was built to demonstrate the exact skill set a healthcare AI engineering role requires: clinical workflow translation, FHIR/HL7 interoperability, agentic pipeline architecture, and the judgment to build a system that is trustworthy, not just impressive, in a domain with near-zero tolerance for a hallucinated diagnosis or a leaked identifier.

Every component is real and runs end to end against a local Postgres database and either bundled synthetic FHIR data or a live HAPI FHIR Docker server — no mocked responses over fake data.

The Problem

A doctor loses 2–4 hours a day to manual EMR data entry, and the reasons don't fix themselves independently:

UI Gap

The same data is re-typed across 6+ screens because nothing pre-populates from what the system already knows.

Processing Gap

Feeding a raw chart straight into an LLM produces hallucinations; data has to be cleaned and structured first.

Intelligence Gap

An LLM answer is only useful if it's traceable back to a real source in the chart.

Cognitive Load Gap

Data organized by type (Labs tab, Meds tab) forces the clinician to mentally reassemble the picture; real clinical reasoning is organized by condition.

The hard part is that closing these gaps requires an AI pipeline touching real patient data, in a domain with near-zero tolerance for the two things generative AI is worst at by default: making things up, and seeing information it shouldn't have touched.

6 screens to 1 screen — condition-centric pre-fill instead of siloed EMR tabs

The Solution

An 11-agent LangGraph StateGraph, RBAC-gated so each role only runs the agents its permissions cover:

EMR Agentic AI Architecture — 11-Agent LangGraph Pipeline for Secure, AI-Powered Clinical Documentation
LayerTechnology
BackendFastAPI, PostgreSQL, SQLAlchemy
OrchestrationLangGraph (StateGraph)
AI / ReasoningOpenAI GPT-4o
RetrievalQdrant (RAG)
InteroperabilityHAPI FHIR (R4), hl7apy (HL7 v2)
AuthJWT (python-jose, HS256)
Testingpytest (426 tests)

How It Was Built

The build was sequenced across five phases, each clearing a named architectural gate before the next workstream was allowed to build on it.

Phase 1 — Foundation

A pluggable FHIR client, a Phase-1 stub pipeline, a condition-centric response schema built directly from real nurse-practitioner workflow feedback, and a real React/TypeScript clinician UI wired to the API.

Phase 2 — PHI De-identification + Audit Trail

Real 18-identifier stripping and free-text NER redaction, a HIPAA audit log, and stable pseudonymous patient identity via an HMAC blind-index lookup.

Phase 3 — Orchestration + RAG + Risk Detection

Migrated to a LangGraph StateGraph with RBAC-driven routing; added Timeline Chronicler, Qdrant-backed Evidence Retrieval, a Clinical Risk Detector, and a real HAPI FHIR Docker backend.

Phase 4 — Reasoning + Verification + Live Terminology

Added evidence-grounded differential diagnosis and a Verification Agent with a numeric confidence score; swapped hardcoded ICD-10/LOINC dictionaries for a live terminology service — which surfaced a real coding-safety bug (see Challenges below).

Phase 5 — Verified Auth + HL7 + Security Hardening

Replaced an honor-system role parameter with a verified JWT; built a from-scratch HL7 v2 → FHIR translator; closed with an RBAC allow-list fix, a tamper-evident audit hash-chain, rate limiting, and security headers.

Key Engineering Decisions

PHI Boundary ≠ UI Boundary

PHI is always stripped before anything reaches the LLM, regardless of caller role. Whether a clinician's own UI may show a patient's real name is a separate, narrowly-scoped RBAC decision — conflating the two is a common healthcare-AI design mistake.

Stable Identity Without a Queryable MRN

The same patient needed the same pseudonymous ID across visits, without ever storing a queryable plaintext MRN. Solved with an HMAC-SHA256 blind index — an equality lookup and uniqueness constraint without ever decrypting the encrypted MRN column.

RBAC as an Allow-List, Not a Deny-List

A deny-list only rejected one role, silently letting a later-added machine-credential role fall through to the full patient list. Fixed by inverting to an explicit allow-list, with every denial audited — a deny-list fails safe until the next role is added; an allow-list fails safe by construction.

Audit Trail Attributable to a Person, Not a Role

Roughly 30 call sites across the pipeline's audit-writing code were passing the role string ("doctor") as the acting user's ID — the audit trail could show what happened and what role did it, but never who. In a real HIPAA audit trail, that's the difference between "a doctor edited this record" and "Dr. Jones edited this record." Fixed by threading a real user_id, distinct from role, through the entire pipeline.

Verification as a Separate Stage, Not a Prompt Instruction

Rather than asking the Reasoning Agent to "please cite your sources," a separate Verification Agent independently cross-checks every claim against the actual retrieved evidence — deterministically first, falling back to an LLM faithfulness judgment only when the deterministic check can't resolve it. A model grading its own homework is a weaker safety property than a second, narrower system whose only job is checking the first one's work.

Challenges Encountered — and How They Were Resolved

A ResponseValidationError invisible to the entire test suite.

GET /prefill/{id} degraded correctly to a stub response for a low-access role, but FastAPI's response_model validated independently of what the handler returned and rejected it with a 500 — invisible to every test because they all called the pipeline function directly, bypassing FastAPI's response layer. Caught only by adding real HTTP tests. The lesson: verify through the interface a user actually touches, not just the function underneath it.

A same-day tamper-evidence false positive, caught during this project's own live-verification pass.

After adding a sha256 hash chain to the audit log, the first live run against real Postgres flagged every row as tampered. Root cause: the hash was computed from a timezone-aware timestamp at insert time, but the column itself is timezone-naive — Postgres silently drops the timezone, so re-reading the row produces a different string than what was hashed. Fixed by normalizing the timestamp consistently before hashing, with a regression test pinning the exact case. The lesson: a live pass against a real database is not optional even when unit tests are green.

A live terminology-API ranking bug that would have silently miscoded new conditions.

After swapping a hardcoded ICD-10 dictionary for a live NLM Clinical Tables lookup, live testing surfaced spurious "ICD-10 conflict" flags against already-correctly-coded conditions. The API's raw #1 search hit doesn't reliably rank the generic/canonical code first — the same bug would have silently miscoded any new condition with nothing existing to catch the mismatch against. Fixed with a safety-conservative disambiguation pass that penalizes subtype/etiology qualifiers, and raises an explicit "ambiguous — needs manual review" flag rather than guessing when it still can't settle on one candidate.

Security & Compliance Posture

Mapped explicitly against HIPAA's technical safeguards:

PHI De-identification Before Any LLM Call
RBAC on Every Endpoint, from a Verified JWT
sha256 Hash-Chained Audit Trail
AES-256-GCM Encrypted PHI Mapping Table
Human-in-the-Loop by Construction
Security Headers + Per-IP Rate Limiting

Just as important, this is stated plainly: TLS termination, KMS/HSM-managed keys, storage-layer encryption at rest, and MFA/SSO are deliberately deferred, built only when a real deployment target exists rather than speculatively. There has been no professional security audit yet.

Engineering Validation

426 Automated tests passing, zero network/Docker/API-key dependency by default
Live-Docker RBAC, audit attribution, hash chain, rate limiting, and headers verified against real Postgres + HAPI FHIR
4 Gates Named architectural gates cleared with live evidence before each dependent workstream proceeded
Swagger UI — GET /audit endpoint response, regulator role

Regulator-facing audit endpoint with role-based access control and patient-level filtering.

Terminal output — audit hash chain verification, intact

Tamper-evident audit chain verification confirming log integrity across the complete audit trail.

Honest Limitations

Stated deliberately, because a system that hides its limitations is worth less than one that states them:

Not yet deployed to a real hospital or validated by practicing clinicians at scale — external expert validation is the next step.
Confidence/accuracy targets (≥99.5% faithfulness, ≥85% pre-fill accuracy) are architectural targets, not yet measured against real clinical use.
Database encryption at rest is local/dev-only — explicitly not production-ready.
A known, accepted concurrent-insert race in the audit hash chain — acceptable for single-clinician demo scope, would need addressing before real concurrent multi-clinician load.
No federated SSO/OIDC yet (local username/password login only).
A real React/TypeScript clinician UI exists and is now feature-complete through Phase 5 (login, condition-centric pre-fill, HITL review, Risk Detector alerts, Reasoning/Verification output) — but there's no automated frontend test suite yet; a manual verification checklist stands in for one.

What This Case Study Demonstrates

  • Clinical workflow understanding translated directly into architecture.
  • FHIR R4 and HL7 v2 interoperability built from the standards themselves.
  • An agentic pipeline with real role-based routing, not a single mega-prompt.
  • A security posture mapped explicitly to HIPAA, stated honestly.
  • The discipline to catch real bugs through live testing — not just green unit tests — and document them rather than quietly patch and move on.

Building a Healthcare AI System of Your Own?

Whether you're building an AI-assisted EMR, a clinical workflow automation platform, or a healthcare interoperability solution, we're always interested in discussing secure, agentic AI architectures.