Synthetic Clinical Data Generator
Welcome! This tool allows you to generate fake patient data. Instead of hard-coding values, this system uses a flexible "blueprint" (a YAML configuration file) to build a relational database of patients, vitals, labs, and medications.
data_config.yaml file is the recipe book that tells the
factory what to build. The Python scripts are the machines that actually
build it.
1. Global Parameters (The "Big Picture")
At the very top of your data_config.yaml file, you will find the
generation_params. This dictates the size and scope of your fake
hospital.
generation_params:
n_patients: 100 # How many fake patients to create
days: 10 # How many days of data to simulate
freq: '4h' # How often to check vitals (every 4 hours)
output_format: 'tidy' # The shape of the final data
default_missing_rate: 0.5 # 50% chance a test isn't taken (makes data realistic)
2. The Clinical Dictionary (The "Menu")
Before building tables, we define universal medical concepts under clinical_concepts. This ensures the generator never creates impossible scenarios (like a body temperature of 100°C) and standardizes coding.
Concepts are grouped into vitals, labs, and neuro. Each concept defines:
code&name: The LOINC code and human-readable string (e.g., "LOINC-8867-4", "Heart Rate").unit: Measurement unit (e.g., 'bpm', 'mg/dL').prob: The probability this specific test is ordered/recorded (overrides the default missing rate).range: The absolute[min, max]bounds for generated values.
3. Table Architectures
tables section dictates how CSVs are structured and how they relate. Let's break down the core architectural rules.
A. Table Types & Row Counts
Every table must define its basic structural behavior:
type: "relational": A standard spreadsheet where each row is a distinct event (like an admission or a prescribed drug).type: "eav_timeseries": Entity-Attribute-Value format. Used for repeating measurements (like heart rate over time). It makes one long table where the "test name" and "result" are stacked in rows rather than spread across 50 different columns.rows_per_patient_range: [1, 5]: This tells the generator: "For every fake patient, generate anywhere between 1 and 5 random rows for this table." Some patients might get 1 medical problem, others might get 5.source: "clinical_concepts.vitals": Used only in EAV tables. It tells the table which dictionary concepts to use to generate the test values.
B. The `map_to` Keyword (The Translator)
In eav_timeseries tables (like vitals or lab results), the generator produces raw data first. You use map_to to tell the generator exactly which column in your final CSV should hold which piece of data.
OBSERVATION_CODE:
map_to: "concept.code" # Takes the LOINC code from the dictionary
OBSERVATION_NAME:
map_to: "concept.name" # Takes the human-readable name
OBSERVATION_RESULT_CLEAN:
map_to: "value" # Takes the actual randomly generated number (e.g., 98.6)
4. Column Types Deep-Dive
Inside the schema of a table, you define your columns. Here is exactly what every column configuration does, with examples:
1. The Combo Meal: categorical_tuple
This is crucial for clinical accuracy. If a patient has diabetes, their diagnosis code should always match the description. If you generated them separately, you might accidentally give a patient an Asthma code with a Diabetes description. A categorical_tuple locks them together.
PROBLEM_TUPLE:
type: "categorical_tuple"
columns: [ "PROBLEM_CODE", "PROBLEM_DESC" ] # The columns to create
values:
# If the generator picks line 1, it inserts both "E11.9" and the Diabetes text safely.
- [ "E11.9", "Type 2 diabetes mellitus" ]
- [ "I10", "Essential (primary) hypertension" ]
- [ "J44.9", "Chronic obstructive pulmonary disease" ]
2. Time Travel Prevention: date and date_offset
Medical data must follow a timeline. You can't resolve a medical problem before you've diagnosed it. The generator uses offsets to enforce this logic.
PROBLEM_DT_TM:
type: "date"
start: "2020-01-01"
end: "2024-01-01" # Picks a random baseline date in this window.
UPDATE_DT_TM:
type: "date_offset"
base_col: "PROBLEM_DT_TM" # Look at the problem date we just generated...
days_range: [ 0, 90 ] # ...and add anywhere from 0 to 90 days to it.
Result: If the problem started on Jan 1st, the update date is mathematically guaranteed to happen between Jan 1st and April 1st. No time paradoxes!
3. The V.I.P. Pass: foreign_key
This is how tables talk to each other. If you generate a prescription, it needs to belong to a valid hospital visit.
ENCNTR_ID:
type: "foreign_key"
source_table: "ICARE_EPISODES_ANON"
This tells the script: "Go look at the Episodes table, find a valid Encounter ID for this specific patient, and paste it here."
4. Simple Generators
unique_id: Spits out a random 7-digit number (e.g., 8472910).enumerated: Think of this as drawing from a hat.values: ["yes", "no", "none"]will randomly pick one of those three words.boolean: Flips a coin.probability: 0.70means there is a 70% chance of generating a 1 (True), and a 30% chance of generating a 0 (False).intandfloat: Generates random whole numbers or decimals between a providedrange: [min, max].
5. The Configured Tables
The data_config.yaml currently builds 6 interconnected tables for your cohort:
| # | Table Name | Description |
|---|---|---|
| 1 | ICARE_EPISODES_ANON | The anchor table. Records admissions, discharges, age, and deprivation deciles. |
| 2 | ICARE_MICROBIOLOGY_ANON | Tracks blood/urine cultures, organism growth (e.g., E. coli, MRSA), and sensitivities. |
| 3 | ICARE_VITAL_SIGNS_ANON | Dense time-series of heart rate, temperature, blood pressure, etc., tracked longitudinally. |
| 4 | ICARE_PROBLEMS_ANON | ICD-10 diagnostic history (diabetes, sepsis, CKD) with onset and resolution dates. |
| 5 | ICARE_PHARMACY_PRESCRIBING_ANON | Medication orders (antibiotics, pressors, fluids) with routes and dosages. |
| 6 | ICARE_PATHOLOGY_BLOOD_ANON | Lab test results (creatinine, CRP, lactate) featuring delayed result timestamps. |
6. How Data is Created
The Python backend (src/generators.py) executes the YAML blueprint sequentially. It establishes parent tables (Episodes) first so child tables (Pharmacy, Vitals) can safely inherit foreign keys. It then applies biological variance, bounds logic, and missingness masks to produce highly realistic datasets.
7. Running the Tool
Use the unified orchestration suite to execute data generation. This will automatically route through Docker (or run locally if specified) and output your CSVs to a timestamped folder in data/synthetic/.
# Default (Runs in Docker Container)
make generate
# Native Execution (Runs on Host CPU)
make generate local
Note for Windows users: Use .\make.bat generate.