heartland-synthetic
Synthetic heart-failure cohort generator with the 10 HEARTLAND risk variables — distance-to-cardiology and social support — that Synthea and other generators do not model.
heartland-synthetic is a Python package that generates clinically-realistic
synthetic HF patient cohorts, computes the HEARTLAND Risk Score for each row,
and exports to REDCap and FHIR R4. It supports reproducible simulation studies
for the HEARTLAND Protocol and lets other researchers prototype validation
work without needing real patient data.
Why this exists
Synthea and other synthetic-patient generators do not model the two variables that distinguish HEARTLAND from MAGGIC / GWTG-HF / SHFM:
- Distance to cardiology care (rural barrier)
- Social support (ENRICHD ESSI scale)
Researchers working on rural HF risk stratification have had to simulate these
manually. heartland-synthetic fills the gap, ships with the exact scoring
engine used in the clinical-decision-support prototype, and is publishable as a
citeable artifact under MIT + Zenodo DOI.
Install
pip install heartland-synthetic
Editable install for development:
git clone https://github.com/vickymuller-md/heartland-synthetic
cd heartland-synthetic
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest -q
Python >= 3.10; core deps: numpy, pandas, scipy.
Quickstart
from heartland_synthetic import generate_cohort, HeartlandCohortConfig
config = HeartlandCohortConfig(
n_patients=500,
rural_fraction=0.7,
hf_type_distribution={"hfref": 0.45, "hfmref": 0.15, "hfpef": 0.40},
age_range=(45, 95),
include_outcomes=True,
seed=42,
)
df = generate_cohort(config)
print(df["heartland_risk_tier"].value_counts())
The returned DataFrame has one row per patient; every patient comes with the HEARTLAND score and tier already computed.
HeartlandCohortConfig
| Field | Default | Description |
|---|---|---|
n_patients |
500 |
Cohort size (must be > 0) |
rural_fraction |
0.5 |
Share of patients with USDA RUCA >= 4 |
hf_type_distribution |
{hfref: .45, hfmref: .15, hfpef: .40} |
HF subtype mix (must sum to 1.0) |
age_range |
(45, 95) |
Inclusive age bounds (min >= 18) |
female_fraction |
0.48 |
Share of female patients |
include_outcomes |
True |
Attach mortality_1yr / hospitalization_1yr |
include_medications |
True |
Attach GDMT utilization columns |
seed |
None |
Integer seed; None yields OS-entropy randomness |
Output schema
| Column | Type | Notes |
|---|---|---|
patient_id |
str | HS-000001-style identifier |
age |
int | Years |
sex |
str | F / M |
race |
str | White / Black / Hispanic / Other |
state |
str | US postal abbreviation |
county_fips |
str | Synthetic 5-digit code |
rural_urban_code |
int | USDA RUCA 1-10 |
hf_type |
str | hfref / hfmref / hfpef |
lvef |
float | % (by stratum) |
egfr |
float | mL/min/1.73 m^2 |
bnp |
float | pg/mL |
sbp, dbp, hr |
int | mmHg / mmHg / bpm |
bmi |
float | kg/m^2 |
diabetes, af |
int | 0/1 |
ckd_stage |
int | 1-5 (KDIGO) |
ckm_stage |
int | 0-4 (AHA 2023) |
distance_to_cardiology_mi |
float | Miles |
social_support_score |
int | ENRICHD ESSI 8-40 |
prior_hf_hosp_6mo |
int | 0/1 |
on_acei_arb_arni / on_beta_blocker / on_mra / on_sglt2i |
int | 0/1 |
gdmt_classes_count |
int | 0-4 |
heartland_risk_score |
int | 0-18 |
heartland_risk_tier |
str | low / moderate / high |
mortality_1yr, hospitalization_1yr |
int | 0/1 (only if include_outcomes=True) |
Distribution sources
| Variable | Model | Source |
|---|---|---|
| Age | Truncated Normal(72, 12) | GWTG-HF |
| Sex | Bernoulli | GWTG-HF |
| Race | Categorical | GWTG-HF / NHANES |
| LVEF (HFrEF / HFmrEF / HFpEF) | Strata-specific | PARADIGM-HF / DELIVER / EMPEROR-Preserved |
| eGFR | LogNormal(ln 60, 0.35), elderly shift | STRONG-HF / CKD-EPI |
| BNP | LogNormal(ln 600, 0.9); HFpEF -30% | PARADIGM-HF |
| SBP | Normal(125, 20); rural +5 mmHg | GWTG-HF |
| DBP | 0.6 * SBP + N(0, 14) (rho ~ 0.65) |
Derived |
| HR | Normal(78, 14) | GWTG-HF |
| BMI | Normal(30, 6); HFpEF +3 | DELIVER |
| Diabetes | Bernoulli(0.42 + HFpEF/obesity bumps) | GWTG-HF |
| AF | Bernoulli(0.35 + elderly bump) | GWTG-HF |
| CKD stage | Deterministic from eGFR | KDIGO 2012 |
| CKM stage | Cascade on diabetes, CKD, BMI, age | AHA 2023 Presidential Advisory |
| Distance to cardiology | LogNormal (rural vs urban) | NPPES NPI / Atlas |
| Social support | Normal (rural 24, urban 29) | ENRICHD ESSI |
| GDMT rates | Bernoulli, rural vs urban | CHAMP-HF |
| 1-yr mortality per tier | 0.06 / 0.15 / 0.32 | MAGGIC + Manemann 2018 |
| 1-yr hospitalization per tier | 0.18 / 0.35 / 0.55 | GWTG-HF readmission |
All numeric constants live in src/heartland_synthetic/registries.py with
inline citations.
Apply HEARTLAND scoring on external data
from heartland_synthetic import apply_heartland_scoring
import pandas as pd
df = pd.read_csv("external_cohort.csv") # must have required columns
scored = apply_heartland_scoring(df)
Required columns: age, prior_hf_hosp_6mo, egfr, bnp, sbp, diabetes, lvef, ckm_stage, distance_to_cardiology_mi, social_support_score.
The scoring logic is a direct port of heartland-app/lib/risk-score/engine.ts
(Protocol v3.3, Table 1): 10 variables, 18 points maximum, tiers
low 0-4 / moderate 5-8 / high >= 9.
Monthly time series
from heartland_synthetic import generate_time_series
ts = generate_time_series(df, months=12, seed=42)
# one row per (patient_id, month); rows after the first death_event are omitted
Columns: patient_id, month, sbp, dbp, hr, weight_kg, bnp, hosp_event, death_event. Vitals follow an AR(1) mean-reverting process around each
patient's baseline. Event probabilities are tier-indexed annual rates
compounded to monthly.
REDCap export
from heartland_synthetic import export_redcap
data_csv, dict_csv = export_redcap(df, "outputs/heartland_cohort")
Writes two files:
outputs/heartland_cohort.csv— data withrecord_id(=patient_id)outputs/heartland_cohort_datadict.csv— 18-column REDCap data dictionary (radio for categoricals, dropdown for coded integers, yesno for booleans, text+number validation for continuous vitals).
Import into a fresh REDCap project: Project Setup -> Upload Data Dictionary then Import Data.
FHIR R4 export
from heartland_synthetic import export_fhir_bundle
paths = export_fhir_bundle(df, "outputs/fhir/")
# one Bundle per patient at outputs/fhir/{patient_id}.json
Each collection Bundle contains:
Patient(with US Core Race extension)Conditionfor HF subtype (ICD-10 I50.2 / I50.3 / I50.4), plus diabetes / CKD stage / AF / prior HF hospitalization when presentObservationfor LVEF / eGFR / BNP / SBP / DBP / HR / BMI (LOINC)MedicationStatementfor each GDMT class the patient is on (RxNorm)Observationfor the HEARTLAND score and tier under the custom CodeSystemhttp://heartlandprotocol.org/fhir/CodeSystem/risk-score
Reproducibility
Every function that draws random numbers accepts a seed parameter. With the
same seed, generate_cohort and generate_time_series return DataFrames that
pandas.testing.assert_frame_equal on.
cfg = HeartlandCohortConfig(n_patients=100, seed=42)
assert generate_cohort(cfg).equals(generate_cohort(cfg))
Limitations
- Synthetic only: this package never contains real patient data. It is not a substitute for real registry or EHR analysis.
- Outcome rates are consistent with, not validated against, literature. The 1-year mortality and hospitalization rates per tier are modeled from MAGGIC survival curves + Manemann 2018 social isolation HR + GWTG-HF readmission patterns. No prospective HEARTLAND-scored cohort has been outcome-validated yet.
- County FIPS codes are synthetic (not mapped to real counties); the RUCA assignment and state pool are modeled but not geo-accurate.
- CKM staging is simplified from the AHA 2023 Presidential Advisory (stages 0-4 via diabetes / CKD / BMI / age); the full 5-stage model with subclinical CVD biomarkers is not yet implemented.
- GDMT utilization is point-prevalence at a single reference date; no titration trajectory is modeled.
- Only the 10 HEARTLAND variables plus supporting clinical context are generated — no labs beyond BNP, eGFR, and BMI; no ECG, echo structural parameters, or NYHA class.
Citation
@software{mullerferreira_heartland_synthetic_2026,
author = {Muller Ferreira, Vicky},
title = {heartland-synthetic: Synthetic heart-failure cohort generator with HEARTLAND risk variables},
year = {2026},
publisher = {Zenodo},
doi = {10.5281/zenodo.19635042},
url = {https://doi.org/10.5281/zenodo.19635042}
}
Cite alongside the HEARTLAND Protocol: Muller Ferreira V. HEARTLAND Protocol v3.3. Zenodo. DOI 10.5281/zenodo.18566403.
License
MIT. See LICENSE.
Landing page
A Next.js static landing page that mirrors the HEARTLAND design system
lives in site/. It's deployed (by the maintainer) at
synthetic.heartlandprotocol.org.
cd site
npm install
npm run dev # http://localhost:3000
npm run build # static export -> site/out/
Deploy on Vercel with Root Directory = site. Add the custom domain
synthetic.heartlandprotocol.org; Vercel issues the CNAME target and
manages TLS.
Publish runbook
For the maintainer releasing a new version:
# 1. Bump version in pyproject.toml and src/heartland_synthetic/__init__.py
# 2. Update CHANGELOG.md with a new [x.y.z] section
# 3. Run the full test suite
.venv/bin/pytest -q
# 4. Build sdist + wheel
.venv/bin/pip install -e ".[publish]"
.venv/bin/python -m build
ls dist/
# 5. Authenticate gh as vickymuller-md (important: not rodrigoeac)
gh auth switch
# 6. Tag and push
git tag vX.Y.Z
git push --tags
# 7. Create the GitHub release from the tag
# (Zenodo webhook mints the DOI automatically when the release is published)
gh release create vX.Y.Z --title "vX.Y.Z" --notes-file CHANGELOG.md
# 8. Upload to PyPI (requires PYPI_API_TOKEN)
.venv/bin/twine upload dist/*
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file heartland_synthetic-0.2.2.tar.gz.
File metadata
- Download URL: heartland_synthetic-0.2.2.tar.gz
- Upload date:
- Size: 93.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
64305354d15b62d31ecc26e8fae983ee54ab2d09e4f69bb379548454bc68365f
|
|
| MD5 |
bbdd0e02f799c086c90b9a13268d63df
|
|
| BLAKE2b-256 |
574f000bbc1662f27a37125fa86db9d4fb99e7101adf7df1c42037876c4bc29b
|
Provenance
The following attestation bundles were made for heartland_synthetic-0.2.2.tar.gz:
Publisher:
publish.yml on vickymuller-md/heartland-synthetic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
heartland_synthetic-0.2.2.tar.gz -
Subject digest:
64305354d15b62d31ecc26e8fae983ee54ab2d09e4f69bb379548454bc68365f - Sigstore transparency entry: 2581829417
- Sigstore integration time:
-
Permalink:
vickymuller-md/heartland-synthetic@415574c1ea4d7ada90df037c6ee5c4d7bea85a82 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/vickymuller-md
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@415574c1ea4d7ada90df037c6ee5c4d7bea85a82 -
Trigger Event:
release
-
Statement type:
File details
Details for the file heartland_synthetic-0.2.2-py3-none-any.whl.
File metadata
- Download URL: heartland_synthetic-0.2.2-py3-none-any.whl
- Upload date:
- Size: 32.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f3e46a1491a0bff1343354ada1ecc82e07639efdd6f5766f68185ffa3992064
|
|
| MD5 |
9a10b3db3c92db78fb4209cf8a0cb08e
|
|
| BLAKE2b-256 |
02dbde7d45f19f8074ff64df570b00185ef5f382db357a05709670d667bfd77e
|
Provenance
The following attestation bundles were made for heartland_synthetic-0.2.2-py3-none-any.whl:
Publisher:
publish.yml on vickymuller-md/heartland-synthetic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
heartland_synthetic-0.2.2-py3-none-any.whl -
Subject digest:
1f3e46a1491a0bff1343354ada1ecc82e07639efdd6f5766f68185ffa3992064 - Sigstore transparency entry: 2581829420
- Sigstore integration time:
-
Permalink:
vickymuller-md/heartland-synthetic@415574c1ea4d7ada90df037c6ee5c4d7bea85a82 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/vickymuller-md
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@415574c1ea4d7ada90df037c6ee5c4d7bea85a82 -
Trigger Event:
release
-
Statement type: