AnpherosAnpheros PlatformDevelopers

Synthetic patient data for testing

Synthea, personas and model-generated data compared, what good test data contains, and what happened when we loaded Synthea patients into a FHIR server.

Synthetic patient data is invented medical records — patients, diagnoses, medications, lab results, visits — generated to look and behave like real ones, so you can build, test and demo healthcare software without touching anyone's health information. It is the default for development: real patient data in a test environment is a privacy risk and usually a legal problem. This page covers the main ways to get synthetic data, what each is good and bad at, how to design tests around it, and how to load it into a FHIR server — including what happened when we loaded Synthea patients into Anpheros.

Three ways to get synthetic patients

Approach Example Good for Watch out for
Rule-based simulation Synthea (open source, from MITRE): simulates lives from birth with disease modules and exports FHIR R4 bundles, C-CDA and CSV Volume, full histories, standard formats US-centred by default (demographics, billing resources, codes); patterns are only as realistic as the modules
Curated personas Hand-designed patients with specific conditions Demos, edge cases, repeatable tests Few patients; someone has to keep them coherent
Statistical or ML generation from real data Models trained on a real dataset Research realism Can leak real people if done badly; needs privacy evaluation, not just "it's synthetic"

Text written by a language model on request is a fourth, risky source: it reads well but often contradicts itself (a medication without the condition it treats, impossible lab values) and should not be the basis of tests.

What good test data contains

Realistic averages are not enough. Code breaks on the edges, so make sure your data, synthetic or not, includes:

Loading Synthea patients into a FHIR server

Synthea writes one FHIR R4 transaction Bundle per patient. We generated two patients (java -jar synthea-with-dependencies.jar -p 2 -s 4242 Massachusetts) and loaded them into a local instance of Anpheros. The first patient's bundle had 221 entries:

Kept (types Anpheros stores) Removed (types Anpheros does not store)
Observation 61, Procedure 48, DiagnosticReport 24, Condition 15, Encounter 12, DocumentReference 12, Immunization 6, Patient, CareTeam, CarePlan, Provenance Claim 13, ExplanationOfBenefit 13, SupplyDelivery 9, Device 2, ImagingStudy 1, MedicationRequest 1

The loader asks the server which types it supports, keeps only those and posts the rest as one transaction:

import { readFile } from 'node:fs/promises';
import { Anpheros, apiKey } from '@anpheros/sdk';

const anpheros = new Anpheros({ auth: apiKey(process.env.ANPHEROS_KEY!) });   // sk_test_… key
const SUPPORTED = new Set(((await anpheros.fhir.metadata()) as any).rest[0].resource.map((r: any) => r.type));
const bundle = JSON.parse(await readFile(process.argv[2], 'utf8'));            // one Synthea patient file
const kept = bundle.entry.filter((e: any) => SUPPORTED.has(e.resource.resourceType));
const out: any = await anpheros.fhir.transaction({ ...bundle, entry: kept } as any);
console.log('created', out.entry.length);

Result: 182 resources created in one transaction. The urn:uuid references inside the bundle were resolved to server ids (a DiagnosticReport's result points to an Observation that exists), and Patient/{id}/$summary produced an International Patient Summary with 15 problems, 6 immunizations, 15 results and 28 vital signs. $everything returned 363 entries, including the provenance the platform adds to each write.

Three things to know before you rely on it:

Names in Synthea include numbers (Marcelo725 Gulgowski816) on purpose, so nobody mistakes them for real people.

Synthetic patients in the Anpheros sandbox

Every Anpheros sandbox project gets its own copy of 30 synthetic patients, generated from hand-designed personas: ages 3 to 85, several EU countries and languages, conditions coded in ICD-10, medications in ATC, labs and vitals in LOINC with UCUM units and reference ranges, six months of history and documents. A freshly seeded project holds 30 patients, 43 conditions, 51 medication statements, 1,811 observations, 57 documents and 6 allergies. The data is deterministic — every project sees the same people with the same values, only different ids — and a reset restores it. Each persona keeps a stable identifier, so Patient?identifier=https://anpheros.com/fhir/sid/sandbox-persona|p02 always finds the same person. See Free FHIR database and sandbox.

Use the personas when you want small, coherent records that cover European languages and the platform's own features; load Synthea when you need volume or US-style histories.

Frequently asked questions

What is synthetic patient data?

Invented health records that resemble real ones in structure and patterns but describe no real person. It lets developers build and test without using protected health information.

Is synthetic data anonymous by definition?

Data generated from rules, like Synthea's, contains no real patients. Data generated by models trained on real records can still reveal real people and needs a privacy evaluation before it is shared.

Can I import Synthea data into a FHIR server?

Yes. Synthea exports FHIR R4 transaction bundles. Remove resource types the server does not support, check how it handles conditional references, and post each bundle as a transaction.

Does the Anpheros sandbox include synthetic patients?

Yes: 30 per project, with six months of coded history, plus up to 500 test patients you create yourself.

Related