From 49a7192d7eafd8ad96e8d7aafabfb68e0e533c2b Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Fri, 16 Jan 2026 11:23:08 +0000 Subject: [PATCH 01/11] Redevelopment of the docs --- docs/cookbook/index.md | 166 +++++- docs/index.md | 9 +- docs/overrides/main.html | 13 + docs/overrides/welcome.html | 171 ++++++ docs/reference/concepts.md | 263 ++++++++++ docs/stylesheets/welcome.css | 571 +++++++++++++++++++++ docs/tutorials/clinicalflow/fhir-basics.md | 143 ++++++ docs/tutorials/clinicalflow/gateway.md | 183 +++++++ docs/tutorials/clinicalflow/index.md | 53 ++ docs/tutorials/clinicalflow/next-steps.md | 156 ++++++ docs/tutorials/clinicalflow/pipeline.md | 144 ++++++ docs/tutorials/clinicalflow/setup.md | 72 +++ docs/tutorials/clinicalflow/testing.md | 182 +++++++ docs/tutorials/index.md | 36 ++ mkdocs.yml | 31 +- 15 files changed, 2173 insertions(+), 20 deletions(-) create mode 100644 docs/overrides/main.html create mode 100644 docs/overrides/welcome.html create mode 100644 docs/reference/concepts.md create mode 100644 docs/stylesheets/welcome.css create mode 100644 docs/tutorials/clinicalflow/fhir-basics.md create mode 100644 docs/tutorials/clinicalflow/gateway.md create mode 100644 docs/tutorials/clinicalflow/index.md create mode 100644 docs/tutorials/clinicalflow/next-steps.md create mode 100644 docs/tutorials/clinicalflow/pipeline.md create mode 100644 docs/tutorials/clinicalflow/setup.md create mode 100644 docs/tutorials/clinicalflow/testing.md create mode 100644 docs/tutorials/index.md diff --git a/docs/cookbook/index.md b/docs/cookbook/index.md index 165624b6..dd2f5629 100644 --- a/docs/cookbook/index.md +++ b/docs/cookbook/index.md @@ -1,31 +1,163 @@ -# ๐Ÿณ Cookbook: Hands-On Examples +# Cookbook -Dive into real-world, production-ready examples to learn how to build interoperable healthcare AI apps with **HealthChain**. +Hands-on, production-ready examples for building healthcare AI applications with HealthChain. ---- +
+ Filter: + HealthTech + GenAI + ML Research + Gateway + Pipeline + Interop + FHIR + CDS Hooks + Sandbox + +
-## ๐Ÿšฆ Getting Started +
+
-- [**Working with FHIR Sandboxes**](./setup_fhir_sandboxes.md) - *Spin up and access free Epic, Medplum, and other FHIR sandboxes for safe experimentation. This is the recommended first step before doing the detailed tutorials below.* + +
๐Ÿšฆ
+
Working with FHIR Sandboxes
+
+ Spin up and access free Epic, Medplum, and other FHIR sandboxes for safe experimentation. Recommended first step before the other tutorials. +
+
+ FHIR + Sandbox +
+
---- + +
๐Ÿ”ฌ
+
Deploy ML Models: Real-Time Alerts & Batch Screening
+
+ Deploy the same ML model two ways: CDS Hooks for point-of-care sepsis alerts, and FHIR Gateway for population-level batch screening with RiskAssessment resources. +
+
+ ML Research + Gateway + CDS Hooks +
+
+ + +
๐Ÿ”—
+
Multi-Source Patient Data Aggregation
+
+ Merge patient data from multiple FHIR sources (Epic, Cerner, etc.), deduplicate conditions, prove provenance, and handle cross-vendor errors. Foundation for RAG and analytics workflows. +
+
+ GenAI + Gateway + FHIR +
+
+ + +
๐Ÿงพ
+
Automate Clinical Coding & FHIR Integration
+
+ Extract medical conditions from clinical documentation using AI, map to SNOMED CT codes, and sync as FHIR Condition resources for billing, analytics, and interoperability. +
+
+ HealthTech + Pipeline + Interop +
+
+ + +
๐Ÿ“
+
Summarize Discharge Notes with CDS Hooks
+
+ Deploy a CDS Hooks-compliant service that listens for discharge events, auto-generates concise plain-language summaries, and delivers actionable clinical cards directly into the EHR workflow. +
+
+ HealthTech + Gateway + CDS Hooks +
+
+ + + +
+
+ + --- -!!! info "What next?" +!!! tip "What next?" See the source code for each recipe, experiment with the sandboxes, and adapt the patterns for your projects! diff --git a/docs/index.md b/docs/index.md index f3681305..da431181 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,11 @@ -# Welcome to HealthChain ๐Ÿ’ซ ๐Ÿฅ +--- +template: welcome.html +hide: + - navigation + - toc +--- + +# Welcome to HealthChain HealthChain is an open-source Python toolkit that streamlines productionizing healthcare AI. Built for AI/ML practitioners, it simplifies the complexity of real-time EHR integrations by providing seamless FHIR integration, unified data pipelines, and production-ready deployment. diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 00000000..922c914f --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block footer %} + +{% endblock %} diff --git a/docs/overrides/welcome.html b/docs/overrides/welcome.html new file mode 100644 index 00000000..021305eb --- /dev/null +++ b/docs/overrides/welcome.html @@ -0,0 +1,171 @@ + + +{% extends "main.html" %} +{% block tabs %} +{{ super() }} +{% endblock %} + +{% block content %} +
+
+

Production-Ready Healthcare AI

+

+ Built-in FHIR support, real-time EHR connectivity, and deployment tooling for healthcare AI/ML systems. Skip months of custom integration work. +

+ + +
+ + +
+{% endblock %} + +{% block footer %} + +{% endblock %} diff --git a/docs/reference/concepts.md b/docs/reference/concepts.md new file mode 100644 index 00000000..6e1cf9e4 --- /dev/null +++ b/docs/reference/concepts.md @@ -0,0 +1,263 @@ +# Core Concepts + +HealthChain has three main components that work together to connect your AI applications to healthcare systems: + +- **Gateway:** Connect to multiple healthcare systems with a single API. +- **Pipelines:** Easily build data processing pipelines for both clinical text and [FHIR](https://www.hl7.org/fhir/) data. +- **InteropEngine:** Seamlessly convert between data formats like [FHIR](https://www.hl7.org/fhir/), [HL7 CDA](https://www.hl7.org/implement/standards/product_brief.cfm?product_id=7), and [HL7v2](https://www.hl7.org/implement/standards/product_brief.cfm?product_id=185). + + +## Gateway + +The [**HealthChainAPI**](./gateway/api.md) provides a unified interface for connecting your AI application and models to multiple healthcare systems through a single API. It automatically handles [FHIR API](https://www.hl7.org/fhir/http.html), [CDS Hooks](https://cds-hooks.org/), and [SOAP/CDA protocols](https://www.hl7.org/implement/standards/product_brief.cfm?product_id=7) with [OAuth2 authentication](https://oauth.net/2/). + +[(Full Documentation on Gateway)](./gateway/gateway.md) + +```python +from healthchain.gateway import HealthChainAPI, FHIRGateway +from fhir.resources.patient import Patient + +# Create your healthcare application +app = HealthChainAPI(title="My Healthcare AI App") + +# Connect to multiple FHIR servers +fhir = FHIRGateway() +fhir.add_source("epic", "fhir://fhir.epic.com/r4?client_id=...") +fhir.add_source("medplum", "fhir://api.medplum.com/fhir/R4/?client_id=...") + +# Add AI transformations to FHIR data +@fhir.transform(Patient) +def enhance_patient(id: str, source: str = None) -> Patient: + patient = fhir.read(Patient, id, source) + # Your AI logic here + patient.active = True + fhir.update(patient, source) + return patient + +# Register and run +app.register_gateway(fhir) + +# Available at: GET /fhir/transform/Patient/123?source=epic +``` + +## Pipeline + +HealthChain [**Pipelines**](./pipeline/pipeline.md) provide a flexible way to build and manage processing pipelines for NLP and ML tasks that can easily integrate with electronic health record (EHR) systems. + +You can build pipelines with three different approaches: + +### 1. Quick Inline Functions + +For quick experiments, start by picking the right [**Container**](./io/containers/containers.md) when you initialize your pipeline (e.g. `Pipeline[Document]()` for clinical text). + +Containers make your pipeline FHIR-native by loading and transforming your data (free text, EHR resources, etc.) into structured FHIR-ready formats. Just add your processing functions with `@add_node`, compile with `.build()`, and your pipeline is ready to process FHIR data end-to-end. + +[(Full Documentation on Containers)](./io/containers/containers.md) + +```python +from healthchain.pipeline import Pipeline +from healthchain.io import Document +from healthchain.fhir import create_condition + +pipeline = Pipeline[Document]() + +@pipeline.add_node +def extract_diabetes(doc: Document) -> Document: + """Adds a FHIR Condition for diabetes if mentioned in the text.""" + if "diabetes" in doc.text.lower(): + condition = create_condition( + code="73211009", + display="Diabetes mellitus", + ) + doc.fhir.problem_list.append(condition) + + return doc + +pipe = pipeline.build() + +doc = Document("Patient has a history of diabetes.") +doc = pipe(doc) + +print(doc.fhir.problem_list) # FHIR Condition +``` + +### 2. Build With Components and Adapters + +[**Components**](./pipeline/components/components.md) are reusable, stateful classes that encapsulate specific processing logic, model loading, or configuration for your pipeline. Use them to organize complex workflows, handle model state, or integrate third-party libraries with minimal setup. + +HealthChain provides a set of ready-to-use [**NLP Integrations**](./pipeline/integrations/integrations.md) for common clinical NLP and ML tasks, and you can easily implement your own. + +[(Full Documentation on Components)](./pipeline/components/components.md) + +```python +from healthchain.pipeline import Pipeline +from healthchain.pipeline.components import TextPreProcessor, SpacyNLP, TextPostProcessor +from healthchain.io import Document + +pipeline = Pipeline[Document]() + +pipeline.add_node(TextPreProcessor()) +pipeline.add_node(SpacyNLP.from_model_id("en_core_sci_sm")) +pipeline.add_node(TextPostProcessor()) + +pipe = pipeline.build() + +doc = Document("Patient presents with hypertension.") +output = pipe(doc) +``` + +You can process legacy healthcare data formats too. [**Adapters**](./io/adapters/adapters.md) convert between healthcare formats like [CDA](https://www.hl7.org/implement/standards/product_brief.cfm?product_id=7) and your pipeline โ€” just parse, process, and format without worrying about low-level data conversion. + +[(Full Documentation on Adapters)](./io/adapters/adapters.md) + +```python +from healthchain.io import CdaAdapter +from healthchain.models import CdaRequest + +# Use adapter for format conversion +adapter = CdaAdapter() +cda_request = CdaRequest(document="") + +# Parse, process, format +doc = adapter.parse(cda_request) +processed_doc = pipe(doc) +output = adapter.format(processed_doc) +``` + +### 3. Use Prebuilt Pipelines + +Prebuilt pipelines are the fastest way to jump into healthcare AI with minimal setup: just load and run. Each pipeline bundles best-practice components and models for common clinical tasks (like coding or summarization) and handles all FHIR/CDA conversion for you. Easily customize or extend pipelines by adding/removing components, or swap models as needed. + +[(Full Documentation on Pipelines)](./pipeline/pipeline.md#prebuilt-) + +```python +from healthchain.pipeline import MedicalCodingPipeline +from healthchain.models import CdaRequest + +# Or load from local model +pipeline = MedicalCodingPipeline.from_local_model("./path/to/model", source="spacy") + +cda_request = CdaRequest(document="") +output = pipeline.process_request(cda_request) +``` + +## Interoperability + +The HealthChain Interoperability module provides tools for converting between different healthcare data formats, including FHIR, CDA, and HL7v2 messages. + +[(Full Documentation on Interoperability Engine)](./interop/interop.md) + +```python +from healthchain.interop import create_interop, FormatType + +# Uses bundled configs - basic CDA โ†” FHIR conversion +engine = create_interop() + +# Load a CDA document +with open("tests/data/test_cda.xml", "r") as f: + cda_xml = f.read() + +# Convert CDA XML to FHIR resources +fhir_resources = engine.to_fhir(cda_xml, src_format=FormatType.CDA) + +# Convert FHIR resources back to CDA +cda_document = engine.from_fhir(fhir_resources, dest_format=FormatType.CDA) +``` + + +## Utilities + +### Sandbox Client + +Use [**SandboxClient**](./utilities/sandbox.md) to quickly test your app against real-world EHR scenarios like CDS Hooks or Clinical Documentation Improvement (CDI) workflows. Load test datasets, send requests to your service, and validate responses in a few lines of code. + +[(Full Documentation on Sandbox)](./utilities/sandbox.md) + +#### Workflows + +A [**workflow**](./utilities/sandbox.md#workflow-protocol-compatibility) represents a specific event in an EHR system that triggers your service (e.g., `patient-view` when opening a patient chart, `encounter-discharge` when discharging a patient). + +Workflows determine the request structure, required FHIR resources, and validation rules. Different workflows are compatible with different protocols: + +| Workflow Type | Protocol | Example Workflows | +|-------------------------------------|------------|--------------------------------------------------------| +| **CDS Hooks** | REST | `patient-view`, `order-select`, `order-sign`, `encounter-discharge` | +| **Clinical Documentation** | SOAP | `sign-note-inpatient`, `sign-note-outpatient` | + + +#### Available Dataset Loaders + +[**Dataset Loaders**](./utilities/sandbox.md#dataset-loaders) are shortcuts for loading common clinical test datasets from file. Currently available: + +| Dataset Key | Description | FHIR Version | Source | Download Link | +|--------------------|---------------------------------------------|--------------|-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| +| `mimic-on-fhir` | **MIMIC-IV on FHIR Demo Dataset** | R4 | [PhysioNet Project](https://physionet.org/content/mimic-iv-fhir-demo/2.1.0/) | [Download ZIP](https://physionet.org/content/mimic-iv-fhir-demo/get-zip/2.1.0/) (49.5 MB) | +| `synthea-patient` | **Synthea FHIR Patient Records** | R4 | [Synthea Downloads](https://synthea.mitre.org/downloads) | [Download ZIP](https://arc.net/l/quote/hoquexhy) (100 Sample, 36 MB) | + + +```python +from healthchain.sandbox import list_available_datasets + +# See all registered datasets with descriptions +datasets = list_available_datasets() +print(datasets) +``` + +#### Basic Usage + +```python +from healthchain.sandbox import SandboxClient + +# Initialize client with your service URL and workflow +client = SandboxClient( + url="http://localhost:8000/cds/encounter-discharge", + workflow="encounter-discharge" +) + +# Load test data from a registered dataset +client.load_from_registry( + "synthea-patient", + data_dir="./data/synthea", + resource_types=["Condition", "DocumentReference"], + sample_size=3 +) + +# Optionally inspect before sending +client.preview_requests() # See what will be sent +client.get_status() # Check client state + +# Send requests to your service +responses = client.send_requests() +``` + +For clinical documentation workflows using SOAP/CDA: + +```python +# Use context manager for automatic result saving +with SandboxClient( + url="http://localhost:8000/notereader/ProcessDocument", + workflow="sign-note-inpatient", + protocol="soap" +) as client: + client.load_from_path("./cookbook/data/notereader_cda.xml") + responses = client.send_requests() + # Results automatically saved to ./output/ on success +``` + +### FHIR Helpers + +Use `healthchain.fhir` helpers to quickly create and manipulate FHIR resources (like `Condition`, `Observation`, etc.) in your code, ensuring they're standards-compliant with minimal boilerplate. + +[(Full Documentation on FHIR Helpers)](./utilities/fhir_helpers.md) + +```python +from healthchain.fhir import create_condition + +condition = create_condition( + code="38341003", + display="Hypertension", + system="http://snomed.info/sct", + subject="Patient/Foo", + clinical_status="active" +) +``` diff --git a/docs/stylesheets/welcome.css b/docs/stylesheets/welcome.css new file mode 100644 index 00000000..4debf984 --- /dev/null +++ b/docs/stylesheets/welcome.css @@ -0,0 +1,571 @@ +/* HealthChain Welcome Page Styles + Adapted from Kedro's documentation pattern */ + +/* CSS Variables for theming */ +:root { + --welcome-page-bg-color: #ffffff; + --cards-section-bg-color: #f8f9fa; + --card-bg-color: #ffffff; + --card-border-color: #e0e0e0; + --card-featured-border-color: #e59875; + --card-featured-bg-color: #fef7f4; + --text-primary-color: #1a1a1a; + --text-secondary-color: #666666; + --accent-color: #e59875; + --accent-secondary: #79a8a9; +} + +[data-md-color-scheme="slate"] { + --welcome-page-bg-color: #1e1e1e; + --cards-section-bg-color: #2d2d2d; + --card-bg-color: #2d2d2d; + --card-border-color: #404040; + --card-featured-border-color: #e59875; + --card-featured-bg-color: #3d2d28; + --text-primary-color: #ffffff; + --text-secondary-color: #b0b0b0; +} + +/* Target welcome page container specifically */ +.md-content__inner:has(.welcome-page-container) { + padding: 0 !important; + margin: 0 !important; + max-width: none !important; + background-color: var(--welcome-page-bg-color); +} + +/* Main Container */ +.welcome-page-container { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +/* Hero Section */ +.welcome-page-container header { + text-align: center; + padding: 0 16px; +} + +.welcome-title { + margin-top: 80px !important; + margin-bottom: 16px !important; + font-weight: 700 !important; + font-size: 2.5rem !important; + color: var(--text-primary-color); + line-height: 1.2; +} + +.welcome-subtitle { + font-size: 1.125rem; + color: var(--text-secondary-color); + max-width: 600px; + margin: 0 auto 24px auto; + line-height: 1.6; +} + +/* Explore/CTA Link */ +.explore-link { + text-align: center; + margin: 32px 0 64px 0; +} + +.explore-link a { + display: inline-flex; + align-items: center; + padding: 12px 24px; + background-color: var(--accent-color); + color: #ffffff !important; + text-decoration: none !important; + border-radius: 8px; + font-weight: 600; + font-size: 1rem; + transition: background-color 0.2s, transform 0.2s; +} + +.explore-link a:hover { + background-color: #d4845f; + transform: translateY(-2px); +} + +.explore-link .arrow { + margin-left: 8px; + font-size: 1.2rem; +} + +/* Cards Section Wrapper */ +.cards-section-wrapper { + background-color: var(--cards-section-bg-color); + width: 100%; + padding: 48px 0 64px 0; +} + +.cards-section-wrapper section { + margin-bottom: 48px; + padding: 0 16px; +} + +.cards-section-wrapper section:last-child { + margin-bottom: 0; +} + +/* Section Titles */ +.section-title { + font-weight: 600; + font-size: 1.25rem; + text-align: center; + margin-bottom: 24px !important; + margin-top: 0; + color: var(--text-primary-color); +} + +/* Card Grid */ +.card-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + margin: 0 auto; + max-width: 900px; +} + +.card-grid.two-col { + grid-template-columns: repeat(2, 1fr); + max-width: 600px; +} + +/* Card Styles */ +.card { + background-color: var(--card-bg-color); + border: 2px solid var(--card-border-color); + border-radius: 16px; + padding: 24px; + transition: transform 0.2s, box-shadow 0.2s; + text-decoration: none !important; + display: flex; + flex-direction: column; +} + +.card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.1); +} + +/* Featured Card (Primary Persona) */ +.card.card-featured { + border-color: var(--card-featured-border-color); + background-color: var(--card-featured-bg-color); + position: relative; +} + +.card.card-featured::before { + content: "Recommended"; + position: absolute; + top: -12px; + left: 16px; + background-color: var(--accent-color); + color: #ffffff; + font-size: 0.75rem; + font-weight: 600; + padding: 4px 12px; + border-radius: 12px; +} + +/* Card Icon */ +.card-icon { + margin-bottom: 16px; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--cards-section-bg-color); + border-radius: 12px; +} + +.card-icon svg { + width: 28px; + height: 28px; +} + +.card-icon svg path { + fill: var(--accent-color); +} + +/* Card Content */ +.card-content { + flex: 1; + display: flex; + flex-direction: column; +} + +.card-title { + font-size: 1rem !important; + font-weight: 600 !important; + color: var(--text-primary-color); + margin-bottom: 8px !important; + margin-top: 0 !important; +} + +.card-description { + font-size: 0.875rem; + color: var(--text-secondary-color); + line-height: 1.5; + margin: 0; + flex: 1; +} + +.card-cta { + margin-top: 16px; + font-size: 0.875rem; + font-weight: 600; + color: var(--accent-color); +} + +/* Welcome Page Footer */ +.welcome-page-footer { + background-color: var(--welcome-page-bg-color); + padding: 32px 16px; + text-align: center; +} + +.welcome-page-footer .badges { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px; +} + +.welcome-page-footer .badges img { + height: 20px; +} + +/* Mobile Responsive */ +@media screen and (max-width: 76.2344em) { + .card-grid, + .card-grid.two-col { + grid-template-columns: 1fr; + gap: 20px; + max-width: 100%; + padding: 0 8px; + } + + .cards-section-wrapper { + padding: 32px 0 48px 0; + } + + .cards-section-wrapper section { + padding: 0 8px; + } + + .card { + padding: 20px; + } + + .welcome-title { + margin-top: 48px !important; + font-size: 1.75rem !important; + } + + .welcome-subtitle { + font-size: 1rem; + } + + .explore-link { + margin: 24px 0 48px 0; + } + + .section-title { + margin-top: 24px; + margin-bottom: 20px !important; + } + + .card.card-featured::before { + top: -10px; + font-size: 0.7rem; + padding: 3px 10px; + } +} + +/* Tablet breakpoint */ +@media screen and (min-width: 48em) and (max-width: 76.2344em) { + .card-grid { + grid-template-columns: repeat(2, 1fr); + } + + .card-grid.two-col { + grid-template-columns: repeat(2, 1fr); + } +} + +/* ============================================ + Cookbook Cards and Tags + ============================================ */ + +/* Cookbook wrapper with grey background */ +.cookbook-wrapper { + background-color: var(--cards-section-bg-color); + margin: 0 -0.8rem 1rem -0.8rem; + padding: 32px 24px; + border-radius: 12px; +} + +/* Cookbook card grid - 2 columns for better readability */ +.cookbook-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 24px; + margin: 0; +} + +@media screen and (max-width: 76.2344em) { + .cookbook-grid { + grid-template-columns: 1fr; + } +} + +/* Cookbook card */ +.cookbook-card { + background-color: var(--md-default-bg-color); + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 12px; + padding: 24px; + transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s; + text-decoration: none !important; + display: flex; + flex-direction: column; + height: 100%; +} + +.cookbook-card:hover { + transform: translateY(-3px); + box-shadow: 0 6px 16px -4px rgba(0, 0, 0, 0.1); + border-color: var(--md-accent-fg-color); +} + +.cookbook-card-icon { + font-size: 2rem; + margin-bottom: 12px; +} + +.cookbook-card-title { + font-size: 1.1rem !important; + font-weight: 600 !important; + color: var(--md-default-fg-color); + margin: 0 0 8px 0 !important; + line-height: 1.3; +} + +.cookbook-card-description { + font-size: 0.9rem; + color: var(--md-default-fg-color--light); + line-height: 1.5; + margin: 0 0 16px 0; + flex: 1; +} + +/* Tag container */ +.cookbook-tags { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: auto; +} + +/* Base tag style */ +.tag { + display: inline-flex; + align-items: center; + padding: 3px 10px; + border-radius: 12px; + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.02em; +} + +/* Persona tags */ +.tag-healthtech { + background-color: #fef3e7; + color: #c76a15; +} + +.tag-genai { + background-color: #e8f4f8; + color: #1976a2; +} + +.tag-ml { + background-color: #f3e8f8; + color: #7b1fa2; +} + +/* Feature tags */ +.tag-gateway { + background-color: #e8f5e9; + color: #2e7d32; +} + +.tag-pipeline { + background-color: #fff3e0; + color: #e65100; +} + +.tag-interop { + background-color: #e3f2fd; + color: #1565c0; +} + +.tag-sandbox { + background-color: #fce4ec; + color: #c2185b; +} + +.tag-fhir { + background-color: #f1f8e9; + color: #558b2f; +} + +.tag-cdshooks { + background-color: #fff8e1; + color: #ff8f00; +} + +/* Dark mode tag adjustments */ +[data-md-color-scheme="slate"] .tag-healthtech { + background-color: #3d2d1f; + color: #ffb74d; +} + +[data-md-color-scheme="slate"] .tag-genai { + background-color: #1a3a4a; + color: #4fc3f7; +} + +[data-md-color-scheme="slate"] .tag-ml { + background-color: #2d1f3d; + color: #ce93d8; +} + +[data-md-color-scheme="slate"] .tag-gateway { + background-color: #1b3d1f; + color: #81c784; +} + +[data-md-color-scheme="slate"] .tag-pipeline { + background-color: #3d2a1a; + color: #ffb74d; +} + +[data-md-color-scheme="slate"] .tag-interop { + background-color: #1a2d3d; + color: #64b5f6; +} + +[data-md-color-scheme="slate"] .tag-sandbox { + background-color: #3d1a2a; + color: #f48fb1; +} + +[data-md-color-scheme="slate"] .tag-fhir { + background-color: #2a3d1a; + color: #aed581; +} + +[data-md-color-scheme="slate"] .tag-cdshooks { + background-color: #3d3a1a; + color: #ffd54f; +} + +[data-md-color-scheme="slate"] .cookbook-card { + background-color: var(--md-default-bg-color); + border-color: var(--md-default-fg-color--lightest); +} + +/* Tag legend / filter bar */ +.tag-legend { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-bottom: 24px; + padding: 16px 20px; + background-color: var(--md-default-bg-color); + border-radius: 8px; + border: 1px solid var(--md-default-fg-color--lightest); +} + +.tag-legend-title { + font-size: 0.8rem; + font-weight: 600; + color: var(--md-default-fg-color--light); + margin-right: 8px; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +/* Clickable filter tags */ +.tag-filter { + cursor: pointer; + transition: transform 0.15s, opacity 0.15s, box-shadow 0.15s; + user-select: none; + opacity: 0.6; +} + +.tag-filter:hover { + transform: scale(1.05); + opacity: 0.85; +} + +.tag-filter.active { + opacity: 1; + box-shadow: 0 0 0 2px var(--md-default-bg-color), 0 0 0 4px currentColor; +} + +/* Clear filter button */ +.tag-clear { + cursor: pointer; + padding: 3px 10px; + border-radius: 12px; + font-size: 0.7rem; + font-weight: 600; + background-color: var(--md-default-fg-color--lightest); + color: var(--md-default-fg-color--light); + transition: background-color 0.15s; + margin-left: auto; +} + +.tag-clear:hover { + background-color: var(--md-default-fg-color--lighter); +} + +.tag-clear.hidden { + display: none; +} + +/* Card filtering states */ +.cookbook-card.filtered-out { + display: none; +} + +.cookbook-card.filtered-in { + animation: fadeIn 0.2s ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +/* No results message */ +.no-results { + grid-column: 1 / -1; + text-align: center; + padding: 48px 24px; + color: var(--md-default-fg-color--light); + font-size: 1rem; +} + +.no-results.hidden { + display: none; +} diff --git a/docs/tutorials/clinicalflow/fhir-basics.md b/docs/tutorials/clinicalflow/fhir-basics.md new file mode 100644 index 00000000..30f93446 --- /dev/null +++ b/docs/tutorials/clinicalflow/fhir-basics.md @@ -0,0 +1,143 @@ +# FHIR Basics + +Understand the healthcare data format you'll work with in HealthChain. + +## What is FHIR? + +**FHIR** (Fast Healthcare Interoperability Resources) is the modern standard for exchanging healthcare data. Think of it as "JSON for healthcare" - structured, standardized data that EHR systems like Epic and Cerner use. + +## Key Resources for CDS + +For our ClinicalFlow service, we'll work with three main FHIR resources: + +### Patient + +Identifies who the patient is: + +```json +{ + "resourceType": "Patient", + "id": "example-patient", + "name": [{"given": ["John"], "family": "Smith"}], + "birthDate": "1970-01-15", + "gender": "male" +} +``` + +### Condition + +Records diagnoses and health problems: + +```json +{ + "resourceType": "Condition", + "id": "example-condition", + "code": { + "coding": [{ + "system": "http://snomed.info/sct", + "code": "38341003", + "display": "Hypertension" + }] + }, + "subject": {"reference": "Patient/example-patient"}, + "clinicalStatus": { + "coding": [{"code": "active"}] + } +} +``` + +### MedicationStatement + +Tracks what medications a patient is taking: + +```json +{ + "resourceType": "MedicationStatement", + "id": "example-med", + "medicationCodeableConcept": { + "coding": [{ + "system": "http://www.nlm.nih.gov/research/umls/rxnorm", + "code": "197361", + "display": "Lisinopril 10 MG" + }] + }, + "subject": {"reference": "Patient/example-patient"}, + "status": "active" +} +``` + +## Working with FHIR in HealthChain + +HealthChain provides utilities to work with FHIR resources easily: + +```python +from healthchain.fhir import create_condition, create_patient +from fhir.resources.bundle import Bundle + +# Create a patient +patient = create_patient( + id="patient-001", + given_name="John", + family_name="Smith", + birth_date="1970-01-15" +) + +# Create a condition +condition = create_condition( + id="condition-001", + code="38341003", + display="Hypertension", + system="http://snomed.info/sct", + patient_reference="Patient/patient-001" +) + +print(f"Created patient: {patient.name[0].given[0]} {patient.name[0].family}") +print(f"With condition: {condition.code.coding[0].display}") +``` + +## FHIR Bundles + +When an EHR sends patient context, it often comes as a **Bundle** - a collection of related resources: + +```python +from fhir.resources.bundle import Bundle + +# A bundle might contain a patient, their conditions, and medications +bundle_data = { + "resourceType": "Bundle", + "type": "collection", + "entry": [ + {"resource": patient.dict()}, + {"resource": condition.dict()} + ] +} + +bundle = Bundle(**bundle_data) +print(f"Bundle contains {len(bundle.entry)} resources") +``` + +## The Document Container + +HealthChain's `Document` container bridges clinical text and FHIR data: + +```python +from healthchain.io import Document + +# Create a document with clinical text +doc = Document( + "Patient presents with chest pain and shortness of breath. " + "History of hypertension and diabetes." +) + +# The document can hold FHIR data extracted from text +print(f"Document text: {doc.text[:50]}...") + +# After NLP processing, the document will contain: +# - Extracted entities +# - Generated FHIR resources +# - Problem lists, medications, etc. +``` + +## What's Next + +Now that you understand FHIR basics, let's [build a pipeline](pipeline.md) that processes clinical text and extracts structured data. diff --git a/docs/tutorials/clinicalflow/gateway.md b/docs/tutorials/clinicalflow/gateway.md new file mode 100644 index 00000000..1469ff8f --- /dev/null +++ b/docs/tutorials/clinicalflow/gateway.md @@ -0,0 +1,183 @@ +# Create Gateway + +Expose your pipeline as a CDS Hooks service that EHRs can call. + +## What is CDS Hooks? + +**CDS Hooks** is a standard for integrating clinical decision support with EHR systems. When a clinician performs an action (like opening a patient chart), the EHR calls your CDS service, and you return helpful information as "cards." + +The flow: + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Clinician โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€>โ”‚ EHR โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€>โ”‚ Your CDS โ”‚ +โ”‚ opens โ”‚ โ”‚ (Epic, โ”‚ HTTP โ”‚ Service โ”‚ +โ”‚ chart โ”‚ โ”‚ Cerner) โ”‚ POST โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ”‚<โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ + โ”‚ CDS Cards โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Display โ”‚ + โ”‚ alerts to โ”‚ + โ”‚ clinician โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## Create the CDS Service + +Create a file called `app.py`: + +```python +from healthchain.gateway import HealthChainAPI, CDSHooksService +from healthchain.io import Document +from pipeline import create_clinical_pipeline + +# Initialize the HealthChain API +app = HealthChainAPI(title="ClinicalFlow CDS Service") + +# Create your pipeline +nlp = create_clinical_pipeline() + +# Define the CDS Hooks service +@app.cds_hooks( + id="patient-alerts", + title="Clinical Alert Service", + description="Analyzes patient data and returns relevant clinical alerts", + hook="patient-view" # Triggers when a clinician views a patient +) +def patient_alerts(context, prefetch): + """ + Process patient context and return CDS cards. + + Args: + context: CDS Hooks context (patient ID, user, etc.) + prefetch: Pre-fetched FHIR resources + """ + cards = [] + + # Get patient conditions from prefetch (if available) + conditions = prefetch.get("conditions", []) + + # If we have clinical notes, process them + if clinical_note := prefetch.get("note"): + doc = Document(clinical_note) + result = nlp(doc) + + # Create cards for each extracted condition + for entity in result.entities: + cards.append({ + "summary": f"Condition detected: {entity['display']}", + "detail": f"SNOMED code: {entity['code']}", + "indicator": "info", + "source": { + "label": "ClinicalFlow", + "url": "https://healthchain.dev" + } + }) + + # Check for drug interaction alerts + if len(conditions) > 2: + cards.append({ + "summary": "Multiple active conditions", + "detail": f"Patient has {len(conditions)} active conditions. Review for potential interactions.", + "indicator": "warning", + "source": {"label": "ClinicalFlow"} + }) + + return cards + + +# Run the server +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +## Understanding the Code + +### The `@app.cds_hooks` Decorator + +This decorator registers your function as a CDS Hooks endpoint: + +- **`id`**: Unique identifier for this service +- **`title`**: Human-readable name +- **`hook`**: When to trigger (e.g., `patient-view`, `order-select`) + +### CDS Cards + +Cards are the responses you return to the EHR. Each card has: + +| Field | Description | +|-------|-------------| +| `summary` | Brief message shown to clinician | +| `detail` | Additional information (optional) | +| `indicator` | Urgency: `info`, `warning`, or `critical` | +| `source` | Attribution for the recommendation | + +## Run the Service + +Start your CDS service: + +```bash +python app.py +``` + +Your service is now running at `http://localhost:8000`. + +## Test the Endpoints + +### Discovery Endpoint + +CDS Hooks services must provide a discovery endpoint. Test it: + +```bash +curl http://localhost:8000/cds-services +``` + +Response: + +```json +{ + "services": [ + { + "id": "patient-alerts", + "title": "Clinical Alert Service", + "description": "Analyzes patient data and returns relevant clinical alerts", + "hook": "patient-view" + } + ] +} +``` + +### Service Endpoint + +Test calling your service: + +```bash +curl -X POST http://localhost:8000/cds-services/patient-alerts \ + -H "Content-Type: application/json" \ + -d '{ + "hookInstance": "test-123", + "hook": "patient-view", + "context": { + "patientId": "patient-001", + "userId": "doctor-001" + }, + "prefetch": { + "note": "Patient presents with chest pain and hypertension." + } + }' +``` + +## Interactive API Docs + +HealthChain generates OpenAPI documentation. Visit: + +- **Swagger UI**: `http://localhost:8000/docs` +- **ReDoc**: `http://localhost:8000/redoc` + +## What's Next + +Your CDS service is running! Now let's [test it properly](testing.md) with realistic patient data using HealthChain's sandbox. diff --git a/docs/tutorials/clinicalflow/index.md b/docs/tutorials/clinicalflow/index.md new file mode 100644 index 00000000..e4a3da54 --- /dev/null +++ b/docs/tutorials/clinicalflow/index.md @@ -0,0 +1,53 @@ +# ClinicalFlow Tutorial + +Build your first Clinical Decision Support (CDS) service with HealthChain. + +## The Scenario + +You're a HealthTech engineer at a hospital system. The clinical informatics team needs a CDS service that: + +1. **Receives patient context** when a physician opens a chart +2. **Analyzes existing conditions and medications** +3. **Returns actionable alerts** for potential drug interactions or care gaps + +By the end of this tutorial, you'll have a working CDS Hooks service that integrates with EHR systems like Epic. + +## What You'll Build + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ EHR System โ”‚โ”€โ”€โ”€โ”€โ”€>โ”‚ Your CDS โ”‚โ”€โ”€โ”€โ”€โ”€>โ”‚ Clinical โ”‚ +โ”‚ (Epic, etc.) โ”‚ โ”‚ Service โ”‚ โ”‚ Alert Cards โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ”‚ โ”‚ + โ–ผ โ–ผ + Patient context NLP Pipeline + (FHIR resources) (HealthChain) +``` + +## What You'll Learn + +| Step | What You'll Learn | +|------|-------------------| +| [Setup](setup.md) | Install dependencies, create project structure | +| [FHIR Basics](fhir-basics.md) | Understand Patient, Condition, and Medication resources | +| [Build Pipeline](pipeline.md) | Create an NLP pipeline with Document containers | +| [Create Gateway](gateway.md) | Expose your pipeline as a CDS Hooks service | +| [Test with Sandbox](testing.md) | Validate with synthetic patient data | +| [Next Steps](next-steps.md) | Production deployment and extending your service | + +## Prerequisites + +- **Python 3.10+** installed +- **Basic Python knowledge** (functions, classes, imports) +- **REST API familiarity** (HTTP methods, JSON) +- Healthcare knowledge is helpful but not required + +## Time Required + +This tutorial takes approximately **45 minutes** to complete. + +## Ready? + +Let's start by [setting up your project](setup.md). diff --git a/docs/tutorials/clinicalflow/next-steps.md b/docs/tutorials/clinicalflow/next-steps.md new file mode 100644 index 00000000..53616e05 --- /dev/null +++ b/docs/tutorials/clinicalflow/next-steps.md @@ -0,0 +1,156 @@ +# Next Steps + +You've built a working CDS service. Here's how to take it further. + +## What You've Accomplished + +In this tutorial, you: + +- Set up a HealthChain development environment +- Learned FHIR basics (Patient, Condition, MedicationStatement) +- Built an NLP pipeline with Document containers +- Created a CDS Hooks gateway service +- Tested with the sandbox and synthetic data + +## Production Considerations + +### Authentication + +Real EHR integrations require OAuth2 authentication: + +```python +from healthchain.gateway import HealthChainAPI + +app = HealthChainAPI( + title="ClinicalFlow CDS Service", + auth_config={ + "type": "oauth2", + "token_url": "https://your-auth-server/token", + "scopes": ["patient/*.read", "user/*.read"] + } +) +``` + +### HTTPS + +Always use HTTPS in production. With uvicorn: + +```bash +uvicorn app:app --host 0.0.0.0 --port 443 --ssl-keyfile key.pem --ssl-certfile cert.pem +``` + +### Logging and Monitoring + +Add structured logging: + +```python +import logging + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger("clinicalflow") + +@app.cds_hooks(id="patient-alerts", ...) +def patient_alerts(context, prefetch): + logger.info(f"Processing request for patient {context.get('patientId')}") + # ... your logic +``` + +## Connect to Real EHR Sandboxes + +### Epic Sandbox + +1. Register at [Epic's Developer Portal](https://fhir.epic.com/) +2. Create an application +3. Configure your service URL +4. Test against Epic's sandbox environment + +### Cerner Sandbox + +1. Register at [Cerner's Developer Portal](https://code.cerner.com/) +2. Follow their CDS Hooks integration guide +3. Test with their Millennium sandbox + +## Extend Your Service + +### Add More Hooks + +Support multiple trigger points: + +```python +@app.cds_hooks( + id="order-check", + title="Medication Order Check", + hook="order-select" +) +def check_medication_orders(context, prefetch): + """Check for drug interactions when orders are selected.""" + # ... drug interaction logic + pass + + +@app.cds_hooks( + id="discharge-summary", + title="Discharge Summary Generator", + hook="encounter-discharge" +) +def generate_discharge_summary(context, prefetch): + """Generate discharge summary at end of encounter.""" + # ... summarization logic + pass +``` + +### Improve NLP + +Replace keyword matching with trained models: + +```python +from healthchain.pipeline.components.integrations import SpacyNLP + +# Use a clinical NLP model +pipeline.add_node(SpacyNLP.from_model_id("en_core_sci_lg")) + +# Or integrate with external services +from healthchain.pipeline.components import LLMComponent + +pipeline.add_node(LLMComponent( + provider="openai", + model="gpt-4", + prompt_template="Extract clinical conditions from: {text}" +)) +``` + +### Add FHIR Output + +Convert extracted entities to FHIR resources: + +```python +from healthchain.pipeline.components import FHIRProblemListExtractor + +pipeline.add_node(FHIRProblemListExtractor()) + +# Now doc.fhir.problem_list contains FHIR Condition resources +``` + +## Learn More + +Explore HealthChain's documentation: + +| Topic | Description | +|-------|-------------| +| [Gateway Reference](../../reference/gateway/gateway.md) | Deep dive into gateway patterns | +| [Pipeline Reference](../../reference/pipeline/pipeline.md) | Advanced pipeline configuration | +| [CDS Hooks Cookbook](../../cookbook/discharge_summarizer.md) | Complete CDS Hooks example | +| [Multi-EHR Integration](../../cookbook/multi_ehr_aggregation.md) | Connect to multiple EHRs | + +## Get Help + +- **Discord**: [Join our community](https://discord.gg/UQC6uAepUz) +- **GitHub**: [Report issues](https://github.com/dotimplement/healthchain/issues) +- **Office Hours**: Thursdays 4:30-5:30pm GMT + +## Congratulations! + +You've completed the ClinicalFlow tutorial. You now have the foundation to build production-ready healthcare AI applications with HealthChain. diff --git a/docs/tutorials/clinicalflow/pipeline.md b/docs/tutorials/clinicalflow/pipeline.md new file mode 100644 index 00000000..9e06255c --- /dev/null +++ b/docs/tutorials/clinicalflow/pipeline.md @@ -0,0 +1,144 @@ +# Build Pipeline + +Create an NLP pipeline to process clinical text and extract structured data. + +## What is a Pipeline? + +A **Pipeline** in HealthChain is a sequence of processing steps that transform input data. For clinical NLP, pipelines typically: + +1. Take clinical text as input +2. Process it through NLP models +3. Extract entities (conditions, medications, etc.) +4. Output structured FHIR resources + +## Create Your Pipeline + +Create a file called `pipeline.py`: + +```python +from healthchain.pipeline import Pipeline +from healthchain.io import Document + +def create_clinical_pipeline(): + """Create a pipeline for processing clinical notes.""" + + # Initialize pipeline with Document as the data container + pipeline = Pipeline[Document]() + + # Add a simple text preprocessing component + @pipeline.add_node + def preprocess(doc: Document) -> Document: + """Clean and normalize clinical text.""" + # Remove extra whitespace + doc.text = " ".join(doc.text.split()) + return doc + + # Add a clinical entity extraction component + @pipeline.add_node + def extract_conditions(doc: Document) -> Document: + """Extract condition mentions from text.""" + # Simple keyword-based extraction for this tutorial + # In production, you'd use a trained NLP model + condition_keywords = { + "hypertension": ("38341003", "Hypertension"), + "diabetes": ("73211009", "Diabetes mellitus"), + "chest pain": ("29857009", "Chest pain"), + "shortness of breath": ("267036007", "Dyspnea"), + } + + text_lower = doc.text.lower() + extracted = [] + + for keyword, (code, display) in condition_keywords.items(): + if keyword in text_lower: + extracted.append({ + "text": keyword, + "code": code, + "display": display, + "system": "http://snomed.info/sct" + }) + + # Store extracted conditions in document + doc.entities = extracted + return doc + + return pipeline.build() +``` + +## Using the Pipeline + +Test your pipeline: + +```python +from pipeline import create_clinical_pipeline +from healthchain.io import Document + +# Create the pipeline +nlp = create_clinical_pipeline() + +# Process a clinical note +doc = Document( + "Patient is a 65-year-old male presenting with chest pain " + "and shortness of breath. History includes hypertension " + "and diabetes, both well-controlled on current medications." +) + +# Run the pipeline +result = nlp(doc) + +# Check extracted entities +print("Extracted conditions:") +for entity in result.entities: + print(f" - {entity['display']} (SNOMED: {entity['code']})") +``` + +Expected output: + +``` +Extracted conditions: + - Hypertension (SNOMED: 38341003) + - Diabetes mellitus (SNOMED: 73211009) + - Chest pain (SNOMED: 29857009) + - Dyspnea (SNOMED: 267036007) +``` + +## Adding SpaCy Integration (Optional) + +For more sophisticated NLP, integrate spaCy: + +```python +from healthchain.pipeline import Pipeline +from healthchain.pipeline.components.integrations import SpacyNLP +from healthchain.io import Document + +def create_spacy_pipeline(): + """Create a pipeline with spaCy NLP.""" + + pipeline = Pipeline[Document]() + + # Add spaCy for tokenization and NER + pipeline.add_node(SpacyNLP.from_model_id("en_core_web_sm")) + + return pipeline.build() +``` + +## Pipeline Architecture + +Your pipeline now follows this flow: + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Clinical โ”‚โ”€โ”€โ”€โ”€>โ”‚ Preprocess โ”‚โ”€โ”€โ”€โ”€>โ”‚ Extract โ”‚ +โ”‚ Text โ”‚ โ”‚ Text โ”‚ โ”‚ Conditions โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Document โ”‚ + โ”‚ + Entities โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## What's Next + +Now that you have a working pipeline, let's [create a Gateway](gateway.md) to expose it as a CDS Hooks service. diff --git a/docs/tutorials/clinicalflow/setup.md b/docs/tutorials/clinicalflow/setup.md new file mode 100644 index 00000000..92b1f560 --- /dev/null +++ b/docs/tutorials/clinicalflow/setup.md @@ -0,0 +1,72 @@ +# Setup + +Get your development environment ready for building the ClinicalFlow service. + +## Install HealthChain + +Create a new project directory and install HealthChain: + +```bash +mkdir clinicalflow +cd clinicalflow +pip install healthchain +``` + +For NLP capabilities, install with the optional spaCy integration: + +```bash +pip install healthchain[nlp] +``` + +## Verify Installation + +Create a file called `check_install.py`: + +```python +import healthchain +from healthchain.io import Document + +print(f"HealthChain version: {healthchain.__version__}") + +# Test creating a simple document +doc = Document("Patient has a history of hypertension.") +print(f"Created document with {len(doc.text)} characters") +``` + +Run it: + +```bash +python check_install.py +``` + +You should see output like: + +``` +HealthChain version: 0.x.x +Created document with 40 characters +``` + +## Project Structure + +Create the following project structure: + +``` +clinicalflow/ +โ”œโ”€โ”€ app.py # Main CDS Hooks service +โ”œโ”€โ”€ pipeline.py # NLP processing pipeline +โ””โ”€โ”€ test_service.py # Testing script +``` + +## Download Sample Data (Optional) + +For testing, you can use Synthea-generated patient data. HealthChain's sandbox can load this automatically, but if you want local data: + +```bash +mkdir data +# Download a sample Synthea bundle (optional) +# We'll use HealthChain's built-in data loaders in the testing step +``` + +## What's Next + +Now that your environment is set up, let's learn about [FHIR basics](fhir-basics.md) - the healthcare data format you'll be working with. diff --git a/docs/tutorials/clinicalflow/testing.md b/docs/tutorials/clinicalflow/testing.md new file mode 100644 index 00000000..1f0c2eab --- /dev/null +++ b/docs/tutorials/clinicalflow/testing.md @@ -0,0 +1,182 @@ +# Test with Sandbox + +Validate your CDS service with realistic patient data using HealthChain's sandbox. + +## What is the Sandbox? + +The **Sandbox** provides tools for testing CDS services without connecting to a real EHR. It can: + +- Generate realistic patient data +- Send CDS Hooks requests to your service +- Validate responses against the specification +- Save results for analysis + +## Create a Test Script + +Create a file called `test_service.py`: + +```python +from healthchain.sandbox import SandboxClient + +# Create a sandbox client pointing to your service +client = SandboxClient( + url="http://localhost:8000/cds-services/patient-alerts", + workflow="patient-view" +) + +# Generate synthetic test data +client.generate_data( + num_patients=3, + conditions_per_patient=2 +) + +# Send requests and collect responses +responses = client.send_requests() + +# Analyze results +print(f"Sent {len(responses)} requests") +for i, response in enumerate(responses): + print(f"\nPatient {i + 1}:") + print(f" Status: {response.status_code}") + if response.ok: + cards = response.json().get("cards", []) + print(f" Cards returned: {len(cards)}") + for card in cards: + print(f" - {card.get('indicator', 'info').upper()}: {card.get('summary')}") +``` + +## Run the Test + +Make sure your service is running, then: + +```bash +python test_service.py +``` + +Expected output: + +``` +Sent 3 requests + +Patient 1: + Status: 200 + Cards returned: 2 + - INFO: Condition detected: Hypertension + - WARNING: Multiple active conditions + +Patient 2: + Status: 200 + Cards returned: 1 + - INFO: Condition detected: Diabetes mellitus + +Patient 3: + Status: 200 + Cards returned: 3 + - INFO: Condition detected: Chest pain + - INFO: Condition detected: Hypertension + - WARNING: Multiple active conditions +``` + +## Using Real Test Datasets + +Load data from Synthea (a synthetic patient generator): + +```python +from healthchain.sandbox import SandboxClient + +client = SandboxClient( + url="http://localhost:8000/cds-services/patient-alerts", + workflow="patient-view" +) + +# Load from Synthea data directory +client.load_from_registry( + "synthea-patient", + data_dir="./data/synthea", + resource_types=["Patient", "Condition", "MedicationStatement"], + sample_size=5 +) + +responses = client.send_requests() +``` + +## Save Test Results + +Save results for reporting or debugging: + +```python +# Save responses to files +client.save_results("./output/test_results/") + +# Results are saved as JSON: +# - output/test_results/request_1.json +# - output/test_results/response_1.json +# - output/test_results/summary.json +``` + +## Validate CDS Hooks Compliance + +The sandbox validates that responses meet the CDS Hooks specification: + +```python +from healthchain.sandbox import SandboxClient + +client = SandboxClient( + url="http://localhost:8000/cds-services/patient-alerts", + workflow="patient-view" +) + +# Enable strict validation +client.validate_responses = True + +responses = client.send_requests() + +# Check for validation errors +for response in responses: + if response.validation_errors: + print(f"Validation errors: {response.validation_errors}") +``` + +## Testing Different Hooks + +Test different CDS Hooks workflows: + +```python +# Test order-select hook +order_client = SandboxClient( + url="http://localhost:8000/cds-services/drug-interactions", + workflow="order-select" +) + +# Test order-sign hook +sign_client = SandboxClient( + url="http://localhost:8000/cds-services/order-review", + workflow="order-sign" +) +``` + +## Debugging Tips + +### Enable Verbose Logging + +```python +import logging +logging.basicConfig(level=logging.DEBUG) + +client = SandboxClient( + url="http://localhost:8000/cds-services/patient-alerts", + workflow="patient-view" +) +``` + +### Inspect Request/Response + +```python +response = client.send_single_request(patient_data) +print("Request sent:", response.request_body) +print("Response received:", response.json()) +``` + +## What's Next + +Your service is tested and working! Learn about [production deployment](next-steps.md) and extending your CDS service. diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md new file mode 100644 index 00000000..8e3f0f76 --- /dev/null +++ b/docs/tutorials/index.md @@ -0,0 +1,36 @@ +# Tutorials + +Learn HealthChain through hands-on, step-by-step tutorials. Each tutorial is designed to teach core concepts while building something practical. + +## Available Tutorials + +### ClinicalFlow Tutorial + +**Build a Clinical Decision Support Service** + +The ClinicalFlow tutorial teaches you how to build a CDS service that integrates with EHR systems. You'll learn HealthChain's core concepts by building a real application: + +- **Time**: ~45 minutes +- **Level**: Beginner to Intermediate +- **Prerequisites**: Python basics, familiarity with REST APIs + +[:octicons-arrow-right-24: Start the ClinicalFlow Tutorial](clinicalflow/index.md) + +--- + +## What You'll Learn + +| Tutorial | Core Concepts | +|----------|---------------| +| **ClinicalFlow** | FHIR resources, Document containers, Pipeline components, CDS Hooks gateway, Sandbox testing | + +## Prerequisites + +Before starting any tutorial, make sure you have: + +- Python 3.10 or higher installed +- HealthChain installed (`pip install healthchain`) +- A code editor of your choice +- Basic understanding of Python and REST APIs + +Healthcare knowledge is helpful but not required - the tutorials explain concepts as you go. diff --git a/mkdocs.yml b/mkdocs.yml index 7737e7c2..1239ee8e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,6 +12,16 @@ nav: - Installation: installation.md - Quickstart: quickstart.md - Licence: distribution.md + - Tutorials: + - tutorials/index.md + - ClinicalFlow Tutorial: + - Introduction: tutorials/clinicalflow/index.md + - Setup: tutorials/clinicalflow/setup.md + - FHIR Basics: tutorials/clinicalflow/fhir-basics.md + - Build Pipeline: tutorials/clinicalflow/pipeline.md + - Create Gateway: tutorials/clinicalflow/gateway.md + - Test with Sandbox: tutorials/clinicalflow/testing.md + - Next Steps: tutorials/clinicalflow/next-steps.md - Cookbook: - cookbook/index.md - Setup FHIR Sandbox: cookbook/setup_fhir_sandboxes.md @@ -82,6 +92,7 @@ nav: copyright: dotimplement theme: name: material + custom_dir: docs/overrides/ favicon: assets/images/healthchain_logo.png logo: assets/images/healthchain_logo.png icon: @@ -90,12 +101,27 @@ theme: - content.code.copy - navigation.expand - navigation.tabs + - navigation.tabs.sticky - navigation.sections + - navigation.footer + - navigation.instant - header.autohide - announce.dismiss + - search.suggest + - search.highlight palette: - primary: white - accent: blue + - scheme: default + primary: white + accent: blue + toggle: + icon: material/white-balance-sunny + name: Switch to dark mode + - scheme: slate + primary: custom + accent: blue + toggle: + icon: material/weather-night + name: Switch to light mode # font: # text: Roboto @@ -132,6 +158,7 @@ markdown_extensions: extra_css: - stylesheets/extra.css + - stylesheets/welcome.css plugins: - blog From 8a4dc612889b014a392f4e7a2bb3a7da62c91124 Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Fri, 16 Jan 2026 11:51:15 +0000 Subject: [PATCH 02/11] More updates --- docs/overrides/welcome.html | 12 +- docs/stylesheets/welcome.css | 256 +++++++++++++++++++++++++++-------- 2 files changed, 209 insertions(+), 59 deletions(-) diff --git a/docs/overrides/welcome.html b/docs/overrides/welcome.html index 021305eb..0c3f522f 100644 --- a/docs/overrides/welcome.html +++ b/docs/overrides/welcome.html @@ -14,7 +14,7 @@

Production-Ready Healthcare AI

@@ -25,7 +25,7 @@

Choose Your Path

- +
@@ -34,12 +34,12 @@

Choose Your Path

HealthTech Engineers

Build clinical workflow integrations with Epic, Cerner, and other EHRs using CDS Hooks and FHIR APIs.

- Start Tutorial > + Start Tutorial
- +
@@ -48,11 +48,12 @@

HealthTech Engineers

LLM / GenAI Developers

Aggregate multi-EHR data for RAG pipelines and build unified patient records with automatic deduplication.

+ View Guide
- + diff --git a/docs/stylesheets/welcome.css b/docs/stylesheets/welcome.css index 4debf984..bff546f2 100644 --- a/docs/stylesheets/welcome.css +++ b/docs/stylesheets/welcome.css @@ -13,6 +13,16 @@ --text-secondary-color: #666666; --accent-color: #e59875; --accent-secondary: #79a8a9; + /* Persona accent colors */ + --persona-healthtech: #e59875; + --persona-healthtech-bg: #fef7f4; + --persona-healthtech-border: #e59875; + --persona-genai: #5c9ead; + --persona-genai-bg: #f0f7f9; + --persona-genai-border: #5c9ead; + --persona-ml: #7c6fb0; + --persona-ml-bg: #f5f3fa; + --persona-ml-border: #7c6fb0; } [data-md-color-scheme="slate"] { @@ -24,6 +34,10 @@ --card-featured-bg-color: #3d2d28; --text-primary-color: #ffffff; --text-secondary-color: #b0b0b0; + /* Dark mode persona colors */ + --persona-healthtech-bg: #3d2d28; + --persona-genai-bg: #263d42; + --persona-ml-bg: #2d2840; } /* Target welcome page container specifically */ @@ -50,60 +64,76 @@ .welcome-title { margin-top: 80px !important; - margin-bottom: 16px !important; + margin-bottom: 20px !important; font-weight: 700 !important; - font-size: 2.5rem !important; + font-size: 3.25rem !important; color: var(--text-primary-color); - line-height: 1.2; + line-height: 1.15; + letter-spacing: -0.02em; } .welcome-subtitle { - font-size: 1.125rem; + font-size: 1.25rem; color: var(--text-secondary-color); - max-width: 600px; - margin: 0 auto 24px auto; + max-width: 640px; + margin: 0 auto 32px auto; line-height: 1.6; } /* Explore/CTA Link */ .explore-link { text-align: center; - margin: 32px 0 64px 0; + margin: 40px 0 72px 0; } .explore-link a { display: inline-flex; align-items: center; - padding: 12px 24px; + padding: 16px 32px; background-color: var(--accent-color); color: #ffffff !important; text-decoration: none !important; - border-radius: 8px; + border-radius: 10px; font-weight: 600; - font-size: 1rem; - transition: background-color 0.2s, transform 0.2s; + font-size: 1.1rem; + transition: background-color 0.2s, transform 0.2s, box-shadow 0.2s; + box-shadow: 0 4px 14px -4px rgba(229, 152, 117, 0.5); } .explore-link a:hover { background-color: #d4845f; transform: translateY(-2px); + box-shadow: 0 6px 20px -4px rgba(229, 152, 117, 0.6); } .explore-link .arrow { - margin-left: 8px; - font-size: 1.2rem; + margin-left: 12px; + font-size: 1.25rem; + transition: transform 0.2s; +} + +.explore-link a:hover .arrow { + transform: translateX(3px); } /* Cards Section Wrapper */ .cards-section-wrapper { background-color: var(--cards-section-bg-color); width: 100%; - padding: 48px 0 64px 0; + padding: 0 0 72px 0; +} + +/* First section (Choose Your Path) has distinct background */ +.cards-section-wrapper section:first-child { + background-color: var(--welcome-page-bg-color); + padding-top: 56px; + padding-bottom: 56px; + margin-bottom: 0; } .cards-section-wrapper section { - margin-bottom: 48px; - padding: 0 16px; + margin-bottom: 64px; + padding: 56px 24px 0 24px; } .cards-section-wrapper section:last-child { @@ -113,25 +143,26 @@ /* Section Titles */ .section-title { font-weight: 600; - font-size: 1.25rem; + font-size: 1.35rem; text-align: center; - margin-bottom: 24px !important; + margin-bottom: 36px !important; margin-top: 0; color: var(--text-primary-color); + letter-spacing: -0.01em; } /* Card Grid */ .card-grid { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 24px; + gap: 28px; margin: 0 auto; - max-width: 900px; + max-width: 1000px; } .card-grid.two-col { grid-template-columns: repeat(2, 1fr); - max-width: 600px; + max-width: 650px; } /* Card Styles */ @@ -139,16 +170,53 @@ background-color: var(--card-bg-color); border: 2px solid var(--card-border-color); border-radius: 16px; - padding: 24px; - transition: transform 0.2s, box-shadow 0.2s; + padding: 28px; + transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; text-decoration: none !important; display: flex; flex-direction: column; + min-height: fit-content; } .card:hover { - transform: translateY(-4px); - box-shadow: 0 8px 16px -4px rgba(0, 0, 0, 0.1); + transform: translateY(-3px); + box-shadow: 0 8px 20px -6px rgba(0, 0, 0, 0.15); +} + +/* Persona cards get larger padding */ +.card.card-healthtech, +.card.card-genai, +.card.card-ml { + padding: 36px; +} + +/* Persona Cards with accent colors */ +.card.card-healthtech { + border-color: var(--persona-healthtech-border); + background-color: var(--persona-healthtech-bg); + position: relative; +} + +.card.card-healthtech .card-icon svg path { + fill: var(--persona-healthtech); +} + +.card.card-genai { + border-color: var(--persona-genai-border); + background-color: var(--persona-genai-bg); +} + +.card.card-genai .card-icon svg path { + fill: var(--persona-genai); +} + +.card.card-ml { + border-color: var(--persona-ml-border); + background-color: var(--persona-ml-bg); +} + +.card.card-ml .card-icon svg path { + fill: var(--persona-ml); } /* Featured Card (Primary Persona) */ @@ -161,31 +229,46 @@ .card.card-featured::before { content: "Recommended"; position: absolute; - top: -12px; - left: 16px; + top: -14px; + left: 24px; background-color: var(--accent-color); color: #ffffff; - font-size: 0.75rem; + font-size: 0.85rem; font-weight: 600; - padding: 4px 12px; - border-radius: 12px; + padding: 6px 16px; + border-radius: 14px; + box-shadow: 0 3px 10px -2px rgba(229, 152, 117, 0.5); + letter-spacing: 0.01em; } /* Card Icon */ .card-icon { - margin-bottom: 16px; - width: 48px; - height: 48px; + margin-bottom: 18px; + width: 56px; + height: 56px; display: flex; align-items: center; justify-content: center; background-color: var(--cards-section-bg-color); - border-radius: 12px; + border-radius: 14px; +} + +/* Persona cards have white icon backgrounds */ +.card.card-healthtech .card-icon, +.card.card-genai .card-icon, +.card.card-ml .card-icon { + background-color: rgba(255, 255, 255, 0.7); +} + +[data-md-color-scheme="slate"] .card.card-healthtech .card-icon, +[data-md-color-scheme="slate"] .card.card-genai .card-icon, +[data-md-color-scheme="slate"] .card.card-ml .card-icon { + background-color: rgba(0, 0, 0, 0.2); } .card-icon svg { - width: 28px; - height: 28px; + width: 32px; + height: 32px; } .card-icon svg path { @@ -200,26 +283,56 @@ } .card-title { - font-size: 1rem !important; + font-size: 1.15rem !important; font-weight: 600 !important; color: var(--text-primary-color); - margin-bottom: 8px !important; + margin-bottom: 12px !important; margin-top: 0 !important; + line-height: 1.3; } .card-description { - font-size: 0.875rem; + font-size: 1rem; color: var(--text-secondary-color); - line-height: 1.5; + line-height: 1.6; margin: 0; flex: 1; } +/* Ensure persona card descriptions are fully visible */ +.card.card-healthtech .card-description, +.card.card-genai .card-description, +.card.card-ml .card-description { + font-size: 0.975rem; + line-height: 1.55; +} + .card-cta { - margin-top: 16px; - font-size: 0.875rem; + margin-top: 24px; + font-size: 0.95rem; font-weight: 600; color: var(--accent-color); + display: inline-flex; + align-items: center; + transition: color 0.2s ease; +} + +/* Persona-specific CTA colors */ +.card.card-healthtech .card-cta { color: var(--persona-healthtech); } +.card.card-genai .card-cta { color: var(--persona-genai); } +.card.card-ml .card-cta { color: var(--persona-ml); } + +.card-cta .arrow { + margin-left: 8px; + transition: transform 0.2s ease; +} + +.card:hover .card-cta .arrow { + transform: translateX(4px); +} + +.card:hover .card-cta { + opacity: 0.85; } /* Welcome Page Footer */ @@ -245,45 +358,80 @@ .card-grid, .card-grid.two-col { grid-template-columns: 1fr; - gap: 20px; + gap: 24px; max-width: 100%; padding: 0 8px; } .cards-section-wrapper { - padding: 32px 0 48px 0; + padding: 0 0 48px 0; + } + + .cards-section-wrapper section:first-child { + padding-top: 40px; + padding-bottom: 40px; } .cards-section-wrapper section { - padding: 0 8px; + padding: 40px 16px 0 16px; + margin-bottom: 0; } .card { - padding: 20px; + padding: 24px; + } + + .card.card-healthtech, + .card.card-genai, + .card.card-ml { + padding: 28px; + } + + .card-icon { + width: 48px; + height: 48px; + } + + .card-icon svg { + width: 28px; + height: 28px; } .welcome-title { margin-top: 48px !important; - font-size: 1.75rem !important; + font-size: 2rem !important; } .welcome-subtitle { - font-size: 1rem; + font-size: 1.05rem; } .explore-link { - margin: 24px 0 48px 0; + margin: 28px 0 48px 0; + } + + .explore-link a { + padding: 14px 28px; + font-size: 1rem; } .section-title { - margin-top: 24px; - margin-bottom: 20px !important; + margin-top: 0; + margin-bottom: 24px !important; } .card.card-featured::before { - top: -10px; - font-size: 0.7rem; - padding: 3px 10px; + top: -12px; + font-size: 0.75rem; + padding: 5px 12px; + } + + .card-description { + font-size: 0.95rem; + } + + .card-title { + font-size: 1.1rem !important; } } From 685bc832a8fb92b95ee3ac0fad311b20f3ac55c4 Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Fri, 16 Jan 2026 16:40:39 +0000 Subject: [PATCH 03/11] Improve dark mode support and navigation styling - Add comprehensive dark mode styles for documentation pages - Fix welcome page to fill full viewport in dark mode - Update persona cards to use neutral backgrounds with colored left borders - Add Kedro-inspired navigation bar with larger tabs and underline hover - Add color accents (coral/teal) to light mode for headings, blockquotes, tables - Ensure WCAG AAA text contrast compliance in dark mode - Restore dark code block backgrounds Co-Authored-By: Claude Opus 4.5 --- docs/stylesheets/extra.css | 483 ++++++++++++++++++++++++++--------- docs/stylesheets/welcome.css | 88 ++++++- 2 files changed, 438 insertions(+), 133 deletions(-) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 405a0d4f..3ef25d40 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,125 +1,360 @@ @font-face { - font-family: 'Source Code Pro Custom', monospace; - src: url(https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap); - } - - :root > * { - --md-default-bg-color: #FFFFFF; - --md-code-bg-color: #2E3440; - --md-code-fg-color: #FFFFFF; - --md-text-font-family: "Roboto"; - --md-code-font: "Source Code Pro Custom"; - --md-default-fg-color--light: #D8DEE9; - --md-default-fg-color--lighter: #E5E9F0; - --md-default-fg-color--lightest: #ECEFF4; - } - - .index-pre-code { - max-width: 700px; - left: 50%; - } - - .index-pre-code pre>code { - text-align: left; - } - - .md-typeset pre>code { - border-radius: .2rem; - box-shadow: 10px 5px 5px #D8DEE9; - } - - .md-typeset p > code { - background: #ECEFF4; - color: #000000; - font-weight: 500; - } - - .md-typeset strong > code { - background: #ECEFF4; - color: #000000; - font-weight: 500; - } - - .md-content p > code { - background: #ECEFF4; - color: #000000; - font-weight: 500; - } - - .md-typeset td > code { - background: #ECEFF4; - color: #000000; - font-weight: 500; - } - - .md-typeset li > code { - background: #ECEFF4; - color: #000000; - font-weight: 500; - } - - .md-typeset code { - font-weight: 500; - } - - .md-typeset pre { - margin-left: .5rem; - margin-right: .5rem; - margin-top: 2rem; - margin-bottom: 2rem; - } - - /* .language-python { - background: #FFFFFF ! important - } */ - - .language-bash { - background: #FFFFFF ! important - } - - h1.title { - color: #FFFFFF; - margin: 0px 0px 5px; - } - - h2.subtitle { - margin: 5px 0px 25px; - } - - .md-typeset { - line-height: 24px; - font-weight: 400; - } - - .md-typeset h1 { - font-weight: bold; - color: #000000; - } - - .md-typeset h2 { - font-weight: bold; - color: #000000; - } - - .md-typeset h3 { - font-weight: bold; - color: #000000; - } - - .md-nav__link--active { - background-color: #ECEFF4; - } - - code { - white-space : pre-wrap !important; - } - - .md-content a { - color: #ed4e80; /* Default link color */ - text-decoration: none; /* Remove underline by default */ - transition: all 0.2s ease-in-out; /* Smooth transition for hover effect */ - } - - .md-content a:hover { - color: #2e78d8; /* Darker shade for hover state */ - } + font-family: 'Source Code Pro Custom', monospace; + src: url(https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap); +} + +/* ============================================ + Light Mode (Default) + ============================================ */ +:root { + --md-code-bg-color: #2E3440; + --md-code-fg-color: #FFFFFF; + --md-text-font-family: "Roboto"; + --md-code-font: "Source Code Pro Custom"; + --hc-heading-color: #1a1a1a; + --hc-inline-code-bg: #ECEFF4; + --hc-inline-code-color: #1a1a1a; + --hc-code-shadow: #D8DEE9; + --hc-nav-active-bg: #ECEFF4; + --hc-link-color: #ed4e80; + --hc-link-hover-color: #2e78d8; +} + +/* ============================================ + Dark Mode (Slate) + ============================================ */ +[data-md-color-scheme="slate"] { + --md-default-bg-color: #1a1a1a; + --md-default-fg-color: #ffffff; + --md-default-fg-color--light: #d1d5db; + --md-default-fg-color--lighter: #9ca3af; + --md-default-fg-color--lightest: #4b5563; + --hc-heading-color: #ffffff; + --hc-inline-code-bg: #374151; + --hc-inline-code-color: #e5e7eb; + --hc-code-shadow: transparent; + --hc-nav-active-bg: #374151; + --hc-link-color: #f472b6; + --hc-link-hover-color: #60a5fa; +} + +/* ============================================ + General Styles + ============================================ */ +.index-pre-code { + max-width: 700px; + left: 50%; +} + +.index-pre-code pre>code { + text-align: left; +} + +.md-typeset pre>code { + border-radius: .2rem; + box-shadow: 10px 5px 5px #D8DEE9; + background-color: #2E3440; + color: #FFFFFF; +} + +[data-md-color-scheme="slate"] .md-typeset pre>code { + box-shadow: none; +} + +.md-typeset p > code, +.md-typeset strong > code, +.md-typeset td > code, +.md-typeset li > code { + background: var(--hc-inline-code-bg); + color: var(--hc-inline-code-color); + font-weight: 500; +} + +.md-content p > code { + background: var(--hc-inline-code-bg); + color: var(--hc-inline-code-color); + font-weight: 500; +} + +.md-typeset code { + font-weight: 500; +} + +.md-typeset pre { + margin-left: .5rem; + margin-right: .5rem; + margin-top: 2rem; + margin-bottom: 2rem; +} + +h1.title { + color: #FFFFFF; + margin: 0px 0px 5px; +} + +h2.subtitle { + margin: 5px 0px 25px; +} + +.md-typeset { + line-height: 24px; + font-weight: 400; +} + +.md-typeset h1, +.md-typeset h2, +.md-typeset h3 { + font-weight: bold; + color: var(--hc-heading-color); +} + +.md-nav__link--active { + background-color: var(--hc-nav-active-bg); +} + +code { + white-space: pre-wrap !important; +} + +.md-content a { + color: var(--hc-link-color); + text-decoration: none; + transition: all 0.2s ease-in-out; +} + +.md-content a:hover { + color: var(--hc-link-hover-color); +} + +/* ============================================ + Dark Mode Specific Overrides + ============================================ */ + +/* Ensure dark background applies to all main areas */ +[data-md-color-scheme="slate"] { + background-color: #1a1a1a; +} + +[data-md-color-scheme="slate"] body { + background-color: #1a1a1a; +} + +[data-md-color-scheme="slate"] .md-main { + background-color: #1a1a1a; +} + +[data-md-color-scheme="slate"] .md-main__inner { + background-color: #1a1a1a; +} + +[data-md-color-scheme="slate"] .md-content { + background-color: #1a1a1a; +} + +[data-md-color-scheme="slate"] .md-content__inner { + background-color: #1a1a1a; +} + +[data-md-color-scheme="slate"] .md-sidebar { + background-color: #1a1a1a; +} + +[data-md-color-scheme="slate"] .md-sidebar__scrollwrap { + background-color: #1a1a1a; +} + +[data-md-color-scheme="slate"] .md-container { + background-color: #1a1a1a; +} + +/* Footer dark mode */ +[data-md-color-scheme="slate"] .md-footer { + background-color: #111827; +} + +[data-md-color-scheme="slate"] .md-footer-meta { + background-color: #111827; +} + +[data-md-color-scheme="slate"] .md-typeset { + color: var(--md-default-fg-color--light); +} + +[data-md-color-scheme="slate"] .md-typeset p, +[data-md-color-scheme="slate"] .md-typeset li, +[data-md-color-scheme="slate"] .md-typeset td { + color: #d1d5db; +} + +/* Dark mode navigation */ +[data-md-color-scheme="slate"] .md-nav__link { + color: #d1d5db; +} + +[data-md-color-scheme="slate"] .md-nav__link:hover { + color: #ffffff; +} + +[data-md-color-scheme="slate"] .md-nav__link--active { + color: #ffffff; +} + +/* ============================================ + Top Navigation Bar (Tabs) - Kedro-inspired + ============================================ */ + +/* Header styling */ +.md-header { + padding-left: 24px; + border-radius: 0px 0px 12px 12px; +} + +.md-header--shadow { + box-shadow: none; +} + +/* Tab link styling - larger like Kedro */ +.md-tabs__link { + font-size: 16px; + opacity: 0.85; + transition: opacity 0.2s ease; +} + +.md-tabs__link:hover { + opacity: 1; +} + +/* Active tab - underline effect like Kedro */ +.md-tabs__item--active { + font-weight: 600; +} + +.md-tabs__item--active .md-tabs__link { + opacity: 1; +} + +/* Underline on active tab */ +.md-tabs__item { + border-bottom: 2px solid transparent; + transition: border-color 0.2s ease; +} + +.md-tabs__item:hover { + border-bottom-color: currentColor; +} + +.md-tabs__item--active { + border-bottom: 2px solid currentColor; +} + +/* ============================================ + Light Mode Color Accents + ============================================ */ + +/* Accent color for headings */ +.md-typeset h1 { + color: #1a1a1a; + border-bottom: 3px solid #e59875; + padding-bottom: 0.4rem; + display: inline-block; +} + +/* Colored left border for admonitions */ +.md-typeset .admonition { + border-left: 4px solid #79a8a9; +} + +.md-typeset .admonition.note { + border-left-color: #79a8a9; +} + +.md-typeset .admonition.tip { + border-left-color: #e59875; +} + +.md-typeset .admonition.warning { + border-left-color: #f59e0b; +} + +/* Accent color for blockquotes */ +.md-typeset blockquote { + border-left: 4px solid #e59875; + background-color: #fef7f4; + padding: 1rem 1.5rem; + margin: 1.5rem 0; +} + +/* Table header accent */ +.md-typeset table:not([class]) th { + background-color: #f8f4f2; + border-bottom: 2px solid #e59875; +} + +/* Sidebar accent - active item */ +.md-nav__link--active { + color: #e59875 !important; + font-weight: 600; +} + +.md-nav__item--active > .md-nav__link { + color: #e59875; +} + +/* Search bar styling */ +.md-search__input { + border-radius: 8px; +} + +.md-search__input::placeholder { + color: rgba(255, 255, 255, 0.7); +} + +/* Button/CTA accent colors */ +.md-typeset .md-button { + background-color: #e59875; + border-color: #e59875; + color: #ffffff; +} + +.md-typeset .md-button:hover { + background-color: #d4845f; + border-color: #d4845f; +} + +.md-typeset .md-button--primary { + background-color: #e59875; + border-color: #e59875; +} + +/* Code block header accent */ +.md-typeset .highlight > .filename { + background-color: #f8f4f2; + border-bottom: 2px solid #e59875; +} + +/* ============================================ + Dark Mode Color Accent Overrides + ============================================ */ +[data-md-color-scheme="slate"] .md-typeset h1 { + color: #ffffff; + border-bottom-color: #e59875; +} + +[data-md-color-scheme="slate"] .md-typeset blockquote { + background-color: #1f2937; + border-left-color: #e59875; +} + +[data-md-color-scheme="slate"] .md-typeset table:not([class]) th { + background-color: #1f2937; + border-bottom-color: #e59875; +} + +[data-md-color-scheme="slate"] .md-nav__link--active { + color: #e59875 !important; +} + +[data-md-color-scheme="slate"] .md-typeset .highlight > .filename { + background-color: #1f2937; + border-bottom-color: #e59875; +} diff --git a/docs/stylesheets/welcome.css b/docs/stylesheets/welcome.css index bff546f2..220e48b8 100644 --- a/docs/stylesheets/welcome.css +++ b/docs/stylesheets/welcome.css @@ -26,21 +26,21 @@ } [data-md-color-scheme="slate"] { - --welcome-page-bg-color: #1e1e1e; - --cards-section-bg-color: #2d2d2d; + --welcome-page-bg-color: #1a1a1a; + --cards-section-bg-color: #1f2937; --card-bg-color: #2d2d2d; --card-border-color: #404040; --card-featured-border-color: #e59875; - --card-featured-bg-color: #3d2d28; + --card-featured-bg-color: #2d2d2d; --text-primary-color: #ffffff; - --text-secondary-color: #b0b0b0; - /* Dark mode persona colors */ - --persona-healthtech-bg: #3d2d28; - --persona-genai-bg: #263d42; - --persona-ml-bg: #2d2840; + --text-secondary-color: #d1d5db; + /* Dark mode persona colors - neutral backgrounds with colored left borders */ + --persona-healthtech-bg: #2d2d2d; + --persona-genai-bg: #2d2d2d; + --persona-ml-bg: #2d2d2d; } -/* Target welcome page container specifically */ +/* Target welcome page container and parent elements */ .md-content__inner:has(.welcome-page-container) { padding: 0 !important; margin: 0 !important; @@ -48,12 +48,28 @@ background-color: var(--welcome-page-bg-color); } +/* Ensure full-width background on welcome page */ +.md-main:has(.welcome-page-container) { + background-color: var(--welcome-page-bg-color); +} + +.md-main__inner:has(.welcome-page-container) { + max-width: none; +} + +.md-content:has(.welcome-page-container) { + max-width: none; + background-color: var(--welcome-page-bg-color); +} + /* Main Container */ .welcome-page-container { width: 100%; + min-height: 100vh; display: flex; flex-direction: column; align-items: center; + background-color: var(--welcome-page-bg-color); } /* Hero Section */ @@ -266,6 +282,60 @@ background-color: rgba(0, 0, 0, 0.2); } +/* Dark mode: Remove full colored borders, use subtle left accent borders */ +[data-md-color-scheme="slate"] .card.card-healthtech, +[data-md-color-scheme="slate"] .card.card-genai, +[data-md-color-scheme="slate"] .card.card-ml { + border: 1px solid #404040; + border-left: 3px solid; +} + +[data-md-color-scheme="slate"] .card.card-healthtech { + border-left-color: #e59875; +} + +[data-md-color-scheme="slate"] .card.card-genai { + border-left-color: #5c9ead; +} + +[data-md-color-scheme="slate"] .card.card-ml { + border-left-color: #7c6fb0; +} + +/* Dark mode featured card styling */ +[data-md-color-scheme="slate"] .card.card-featured { + border: 1px solid #404040; + border-left: 3px solid #e59875; +} + +/* Dark mode text contrast - WCAG AAA compliance */ +[data-md-color-scheme="slate"] .card-title, +[data-md-color-scheme="slate"] .card.card-healthtech .card-title, +[data-md-color-scheme="slate"] .card.card-genai .card-title, +[data-md-color-scheme="slate"] .card.card-ml .card-title { + color: #ffffff; +} + +[data-md-color-scheme="slate"] .card-description, +[data-md-color-scheme="slate"] .card.card-healthtech .card-description, +[data-md-color-scheme="slate"] .card.card-genai .card-description, +[data-md-color-scheme="slate"] .card.card-ml .card-description { + color: #d1d5db; +} + +/* Dark mode hero text - ensure high contrast */ +[data-md-color-scheme="slate"] .welcome-title { + color: #ffffff; +} + +[data-md-color-scheme="slate"] .welcome-subtitle { + color: #e5e7eb; +} + +[data-md-color-scheme="slate"] .section-title { + color: #ffffff; +} + .card-icon svg { width: 32px; height: 32px; From f5761a497c529534f03272de623f565f05404e0c Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Fri, 16 Jan 2026 16:52:16 +0000 Subject: [PATCH 04/11] Reduce vertical gap --- docs/stylesheets/extra.css | 11 +++++++++++ docs/stylesheets/welcome.css | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 3ef25d40..c26ff5ba 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -259,6 +259,13 @@ code { display: inline-block; } +/* H2 section styling - left accent border for visual anchoring */ +.md-typeset h2 { + border-left: 3px solid #e59875; + padding-left: 0.75rem; + margin-top: 2.5rem; +} + /* Colored left border for admonitions */ .md-typeset .admonition { border-left: 4px solid #79a8a9; @@ -340,6 +347,10 @@ code { border-bottom-color: #e59875; } +[data-md-color-scheme="slate"] .md-typeset h2 { + border-left-color: #e59875; +} + [data-md-color-scheme="slate"] .md-typeset blockquote { background-color: #1f2937; border-left-color: #e59875; diff --git a/docs/stylesheets/welcome.css b/docs/stylesheets/welcome.css index 220e48b8..b80579ce 100644 --- a/docs/stylesheets/welcome.css +++ b/docs/stylesheets/welcome.css @@ -99,7 +99,7 @@ /* Explore/CTA Link */ .explore-link { text-align: center; - margin: 40px 0 72px 0; + margin: 40px 0 48px 0; } .explore-link a { @@ -142,7 +142,7 @@ /* First section (Choose Your Path) has distinct background */ .cards-section-wrapper section:first-child { background-color: var(--welcome-page-bg-color); - padding-top: 56px; + padding-top: 40px; padding-bottom: 56px; margin-bottom: 0; } From af6a29862e7b9316f70e0d5d88c3ca6da58bffd1 Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Fri, 16 Jan 2026 17:43:04 +0000 Subject: [PATCH 05/11] Fix cookbook filter clear button not working Define clearAllFilters function before attaching event listener to avoid undefined reference. Function expressions assigned to window are not hoisted, so the handler was undefined when listener attached. Co-Authored-By: Claude Opus 4.5 --- docs/cookbook/index.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/cookbook/index.md b/docs/cookbook/index.md index dd2f5629..fe5a84e8 100644 --- a/docs/cookbook/index.md +++ b/docs/cookbook/index.md @@ -147,13 +147,14 @@ Hands-on, production-ready examples for building healthcare AI applications with }); }); - clearBtn.addEventListener('click', clearAllFilters); - - window.clearAllFilters = function() { + function clearAllFilters() { activeFilters.clear(); filterTags.forEach(tag => tag.classList.remove('active')); updateCards(); - }; + } + + clearBtn.addEventListener('click', clearAllFilters); + window.clearAllFilters = clearAllFilters; })(); From 687cbb4555d6ce6f0dc527d2a4ea18faea7cef5a Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Thu, 22 Jan 2026 16:16:33 +0000 Subject: [PATCH 06/11] fix issue with docs button --- docs/cookbook/index.md | 68 ------------- docs/javascripts/cookbook-filter.js | 113 +++++++++++++++++++++ docs/overrides/welcome.html | 2 +- docs/stylesheets/extra.css | 48 ++------- docs/tutorials/clinicalflow/fhir-basics.md | 26 ++--- docs/tutorials/clinicalflow/gateway.md | 62 ++++++----- mkdocs.yml | 3 + 7 files changed, 175 insertions(+), 147 deletions(-) create mode 100644 docs/javascripts/cookbook-filter.js diff --git a/docs/cookbook/index.md b/docs/cookbook/index.md index fe5a84e8..5698f8a5 100644 --- a/docs/cookbook/index.md +++ b/docs/cookbook/index.md @@ -90,74 +90,6 @@ Hands-on, production-ready examples for building healthcare AI applications with
- - --- !!! tip "What next?" diff --git a/docs/javascripts/cookbook-filter.js b/docs/javascripts/cookbook-filter.js new file mode 100644 index 00000000..83c8911e --- /dev/null +++ b/docs/javascripts/cookbook-filter.js @@ -0,0 +1,113 @@ +/** + * Cookbook Filter - Tag-based filtering for cookbook cards + * Compatible with MkDocs Material's instant loading feature + */ +function initCookbookFilter() { + const filterTags = document.querySelectorAll('.tag-filter'); + const cards = document.querySelectorAll('.cookbook-card'); + const clearBtn = document.getElementById('clearFilters'); + const noResults = document.getElementById('noResults'); + + // Exit early if elements don't exist (not on cookbook page) + if (!filterTags.length || !cards.length) { + return; + } + + // Prevent duplicate initialization by checking for marker + if (filterTags[0].dataset.initialized === 'true') { + return; + } + filterTags[0].dataset.initialized = 'true'; + + const activeFilters = new Set(); + + function updateCards() { + let visibleCount = 0; + + cards.forEach(card => { + const cardTags = card.dataset.tags.split(' '); + const matches = activeFilters.size === 0 || + [...activeFilters].some(filter => cardTags.includes(filter)); + + if (matches) { + card.classList.remove('filtered-out'); + card.classList.add('filtered-in'); + visibleCount++; + } else { + card.classList.add('filtered-out'); + card.classList.remove('filtered-in'); + } + }); + + // Show/hide no results message + if (noResults) { + if (visibleCount === 0 && activeFilters.size > 0) { + noResults.classList.remove('hidden'); + } else { + noResults.classList.add('hidden'); + } + } + + // Show/hide clear button + if (clearBtn) { + if (activeFilters.size > 0) { + clearBtn.classList.remove('hidden'); + } else { + clearBtn.classList.add('hidden'); + } + } + } + + filterTags.forEach(tag => { + tag.addEventListener('click', () => { + const filterValue = tag.dataset.tag; + + if (activeFilters.has(filterValue)) { + activeFilters.delete(filterValue); + tag.classList.remove('active'); + } else { + activeFilters.add(filterValue); + tag.classList.add('active'); + } + + updateCards(); + }); + }); + + function clearAllFilters() { + activeFilters.clear(); + filterTags.forEach(tag => tag.classList.remove('active')); + updateCards(); + } + + if (clearBtn) { + clearBtn.addEventListener('click', clearAllFilters); + } + + // Expose globally for inline onclick handlers + window.clearAllFilters = clearAllFilters; +} + +// Run immediately since extra_javascript loads after DOM is ready +initCookbookFilter(); + +// Also handle MkDocs Material instant navigation if available +// This uses a polling approach to wait for document$ to be defined +(function waitForInstantNav() { + if (typeof document$ !== 'undefined') { + document$.subscribe(function() { + initCookbookFilter(); + }); + } else { + // Fallback: use location hash change as a proxy for navigation + window.addEventListener('hashchange', initCookbookFilter); + // Also try again after a short delay in case document$ loads later + setTimeout(function() { + if (typeof document$ !== 'undefined') { + document$.subscribe(function() { + initCookbookFilter(); + }); + } + }, 1000); + } +})(); diff --git a/docs/overrides/welcome.html b/docs/overrides/welcome.html index 0c3f522f..d821ca52 100644 --- a/docs/overrides/welcome.html +++ b/docs/overrides/welcome.html @@ -75,7 +75,7 @@

ML Researchers

Get Started

- +
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index c26ff5ba..d2d520bb 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,7 +1,5 @@ -@font-face { - font-family: 'Source Code Pro Custom', monospace; - src: url(https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap); -} +/* Import Source Code Pro from Google Fonts */ +@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap'); /* ============================================ Light Mode (Default) @@ -10,7 +8,7 @@ --md-code-bg-color: #2E3440; --md-code-fg-color: #FFFFFF; --md-text-font-family: "Roboto"; - --md-code-font: "Source Code Pro Custom"; + --md-code-font: "Source Code Pro", monospace; --hc-heading-color: #1a1a1a; --hc-inline-code-bg: #ECEFF4; --hc-inline-code-color: #1a1a1a; @@ -131,38 +129,14 @@ code { ============================================ */ /* Ensure dark background applies to all main areas */ -[data-md-color-scheme="slate"] { - background-color: #1a1a1a; -} - -[data-md-color-scheme="slate"] body { - background-color: #1a1a1a; -} - -[data-md-color-scheme="slate"] .md-main { - background-color: #1a1a1a; -} - -[data-md-color-scheme="slate"] .md-main__inner { - background-color: #1a1a1a; -} - -[data-md-color-scheme="slate"] .md-content { - background-color: #1a1a1a; -} - -[data-md-color-scheme="slate"] .md-content__inner { - background-color: #1a1a1a; -} - -[data-md-color-scheme="slate"] .md-sidebar { - background-color: #1a1a1a; -} - -[data-md-color-scheme="slate"] .md-sidebar__scrollwrap { - background-color: #1a1a1a; -} - +[data-md-color-scheme="slate"], +[data-md-color-scheme="slate"] body, +[data-md-color-scheme="slate"] .md-main, +[data-md-color-scheme="slate"] .md-main__inner, +[data-md-color-scheme="slate"] .md-content, +[data-md-color-scheme="slate"] .md-content__inner, +[data-md-color-scheme="slate"] .md-sidebar, +[data-md-color-scheme="slate"] .md-sidebar__scrollwrap, [data-md-color-scheme="slate"] .md-container { background-color: #1a1a1a; } diff --git a/docs/tutorials/clinicalflow/fhir-basics.md b/docs/tutorials/clinicalflow/fhir-basics.md index 30f93446..61597613 100644 --- a/docs/tutorials/clinicalflow/fhir-basics.md +++ b/docs/tutorials/clinicalflow/fhir-basics.md @@ -72,26 +72,26 @@ HealthChain provides utilities to work with FHIR resources easily: ```python from healthchain.fhir import create_condition, create_patient -from fhir.resources.bundle import Bundle +from fhir.resources.patient import Patient -# Create a patient +# Create a patient with basic demographics +# Note: create_patient generates an auto-prefixed ID (e.g., "hc-abc123") patient = create_patient( - id="patient-001", - given_name="John", - family_name="Smith", - birth_date="1970-01-15" + gender="male", + birth_date="1970-01-15", + identifier="MRN-12345" # Optional patient identifier (e.g., MRN) ) -# Create a condition +# Create a condition linked to the patient +# The 'subject' parameter is required and references the patient condition = create_condition( - id="condition-001", + subject=f"Patient/{patient.id}", # Reference to the patient code="38341003", display="Hypertension", - system="http://snomed.info/sct", - patient_reference="Patient/patient-001" + system="http://snomed.info/sct" ) -print(f"Created patient: {patient.name[0].given[0]} {patient.name[0].family}") +print(f"Created patient with ID: {patient.id}") print(f"With condition: {condition.code.coding[0].display}") ``` @@ -107,8 +107,8 @@ bundle_data = { "resourceType": "Bundle", "type": "collection", "entry": [ - {"resource": patient.dict()}, - {"resource": condition.dict()} + {"resource": patient.model_dump()}, + {"resource": condition.model_dump()} ] } diff --git a/docs/tutorials/clinicalflow/gateway.md b/docs/tutorials/clinicalflow/gateway.md index 1469ff8f..cd541aaf 100644 --- a/docs/tutorials/clinicalflow/gateway.md +++ b/docs/tutorials/clinicalflow/gateway.md @@ -32,6 +32,8 @@ Create a file called `app.py`: ```python from healthchain.gateway import HealthChainAPI, CDSHooksService from healthchain.io import Document +from healthchain.models.requests.cdsrequest import CDSRequest +from healthchain.models.responses.cdsresponse import CDSResponse, Card from pipeline import create_clinical_pipeline # Initialize the HealthChain API @@ -40,24 +42,27 @@ app = HealthChainAPI(title="ClinicalFlow CDS Service") # Create your pipeline nlp = create_clinical_pipeline() -# Define the CDS Hooks service -@app.cds_hooks( +# Create a CDS Hooks service +cds_service = CDSHooksService() + +# Register a hook handler using the decorator +@cds_service.hook( + "patient-view", # Hook type: triggers when a clinician views a patient id="patient-alerts", title="Clinical Alert Service", description="Analyzes patient data and returns relevant clinical alerts", - hook="patient-view" # Triggers when a clinician views a patient ) -def patient_alerts(context, prefetch): +def patient_alerts(request: CDSRequest) -> CDSResponse: """ Process patient context and return CDS cards. Args: - context: CDS Hooks context (patient ID, user, etc.) - prefetch: Pre-fetched FHIR resources + request: CDSRequest containing context and prefetch data """ cards = [] # Get patient conditions from prefetch (if available) + prefetch = request.prefetch or {} conditions = prefetch.get("conditions", []) # If we have clinical notes, process them @@ -67,27 +72,27 @@ def patient_alerts(context, prefetch): # Create cards for each extracted condition for entity in result.entities: - cards.append({ - "summary": f"Condition detected: {entity['display']}", - "detail": f"SNOMED code: {entity['code']}", - "indicator": "info", - "source": { - "label": "ClinicalFlow", - "url": "https://healthchain.dev" - } - }) + cards.append(Card( + summary=f"Condition detected: {entity['display']}", + detail=f"SNOMED code: {entity['code']}", + indicator="info", + source={"label": "ClinicalFlow", "url": "https://healthchain.dev"} + )) # Check for drug interaction alerts if len(conditions) > 2: - cards.append({ - "summary": "Multiple active conditions", - "detail": f"Patient has {len(conditions)} active conditions. Review for potential interactions.", - "indicator": "warning", - "source": {"label": "ClinicalFlow"} - }) + cards.append(Card( + summary="Multiple active conditions", + detail=f"Patient has {len(conditions)} active conditions. Review for potential interactions.", + indicator="warning", + source={"label": "ClinicalFlow"} + )) + + return CDSResponse(cards=cards) - return cards +# Register the CDS service with the app +app.include_router(cds_service) # Run the server if __name__ == "__main__": @@ -97,13 +102,14 @@ if __name__ == "__main__": ## Understanding the Code -### The `@app.cds_hooks` Decorator +### The `@cds_service.hook` Decorator -This decorator registers your function as a CDS Hooks endpoint: +This decorator registers your function as a CDS Hooks handler: -- **`id`**: Unique identifier for this service +- **First argument**: The hook type (e.g., `patient-view`, `order-select`) +- **`id`**: Unique identifier for this service endpoint - **`title`**: Human-readable name -- **`hook`**: When to trigger (e.g., `patient-view`, `order-select`) +- **`description`**: What the service does ### CDS Cards @@ -133,7 +139,7 @@ Your service is now running at `http://localhost:8000`. CDS Hooks services must provide a discovery endpoint. Test it: ```bash -curl http://localhost:8000/cds-services +curl http://localhost:8000/cds/cds-discovery ``` Response: @@ -156,7 +162,7 @@ Response: Test calling your service: ```bash -curl -X POST http://localhost:8000/cds-services/patient-alerts \ +curl -X POST http://localhost:8000/cds/cds-services/patient-alerts \ -H "Content-Type: application/json" \ -d '{ "hookInstance": "test-123", diff --git a/mkdocs.yml b/mkdocs.yml index 1239ee8e..ba9444d2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -160,6 +160,9 @@ extra_css: - stylesheets/extra.css - stylesheets/welcome.css +extra_javascript: + - javascripts/cookbook-filter.js + plugins: - blog - mkdocstrings: From 8532e8ecddcc62b9d0b4528fb594c12ebd1a30dc Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Mon, 26 Jan 2026 13:14:37 +0000 Subject: [PATCH 07/11] Update ClinicalFlow tutorial with uv support and API fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add uv installation instructions alongside pip in setup.md - Fix Document.entities โ†’ doc.nlp.get_entities() API usage - Add scispaCy installation with Python 3.12 compatibility note - Add troubleshooting sections for common installation issues - Fix SandboxClient API usage in testing.md (correct URL paths, methods) - Add FHIR version callout in fhir-basics.md - Simplify model imports in gateway.md Co-Authored-By: Claude Opus 4.5 --- docs/tutorials/clinicalflow/fhir-basics.md | 8 + docs/tutorials/clinicalflow/gateway.md | 39 +++- docs/tutorials/clinicalflow/pipeline.md | 94 +++++++-- docs/tutorials/clinicalflow/setup.md | 45 +++-- docs/tutorials/clinicalflow/testing.md | 218 +++++++++++++-------- 5 files changed, 290 insertions(+), 114 deletions(-) diff --git a/docs/tutorials/clinicalflow/fhir-basics.md b/docs/tutorials/clinicalflow/fhir-basics.md index 61597613..85c069cf 100644 --- a/docs/tutorials/clinicalflow/fhir-basics.md +++ b/docs/tutorials/clinicalflow/fhir-basics.md @@ -95,6 +95,14 @@ print(f"Created patient with ID: {patient.id}") print(f"With condition: {condition.code.coding[0].display}") ``` +??? info "FHIR Versions in HealthChain" + + HealthChain uses **FHIR R5** as the default version. However, **STU3** and **R4B** are also supported for compatibility with different EHR systems. + + You can specify the version when working with FHIR resources, and HealthChain provides utilities for converting between versions when needed. + + For more details on version handling, see the [FHIR utilities reference](../../reference/utilities/fhir_helpers.md). + ## FHIR Bundles When an EHR sends patient context, it often comes as a **Bundle** - a collection of related resources: diff --git a/docs/tutorials/clinicalflow/gateway.md b/docs/tutorials/clinicalflow/gateway.md index cd541aaf..51431d12 100644 --- a/docs/tutorials/clinicalflow/gateway.md +++ b/docs/tutorials/clinicalflow/gateway.md @@ -27,7 +27,7 @@ The flow: ## Create the CDS Service -Create a file called `app.py`: +Create a file called `app.py`. This imports the pipeline you created in the [previous step](pipeline.md): ```python from healthchain.gateway import HealthChainAPI, CDSHooksService @@ -71,7 +71,7 @@ def patient_alerts(request: CDSRequest) -> CDSResponse: result = nlp(doc) # Create cards for each extracted condition - for entity in result.entities: + for entity in result.nlp.get_entities(): cards.append(Card( summary=f"Condition detected: {entity['display']}", detail=f"SNOMED code: {entity['code']}", @@ -126,9 +126,38 @@ Cards are the responses you return to the EHR. Each card has: Start your CDS service: -```bash -python app.py -``` +=== "uv" + + ```bash + uv run python app.py + ``` + +=== "pip" + + ```bash + python app.py + ``` + +??? failure "Troubleshooting: ModuleNotFoundError" + + If you see `ModuleNotFoundError: No module named 'healthchain.models'` or similar import errors: + + 1. **Make sure you're using `uv run`** - This ensures the correct environment is used: + ```bash + uv run python app.py + ``` + + 2. **Verify healthchain is installed** in your current environment: + ```bash + uv pip list | grep healthchain + ``` + + 3. **Check you're in the right directory** - Run from your `clinicalflow/` project directory, not from inside the healthchain source code. + + 4. **Reinstall if needed**: + ```bash + uv pip install --force-reinstall healthchain + ``` Your service is now running at `http://localhost:8000`. diff --git a/docs/tutorials/clinicalflow/pipeline.md b/docs/tutorials/clinicalflow/pipeline.md index 9e06255c..9ee717a4 100644 --- a/docs/tutorials/clinicalflow/pipeline.md +++ b/docs/tutorials/clinicalflow/pipeline.md @@ -58,8 +58,8 @@ def create_clinical_pipeline(): "system": "http://snomed.info/sct" }) - # Store extracted conditions in document - doc.entities = extracted + # Store extracted conditions in document's NLP annotations + doc.nlp.set_entities(extracted) return doc return pipeline.build() @@ -67,7 +67,7 @@ def create_clinical_pipeline(): ## Using the Pipeline -Test your pipeline: +Create a file called `test_pipeline.py` to test your pipeline: ```python from pipeline import create_clinical_pipeline @@ -88,10 +88,16 @@ result = nlp(doc) # Check extracted entities print("Extracted conditions:") -for entity in result.entities: +for entity in result.nlp.get_entities(): print(f" - {entity['display']} (SNOMED: {entity['code']})") ``` +Run it: + +```bash +python test_pipeline.py +``` + Expected output: ``` @@ -104,22 +110,86 @@ Extracted conditions: ## Adding SpaCy Integration (Optional) -For more sophisticated NLP, integrate spaCy: +For more sophisticated NLP, you can integrate spaCy. For **medical text**, you'll need [scispaCy](https://allenai.github.io/scispacy/) - a spaCy package with biomedical models that can recognize clinical entities. + +!!! warning "Model choice matters" + + General spaCy models like `en_core_web_sm` only recognize common entities (people, organizations, locations). They won't extract medical terms like "hypertension" or "diabetes". Use scispaCy models for clinical text. + +!!! warning "Python version compatibility" + + scispaCy requires **Python 3.12 or earlier**. If you're using Python 3.13+, you'll encounter build errors. Check your version with `python --version`. + +!!! warning "Slow installation" + + Installing scispaCy and the associated model can be slow (5-10 minutes). This is not essential to move forward with the tutorial. + +Install scispaCy and a biomedical model: + +=== "uv" + + ```bash + uv pip install scispacy + uv pip install https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.4/en_core_sci_sm-0.5.4.tar.gz + ``` + + !!! note + We use `uv pip install` instead of `uv add` here because scispaCy has strict dependency requirements that can cause conflicts. Using `uv pip install` installs the package without adding it to your `pyproject.toml`. + +=== "pip" + + ```bash + pip install scispacy + pip install https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.4/en_core_sci_sm-0.5.4.tar.gz + ``` + +??? failure "Troubleshooting: Build errors with blis" + + If you see compilation errors mentioning `blis` or `'/usr/bin/cc' failed`, try installing pre-built binaries first: + + ```bash + uv pip install blis --only-binary :all: + uv pip install scispacy + uv pip install https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.4/en_core_sci_sm-0.5.4.tar.gz + ``` + + If this still fails, you may need to install build tools (`build-essential` on Ubuntu/Debian, Xcode Command Line Tools on macOS) or use a different Python version. The keyword-based pipeline shown earlier in this tutorial works without these dependencies. + +Then create a pipeline with the biomedical model: ```python from healthchain.pipeline import Pipeline from healthchain.pipeline.components.integrations import SpacyNLP from healthchain.io import Document -def create_spacy_pipeline(): - """Create a pipeline with spaCy NLP.""" +# Create pipeline with scispaCy biomedical model +pipeline = Pipeline[Document]() +pipeline.add_node(SpacyNLP.from_model_id("en_core_sci_sm")) +nlp = pipeline.build() - pipeline = Pipeline[Document]() +# Process a document +doc = Document("Patient presents with hypertension and diabetes.") +result = nlp(doc) - # Add spaCy for tokenization and NER - pipeline.add_node(SpacyNLP.from_model_id("en_core_web_sm")) +# Access the spaCy doc for full NLP features +spacy_doc = result.nlp.get_spacy_doc() +print(f"Entities: {[(ent.text, ent.label_) for ent in spacy_doc.ents]}") - return pipeline.build() +# Access tokens +tokens = result.nlp.get_tokens() +print(f"Tokens: {tokens[:5]}...") # First 5 tokens + +# Access entities as dictionaries +entities = result.nlp.get_entities() +print(f"Extracted: {entities}") +``` + +Expected output: + +``` +Entities: [('hypertension', 'DISEASE'), ('diabetes', 'DISEASE')] +Tokens: ['Patient', 'presents', 'with', 'hypertension', 'and']... +Extracted: [{'text': 'hypertension', 'label': 'DISEASE', ...}, {'text': 'diabetes', 'label': 'DISEASE', ...}] ``` ## Pipeline Architecture @@ -141,4 +211,4 @@ Your pipeline now follows this flow: ## What's Next -Now that you have a working pipeline, let's [create a Gateway](gateway.md) to expose it as a CDS Hooks service. +Now that you have a working pipeline, let's [create a Gateway](gateway.md) to expose it as a CDS Hooks service. Don't worry if these terms don't make sense, all will be explained on the next page! diff --git a/docs/tutorials/clinicalflow/setup.md b/docs/tutorials/clinicalflow/setup.md index 92b1f560..65868422 100644 --- a/docs/tutorials/clinicalflow/setup.md +++ b/docs/tutorials/clinicalflow/setup.md @@ -4,20 +4,38 @@ Get your development environment ready for building the ClinicalFlow service. ## Install HealthChain -Create a new project directory and install HealthChain: +Create a new project directory: ```bash mkdir clinicalflow cd clinicalflow -pip install healthchain ``` -For NLP capabilities, install with the optional spaCy integration: +### Option 1: Using uv (Recommended) + +[uv](https://docs.astral.sh/uv/) is a fast Python package manager. If you don't have it installed, you can install it by following the instructions [here](https://docs.astral.sh/uv/getting-started/installation/). + +Then initialize a project and install HealthChain: + +```bash +uv init +uv add healthchain +``` + + +### Option 2: Using pip with a virtual environment + +If you prefer using pip, create and activate a virtual environment first: ```bash -pip install healthchain[nlp] +python -m venv .venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate +pip install healthchain ``` +All the code running examples in this tutorial follow uv pattern of `uv run` followed by the command. If you are using pip then simply remove uv run from the beginning of each command. + + ## Verify Installation Create a file called `check_install.py`: @@ -26,8 +44,6 @@ Create a file called `check_install.py`: import healthchain from healthchain.io import Document -print(f"HealthChain version: {healthchain.__version__}") - # Test creating a simple document doc = Document("Patient has a history of hypertension.") print(f"Created document with {len(doc.text)} characters") @@ -36,14 +52,13 @@ print(f"Created document with {len(doc.text)} characters") Run it: ```bash -python check_install.py +uv run python check_install.py ``` -You should see output like: +You should see the following output: ``` -HealthChain version: 0.x.x -Created document with 40 characters +Created document with 38 characters ``` ## Project Structure @@ -57,16 +72,6 @@ clinicalflow/ โ””โ”€โ”€ test_service.py # Testing script ``` -## Download Sample Data (Optional) - -For testing, you can use Synthea-generated patient data. HealthChain's sandbox can load this automatically, but if you want local data: - -```bash -mkdir data -# Download a sample Synthea bundle (optional) -# We'll use HealthChain's built-in data loaders in the testing step -``` - ## What's Next Now that your environment is set up, let's learn about [FHIR basics](fhir-basics.md) - the healthcare data format you'll be working with. diff --git a/docs/tutorials/clinicalflow/testing.md b/docs/tutorials/clinicalflow/testing.md index 1f0c2eab..55ca01a1 100644 --- a/docs/tutorials/clinicalflow/testing.md +++ b/docs/tutorials/clinicalflow/testing.md @@ -6,9 +6,8 @@ Validate your CDS service with realistic patient data using HealthChain's sandbo The **Sandbox** provides tools for testing CDS services without connecting to a real EHR. It can: -- Generate realistic patient data +- Load test data from files or registries - Send CDS Hooks requests to your service -- Validate responses against the specification - Save results for analysis ## Create a Test Script @@ -20,15 +19,12 @@ from healthchain.sandbox import SandboxClient # Create a sandbox client pointing to your service client = SandboxClient( - url="http://localhost:8000/cds-services/patient-alerts", + url="http://localhost:8000/cds/cds-services/patient-alerts", workflow="patient-view" ) -# Generate synthetic test data -client.generate_data( - num_patients=3, - conditions_per_patient=2 -) +# Load test data from a directory of FHIR bundles +client.load_from_path("./data/", pattern="*.json") # Send requests and collect responses responses = client.send_requests() @@ -37,62 +33,126 @@ responses = client.send_requests() print(f"Sent {len(responses)} requests") for i, response in enumerate(responses): print(f"\nPatient {i + 1}:") - print(f" Status: {response.status_code}") - if response.ok: - cards = response.json().get("cards", []) - print(f" Cards returned: {len(cards)}") - for card in cards: - print(f" - {card.get('indicator', 'info').upper()}: {card.get('summary')}") + cards = response.get("cards", []) + print(f" Cards returned: {len(cards)}") + for card in cards: + print(f" - {card.get('indicator', 'info').upper()}: {card.get('summary')}") ``` -## Run the Test +## Prepare Test Data -Make sure your service is running, then: +Before running the test, create some sample FHIR data. Create a `data` directory with a test file: ```bash -python test_service.py +mkdir -p data ``` -Expected output: - +Create `data/test_patient.json`: + +```json +{ + "resourceType": "Bundle", + "type": "collection", + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "test-patient-1", + "name": [{"given": ["John"], "family": "Smith"}], + "gender": "male", + "birthDate": "1970-01-15" + } + }, + { + "resource": { + "resourceType": "Condition", + "id": "condition-1", + "subject": {"reference": "Patient/test-patient-1"}, + "code": { + "coding": [{ + "system": "http://snomed.info/sct", + "code": "38341003", + "display": "Hypertension" + }] + }, + "clinicalStatus": { + "coding": [{"code": "active"}] + } + } + } + ] +} ``` -Sent 3 requests - -Patient 1: - Status: 200 - Cards returned: 2 - - INFO: Condition detected: Hypertension - - WARNING: Multiple active conditions - -Patient 2: - Status: 200 - Cards returned: 1 - - INFO: Condition detected: Diabetes mellitus - -Patient 3: - Status: 200 - Cards returned: 3 - - INFO: Condition detected: Chest pain - - INFO: Condition detected: Hypertension - - WARNING: Multiple active conditions + +## Run the Test + +Make sure your service is running in one terminal: + +=== "uv" + + ```bash + uv run python app.py + ``` + +=== "pip" + + ```bash + python app.py + ``` + +Then in another terminal, run the test: + +=== "uv" + + ```bash + uv run python test_service.py + ``` + +=== "pip" + + ```bash + python test_service.py + ``` + +## Using Clinical Notes + +If your pipeline processes clinical text (like the one we built), you can load free-text notes from a CSV file: + +```python +from healthchain.sandbox import SandboxClient + +client = SandboxClient( + url="http://localhost:8000/cds/cds-services/patient-alerts", + workflow="patient-view" +) + +# Load clinical notes from CSV +# The CSV should have a column containing the clinical text +client.load_free_text( + csv_path="./data/clinical_notes.csv", + column_name="note_text", + generate_synthetic=True # Generates synthetic FHIR resources +) + +responses = client.send_requests() ``` -## Using Real Test Datasets +## Using Dataset Registries -Load data from Synthea (a synthetic patient generator): +Load data from supported dataset registries like MIMIC-on-FHIR: ```python from healthchain.sandbox import SandboxClient client = SandboxClient( - url="http://localhost:8000/cds-services/patient-alerts", + url="http://localhost:8000/cds/cds-services/patient-alerts", workflow="patient-view" ) -# Load from Synthea data directory +# Load from a dataset registry client.load_from_registry( - "synthea-patient", - data_dir="./data/synthea", + "mimic-on-fhir", + data_dir="./data/mimic-fhir", resource_types=["Patient", "Condition", "MedicationStatement"], sample_size=5 ) @@ -106,35 +166,25 @@ Save results for reporting or debugging: ```python # Save responses to files -client.save_results("./output/test_results/") - -# Results are saved as JSON: -# - output/test_results/request_1.json -# - output/test_results/response_1.json -# - output/test_results/summary.json +client.save_results( + directory="./output/", + save_request=True, + save_response=True +) ``` -## Validate CDS Hooks Compliance +## Preview Requests Before Sending -The sandbox validates that responses meet the CDS Hooks specification: +Inspect what will be sent without actually calling the service: ```python -from healthchain.sandbox import SandboxClient - -client = SandboxClient( - url="http://localhost:8000/cds-services/patient-alerts", - workflow="patient-view" -) - -# Enable strict validation -client.validate_responses = True +# Preview queued requests +previews = client.preview_requests(limit=5) +for preview in previews: + print(f"Request {preview['index']}: {preview['hook']}") -responses = client.send_requests() - -# Check for validation errors -for response in responses: - if response.validation_errors: - print(f"Validation errors: {response.validation_errors}") +# Get full request data +request_data = client.get_request_data(format="dict") ``` ## Testing Different Hooks @@ -144,14 +194,14 @@ Test different CDS Hooks workflows: ```python # Test order-select hook order_client = SandboxClient( - url="http://localhost:8000/cds-services/drug-interactions", + url="http://localhost:8000/cds/cds-services/drug-interactions", workflow="order-select" ) -# Test order-sign hook -sign_client = SandboxClient( - url="http://localhost:8000/cds-services/order-review", - workflow="order-sign" +# Test encounter-discharge hook +discharge_client = SandboxClient( + url="http://localhost:8000/cds/cds-services/discharge-summary", + workflow="encounter-discharge" ) ``` @@ -164,17 +214,31 @@ import logging logging.basicConfig(level=logging.DEBUG) client = SandboxClient( - url="http://localhost:8000/cds-services/patient-alerts", + url="http://localhost:8000/cds/cds-services/patient-alerts", workflow="patient-view" ) ``` -### Inspect Request/Response +### Check Client Status ```python -response = client.send_single_request(patient_data) -print("Request sent:", response.request_body) -print("Response received:", response.json()) +status = client.get_status() +print(f"Requests queued: {status['requests_queued']}") +print(f"Responses received: {status['responses_received']}") +``` + +### Use Context Manager + +The sandbox client supports context manager usage for automatic cleanup: + +```python +with SandboxClient( + url="http://localhost:8000/cds/cds-services/patient-alerts", + workflow="patient-view" +) as client: + client.load_from_path("./data/") + responses = client.send_requests() + # Results are auto-saved on exit if responses exist ``` ## What's Next From d1a56e6d9eac6170ad4e91d941d7b40cd364a6bb Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Mon, 26 Jan 2026 17:03:52 +0000 Subject: [PATCH 08/11] Move interop tutorial to cookbook as format conversion recipe The orphaned tutorial at docs/tutorials/interop/ was unreferenced in the documentation navigation. Refactored it as a cookbook entry with updated styling to match other cookbook recipes. Co-Authored-By: Claude Opus 4.5 --- .../format_conversion.md} | 80 +++++++++---------- docs/cookbook/index.md | 13 +++ mkdocs.yml | 1 + 3 files changed, 50 insertions(+), 44 deletions(-) rename docs/{tutorials/interop/basic_conversion.md => cookbook/format_conversion.md} (74%) diff --git a/docs/tutorials/interop/basic_conversion.md b/docs/cookbook/format_conversion.md similarity index 74% rename from docs/tutorials/interop/basic_conversion.md rename to docs/cookbook/format_conversion.md index 25c8f3ac..5dd3b364 100644 --- a/docs/tutorials/interop/basic_conversion.md +++ b/docs/cookbook/format_conversion.md @@ -1,37 +1,38 @@ -# Basic Format Conversion +# Convert Between Healthcare Data Formats -This tutorial demonstrates how to use the HealthChain interoperability module to convert between different healthcare data formats. +Convert between CDA, HL7v2, and FHIR formats using HealthChain's interoperability engine. This recipe covers the most common conversion scenarios for integrating legacy healthcare systems with modern FHIR-based applications. -## Prerequisites - -- HealthChain installed (`pip install healthchain`) -- Basic understanding of FHIR resources -- Sample CDA or HL7v2 files (or you can use the examples below) +The [InteropEngine](../reference/interop/engine.md) provides a unified interface for bidirectional format conversion, handling the complexity of mapping between different healthcare data standards. ## Setup -First, let's import the required modules and create an interoperability engine: +Install HealthChain: + +```bash +pip install healthchain +``` + +Create an interoperability engine: ```python from healthchain.interop import create_interop, FormatType from pathlib import Path import json -# Create an engine engine = create_interop() ``` ## Converting CDA to FHIR -Let's convert a CDA document to FHIR resources: +Parse a CDA document and extract FHIR resources: ```python -# Sample CDA document cda_xml = """ - + Example CDA Document @@ -54,7 +55,8 @@ cda_xml = """
- + Problems Hypertension @@ -62,10 +64,13 @@ cda_xml = """ - + - +
@@ -78,22 +83,21 @@ cda_xml = """ # Convert CDA to FHIR resources fhir_resources = engine.to_fhir(cda_xml, src_format=FormatType.CDA) -# Print the resulting FHIR resources +# Inspect the results for resource in fhir_resources: print(f"Resource Type: {resource.resource_type}") print(json.dumps(resource.dict(), indent=2)) - print("-" * 40) ``` ## Converting FHIR to CDA -You can also convert FHIR resources to a CDA document: +Generate a CDA document from FHIR resources: ```python from fhir.resources.condition import Condition from fhir.resources.patient import Patient -# Create some FHIR resources +# Create FHIR resources patient = Patient( resourceType="Patient", id="patient-1", @@ -135,20 +139,18 @@ condition = Condition( onsetDateTime="2018-01-01" ) -# Convert FHIR resources to CDA +# Convert to CDA resources = [patient, condition] cda_document = engine.from_fhir(resources, dest_format=FormatType.CDA) -# Print the resulting CDA document print(cda_document) ``` ## Converting HL7v2 to FHIR -Let's convert an HL7v2 message to FHIR resources: +Parse an HL7v2 message and extract FHIR resources: ```python -# Sample HL7v2 message hl7v2_message = """ MSH|^~\&|EPIC|EPICADT|SMS|SMSADT|199912271408|CHARRIS|ADT^A01|1817457|D|2.5| PID|1||PATID1234^5^M11^ADT1^MR^GOOD HEALTH HOSPITAL~123456789^^^USSSA^SS||EVERYMAN^ADAM^A^III||19610615|M||C|2222 HOME STREET^^GREENSBORO^NC^27401-1020|GL|(555) 555-2004|(555)555-2004||S||PATID12345001^2^M10^ADT1^AN^A|444333333|987654^NC| @@ -159,22 +161,19 @@ PV1|1|I|2000^2012^01||||004777^ATTEND^AARON^A|||SUR||||ADM|A0| # Convert HL7v2 to FHIR resources fhir_resources = engine.to_fhir(hl7v2_message, src_format=FormatType.HL7V2) -# Print the resulting FHIR resources for resource in fhir_resources: print(f"Resource Type: {resource.resource_type}") print(json.dumps(resource.dict(), indent=2)) - print("-" * 40) ``` ## Converting FHIR to HL7v2 -You can also convert FHIR resources to an HL7v2 message: +Generate an HL7v2 message from FHIR resources: ```python from fhir.resources.patient import Patient from fhir.resources.encounter import Encounter -# Create some FHIR resources patient = Patient( resourceType="Patient", id="patient-1", @@ -218,45 +217,38 @@ encounter = Encounter( } ) -# Convert FHIR resources to HL7v2 +# Convert to HL7v2 resources = [patient, encounter] hl7v2_message = engine.from_fhir(resources, dest_format=FormatType.HL7V2) -# Print the resulting HL7v2 message print(hl7v2_message) ``` -## Saving Conversion Results +## Saving Results -Let's save our conversion results to files: +Save converted data to files: ```python -# Save FHIR resources to JSON files output_dir = Path("./output") output_dir.mkdir(exist_ok=True) +# Save FHIR resources as JSON for resource in fhir_resources: filename = f"{resource.resource_type.lower()}_{resource.id}.json" with open(output_dir / filename, "w") as f: json.dump(resource.dict(), f, indent=2) -# Save CDA document to XML file +# Save CDA document as XML with open(output_dir / "document.xml", "w") as f: f.write(cda_document) -# Save HL7v2 message to file +# Save HL7v2 message with open(output_dir / "message.hl7", "w") as f: f.write(hl7v2_message) ``` -## Conclusion - -In this tutorial, you learned how to: - -- Convert CDA documents to FHIR resources -- Convert FHIR resources to CDA documents -- Convert HL7v2 messages to FHIR resources -- Convert FHIR resources to HL7v2 messages -- Save conversion results to files +## Next Steps -For more advanced interoperability features, see the [InteropEngine documentation](../../reference/interop/engine.md) and other cookbook examples. +- [InteropEngine documentation](../reference/interop/engine.md) - Advanced configuration and customization +- [Configuration guide](../reference/interop/configuration.md) - Custom templates and mappings +- [CDA Adapter](../reference/io/adapters/cdaadapter.md) - Higher-level CDA integration with Document containers diff --git a/docs/cookbook/index.md b/docs/cookbook/index.md index 5698f8a5..0791585f 100644 --- a/docs/cookbook/index.md +++ b/docs/cookbook/index.md @@ -83,6 +83,19 @@ Hands-on, production-ready examples for building healthcare AI applications with
+ +
๐Ÿ”„
+
Convert Between Healthcare Data Formats
+
+ Convert between CDA, HL7v2, and FHIR formats using the interoperability engine. Handle bidirectional conversion for integrating legacy systems with modern FHIR applications. +
+
+ HealthTech + Interop + FHIR +
+
+ diff --git a/mkdocs.yml b/mkdocs.yml index ba9444d2..e84e4350 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,6 +29,7 @@ nav: - Automated Clinical Coding: cookbook/clinical_coding.md - Discharge Summarizer: cookbook/discharge_summarizer.md - ML Model Deployment: cookbook/ml_model_deployment.md + - Format Conversion: cookbook/format_conversion.md - Docs: - Welcome: reference/index.md - Gateway: From 582fb8603af0b10e8838590398326b12afe45f8a Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Mon, 26 Jan 2026 17:10:17 +0000 Subject: [PATCH 09/11] Fix broken documentation links and anchors - Fix #prebuilt- anchor to #prebuilt in quickstart.md and concepts.md - Fix #dataset-loaders anchor to #dataset-registry - Fix ./reference/ link to point to components.md - Fix broken contribution_guide.md link to GitHub CONTRIBUTING.md Co-Authored-By: Claude Opus 4.5 --- docs/quickstart.md | 6 +++--- docs/reference/concepts.md | 4 ++-- docs/reference/interop/experimental.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index 08f03972..30b3f2bd 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -86,7 +86,7 @@ print(doc.fhir.problem_list) # FHIR Condition #### 2. Build With Components and Adapters -[**Components**](./reference/) are reusable, stateful classes that encapsulate specific processing logic, model loading, or configuration for your pipeline. Use them to organize complex workflows, handle model state, or integrate third-party libraries with minimal setup. +[**Components**](./reference/pipeline/components/components.md) are reusable, stateful classes that encapsulate specific processing logic, model loading, or configuration for your pipeline. Use them to organize complex workflows, handle model state, or integrate third-party libraries with minimal setup. HealthChain provides a set of ready-to-use [**NLP Integrations**](./reference/pipeline/integrations/integrations.md) for common clinical NLP and ML tasks, and you can easily implement your own. @@ -131,7 +131,7 @@ output = adapter.format(processed_doc) Prebuilt pipelines are the fastest way to jump into healthcare AI with minimal setup: just load and run. Each pipeline bundles best-practice components and models for common clinical tasks (like coding or summarization) and handles all FHIR/CDA conversion for you. Easily customize or extend pipelines by adding/removing components, or swap models as needed. -[(Full Documentation on Pipelines)](./reference/pipeline/pipeline.md#prebuilt-) +[(Full Documentation on Pipelines)](./reference/pipeline/pipeline.md#prebuilt) ```python from healthchain.pipeline import MedicalCodingPipeline @@ -190,7 +190,7 @@ Workflows determine the request structure, required FHIR resources, and validati #### Available Dataset Loaders -[**Dataset Loaders**](./reference/utilities/sandbox.md#dataset-loaders) are shortcuts for loading common clinical test datasets from file. Currently available: +[**Dataset Loaders**](./reference/utilities/sandbox.md#dataset-registry) are shortcuts for loading common clinical test datasets from file. Currently available: | Dataset Key | Description | FHIR Version | Source | Download Link | |--------------------|---------------------------------------------|--------------|-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| diff --git a/docs/reference/concepts.md b/docs/reference/concepts.md index 6e1cf9e4..0dea45cb 100644 --- a/docs/reference/concepts.md +++ b/docs/reference/concepts.md @@ -128,7 +128,7 @@ output = adapter.format(processed_doc) Prebuilt pipelines are the fastest way to jump into healthcare AI with minimal setup: just load and run. Each pipeline bundles best-practice components and models for common clinical tasks (like coding or summarization) and handles all FHIR/CDA conversion for you. Easily customize or extend pipelines by adding/removing components, or swap models as needed. -[(Full Documentation on Pipelines)](./pipeline/pipeline.md#prebuilt-) +[(Full Documentation on Pipelines)](./pipeline/pipeline.md#prebuilt) ```python from healthchain.pipeline import MedicalCodingPipeline @@ -187,7 +187,7 @@ Workflows determine the request structure, required FHIR resources, and validati #### Available Dataset Loaders -[**Dataset Loaders**](./utilities/sandbox.md#dataset-loaders) are shortcuts for loading common clinical test datasets from file. Currently available: +[**Dataset Loaders**](./utilities/sandbox.md#dataset-registry) are shortcuts for loading common clinical test datasets from file. Currently available: | Dataset Key | Description | FHIR Version | Source | Download Link | |--------------------|---------------------------------------------|--------------|-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| diff --git a/docs/reference/interop/experimental.md b/docs/reference/interop/experimental.md index baa15677..ba8793db 100644 --- a/docs/reference/interop/experimental.md +++ b/docs/reference/interop/experimental.md @@ -76,4 +76,4 @@ We welcome contributions to improve experimental templates! 2. ๐Ÿ”ฎ **Future sections** - Procedures, Vital Signs, Lab Results 3. ๐Ÿ”ง **Template tooling** - Better validation and testing framework -Want to help? Check our [contribution guidelines](../../community/contribution_guide.md) and pick up one of these challenges! +Want to help? Check our [contribution guidelines](https://github.com/dotimplement/HealthChain/blob/main/CONTRIBUTING.md) and pick up one of these challenges! From 50eabfcfccc5c0ea1388180ab6cbaea0ea4defb9 Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Mon, 26 Jan 2026 19:25:50 +0000 Subject: [PATCH 10/11] tested new tutorial --- docs/tutorials/clinicalflow/fhir-basics.md | 72 +++++- docs/tutorials/clinicalflow/gateway.md | 21 -- docs/tutorials/clinicalflow/index.md | 25 +- docs/tutorials/clinicalflow/next-steps.md | 122 +-------- docs/tutorials/clinicalflow/pipeline.md | 90 +------ docs/tutorials/clinicalflow/setup.md | 25 +- docs/tutorials/clinicalflow/testing.md | 278 ++++++++++----------- docs/tutorials/index.md | 22 +- 8 files changed, 246 insertions(+), 409 deletions(-) diff --git a/docs/tutorials/clinicalflow/fhir-basics.md b/docs/tutorials/clinicalflow/fhir-basics.md index 85c069cf..8d25c052 100644 --- a/docs/tutorials/clinicalflow/fhir-basics.md +++ b/docs/tutorials/clinicalflow/fhir-basics.md @@ -146,6 +146,76 @@ print(f"Document text: {doc.text[:50]}...") # - Problem lists, medications, etc. ``` +## How FHIR Flows into CDS Hooks + +When an EHR like Epic calls your CDS service, it sends a **CDS Hooks request** containing patient data. The FHIR resources you just learned about arrive in the `prefetch` field: + +```json +{ + "hookInstance": "abc-123-def-456", + "hook": "patient-view", + "context": { + "userId": "Practitioner/dr-smith", + "patientId": "patient-001" + }, + "prefetch": { + "patient": { + "resourceType": "Patient", + "id": "patient-001", + "name": [{"given": ["John"], "family": "Smith"}], + "birthDate": "1970-01-15", + "gender": "male" + }, + "conditions": { + "resourceType": "Bundle", + "type": "searchset", + "entry": [ + { + "resource": { + "resourceType": "Condition", + "id": "condition-hypertension", + "code": { + "coding": [{ + "system": "http://snomed.info/sct", + "code": "38341003", + "display": "Hypertension" + }] + }, + "subject": {"reference": "Patient/patient-001"}, + "clinicalStatus": {"coding": [{"code": "active"}]} + } + }, + { + "resource": { + "resourceType": "Condition", + "id": "condition-diabetes", + "code": { + "coding": [{ + "system": "http://snomed.info/sct", + "code": "73211009", + "display": "Diabetes mellitus" + }] + }, + "subject": {"reference": "Patient/patient-001"}, + "clinicalStatus": {"coding": [{"code": "active"}]} + } + } + ] + }, + "note": "Patient is a 65-year-old male presenting with chest pain and shortness of breath. History includes hypertension and diabetes, both well-controlled on current medications." + } +} +``` + +This is the complete picture: + +1. **`context`** - Tells you who triggered the hook (the practitioner) and which patient +2. **`prefetch.patient`** - The Patient resource with demographics +3. **`prefetch.conditions`** - A Bundle of the patient's active conditions +4. **`prefetch.note`** - Clinical text your NLP pipeline will process + +Your CDS service receives this request, processes the data, and returns clinical alert cards. We'll use this same sample data throughout the tutorial for testing. + ## What's Next -Now that you understand FHIR basics, let's [build a pipeline](pipeline.md) that processes clinical text and extracts structured data. +Now that you understand FHIR basics and how data flows into CDS, let's [build a pipeline](pipeline.md) that processes clinical text and extracts structured data. diff --git a/docs/tutorials/clinicalflow/gateway.md b/docs/tutorials/clinicalflow/gateway.md index 51431d12..80648615 100644 --- a/docs/tutorials/clinicalflow/gateway.md +++ b/docs/tutorials/clinicalflow/gateway.md @@ -138,27 +138,6 @@ Start your CDS service: python app.py ``` -??? failure "Troubleshooting: ModuleNotFoundError" - - If you see `ModuleNotFoundError: No module named 'healthchain.models'` or similar import errors: - - 1. **Make sure you're using `uv run`** - This ensures the correct environment is used: - ```bash - uv run python app.py - ``` - - 2. **Verify healthchain is installed** in your current environment: - ```bash - uv pip list | grep healthchain - ``` - - 3. **Check you're in the right directory** - Run from your `clinicalflow/` project directory, not from inside the healthchain source code. - - 4. **Reinstall if needed**: - ```bash - uv pip install --force-reinstall healthchain - ``` - Your service is now running at `http://localhost:8000`. ## Test the Endpoints diff --git a/docs/tutorials/clinicalflow/index.md b/docs/tutorials/clinicalflow/index.md index e4a3da54..c952a914 100644 --- a/docs/tutorials/clinicalflow/index.md +++ b/docs/tutorials/clinicalflow/index.md @@ -4,16 +4,27 @@ Build your first Clinical Decision Support (CDS) service with HealthChain. ## The Scenario -You're a HealthTech engineer at a hospital system. The clinical informatics team needs a CDS service that: +You're a HealthTech engineer at a hospital. The clinical informatics team needs a service that: 1. **Receives patient context** when a physician opens a chart 2. **Analyzes existing conditions and medications** 3. **Returns actionable alerts** for potential drug interactions or care gaps +Doing this in practice has a number of pain points: + +- **Complex protocol requirements** - CDS Hooks and FHIR have strict specifications that take weeks to implement correctly +- **Fragmented EHR data** - Patient information comes in different formats across systems, requiring custom parsing logic +- **No easy testing path** - Validating your service against realistic clinical scenarios typically requires access to live EHR systems +- **Integration boilerplate** - Writing the HTTP endpoints, request validation, and response formatting is repetitive but error-prone + +HealthChain handles all of this for you, so you can focus on the clinical logic that matters. + By the end of this tutorial, you'll have a working CDS Hooks service that integrates with EHR systems like Epic. ## What You'll Build +We'll build a Pipeline that processes clinical text from an EHR and a Service that wraps around it to return Clinical Alert Cards. + ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ EHR System โ”‚โ”€โ”€โ”€โ”€โ”€>โ”‚ Your CDS โ”‚โ”€โ”€โ”€โ”€โ”€>โ”‚ Clinical โ”‚ @@ -26,15 +37,17 @@ By the end of this tutorial, you'll have a working CDS Hooks service that integr (FHIR resources) (HealthChain) ``` +Throughout this tutorial, we'll use the same sample patient - **John Smith** with hypertension and diabetes - so you can see how data flows from FHIR resources through your pipeline to clinical alerts. + ## What You'll Learn | Step | What You'll Learn | |------|-------------------| | [Setup](setup.md) | Install dependencies, create project structure | -| [FHIR Basics](fhir-basics.md) | Understand Patient, Condition, and Medication resources | -| [Build Pipeline](pipeline.md) | Create an NLP pipeline with Document containers | -| [Create Gateway](gateway.md) | Expose your pipeline as a CDS Hooks service | -| [Test with Sandbox](testing.md) | Validate with synthetic patient data | +| [FHIR Basics](fhir-basics.md) | Understand FHIR resources and how they flow into CDS Hooks requests | +| [Build Pipeline](pipeline.md) | Create an NLP pipeline that extracts conditions from clinical text | +| [Create Gateway](gateway.md) | Expose your pipeline as a CDS Hooks service that EHRs can call | +| [Test with Sandbox](testing.md) | Validate with sample patient data (simulating what Epic would send) | | [Next Steps](next-steps.md) | Production deployment and extending your service | ## Prerequisites @@ -46,7 +59,7 @@ By the end of this tutorial, you'll have a working CDS Hooks service that integr ## Time Required -This tutorial takes approximately **45 minutes** to complete. +This tutorial takes approximately **30 minutes** to complete. ## Ready? diff --git a/docs/tutorials/clinicalflow/next-steps.md b/docs/tutorials/clinicalflow/next-steps.md index 53616e05..3e71164e 100644 --- a/docs/tutorials/clinicalflow/next-steps.md +++ b/docs/tutorials/clinicalflow/next-steps.md @@ -12,115 +12,13 @@ In this tutorial, you: - Created a CDS Hooks gateway service - Tested with the sandbox and synthetic data -## Production Considerations -### Authentication +## What to Do Next -Real EHR integrations require OAuth2 authentication: +### Improve the NLP -```python -from healthchain.gateway import HealthChainAPI - -app = HealthChainAPI( - title="ClinicalFlow CDS Service", - auth_config={ - "type": "oauth2", - "token_url": "https://your-auth-server/token", - "scopes": ["patient/*.read", "user/*.read"] - } -) -``` - -### HTTPS - -Always use HTTPS in production. With uvicorn: - -```bash -uvicorn app:app --host 0.0.0.0 --port 443 --ssl-keyfile key.pem --ssl-certfile cert.pem -``` - -### Logging and Monitoring - -Add structured logging: - -```python -import logging - -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger("clinicalflow") - -@app.cds_hooks(id="patient-alerts", ...) -def patient_alerts(context, prefetch): - logger.info(f"Processing request for patient {context.get('patientId')}") - # ... your logic -``` - -## Connect to Real EHR Sandboxes - -### Epic Sandbox - -1. Register at [Epic's Developer Portal](https://fhir.epic.com/) -2. Create an application -3. Configure your service URL -4. Test against Epic's sandbox environment - -### Cerner Sandbox - -1. Register at [Cerner's Developer Portal](https://code.cerner.com/) -2. Follow their CDS Hooks integration guide -3. Test with their Millennium sandbox +The NLP was hard coded in our example but HealthChain has simple pipeline integrations for spacy, huggingface and Langchain. Try to replace keyword matching with trained models. -## Extend Your Service - -### Add More Hooks - -Support multiple trigger points: - -```python -@app.cds_hooks( - id="order-check", - title="Medication Order Check", - hook="order-select" -) -def check_medication_orders(context, prefetch): - """Check for drug interactions when orders are selected.""" - # ... drug interaction logic - pass - - -@app.cds_hooks( - id="discharge-summary", - title="Discharge Summary Generator", - hook="encounter-discharge" -) -def generate_discharge_summary(context, prefetch): - """Generate discharge summary at end of encounter.""" - # ... summarization logic - pass -``` - -### Improve NLP - -Replace keyword matching with trained models: - -```python -from healthchain.pipeline.components.integrations import SpacyNLP - -# Use a clinical NLP model -pipeline.add_node(SpacyNLP.from_model_id("en_core_sci_lg")) - -# Or integrate with external services -from healthchain.pipeline.components import LLMComponent - -pipeline.add_node(LLMComponent( - provider="openai", - model="gpt-4", - prompt_template="Extract clinical conditions from: {text}" -)) -``` ### Add FHIR Output @@ -136,20 +34,8 @@ pipeline.add_node(FHIRProblemListExtractor()) ## Learn More -Explore HealthChain's documentation: - -| Topic | Description | -|-------|-------------| -| [Gateway Reference](../../reference/gateway/gateway.md) | Deep dive into gateway patterns | -| [Pipeline Reference](../../reference/pipeline/pipeline.md) | Advanced pipeline configuration | -| [CDS Hooks Cookbook](../../cookbook/discharge_summarizer.md) | Complete CDS Hooks example | -| [Multi-EHR Integration](../../cookbook/multi_ehr_aggregation.md) | Connect to multiple EHRs | - -## Get Help +Explore HealthChain's [cookbook](../../../cookbook/index.md) documentation, we have a variety of cookbooks that will let you build upon the basics from this tutorial. -- **Discord**: [Join our community](https://discord.gg/UQC6uAepUz) -- **GitHub**: [Report issues](https://github.com/dotimplement/healthchain/issues) -- **Office Hours**: Thursdays 4:30-5:30pm GMT ## Congratulations! diff --git a/docs/tutorials/clinicalflow/pipeline.md b/docs/tutorials/clinicalflow/pipeline.md index 9ee717a4..2d7b737b 100644 --- a/docs/tutorials/clinicalflow/pipeline.md +++ b/docs/tutorials/clinicalflow/pipeline.md @@ -94,102 +94,26 @@ for entity in result.nlp.get_entities(): Run it: -```bash -python test_pipeline.py -``` - -Expected output: - -``` -Extracted conditions: - - Hypertension (SNOMED: 38341003) - - Diabetes mellitus (SNOMED: 73211009) - - Chest pain (SNOMED: 29857009) - - Dyspnea (SNOMED: 267036007) -``` - -## Adding SpaCy Integration (Optional) - -For more sophisticated NLP, you can integrate spaCy. For **medical text**, you'll need [scispaCy](https://allenai.github.io/scispacy/) - a spaCy package with biomedical models that can recognize clinical entities. - -!!! warning "Model choice matters" - - General spaCy models like `en_core_web_sm` only recognize common entities (people, organizations, locations). They won't extract medical terms like "hypertension" or "diabetes". Use scispaCy models for clinical text. - -!!! warning "Python version compatibility" - - scispaCy requires **Python 3.12 or earlier**. If you're using Python 3.13+, you'll encounter build errors. Check your version with `python --version`. - -!!! warning "Slow installation" - - Installing scispaCy and the associated model can be slow (5-10 minutes). This is not essential to move forward with the tutorial. - -Install scispaCy and a biomedical model: - === "uv" ```bash - uv pip install scispacy - uv pip install https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.4/en_core_sci_sm-0.5.4.tar.gz + uv run python test_pipeline.py ``` - !!! note - We use `uv pip install` instead of `uv add` here because scispaCy has strict dependency requirements that can cause conflicts. Using `uv pip install` installs the package without adding it to your `pyproject.toml`. - === "pip" ```bash - pip install scispacy - pip install https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.4/en_core_sci_sm-0.5.4.tar.gz + python test_pipeline.py ``` -??? failure "Troubleshooting: Build errors with blis" - - If you see compilation errors mentioning `blis` or `'/usr/bin/cc' failed`, try installing pre-built binaries first: - - ```bash - uv pip install blis --only-binary :all: - uv pip install scispacy - uv pip install https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.4/en_core_sci_sm-0.5.4.tar.gz - ``` - - If this still fails, you may need to install build tools (`build-essential` on Ubuntu/Debian, Xcode Command Line Tools on macOS) or use a different Python version. The keyword-based pipeline shown earlier in this tutorial works without these dependencies. - -Then create a pipeline with the biomedical model: - -```python -from healthchain.pipeline import Pipeline -from healthchain.pipeline.components.integrations import SpacyNLP -from healthchain.io import Document - -# Create pipeline with scispaCy biomedical model -pipeline = Pipeline[Document]() -pipeline.add_node(SpacyNLP.from_model_id("en_core_sci_sm")) -nlp = pipeline.build() - -# Process a document -doc = Document("Patient presents with hypertension and diabetes.") -result = nlp(doc) - -# Access the spaCy doc for full NLP features -spacy_doc = result.nlp.get_spacy_doc() -print(f"Entities: {[(ent.text, ent.label_) for ent in spacy_doc.ents]}") - -# Access tokens -tokens = result.nlp.get_tokens() -print(f"Tokens: {tokens[:5]}...") # First 5 tokens - -# Access entities as dictionaries -entities = result.nlp.get_entities() -print(f"Extracted: {entities}") -``` - Expected output: ``` -Entities: [('hypertension', 'DISEASE'), ('diabetes', 'DISEASE')] -Tokens: ['Patient', 'presents', 'with', 'hypertension', 'and']... -Extracted: [{'text': 'hypertension', 'label': 'DISEASE', ...}, {'text': 'diabetes', 'label': 'DISEASE', ...}] +Extracted conditions: + - Hypertension (SNOMED: 38341003) + - Diabetes mellitus (SNOMED: 73211009) + - Chest pain (SNOMED: 29857009) + - Dyspnea (SNOMED: 267036007) ``` ## Pipeline Architecture diff --git a/docs/tutorials/clinicalflow/setup.md b/docs/tutorials/clinicalflow/setup.md index 65868422..c6fea2f6 100644 --- a/docs/tutorials/clinicalflow/setup.md +++ b/docs/tutorials/clinicalflow/setup.md @@ -33,7 +33,7 @@ source .venv/bin/activate # On Windows: .venv\Scripts\activate pip install healthchain ``` -All the code running examples in this tutorial follow uv pattern of `uv run` followed by the command. If you are using pip then simply remove uv run from the beginning of each command. +All the code running examples in this tutorial will show both the uv and pip versions of the commands. These are typically the same up to a factor of adding `uv run` at the beginning of the command. ## Verify Installation @@ -51,25 +51,22 @@ print(f"Created document with {len(doc.text)} characters") Run it: -```bash -uv run python check_install.py -``` +=== "uv" -You should see the following output: + ```bash + uv run python check_install.py + ``` -``` -Created document with 38 characters -``` +=== "pip" -## Project Structure + ```bash + python check_install.py + ``` -Create the following project structure: +You should see the following output: ``` -clinicalflow/ -โ”œโ”€โ”€ app.py # Main CDS Hooks service -โ”œโ”€โ”€ pipeline.py # NLP processing pipeline -โ””โ”€โ”€ test_service.py # Testing script +Created document with 38 characters ``` ## What's Next diff --git a/docs/tutorials/clinicalflow/testing.md b/docs/tutorials/clinicalflow/testing.md index 55ca01a1..1f24d4e2 100644 --- a/docs/tutorials/clinicalflow/testing.md +++ b/docs/tutorials/clinicalflow/testing.md @@ -4,86 +4,130 @@ Validate your CDS service with realistic patient data using HealthChain's sandbo ## What is the Sandbox? -The **Sandbox** provides tools for testing CDS services without connecting to a real EHR. It can: +The **Sandbox** simulates what an EHR like Epic does - it sends CDS Hooks requests to your service with patient data. Instead of connecting to a real EHR (which requires authentication, network access, and real patients), the sandbox lets you: -- Load test data from files or registries -- Send CDS Hooks requests to your service -- Save results for analysis +- Load test data from files or generate synthetic patients +- Send CDS Hooks requests to your service (just like Epic would) +- Collect and analyze the responses -## Create a Test Script - -Create a file called `test_service.py`: - -```python -from healthchain.sandbox import SandboxClient - -# Create a sandbox client pointing to your service -client = SandboxClient( - url="http://localhost:8000/cds/cds-services/patient-alerts", - workflow="patient-view" -) - -# Load test data from a directory of FHIR bundles -client.load_from_path("./data/", pattern="*.json") - -# Send requests and collect responses -responses = client.send_requests() - -# Analyze results -print(f"Sent {len(responses)} requests") -for i, response in enumerate(responses): - print(f"\nPatient {i + 1}:") - cards = response.get("cards", []) - print(f" Cards returned: {len(cards)}") - for card in cards: - print(f" - {card.get('indicator', 'info').upper()}: {card.get('summary')}") -``` +Think of it as a "fake EHR" for development and testing. ## Prepare Test Data -Before running the test, create some sample FHIR data. Create a `data` directory with a test file: +First, create sample data that matches what an EHR would send. This is the same CDS Hooks request format we saw in [FHIR Basics](fhir-basics.md#how-fhir-flows-into-cds-hooks). + +Create a `data` directory: ```bash -mkdir -p data +mkdir data ``` -Create `data/test_patient.json`: +Create `data/test_request.json` - this is a complete CDS Hooks request: ```json { - "resourceType": "Bundle", - "type": "collection", - "entry": [ - { - "resource": { - "resourceType": "Patient", - "id": "test-patient-1", - "name": [{"given": ["John"], "family": "Smith"}], - "gender": "male", - "birthDate": "1970-01-15" - } + "hookInstance": "test-instance-001", + "hook": "patient-view", + "context": { + "userId": "Practitioner/dr-smith", + "patientId": "patient-001" + }, + "prefetch": { + "patient": { + "resourceType": "Patient", + "id": "patient-001", + "name": [{"given": ["John"], "family": "Smith"}], + "birthDate": "1970-01-15", + "gender": "male" }, - { - "resource": { - "resourceType": "Condition", - "id": "condition-1", - "subject": {"reference": "Patient/test-patient-1"}, - "code": { - "coding": [{ - "system": "http://snomed.info/sct", - "code": "38341003", - "display": "Hypertension" - }] + "conditions": { + "resourceType": "Bundle", + "type": "searchset", + "entry": [ + { + "resource": { + "resourceType": "Condition", + "id": "condition-hypertension", + "code": { + "coding": [{ + "system": "http://snomed.info/sct", + "code": "38341003", + "display": "Hypertension" + }] + }, + "subject": {"reference": "Patient/patient-001"}, + "clinicalStatus": {"coding": [{"code": "active"}]} + } }, - "clinicalStatus": { - "coding": [{"code": "active"}] + { + "resource": { + "resourceType": "Condition", + "id": "condition-diabetes", + "code": { + "coding": [{ + "system": "http://snomed.info/sct", + "code": "73211009", + "display": "Diabetes mellitus" + }] + }, + "subject": {"reference": "Patient/patient-001"}, + "clinicalStatus": {"coding": [{"code": "active"}]} + } } - } - } - ] + ] + }, + "note": "Patient is a 65-year-old male presenting with chest pain and shortness of breath. History includes hypertension and diabetes, both well-controlled on current medications." + } } ``` +This sample includes: + +- **Patient demographics** - John Smith, born 1970 +- **Two conditions** - Hypertension and Diabetes (matching what our pipeline recognizes) +- **Clinical note** - Free text that our NLP pipeline will process + +## Quick Test with curl + +The fastest way to test is with the sample data directly. With your service running, send the request: + +```bash +curl -X POST http://localhost:8000/cds/cds-services/patient-alerts \ + -H "Content-Type: application/json" \ + -d @data/test_request.json +``` + +You should see a response with cards for the detected conditions. + +## Create a Test Script + +For more control, create `test_service.py`: + +```python +import json +import requests + +# Load the test request +with open("./data/test_request.json") as f: + request_data = json.load(f) + +# Send to your CDS service +response = requests.post( + "http://localhost:8000/cds/cds-services/patient-alerts", + json=request_data +) + +# Analyze the response +result = response.json() +cards = result.get("cards", []) + +print(f"Cards returned: {len(cards)}") +for card in cards: + indicator = card.get("indicator", "info").upper() + summary = card.get("summary") + print(f" [{indicator}] {summary}") +``` + ## Run the Test Make sure your service is running in one terminal: @@ -114,53 +158,47 @@ Then in another terminal, run the test: python test_service.py ``` -## Using Clinical Notes +Expected output: -If your pipeline processes clinical text (like the one we built), you can load free-text notes from a CSV file: - -```python -from healthchain.sandbox import SandboxClient - -client = SandboxClient( - url="http://localhost:8000/cds/cds-services/patient-alerts", - workflow="patient-view" -) - -# Load clinical notes from CSV -# The CSV should have a column containing the clinical text -client.load_free_text( - csv_path="./data/clinical_notes.csv", - column_name="note_text", - generate_synthetic=True # Generates synthetic FHIR resources -) - -responses = client.send_requests() ``` +Cards returned: 4 + [INFO] Condition detected: Hypertension + [INFO] Condition detected: Diabetes mellitus + [INFO] Condition detected: Chest pain + [INFO] Condition detected: Dyspnea + [WARNING] Multiple active conditions +``` + +The pipeline extracted four conditions from the clinical note text - exactly what we built it to do. -## Using Dataset Registries +## Advanced Usage +### Batch Testing with SandboxClient -Load data from supported dataset registries like MIMIC-on-FHIR: +For testing with multiple patients or larger datasets, use the `SandboxClient`: ```python from healthchain.sandbox import SandboxClient +# Create a client pointing to your service client = SandboxClient( url="http://localhost:8000/cds/cds-services/patient-alerts", workflow="patient-view" ) -# Load from a dataset registry -client.load_from_registry( - "mimic-on-fhir", - data_dir="./data/mimic-fhir", - resource_types=["Patient", "Condition", "MedicationStatement"], - sample_size=5 -) +# Load FHIR bundles - SandboxClient converts them to CDS requests +client.load_from_path("./data/bundles/", pattern="*.json") +# Send all requests and collect responses responses = client.send_requests() + +# Analyze results +print(f"Tested {len(responses)} patients") +for i, response in enumerate(responses): + cards = response.get("cards", []) + print(f" Patient {i + 1}: {len(cards)} cards") ``` -## Save Test Results +### Save Test Results Save results for reporting or debugging: @@ -173,7 +211,7 @@ client.save_results( ) ``` -## Preview Requests Before Sending +### Preview Requests Before Sending Inspect what will be sent without actually calling the service: @@ -187,60 +225,6 @@ for preview in previews: request_data = client.get_request_data(format="dict") ``` -## Testing Different Hooks - -Test different CDS Hooks workflows: - -```python -# Test order-select hook -order_client = SandboxClient( - url="http://localhost:8000/cds/cds-services/drug-interactions", - workflow="order-select" -) - -# Test encounter-discharge hook -discharge_client = SandboxClient( - url="http://localhost:8000/cds/cds-services/discharge-summary", - workflow="encounter-discharge" -) -``` - -## Debugging Tips - -### Enable Verbose Logging - -```python -import logging -logging.basicConfig(level=logging.DEBUG) - -client = SandboxClient( - url="http://localhost:8000/cds/cds-services/patient-alerts", - workflow="patient-view" -) -``` - -### Check Client Status - -```python -status = client.get_status() -print(f"Requests queued: {status['requests_queued']}") -print(f"Responses received: {status['responses_received']}") -``` - -### Use Context Manager - -The sandbox client supports context manager usage for automatic cleanup: - -```python -with SandboxClient( - url="http://localhost:8000/cds/cds-services/patient-alerts", - workflow="patient-view" -) as client: - client.load_from_path("./data/") - responses = client.send_requests() - # Results are auto-saved on exit if responses exist -``` - ## What's Next Your service is tested and working! Learn about [production deployment](next-steps.md) and extending your CDS service. diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 8e3f0f76..67eee44d 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -8,29 +8,13 @@ Learn HealthChain through hands-on, step-by-step tutorials. Each tutorial is des **Build a Clinical Decision Support Service** -The ClinicalFlow tutorial teaches you how to build a CDS service that integrates with EHR systems. You'll learn HealthChain's core concepts by building a real application: +The ClinicalFlow tutorial teaches you how to build a CDS service that integrates with EHR systems. You'll learn HealthChain's core concepts by building a real application. Healthcare knowledge is helpful but not required - the tutorials explain all the concepts as you go. -- **Time**: ~45 minutes + +- **Time**: ~30 minutes - **Level**: Beginner to Intermediate - **Prerequisites**: Python basics, familiarity with REST APIs [:octicons-arrow-right-24: Start the ClinicalFlow Tutorial](clinicalflow/index.md) --- - -## What You'll Learn - -| Tutorial | Core Concepts | -|----------|---------------| -| **ClinicalFlow** | FHIR resources, Document containers, Pipeline components, CDS Hooks gateway, Sandbox testing | - -## Prerequisites - -Before starting any tutorial, make sure you have: - -- Python 3.10 or higher installed -- HealthChain installed (`pip install healthchain`) -- A code editor of your choice -- Basic understanding of Python and REST APIs - -Healthcare knowledge is helpful but not required - the tutorials explain concepts as you go. From 0d9f812efd28303e99ead79cd977d4ba59b997e6 Mon Sep 17 00:00:00 2001 From: Adam Kells Date: Mon, 26 Jan 2026 19:29:41 +0000 Subject: [PATCH 11/11] remove time estimates --- docs/tutorials/clinicalflow/index.md | 3 --- docs/tutorials/index.md | 1 - 2 files changed, 4 deletions(-) diff --git a/docs/tutorials/clinicalflow/index.md b/docs/tutorials/clinicalflow/index.md index c952a914..2c8dfc7d 100644 --- a/docs/tutorials/clinicalflow/index.md +++ b/docs/tutorials/clinicalflow/index.md @@ -57,9 +57,6 @@ Throughout this tutorial, we'll use the same sample patient - **John Smith** wit - **REST API familiarity** (HTTP methods, JSON) - Healthcare knowledge is helpful but not required -## Time Required - -This tutorial takes approximately **30 minutes** to complete. ## Ready? diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 67eee44d..fc2ee897 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -11,7 +11,6 @@ Learn HealthChain through hands-on, step-by-step tutorials. Each tutorial is des The ClinicalFlow tutorial teaches you how to build a CDS service that integrates with EHR systems. You'll learn HealthChain's core concepts by building a real application. Healthcare knowledge is helpful but not required - the tutorials explain all the concepts as you go. -- **Time**: ~30 minutes - **Level**: Beginner to Intermediate - **Prerequisites**: Python basics, familiarity with REST APIs