- Overview
- Architecture
- Installation and Usage
- Troubleshooting
- Model Development
- MCP Server
- Development
- Miscellaneous
CHARMTwinsight is part of the CHARM suite of tools, focused on health data storage, predictive analytics, and synthetic data generation.
It is under active development, and currently supports the following features via REST API:
- Synthetic patient data generation with Synthea.
- Internal FHIR data storage and cohort management with HAPI FHIR.
- Data summary statistics and querying.
- Predictive analytics, supporting health risk models and synthetic data generation with GAN and similar models.
- Ingestion of FHIR data from external sources such as FHIR-HOSE.
CHARMTwinsight is designed as a microservices architecture managed with docker compose.
Note: this architecture represents implemented data flows; future features may connect HAPI FHIR to the model server, etc.
router: Externally-facing REST API providing access to backend services. Built with FastAPI for flexibility.
hapi: HAPI FHIR server for efficient and flexible storage and querying of FHIR data.
hapi_db: Postgres database providing storage for HAPI.
synthea_server: REST API frontend to the Synthea synthetic data generator; generates FHIR data and corresponding cohort IDs for storage in HAPI.
stat_server_r: REST API for R-based analytics (summary statistics etc.) on stored FHIR or other data. Uses plumbr and fhircrackr R libraries, amongst others.
stat_server_py: REST API for Python-based analytics (summary statistics etc.) on stored FHIR or other data. Uses FastAPI, fhiry, and other Python libraries.
model_server: REST API hosting and serving arbitrary ML and statistical models packaged as Docker containers.
model_server_db: MongoDB database for storing metadata on hosted models.
mcp_server: Model Context Protocol (MCP) server for cohort generation, patient querying, and model access
All services are containerized, most developed with python>=3.13.
You will need docker; if using a Mac install Docker Desktop.
- Mac users may also wish to install GNU versions of coreutils
- Mac users may also need to enable Docker volume storage under
System Preferences -> Privacy and Security -> Files and Folders -> Docker
New simplified process: CHARMTwinsights now automatically builds and registers all components in one step.
# working dir: app
./build_all.sh
docker compose up --detachThe build_all.sh script builds both the application images and the built-in ML models. All docker compose commands need to be run in the same directory as the docker-compose.yml file.
When running, public-facing REST endpoints are found at http://localhost:8000/, with documentation at http://localhost:8000/docs (documentation link, for the bots reading this).
Synthetic patient data in FHIR format may be generated via a POST request to http://localhost:8000/synthetic/synthea/generate-synthetic-patients, with url parameters num_patients, num_years,
and cohort_id. The patients will be simulated with Synthea, and their records will be tagged with the provided cohort_id (defaulting to default).
It is possible to re-use the same cohort ID across multiple generations, in which case newly generated patients will be added to the cohort. It is also possible to specify the US states patients should be sampled from, and large cohort generations is broken into batches managed by an internal job queue.
A testing script demonstrates this (retrying until the Synthea and HAPI services are up and running), creating a cohort1 with 6 members, and a cohort2 with 3. Each generation takes a few seconds.
# working dir: app
synthea_server/gen_patients.shThese data are pushed to the FHIR server accessible at http://localhost:8080 for development purposes.
External FHIR data from sources like mobile apps, wearables, or EHRs can be ingested via POST request to http://localhost:8000/ingest/fhir. The endpoint accepts a FHIR Bundle and automatically:
- Prefixes patient IDs with
ext-to prevent conflicts with synthetic data - Updates all references throughout the bundle
- Applies CHARM tags for cohort organization
- Supports updates to existing patient data (re-ingesting updates rather than duplicates)
A comprehensive test script demonstrates ingestion, validation, and update capabilities:
# working dir: app
synthea_server/test_external_ingest.shThis script tests basic ingestion, custom parameters, validation, and verifies that patient data is properly updated rather than duplicated. See the API documentation for example FHIR bundles and detailed usage.
Predictive model capabilities are accessed under endpoints at http://localhost:8000/modeling. Example CURLs are available via script:
# working dir: app
model_server/models/test_predict_models.shAs above, skip if you are not developing or using models.
Summary statistics about generated patient data are available under endpoints at http://localhost:8000/stats. Examples CURLs are available via script:
# working dir: app
stat_server_py/test_stats.shCI entrypoints are in ci/, and GitHub Actions calls the same scripts. This lets you test workflow logic locally before pushing.
# project root
./ci/run.sh model-validation
./ci/run.sh router-validation
./ci/run.sh allSee ci/README.md for the full list of targets, how to keep services running for debugging, and how to add new CI targets.
To stop the application but keep generated/imported FHIR data:
# working dir: app
docker compose downTo completely reset everything (useful if you encounter issues):
# working dir: app
# Stop all services
docker compose down
# Remove stored FHIR data (optional - this data takes time to regenerate)
rm -rf hapi/postgres_data/*
# For a completely fresh start, you can also remove model metadata
# (models will be automatically re-registered on next startup)
docker volume rm app_shared_tmp 2>/dev/null || trueNote: The FHIR database data is persisted in hapi/postgres_data/ to avoid having to regenerate synthetic patients every time. Model metadata is stored in MongoDB and will be automatically recreated when you restart.
If you're experiencing persistent Docker issues, you can use the cleanup script:
# working dir: project root
scripts/docker_clean.shWARNING: This script removes ALL Docker containers and networks on your system, not just CHARMTwinsights!
Having Docker issues? See DOCKER_TIPS.md for detailed troubleshooting help.
Quick fixes:
- Won't start: Make sure Docker Desktop is running
- Port conflicts: Run
docker compose downfirst - Build errors: Try
./build_all.sh --no-cache - Still broken:
docker compose down && ./build_all.sh && docker compose up --detach
CHARMTwinsights provides templates and tools to help developers create new machine learning models without needing Docker expertise.
-
Choose a template:
# For Python models cp -r model-templates/python-model my-new-model # For R models cp -r model-templates/r-model my-new-model
-
Customize your model:
- Edit
predict.pyorpredict.Rwith your model logic - Update
README.mdwith model documentation - Update
examples.jsonwith test data - Add dependencies to
pyproject.tomlorDESCRIPTION
- Edit
-
Validate and build:
# Validate Dockerfile (prevents common mistakes) python model-templates/validate-dockerfile.py my-new-model/Dockerfile # Build Docker image docker build -t my-new-model:latest my-new-model/
-
Register with CHARMTwinsights:
curl -X POST http://localhost:8000/modeling/models \ -H "Content-Type: application/json" \ -d '{ "image": "my-new-model:latest", "title": "My New Model", "short_description": "What your model does", "authors": "Your Name" }'
- Templates: Ready-to-use Python and R model templates
- Container-based metadata: Include
README.mdandexamples.jsonin your model container - Validation tools: Prevent common Docker mistakes with automated validation
- File-based I/O: Models read JSON input files and write JSON output files
- Example models: Working examples to learn from
- Full guide:
model-templates/README.md - Python template:
model-templates/python-model/ - R template:
model-templates/r-model/ - Working example:
model-templates/examples/simple-classifier/ - Dockerfile validator:
model-templates/validate-dockerfile.py
CHARMTwinsights includes a Model Context Protocol (MCP) server that allows AI assistants to interact with the platform programmatically.
The MCP server is automatically started with docker compose up and runs on port 8006.
Available capabilities:
- Patient Data Access: Search patients, retrieve demographics, get structured clinical data (Observations, Conditions, Procedures, Medications), access narrative reports
- Predictive Models: List available models, view model documentation and requirements, execute predictions
Example configuration for Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"charmtwinsight": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:8006/mcp"]
}
}
}For more information see app/mcp_server/README.md.
Ideally, features are added to the service they are most aligned with, adding additional services for feature sets sufficiently unique. To keep with a microservice architecture, services should not share disk storage, but communicate over the docker-internal network via REST.
As noted above, FHIR data stored in the HAPI server is persisted across runs; this allows the developer to generate synthetic data (a time-consuming process) once while while iterating on other features.
For development purposes, each service is exposed to the localhost on independent ports (applied automatically via app/docker-compose.override.yaml):
- router: localhost:8000
- hapi: localhost:8080
- stat_server_py: localhost:8001
- stat_server_r: localhost:8002
- synthea_server: localhost:8003
- model_server: localhost:8004
- mcp_server: localhost:8006
Development/Demo Mode (default):
- HAPI UI: http://localhost:8080/fhir
- Port exposed via
docker-compose.override.yaml - Useful for browsing FHIR data and testing ad-hoc queries
Production Mode:
# Skip override file to prevent external HAPI access
docker compose -f docker-compose.yml up -d- HAPI port not exposed externally for security
- FHIR data accessible only through controlled router endpoints at
/stats - Internal services (stat servers, synthea) can still access HAPI directly
Individual services can be rapidly iterated on even if they depend on others. After the application has been initialized and databases populated, a typical workflow would be:
-
Initialize application as above in a detached state.
-
Make changes to service code (e.g. in
app/stat_server_py/pyserver/main.py).- For Python services, add packages by running
poetry add <package-name>next to thepyproject.tomlfile; for R services, add the package to the Dockerfile.
- For Python services, add packages by running
-
Rebuild relevant containers with
docker compose build --with-dependencies <service_name>-
This should only rebuild services that have changed and that are dependent on the service; Dockerfiles have also been designed to maximize use of layer caching for fast rebuilds. If you run into trouble and think caching or persistent data are causing issues, try these approaches in order:
- Simple rebuild:
docker compose build <service_name> --no-cache - Reset service data:
docker compose down && docker compose up --detach - Full reset:
docker compose down && rm -rf hapi/postgres_data/* && ./build_all.sh --no-cache && docker compose up --detach - Nuclear option: Use
scripts/docker_clean.sh(removes ALL Docker containers/networks on your system)
- Simple rebuild:
-
-
Restart the services with
docker compose up <service_name>.- Re-upping only the relevant service will cause only it and dependent services that have changed to be re-initialized; you won't have to wait for HAPI or other dependent services to restart. Bringing up an an attached state, with a given target, will only show logs for the specific service (useful for print-debugging).
A common development loop is thus simply docker compose build --with-dependencies <service_name> && docker compose up <service_name>, using Ctrl-C and rerunning to effectuate code changes, accessing services directly on their local port for testing (e.g. http://localhost:8001 for stat_server_py).
DOCKER_TIPS.md: Comprehensive Docker troubleshooting guide for beginnersmodel-templates/: Templates and guides for developing new models
The scripts folder contains a few useful scripts:
docker_status.sh: list various running docker resourcesdocker_clean.sh: clobber running docker resources (containers, networks, volumes)mimic_fetch_decrypt.sh: fetch MIMIC IV FHIR data and decrypt it (MIMIC IV access approval required, contact Shawn for password)mimic_sample_push_hapi.py: pushes a rich subsample of the MIMIC IV FHIR data to the running HAPI server for testing
