synthea-healthcare-data
Author and run Synthea (MITRE's open-source synthetic patient population simulator) to produce HIPAA-safe synthetic medical records for testing health IT systems. Covers Gradle build, population-size and state-specific generation, FHIR R4 / STU3 / DSTU2 / C-CDA / CSV / CPCDS output formats, disease-module customisation, and the lifecycle-simulation approach (birth-through-death patient journeys with realistic demographics). Use when testing FHIR servers, EHR integrations, claims processing, or any health IT system that needs realistic patient records without HIPAA exposure (distinct from the generic Faker family - qa-test-data faker-data for fixtures, the pii-masking-pipeline-builder faker-masking-operators reference for masking substitution; this is health-domain-specific).
Install with skills.sh (any agent)
npx skills add testland/qa --skill synthea-healthcare-datasynthea-healthcare-data
Overview
Synthea is MITRE's open-source synthetic-patient population simulator that generates realistic but fictional medical records across the full patient lifecycle (birth through death). The output is structurally valid FHIR / C-CDA / CSV that downstream health IT systems consume without exposing any real patient data.
Source: github.com/synthetichealth/synthea (opens in new window).
Use this when:
For non-health-domain fixture data use the qa-test-data plugin's faker-data; for Faker-driven masking substitution see the faker-masking-operators catalog in pii-masking-pipeline-builder references/. For the categories of PHI that Synthea avoids exposing see the pii-categories catalog there (HIPAA Safe Harbor 18 identifiers).
When to use
How to use
Authoring
Install + build
Per github.com/synthetichealth/synthea (opens in new window):
git clone https://github.com/synthetichealth/synthea.git
cd synthea
./gradlew build check testRequires Java JDK 17 or newer (LTS versions recommended per the README).
Generate a population
Basic invocation:
./run_syntheaWith explicit population size and state:
./run_synthea -p 1000 MassachusettsPer the README: -p sets population size; the trailing argument sets the US state (locale-aware demographics and provider networks).
Common run flags
| Flag | Purpose |
|---|---|
-p <n> | Population size |
-s <seed> | Random seed (deterministic output) |
-cs <seed> | Clinician seed |
-r <date> | Reference date (YYYYMMDD) |
-e <date> | End date |
-g <M|F> | Filter by gender |
-a <minAge>-<maxAge> | Age range |
-c <config.properties> | Override configuration file |
Output formats
Per the README the system emits:
Output destination: ./output/fhir/, ./output/csv/, etc.
Configure formats in src/main/resources/synthea.properties:
exporter.fhir.export = true
exporter.fhir_stu3.export = false
exporter.ccda.export = false
exporter.csv.export = trueRunning
Pre-generated population
For quick starts MITRE distributes pre-generated SyntheaMass populations (1M patient Massachusetts simulation, etc.) on the project site - search for "Synthea downloadable populations" if you don't need to regenerate.
Disease modules
Synthea uses a Modular Rule System (per README) where each disease / condition is a JSON-defined state machine in src/main/resources/modules/. Examples include diabetes, hypertension, COPD, opioid addiction, COVID-19, sepsis, and dozens more. The module drives the patient's clinical journey probabilistically.
To add a custom module, drop a JSON spec into the modules directory; the engine picks it up on next run.
Loading into a FHIR server
# After ./run_synthea generates output/fhir/*.json
for f in output/fhir/*.json; do
curl -X POST -H "Content-Type: application/fhir+json" \
-d @"$f" http://localhost:8080/fhir/
doneFor bulk-FHIR ingestion, use the output/fhir/*.ndjson files with your server's bulk-data endpoint.
Parsing results
CSV outputs have predictable schemas:
output/csv/
patients.csv - patient_id, birthdate, deathdate, ssn, drivers, ...
encounters.csv - encounter_id, patient, organization, ...
conditions.csv - start, stop, patient, encounter, code, description
medications.csv - start, stop, patient, code, description, ...
observations.csv - date, patient, encounter, code, value, units
procedures.csv
immunizations.csv
allergies.csv
imaging_studies.csv
careplans.csv
claims.csvThe patients.csv ssn column contains fake SSNs in Synthea's reserved test range - they look real-formatted but don't correspond to issued SSAs. This is the intended HIPAA-safe replacement.
For FHIR output, parse with any standard FHIR client (HAPI FHIR Java, fhir.resources for Python, etc.).
CI integration
For health IT projects, regenerate Synthea data on every PR with a pinned seed so the dataset is reproducible. The full GitHub Actions job (build, generate, load into a local HAPI FHIR server, run integration tests) plus the pin-to-a-tag note is in references/ci-integration.md.
Worked example
Generate 100 diabetic patients aged 40-75 in Massachusetts, then verify the output before loading it into a test FHIR server:
./run_synthea -p 100 -s 42 -a 40-75 Massachusetts \
-m diabetes-m <module> filters to runs that include the named module; -s 42 pins the seed so the same 100 patients regenerate every time. Output appears in ./output/ (fhir/, csv/, c-cda/ per synthea.properties).
Confirm the run: output/csv/conditions.csv should carry diabetes condition rows linked by patient to output/csv/patients.csv, and every patient's birthdate should place them in the 40-75 age band. Once verified, POST the output/fhir/*.json bundles into the test FHIR server as shown under "Loading into a FHIR server".
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Using a real-patient seed file then claiming "synthetic" | Real PHI inadvertently embedded; HIPAA exposure | Always start from Synthea defaults or audited synthetic seed |
| Running without a pinned seed in CI | Output drifts across runs; test fixtures unstable | -s <seed> per CI run; pin Synthea version |
| Faking demographics with generic Faker substitution for a health context | Faker generates uncorrelated values; ICD codes, medications, encounters don't link | Use Synthea for any health-domain dataset |
| Loading Synthea output into a "real" FHIR server without isolation | If a misconfigured environment crosses into production, fake patients land in real EHR | Strict env separation; namespace Synthea patient IDs (prefix with synth-) |
| Treating Synthea SSNs as truly safe in all jurisdictions | Synthea uses reserved SSN ranges but format is still HIPAA-flagged | Pair with presidio-pii-detection on logs to confirm no SSN leakage |
| Custom module without validation | Malformed module silently runs (or doesn't); fixtures look right but cover nothing | Validate JSON modules against Synthea's schema before running large populations |
| Single-state generation for a national rollout test | Demographic skew (e.g., MA is not Texas) | Generate per state and merge |
Limitations
References
Synthea CI integration
View source (opens in new window)Synthea CI integration
Referenced from SKILL.md (opens in new window). For health IT projects, regenerate Synthea data on every PR with a pinned seed so the dataset is reproducible:
jobs:
fhir-integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-java@v5
with: { java-version: '17', distribution: 'temurin' }
- run: git clone https://github.com/synthetichealth/synthea.git
- run: cd synthea && ./gradlew build -x test
- run: cd synthea && ./run_synthea -p 50 -s 2026 Massachusetts
- run: |
# Load Synthea output into local FHIR server
docker-compose up -d hapi-fhir
for f in synthea/output/fhir/*.json; do
curl -sS -X POST -H "Content-Type: application/fhir+json" \
--data-binary @"$f" http://localhost:8080/fhir/
done
- run: pytest tests/integration/For repeatable tests, pin Synthea to a tag (git checkout v3.x.x) since modules evolve.
Related skills
k-anonymity-verifier
Verifies that a masked dataset satisfies k-anonymity, l-diversity, and t-closeness by computing equivalence classes over chosen quasi-identifiers and reporting re-identification risk. Covers quasi-identifier selection heuristics, threshold guidance, pycanon API (k_anonymity / l_diversity / t_closeness / report), ARX Java API and GUI workflow, SmartNoise for differential-privacy comparison, and CI-gate integration. Distinct from pii-masking-pipeline-builder's masking-techniques catalog (which lists masking operators but defers k-anonymity measurement to dedicated tooling) and from presidio-pii-detection (which detects PII spans but offers no equivalence-class analysis). Use when you need to confirm whether a masked dataset meets a stated k, l, or t threshold before promoting it to a non-production environment.
pii-masking-pipeline-builder
Build-an-X workflow that owns the full detect → mask → verify pipeline for PII in test data. Walks the author through (1) classifying each field against the cross-regime PII catalog (GDPR / CCPA-CPRA / NIST SP 800-122 / HIPAA, in references/pii-categories.md), (2) picking a masking operator from the techniques catalog (seven canonical operators + Presidio operators + privacy models, in references/masking-techniques.md), (3) deciding pseudonymisation (reversible, in GDPR scope) vs anonymisation (irreversible, out of scope), (4) ordering the pipeline (detect → operator → audit) and emitting a deployable YAML config for Presidio + Faker + Synthea wrappers (Faker-as-masking-operator detail in references/faker-masking-operators.md), and (5) running the adversarial verification pass that re-detects PII in the masked output and blocks promotion on a leak. Use when non-production environments need masked production data - from field classification through runnable masking config to the leak audit.
presidio-pii-detection
Author and run Microsoft Presidio PII detection - wraps presidio-analyzer (PII detector) + presidio-anonymizer (replace/redact/mask/hash/encrypt operators) for scanning datasets, log streams, and free-text fields. Covers AnalyzerEngine + AnonymizerEngine setup, built-in recognizers (PERSON, EMAIL_ADDRESS, CREDIT_CARD, US_SSN, IBAN_CODE, country-specific IDs across US/UK/Spain/Italy/Poland/Singapore/Australia/India and more), custom PatternRecognizer authoring, score thresholds, and CI gating. Use when scanning *existing* data for PII (vs synthesising fresh fixtures with synthetic-pii-generator).