Repository navigation
Physiology primitives for wearables and computational models - #1707
Open
awatson1978 wants to merge 56 commits into
Open
awatson1978 wants to merge 56 commits into
awatson1978 wants to merge 56 commits into
Conversation
…h/synthea into wearables-rebuild
- Re-encode HPA/cortisol model (Major_Depressive_Disorder_Model) with rate rules plus a time-stepped chronic stressor. It was previously inert (every species was boundaryCondition=true and derivatives were emitted as product species), so the whole axis stayed flat. It now produces a blunted-cortisol stress response. - Fix plasma_melatonin duration to reach the nocturnal secretion window (clean melatonin pulse instead of a decay) and label axes. - Relabel pulmonary_fluid_dynamics and document that it needs an input perturbation. - Add a FHIR facade (PhysiologySimulationExporter): standalone sims can emit a FHIR R4 Bundle of SampledData Observations via 'exportFhir: true', reusing FhirR4.mapValueToSampledData. Enabled for ecg and cortisol_depression. - Add PhysiologyModuleStateTest (state-path modules run when enabled) and a cortisol stress-response test in PhysiologySimulatorTest. - Reorganize config/simulations/README.md by device x system x time-scale, with both entry paths and the enabling flags documented. - Drop liver_metabolism (Jerby2010): unbounded genome-scale ODE, not viable as a time-course simulation. Physiology remains opt-in (defaults unchanged). checkstyle + full test suite green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Superseded by Leloup2004_CircadianRhythmsSet4.xml, which is what mammalian_circadian_rhythm_non_24hr.yml actually loads. The Non_24hr file had no sim config, no module, and no PDF referencing it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace placeholder '???' / mislabeled axes and terse titles on o2_transport_metabolism, telomere_associated_dna_damage, and weight_change with descriptive titles and honest 'level (mixed units)' y-axis labels (each plots several heterogeneous-unit series). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The model was orphaned (no sim config) but is already a well-formed 5-state rate-rule ODE and renders a clean ultradian pulsatile cortisol rhythm. Add a sim config (states y0-y4) plus README catalog/examples entries. It complements cortisol_depression.yml: this is the healthy rhythm, that is the stressor response. Biomarker naming of y0-y4 is a documented follow-up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add readable alias outputs to the SBML (CRH=y0, ACTH=y1, cortisol=y2) and plot the named hormones instead of the abstract states. The mapping is derived from the model's own interaction topology (the r0-r4 assignment rules) and canonical HPA feedback (CRH -> ACTH -> cortisol; cortisol inhibits CRH and ACTH). The three hormone identities are high-confidence; the pituitary-vs-adrenal assignment of the two gland-size states (y3/y4) is uncertain and left as-is. The mapping basis is documented in the model file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closed
FHIRR4ExporterTest US Core 5/6 runs failed intermittently on invariant us-core-2 (an Observation needs value[x] or dataAbsentReason when it has no component/hasMember). Two Observation states in the new wearables modules recorded no value at all, and FhirR4 never emits a dataAbsentReason; the failures were intermittent because the test population is randomly generated. - wearables_sleepapnea SleepAnalysis (LOINC 93832-4 sleep duration): add a 4-7h range value, mirroring the cardiac module's SleepAnalysis. This state is reached both via CircadianClock_Sim's physiology-disabled alt path and the CPAP path. - wearables_cardiac_monitoring Fitness_Baseline: convert to a Procedure state - its SNOMED code (410189008, vitals education) is a procedure code and there was nothing being measured. Add a regression test that drives every Observation state in the three new modules and asserts each recorded observation carries a value (verified to fail against the previous module definitions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seven chart configs still used their raw config-name as the chart title (e.g. "insulin_signalling_normal"). Title them like the already-descriptive charts, and replace two cryptic y-axis labels (kms, MB) with the plotted species. Also rename "ECG: AFib" - the chart renderer truncated the title at the colon, so the rendered chart just said "ECG". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
State.ENABLE_PHYSIOLOGY_STATE is captured in a static initializer, which runs when module scanning first loads the State class - before App.main applies --config command-line overrides. As a result the documented "--physiology.state.enabled=true" argument was silently ignored, and the flag only took effect when set in synthea.properties. Re-read the flag in Generator.init(), which always runs after configuration is final. Verified end to end: a population generated with the flag on the command line now contains SampledData Observations and Media chart resources from the wearables modules, where before it contained none. TestHelper now enables the flag after constructing the Generator, since Generator.init() refreshes it from configuration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
awatson1978
marked this pull request as ready for review
September 17, 2026 17:09
Collaborator
Author
|
Hi @dehall, @hadleynet, @jawalonoski, FYI, I have a grant application in with NASA Translational Research Institute for Space Health (TRISH) to study venous thrombosis (blood clots), and included references to Synthea and Biomodels. It would include funding for a new venous-thrombosis model with an additional 3 models in addition to these 14. Hope things are doing well with everyone, |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #1347 (which was itself a rebuild of #1254). This branch continues that work: it addresses the review feedback on #1347, cleans up the model collection for merge-readiness, and is up to date with current master.
What this is
This PR adds a library of physiology primitives to Synthea: fourteen published, peer-reviewed ODE models (from the EMBL-EBI BioModels repository) wired into Synthea's existing physiology engine, along with simulation configs, three example disease/device modules, documentation, and tests.
The same models serve two purposes:
The project started as a single simulated Apple Watch heart-rate signal and grew into a collection of building blocks organized along three axes: device type (smartwatch, sleep monitor, CGM, cycle tracker, smart scale), physiology system (cardiac, pulmonary, endocrine/circadian, metabolic, cellular/aging), and time scale (from ~1 beat per second to ~1 cycle per month).
Models
What's now included
Three example modules consume these models during a normal Synthea run:
wearables_cardiac_monitoring(ECG),wearables_sleepapnea(circadian clock + pulmonary oxygen intake), andendometriosis(menstrual cycle). Each model can also be run standalone from the command line to produce charts, CSV data, and — new in this PR — a FHIR R4 Bundle ofSampledDataObservations.Everything is opt-in
No default behavior changes. Both integration paths are off unless explicitly enabled:
With both flags at their default (
false), Synthea runs exactly as before — which is how CI stays green.Review feedback from #1347, addressed
@jawalonoski's review of the sleep apnea module has been addressed on this branch:
emergencytoambulatory, and encounters no longer overlap (the baseline encounter ends before the sleep study begins).Cleanup since #1347
PhysiologySimulationExporter) so standalone simulations can emit FHIR R4SampledDataObservations, reusing the existingFhirR4.mapValueToSampledData.PhysiologyModuleStateTest(module-state path runs when enabled) and a cortisol stress-response test inPhysiologySimulatorTest.config/simulations/README.mdaround the device × system × time-scale catalog, with both entry paths and the enabling flags documented.Checkstyle and the full test suite are green.
Trying it out
Run any model standalone:
Charts (PNG) and raw data (CSV) land in
output/physiology/{name}/. Or generate a population with physiology enabled:See
config/simulations/README.mdfor the full catalog and usage.Finding the data
Population runs write FHIR bundles to
output/fhir/. Two quick searches find the physiology data:(Standalone runs put their CSV, PNG, and optional FHIR bundle in
output/physiology/{name}/.)Observations with SampledData
When a module's
Physiologystate runs, the model's output time series is stored on the person and recorded by the nextObservationstate as a FHIRSampledDatavalue. Here is an ECG fromwearables_cardiac_monitoring, exactly as exported (data truncated):{ "resourceType": "Observation", "status": "final", "category": [ { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "procedure", "display": "Procedure" } ] } ], "code": { "coding": [ { "system": "http://snomed.info/sct", "code": "29303009", "display": "Electrocardiographic procedure" } ], "text": "Electrocardiographic procedure" }, "effectiveDateTime": "2020-05-16T10:07:08-05:00", "valueSampledData": { "origin": { "value": 0.0, "system": "http://unitsofmeasure.org" }, "period": 10.0, "dimensions": 1, "data": "0.038 0.031 0.004 -0.014 -0.02 -0.017 -0.014 -0.01 ..." } }Media (rendered charts)
Observation states with a chart
attachmentexport asMediaresources carrying the rendered PNG (R4 Observations cannot hold attachments), so the same encounter also contains the picture of that waveform:Media resource example (base64 truncated)
{ "resourceType": "Media", "status": "completed", "type": { "coding": [ { "system": "http://terminology.hl7.org/CodeSystem/media-type", "code": "image", "display": "Image" } ], "text": "Image" }, "reasonCode": [ { "coding": [ { "system": "http://snomed.info/sct", "code": "29303009", "display": "Electrocardiographic procedure" } ] } ], "height": 200, "width": 400, "content": { "contentType": "image/png", "data": "iVBORw0KGgoAAAANSUhEUgAAAZAAAADICAYAAADGFbfi...", "title": "Electrocardiogram" } }Standalone simulations can emit FHIR too: with
exportFhir: truein the sim config, the newPhysiologySimulationExporterwrites a Bundle ofSampledDataObservations (one per non-constant model variable) tooutput/physiology/{name}/{name}.fhir.json.Model inputs — what drives a simulation, and where it lives
Inputs are expressed in three places, depending on the entry path:
1. Standalone sim configs (
config/simulations/*.yml) set model parameters to literal values. Fromecg.yml— mean heart rate and the arrhythmia-episode window:2. Module
Physiologystates map attributes of the simulated person into model parameters through expressions, so patient state drives the physiology. Fromwearables_cardiac_monitoring, the person's BMI modulates the ECG model's mean heart rate, and the model's waveform comes back as a person attribute that the next Observation state records:Any person attribute or vital sign can appear in a
#{...}expression (#{BMI},#{age}, boolean attributes as#b{...}).3. Physiology generators (
src/main/resources/physiology/generators/*.yml) use the same expression language plus avariancethreshold (skip re-running the simulation for small input changes) and apreGenerator(values to use before the model first runs). These ship dormant in this PR — theirVITAL_SIGNoutput types are commented out — so they are configuration examples rather than active data sources.One fix included here makes the command-line flag work as documented:
State.ENABLE_PHYSIOLOGY_STATEused to be frozen when the class loaded (before--configarguments were applied), so--physiology.state.enabled=truewas silently ignored and the flag only worked fromsynthea.properties.Generator.init()now re-reads it once configuration is final.References