diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..788c9aed
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,16 @@
+.git
+.idea
+.pytest_cache
+.ruff_cache
+__pycache__
+*.pyc
+*.pyo
+*.pyd
+.env
+.env.*
+tests/
+docs/
+notebooks/
+output/
+*.md
+!README.md
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..87172247
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,48 @@
+# HealthChain application Dockerfile
+#
+# Usage:
+# Build: docker build -t my-healthcare-app .
+# Run: docker run -p 8000:8000 --env-file .env my-healthcare-app
+#
+# Required environment variables (set in .env or pass via -e):
+# APP_MODULE Python module path to your app, e.g. "myapp:app" (default: "app:app")
+#
+# FHIR source credentials (if connecting to Epic/Cerner):
+# FHIR_BASE_URL, CLIENT_ID, CLIENT_SECRET or CLIENT_SECRET_PATH
+#
+# See docs: https://dotimplement.github.io/HealthChain/reference/gateway/gateway/
+
+FROM python:3.11-slim
+
+# Keeps Python from buffering stdout/stderr
+ENV PYTHONUNBUFFERED=1 \
+ PYTHONDONTWRITEBYTECODE=1 \
+ APP_MODULE=app:app \
+ PORT=8000
+
+WORKDIR /app
+
+# Install system dependencies needed by lxml and spaCy
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ gcc \
+ g++ \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install healthchain from PyPI
+RUN pip install --no-cache-dir healthchain
+
+# Install any additional dependencies your application needs
+# (copy requirements first to leverage Docker layer caching)
+COPY requirements.txt* ./
+RUN if [ -f requirements.txt ]; then pip install --no-cache-dir -r requirements.txt; fi
+
+# Copy application code
+COPY . .
+
+# Run as non-root user
+RUN useradd -m appuser && chown -R appuser /app
+USER appuser
+
+EXPOSE $PORT
+
+CMD uvicorn $APP_MODULE --host 0.0.0.0 --port $PORT
diff --git a/README.md b/README.md
index a004dda6..877a755b 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@
-HealthChain is an open-source developer framework to build healthcare AI applications with native protocol understanding. Skip months of custom integration with **built-in FHIR support**, **real-time EHR connectivity**, and **production-ready deployment** - all in Python.
+HealthChain is an open-source SDK for production-ready healthcare AI. Skip months of custom integration work with **built-in FHIR support**, **real-time EHR connectivity**, and **deployment tooling for healthcare AI/ML systems** — all in Python.
@@ -34,8 +34,17 @@ HealthChain is an open-source developer framework to build healthcare AI applica
```bash
pip install healthchain
+
+# Scaffold a new project
+healthchain new my-app
+cd my-app
+
+# Run locally
+healthchain serve
```
+See the [CLI reference](https://dotimplement.github.io/HealthChain/cli/) for all commands.
+
## Core Features
HealthChain is the **quickest way for AI/ML engineers to integrate their models with real healthcare systems**.
@@ -232,7 +241,7 @@ client.save_results("./output/")
- [ ] 🔍 Data provenance and observability
- [ ] 🔒 Production security and compliance (Authentication, audit logging, HIPAA)
- [ ] 🔄 HL7v2 parsing, FHIR profile conversion and OMOP mapping support
-- [ ] 🚀 Enhanced deployment support (Docker, Kubernetes, telemetry)
+- [ ] 🚀 Kubernetes and telemetry support
- [ ] 📊 Model performance monitoring with MLFlow integration
- [ ] 🤖 MCP server integration
diff --git a/docs/cli.md b/docs/cli.md
new file mode 100644
index 00000000..de36fa8f
--- /dev/null
+++ b/docs/cli.md
@@ -0,0 +1,94 @@
+# CLI Reference
+
+HealthChain ships with a CLI to help you scaffold, run, and customize projects.
+
+```bash
+healthchain --help
+```
+
+---
+
+## `healthchain new`
+
+Scaffold a new project directory with everything you need to get started.
+
+```bash
+healthchain new my-app
+```
+
+Creates:
+
+```
+my-app/
+├── app.py # your application entry point
+├── .env.example # FHIR credential template — copy to .env and fill in
+├── requirements.txt # add extra dependencies here
+├── Dockerfile
+└── .dockerignore
+```
+
+---
+
+## `healthchain serve`
+
+Start your app locally with uvicorn.
+
+```bash
+healthchain serve # defaults to app:app on port 8000
+healthchain serve app:app
+healthchain serve app:app --port 8080
+healthchain serve app:app --host 127.0.0.1 --port 8080
+```
+
+The `app_module` argument is the Python import path to your FastAPI app instance — `:`. If your app is defined as `app = HealthChainAPI()` in `app.py`, the default `app:app` works as-is.
+
+To run in Docker instead:
+
+```bash
+docker build -t my-app .
+docker run -p 8000:8000 --env-file .env my-app
+```
+
+---
+
+## `healthchain eject-templates`
+
+Copy the built-in interop templates into your project so you can customize them.
+
+```bash
+healthchain eject-templates ./my_configs
+```
+
+Only needed if you're using the [InteropEngine](reference/interop/interop.md) and want to customize FHIR↔CDA conversion beyond the defaults. After ejecting:
+
+```python
+from healthchain.interop import create_interop
+
+engine = create_interop(config_dir="./my_configs")
+```
+
+See [Interoperability](reference/interop/interop.md) for details.
+
+---
+
+## Typical workflow
+
+```bash
+# 1. Scaffold a new project
+healthchain new my-cds-service
+cd my-cds-service
+
+# 2. Build your app in app.py
+# See https://dotimplement.github.io/HealthChain/cookbook/ for examples
+
+# 3. Set credentials
+cp .env.example .env
+# edit .env with your FHIR_BASE_URL, CLIENT_ID, CLIENT_SECRET
+
+# 4. Run locally
+healthchain serve
+
+# 5. Ship it
+docker build -t my-cds-service .
+docker run -p 8000:8000 --env-file .env my-cds-service
+```
diff --git a/docs/index.md b/docs/index.md
index da431181..65b5505d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -53,7 +53,7 @@ HealthChain is an open-source Python toolkit that streamlines productionizing he
## Getting Started with Healthcare AI
-HealthChain provides the missing middleware layer between healthcare systems and modern AI/ML development. Whether you're building clinical decision support tools, processing medical documents, or creating multi-system integrations, these docs will guide you through:
+HealthChain is production-ready healthcare AI infrastructure: built-in FHIR support, real-time EHR connectivity, and deployment tooling for healthcare AI/ML systems. Skip months of custom integration work. Whether you're building clinical decision support tools, processing medical documents, or creating multi-system integrations, these docs will guide you through:
- **🔧 Core concepts** - Understand FHIR resources, pipelines, and gateway patterns
- **📚 Real examples** - Step-by-step tutorials for common healthcare AI use cases
diff --git a/docs/reference/interop/configuration.md b/docs/reference/interop/configuration.md
index 5eb66304..e4548203 100644
--- a/docs/reference/interop/configuration.md
+++ b/docs/reference/interop/configuration.md
@@ -27,8 +27,8 @@ engine = create_interop(config_dir="/path/to/custom/configs")
To create editable configuration templates:
```bash
-# Create customizable config templates
-healthchain init-configs ./my_configs
+# Eject built-in templates for customization
+healthchain eject-templates ./my_configs
# Then use them in your code
engine = create_interop(config_dir="./my_configs")
diff --git a/docs/reference/interop/engine.md b/docs/reference/interop/engine.md
index 903dabe5..67d79509 100644
--- a/docs/reference/interop/engine.md
+++ b/docs/reference/interop/engine.md
@@ -43,10 +43,10 @@ engine = create_interop(validation_level="warn", environment="production")
> **💡 Tip:**
-> To create editable configuration templates, run:
+> To eject the built-in templates for customization, run:
>
> ```bash
-> healthchain init-configs ./my_configs
+> healthchain eject-templates ./my_configs
> ```
> This will create a `my_configs` directory with editable default configuration templates.
diff --git a/docs/reference/interop/experimental.md b/docs/reference/interop/experimental.md
index ba8793db..cc757e69 100644
--- a/docs/reference/interop/experimental.md
+++ b/docs/reference/interop/experimental.md
@@ -28,7 +28,7 @@ This page tracks templates that are under development or have known issues. Use
1. Copy experimental files to your custom config:
```bash
- # After running: healthchain init-configs my_configs
+ # After running: healthchain eject-templates my_configs
cp dev-templates/allergies/allergies.yaml my_configs/interop/cda/sections/
cp dev-templates/allergies/allergy_*.liquid my_configs/templates/cda_fhir/
cp dev-templates/allergies/allergy_*.liquid my_configs/templates/fhir_cda/
diff --git a/docs/reference/interop/interop.md b/docs/reference/interop/interop.md
index 1c073e38..29918f8f 100644
--- a/docs/reference/interop/interop.md
+++ b/docs/reference/interop/interop.md
@@ -65,11 +65,11 @@ cda_document = engine.from_fhir(fhir_resources, dest_format="cda")
```
### Custom Configs
-The default templates that come with the package are limited to problems, medications, and notes and are meant for basic testing and prototyping. Use the `healthchain init-configs` command to create editable configuration templates:
+The default templates that come with the package are limited to problems, medications, and notes and are meant for basic testing and prototyping. Use the `healthchain eject-templates` command to create editable configuration templates:
```bash
-# Create editable configuration templates
-healthchain init-configs ./my_configs
+# Eject built-in templates for customization
+healthchain eject-templates ./my_configs
```
Then use the `config_dir` parameter to specify the path to your custom configs:
diff --git a/healthchain/cli.py b/healthchain/cli.py
index bf4577c6..f720367e 100644
--- a/healthchain/cli.py
+++ b/healthchain/cli.py
@@ -1,64 +1,219 @@
import argparse
import subprocess
+import sys
+from pathlib import Path
+_DOCKERFILE = """\
+# HealthChain application Dockerfile
+#
+# Usage:
+# Build: docker build -t my-healthcare-app .
+# Run: docker run -p 8000:8000 --env-file .env my-healthcare-app
+#
+# Required environment variables (set in .env or pass via -e):
+# APP_MODULE Python module path to your app, e.g. "myapp:app" (default: "app:app")
+#
+# FHIR source credentials (if connecting to Epic/Cerner):
+# FHIR_BASE_URL, CLIENT_ID, CLIENT_SECRET or CLIENT_SECRET_PATH
+#
+# See docs: https://dotimplement.github.io/HealthChain/reference/gateway/gateway/
-def run_file(filename):
- try:
- result = subprocess.run(["uv", "run", "python", filename], check=True)
- print(result.stdout)
- except subprocess.CalledProcessError as e:
- print(f"An error occurred while trying to run the file: {e}")
+FROM python:3.11-slim
+
+# Keeps Python from buffering stdout/stderr
+ENV PYTHONUNBUFFERED=1 \\
+ PYTHONDONTWRITEBYTECODE=1 \\
+ APP_MODULE=app:app \\
+ PORT=8000
+
+WORKDIR /app
+
+# Install system dependencies needed by lxml and spaCy
+RUN apt-get update && apt-get install -y --no-install-recommends \\
+ gcc \\
+ g++ \\
+ && rm -rf /var/lib/apt/lists/*
+
+# Install healthchain from PyPI
+RUN pip install --no-cache-dir healthchain
+
+# Install any additional dependencies your application needs
+# (copy requirements first to leverage Docker layer caching)
+COPY requirements.txt* ./
+RUN if [ -f requirements.txt ]; then pip install --no-cache-dir -r requirements.txt; fi
+
+# Copy application code
+COPY . .
+
+# Run as non-root user
+RUN useradd -m appuser && chown -R appuser /app
+USER appuser
+
+EXPOSE $PORT
+
+CMD uvicorn $APP_MODULE --host 0.0.0.0 --port $PORT
+"""
+
+_DOCKERIGNORE = """\
+.git
+.idea
+.pytest_cache
+.ruff_cache
+__pycache__
+*.pyc
+*.pyo
+*.pyd
+.env
+.env.*
+tests/
+docs/
+notebooks/
+output/
+*.md
+!README.md
+"""
+
+_ENV_EXAMPLE = """\
+# FHIR source credentials
+FHIR_BASE_URL=
+CLIENT_ID=
+CLIENT_SECRET=
+
+# For JWT assertion flow (e.g. Epic SMART on FHIR)
+# CLIENT_SECRET_PATH=/path/to/private_key.pem
+"""
+
+_REQUIREMENTS = "healthchain\n"
+
+_APP_PY = """\
+# Your HealthChain application goes here.
+# See https://dotimplement.github.io/HealthChain/ for examples.
+"""
-def init_configs(target_dir: str):
- """Initialize configuration templates for customization."""
+def new_project(name: str):
+ """Scaffold a new HealthChain project."""
+ project_dir = Path(name)
+
+ if project_dir.exists():
+ print(f"❌ Directory '{name}' already exists.")
+ return
+
+ project_dir.mkdir()
+ (project_dir / "app.py").write_text(_APP_PY)
+ (project_dir / ".env.example").write_text(_ENV_EXAMPLE)
+ (project_dir / "requirements.txt").write_text(_REQUIREMENTS)
+ (project_dir / "Dockerfile").write_text(_DOCKERFILE)
+ (project_dir / ".dockerignore").write_text(_DOCKERIGNORE)
+
+ print(f"\nCreated project '{name}/'")
+ print(f" {name}/app.py")
+ print(f" {name}/.env.example")
+ print(f" {name}/requirements.txt")
+ print(f" {name}/Dockerfile")
+ print("\nNext steps:")
+ print(f" 1. Build your app in {name}/app.py")
+ print(" 2. Copy .env.example to .env and fill in your credentials")
+ print(f" 3. healthchain serve app:app (from inside {name}/)")
+ print(f" 4. docker build -t {name} {name}/")
+ print(f" 5. docker run -p 8000:8000 --env-file {name}/.env {name}")
+ print("\nUsing format conversion? Run: healthchain eject-templates ./configs")
+ print("See https://dotimplement.github.io/HealthChain/ for examples.")
+
+
+def eject_templates(target_dir: str):
+ """Eject built-in interop templates for customization."""
try:
from healthchain.interop import init_config_templates
target_path = init_config_templates(target_dir)
- print(f"\n🎉 Success! Configuration templates created at: {target_path}")
- print("\n📖 Next steps:")
- print(" 1. Customize the configuration files in the created directory")
+ print(f"\n✅ Templates ejected to: {target_path}")
+ print("\nNext steps:")
+ print(" 1. Customize the templates in the created directory")
print(" 2. Use them in your code:")
print(" from healthchain.interop import create_interop")
print(f" engine = create_interop(config_dir='{target_dir}')")
- print("\n📚 See documentation for configuration options")
+ print(
+ "\nSee https://dotimplement.github.io/HealthChain/reference/interop/ for details."
+ )
except FileExistsError as e:
print(f"❌ Error: {str(e)}")
- print("💡 Tip: Choose a different directory name or remove the existing one")
+ print("💡 Choose a different directory name or remove the existing one.")
except Exception as e:
- print(f"❌ Error initializing configs: {str(e)}")
- print("💡 Tip: Make sure HealthChain is properly installed")
+ print(f"❌ Error ejecting templates: {str(e)}")
+ print("💡 Make sure HealthChain is properly installed.")
+
+
+def serve(app_module: str, host: str, port: int):
+ """Start a HealthChain app with uvicorn."""
+ try:
+ subprocess.run(
+ [
+ sys.executable,
+ "-m",
+ "uvicorn",
+ app_module,
+ "--host",
+ host,
+ "--port",
+ str(port),
+ ],
+ check=True,
+ )
+ except subprocess.CalledProcessError as e:
+ print(f"❌ Server error: {e}")
+ except KeyboardInterrupt:
+ pass
def main():
parser = argparse.ArgumentParser(description="HealthChain command-line interface")
subparsers = parser.add_subparsers(dest="command", required=True)
- # Subparser for the 'run' command
- run_parser = subparsers.add_parser("run", help="Run a specified file")
- run_parser.add_argument("filename", type=str, help="The filename to run")
+ # Subparser for the 'new' command
+ new_parser = subparsers.add_parser("new", help="Scaffold a new HealthChain project")
+ new_parser.add_argument("name", type=str, help="Project name (creates a directory)")
+
+ # Subparser for the 'serve' command
+ serve_parser = subparsers.add_parser(
+ "serve", help="Start a HealthChain app with uvicorn"
+ )
+ serve_parser.add_argument(
+ "app_module",
+ type=str,
+ nargs="?",
+ default="app:app",
+ help="Module path to your app (default: app:app)",
+ )
+ serve_parser.add_argument(
+ "--host", type=str, default="0.0.0.0", help="Host (default: 0.0.0.0)"
+ )
+ serve_parser.add_argument(
+ "--port", type=int, default=8000, help="Port (default: 8000)"
+ )
- # Subparser for the 'init-configs' command
- init_parser = subparsers.add_parser(
- "init-configs",
- help="Initialize configuration templates for interop customization",
+ # Subparser for the 'eject-templates' command
+ eject_parser = subparsers.add_parser(
+ "eject-templates",
+ help="Eject built-in interop templates for customization",
)
- init_parser.add_argument(
+ eject_parser.add_argument(
"target_dir",
type=str,
nargs="?",
default="./healthchain_configs",
- help="Directory to create configuration templates (default: ./healthchain_configs)",
+ help="Directory to eject templates into (default: ./healthchain_configs)",
)
args = parser.parse_args()
- if args.command == "run":
- run_file(args.filename)
- elif args.command == "init-configs":
- init_configs(args.target_dir)
+ if args.command == "new":
+ new_project(args.name)
+ elif args.command == "serve":
+ serve(args.app_module, args.host, args.port)
+ elif args.command == "eject-templates":
+ eject_templates(args.target_dir)
if __name__ == "__main__":
diff --git a/mkdocs.yml b/mkdocs.yml
index e84e4350..77d7c3ae 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -11,6 +11,7 @@ nav:
- Getting Started:
- Installation: installation.md
- Quickstart: quickstart.md
+ - CLI: cli.md
- Licence: distribution.md
- Tutorials:
- tutorials/index.md
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 9d513865..d1448e47 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -4,7 +4,7 @@
import subprocess
from unittest.mock import patch
-from healthchain.cli import init_configs, run_file, main
+from healthchain.cli import eject_templates, serve, main
@pytest.mark.parametrize(
@@ -14,27 +14,27 @@
FileExistsError("Directory already exists"),
[
"❌ Error: Directory already exists",
- "💡 Tip: Choose a different directory name or remove the existing one",
+ "💡 Choose a different directory name or remove the existing one",
],
),
(
Exception("Something went wrong"),
[
- "❌ Error initializing configs: Something went wrong",
- "💡 Tip: Make sure HealthChain is properly installed",
+ "❌ Error ejecting templates: Something went wrong",
+ "💡 Make sure HealthChain is properly installed",
],
),
],
)
@patch("healthchain.interop.init_config_templates")
-def test_init_configs_error_handling_provides_helpful_guidance(
+def test_eject_templates_error_handling_provides_helpful_guidance(
mock_init_templates, error, expected_messages
):
- """init_configs provides helpful error messages and guidance when template creation fails."""
+ """eject_templates provides helpful error messages and guidance when template creation fails."""
mock_init_templates.side_effect = error
with patch("builtins.print") as mock_print:
- init_configs("./test_configs")
+ eject_templates("./test_configs")
# Verify helpful error messages are displayed
for expected_msg in expected_messages:
@@ -42,50 +42,59 @@ def test_init_configs_error_handling_provides_helpful_guidance(
@patch("healthchain.interop.init_config_templates")
-def test_init_configs_success_provides_usage_instructions(mock_init_templates):
- """init_configs provides clear usage instructions when successful."""
+def test_eject_templates_success_provides_usage_instructions(mock_init_templates):
+ """eject_templates provides clear usage instructions when successful."""
target_dir = "./test_configs"
mock_init_templates.return_value = target_dir
with patch("builtins.print") as mock_print:
- init_configs(target_dir)
+ eject_templates(target_dir)
# Verify success message and usage instructions are provided
print_output = " ".join(str(call) for call in mock_print.call_args_list)
- assert "🎉 Success!" in print_output
+ assert "✅ Templates ejected to:" in print_output
assert "create_interop(config_dir=" in print_output
- assert "📖 Next steps:" in print_output
+ assert "Next steps:" in print_output
@patch("subprocess.run")
-def test_run_file_handles_execution_errors_gracefully(mock_run):
- """run_file provides clear error message when script execution fails."""
- mock_run.side_effect = subprocess.CalledProcessError(1, "uv")
+def test_serve_handles_execution_errors_gracefully(mock_run):
+ """serve provides clear error message when server fails to start."""
+ mock_run.side_effect = subprocess.CalledProcessError(1, "uvicorn")
with patch("builtins.print") as mock_print:
- run_file("failing_script.py")
+ serve("app:app", "0.0.0.0", 8000)
# Verify error message is informative
error_message = mock_print.call_args[0][0]
- assert "An error occurred while trying to run the file:" in error_message
+ assert "❌ Server error:" in error_message
@pytest.mark.parametrize(
"args,expected_call",
[
- (["healthchain", "run", "test.py"], ("run_file", "test.py")),
- (["healthchain", "init-configs", "my_configs"], ("init_configs", "my_configs")),
- (["healthchain", "init-configs"], ("init_configs", "./healthchain_configs")),
+ (
+ ["healthchain", "serve", "test.py:app"],
+ ("serve", ("test.py:app", "0.0.0.0", 8000)),
+ ),
+ (
+ ["healthchain", "eject-templates", "my_configs"],
+ ("eject_templates", ("my_configs",)),
+ ),
+ (
+ ["healthchain", "eject-templates"],
+ ("eject_templates", ("./healthchain_configs",)),
+ ),
],
)
def test_main_routes_commands_correctly(args, expected_call):
"""Main function correctly routes CLI commands to appropriate handlers."""
- function_name, expected_arg = expected_call
+ function_name, expected_args = expected_call
with patch(f"healthchain.cli.{function_name}") as mock_function:
with patch("sys.argv", args):
main()
- mock_function.assert_called_once_with(expected_arg)
+ mock_function.assert_called_once_with(*expected_args)
def test_main_requires_command_argument():