From cf1bffdef23163ced260d37ae33cbaee8dccf14b Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Tue, 12 Dec 2023 10:30:48 -0500 Subject: [PATCH 1/2] templates and keep modules --- src/main/java/App.java | 19 +- .../org/mitre/synthea/engine/Generator.java | 6 +- .../java/org/mitre/synthea/engine/Module.java | 21 +- .../org/mitre/synthea/helpers/Utilities.java | 29 ++ .../resources/keep_modules/keep_diabetes.json | 42 +++ .../resources/keep_modules/keep_hospice.json | 28 ++ .../keep_medicare_beneficiaries.json | 181 +++++++++ .../keep_modules/must_have_cabg.json | 0 .../must_have_cardiac_surgery.json | 0 .../templates/modules/incidence_1.json | 150 ++++++++ .../templates/modules/incidence_2.json | 196 ++++++++++ .../resources/templates/modules/keep.json | 42 +++ .../templates/modules/onset_distribution.json | 158 ++++++++ .../templates/modules/prevalence.json | 351 ++++++++++++++++++ .../mitre/synthea/engine/GeneratorTest.java | 4 +- 15 files changed, 1199 insertions(+), 28 deletions(-) create mode 100644 src/main/resources/keep_modules/keep_diabetes.json create mode 100644 src/main/resources/keep_modules/keep_hospice.json create mode 100644 src/main/resources/keep_modules/keep_medicare_beneficiaries.json rename must_have_cabg.json => src/main/resources/keep_modules/must_have_cabg.json (100%) rename must_have_cardiac_surgery.json => src/main/resources/keep_modules/must_have_cardiac_surgery.json (100%) create mode 100644 src/main/resources/templates/modules/incidence_1.json create mode 100644 src/main/resources/templates/modules/incidence_2.json create mode 100644 src/main/resources/templates/modules/keep.json create mode 100644 src/main/resources/templates/modules/onset_distribution.json create mode 100644 src/main/resources/templates/modules/prevalence.json diff --git a/src/main/java/App.java b/src/main/java/App.java index e790479a45..9fe0d6e752 100644 --- a/src/main/java/App.java +++ b/src/main/java/App.java @@ -1,6 +1,10 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.LinkedList; @@ -195,12 +199,21 @@ public static void main(String[] args) throws Exception { } } else if (currArg.equals("-k")) { String value = argsQ.poll(); + // first check if it's an absolute path, or path relative to . File keepPatientsModule = new File(value); if (keepPatientsModule.exists()) { - options.keepPatientsModulePath = keepPatientsModule; + options.keepPatientsModulePath = keepPatientsModule.toPath(); } else { - throw new FileNotFoundException(String.format( - "Specified keep-patients file (%s) does not exist", value)); + // look inside the src/main/resources/keep_modules folder + URI keepModulesURI = App.class.getClassLoader().getResource("keep_modules").toURI(); + Utilities.fixPathFromJar(keepModulesURI); + Path possibleLocation = Paths.get(keepModulesURI).resolve(value); + if (Files.exists(possibleLocation)) { + options.keepPatientsModulePath = possibleLocation; + } else { + throw new FileNotFoundException(String.format( + "Specified keep-patients file (%s) does not exist", value)); + } } } else if (currArg.equals("-fm")) { String value = argsQ.poll(); diff --git a/src/main/java/org/mitre/synthea/engine/Generator.java b/src/main/java/org/mitre/synthea/engine/Generator.java index 013385cf21..7b2c2388c6 100644 --- a/src/main/java/org/mitre/synthea/engine/Generator.java +++ b/src/main/java/org/mitre/synthea/engine/Generator.java @@ -141,7 +141,7 @@ public static class GeneratorOptions { * value of -1 will evolve the population to the current system time. */ public int daysToTravelForward = -1; /** Path to a module defining which patients should be kept and exported. */ - public File keepPatientsModulePath; + public Path keepPatientsModulePath; } /** @@ -278,8 +278,8 @@ private void init() { if (options.keepPatientsModulePath != null) { try { - Path path = options.keepPatientsModulePath.toPath().toAbsolutePath(); - this.keepPatientsModule = Module.loadFile(path, false, null, true); + this.keepPatientsModule = + Module.loadFile(options.keepPatientsModulePath, false, null, true); } catch (Exception e) { throw new ExceptionInInitializerError(e); } diff --git a/src/main/java/org/mitre/synthea/engine/Module.java b/src/main/java/org/mitre/synthea/engine/Module.java index 5c59bbfd9d..2663c37d8d 100644 --- a/src/main/java/org/mitre/synthea/engine/Module.java +++ b/src/main/java/org/mitre/synthea/engine/Module.java @@ -16,11 +16,9 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystemNotFoundException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.spi.FileSystemProvider; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -102,7 +100,7 @@ private static Map loadModules() { */ public static Path getModulesPath() throws URISyntaxException, IOException { URI modulesURI = Module.class.getClassLoader().getResource("modules").toURI(); - fixPathFromJar(modulesURI); + Utilities.fixPathFromJar(modulesURI); return Paths.get(modulesURI); } @@ -166,23 +164,6 @@ public static void addModules(File dir) { submoduleCount); } - private static void fixPathFromJar(URI uri) throws IOException { - // this function is a hack to enable reading modules from within a JAR file - // see https://stackoverflow.com/a/48298758 - if ("jar".equals(uri.getScheme())) { - for (FileSystemProvider provider: FileSystemProvider.installedProviders()) { - if (provider.getScheme().equalsIgnoreCase("jar")) { - try { - provider.getFileSystem(uri); - } catch (FileSystemNotFoundException e) { - // in this case we need to initialize it first: - provider.newFileSystem(uri, Collections.emptyMap()); - } - } - } - } - } - /** * Create a relative path from a folder to a file, removing the file extension and normalizing * path segment separators. diff --git a/src/main/java/org/mitre/synthea/helpers/Utilities.java b/src/main/java/org/mitre/synthea/helpers/Utilities.java index a55cf53f81..983670be89 100644 --- a/src/main/java/org/mitre/synthea/helpers/Utilities.java +++ b/src/main/java/org/mitre/synthea/helpers/Utilities.java @@ -10,16 +10,20 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystemNotFoundException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.spi.FileSystemProvider; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Random; @@ -638,4 +642,29 @@ public static Map cleanMap(Map input) { }); return input; } + + /** + * Enable reading the given URI from within a JAR file. + * For example, for command-line args which may refer to internal or external paths. + * Note that it's not always possible to know when a user-provided path + * is within a JAR file, so this method should be called if it is possible the + * path refers to an internal location. + * @param uri URI to be accessed + */ + public static void fixPathFromJar(URI uri) throws IOException { + // this function is a hack to enable reading modules from within a JAR file + // see https://stackoverflow.com/a/48298758 + if ("jar".equals(uri.getScheme())) { + for (FileSystemProvider provider: FileSystemProvider.installedProviders()) { + if (provider.getScheme().equalsIgnoreCase("jar")) { + try { + provider.getFileSystem(uri); + } catch (FileSystemNotFoundException e) { + // in this case we need to initialize it first: + provider.newFileSystem(uri, Collections.emptyMap()); + } + } + } + } + } } \ No newline at end of file diff --git a/src/main/resources/keep_modules/keep_diabetes.json b/src/main/resources/keep_modules/keep_diabetes.json new file mode 100644 index 0000000000..2652096c72 --- /dev/null +++ b/src/main/resources/keep_modules/keep_diabetes.json @@ -0,0 +1,42 @@ +{ + "name": "Generated Keep Module", + "states": { + "Initial": { + "type": "Initial", + "name": "Initial", + "conditional_transition": [ + { + "transition": "Keep", + "condition": { + "condition_type": "And", + "conditions": [ + { + "condition_type": "Active Condition", + "codes": [ + { + "system": "SNOMED-CT", + "code": "44054006", + "display": "Diabetes" + } + ] + } + ] + } + }, + { + "transition": "Terminal" + } + ] + }, + "Terminal": { + "type": "Terminal", + "name": "Terminal" + }, + "Keep": { + "type": "Terminal", + "name": "Keep" + } + }, + "gmf_version": 2 +} + \ No newline at end of file diff --git a/src/main/resources/keep_modules/keep_hospice.json b/src/main/resources/keep_modules/keep_hospice.json new file mode 100644 index 0000000000..7b6e0dcfd9 --- /dev/null +++ b/src/main/resources/keep_modules/keep_hospice.json @@ -0,0 +1,28 @@ +{ + "name": "keep_hospice", + "states": { + "Initial": { + "type": "Initial", + "conditional_transition": [ + { + "transition": "Keep", + "condition": { + "condition_type": "Attribute", + "attribute": "hospice", + "operator": "is not nil" + } + }, + { + "transition": "Terminal" + } + ] + }, + "Terminal": { + "type": "Terminal" + }, + "Keep": { + "type": "Terminal" + } + }, + "gmf_version": 2 +} \ No newline at end of file diff --git a/src/main/resources/keep_modules/keep_medicare_beneficiaries.json b/src/main/resources/keep_modules/keep_medicare_beneficiaries.json new file mode 100644 index 0000000000..26507bf0a1 --- /dev/null +++ b/src/main/resources/keep_modules/keep_medicare_beneficiaries.json @@ -0,0 +1,181 @@ +{ + "name": "Keep Medicare Beneficiaries", + "remarks": [ + "Keep Medicare Beneficiaries." + ], + "states": { + "Initial": { + "type": "Initial", + "conditional_transition": [ + { + "transition": "Keep", + "condition": { + "condition_type": "Or", + "conditions": [ + { + "condition_type": "Age", + "operator": ">=", + "quantity": 65, + "unit": "years", + "value": 0 + }, + { + "condition_type": "Or", + "conditions": [ + { + "condition_type": "Attribute", + "attribute": "insurance_status", + "operator": "==", + "value": "medicare" + }, + { + "condition_type": "Or", + "conditions": [ + { + "condition_type": "Attribute", + "attribute": "insurance_status", + "operator": "==", + "value": "medicaid" + }, + { + "condition_type": "Attribute", + "attribute": "insurance_status", + "operator": "==", + "value": "dual eligible" + } + ] + } + ] + } + ] + } + }, + { + "transition": "Disability Check" + } + ] + }, + "Terminal": { + "type": "Terminal" + }, + "Keep": { + "type": "Terminal" + }, + "Disability Check": { + "type": "Simple", + "conditional_transition": [ + { + "transition": "Keep", + "condition": { + "condition_type": "Or", + "conditions": [ + { + "condition_type": "Attribute", + "attribute": "disabled", + "operator": "==", + "value": true + }, + { + "condition_type": "Attribute", + "attribute": "blindness", + "operator": "==", + "value": true + } + ] + } + }, + { + "transition": "ESRD Check" + } + ] + }, + "ESRD Check": { + "type": "Simple", + "conditional_transition": [ + { + "transition": "Keep", + "condition": { + "condition_type": "At Least", + "minimum": 1, + "conditions": [ + { + "condition_type": "Attribute", + "attribute": "dialysis_reason", + "operator": "is not nil" + }, + { + "condition_type": "Attribute", + "attribute": "kidney_transplant", + "operator": "is not nil" + }, + { + "condition_type": "Active Condition", + "codes": [ + { + "system": "SNOMED-CT", + "code": 46177005, + "display": "End stage renal disease (disorder)" + } + ] + }, + { + "condition_type": "Active Condition", + "codes": [ + { + "system": "SNOMED-CT", + "code": 431857002, + "display": "Chronic kidney disease stage 4 (disorder)" + } + ] + }, + { + "condition_type": "Active Condition", + "codes": [ + { + "system": "SNOMED-CT", + "code": 204949001, + "display": "Renal dysplasia (disorder)" + } + ] + }, + { + "condition_type": "Active Condition", + "codes": [ + { + "system": "SNOMED-CT", + "code": 213150003, + "display": "Kidney transplant failure and rejection (disorder)" + } + ] + }, + { + "condition_type": "Active Condition", + "codes": [ + { + "system": "SNOMED-CT", + "code": 161665007, + "display": "History of renal transplant (situation)" + } + ] + }, + { + "condition_type": "Active Condition", + "codes": [ + { + "system": "SNOMED-CT", + "code": 698306007, + "display": "Awaiting transplantation of kidney (situation)" + } + ] + } + ] + } + }, + { + "transition": "Terminal" + } + ] + } + }, + "gmf_version": 2 +} \ No newline at end of file diff --git a/must_have_cabg.json b/src/main/resources/keep_modules/must_have_cabg.json similarity index 100% rename from must_have_cabg.json rename to src/main/resources/keep_modules/must_have_cabg.json diff --git a/must_have_cardiac_surgery.json b/src/main/resources/keep_modules/must_have_cardiac_surgery.json similarity index 100% rename from must_have_cardiac_surgery.json rename to src/main/resources/keep_modules/must_have_cardiac_surgery.json diff --git a/src/main/resources/templates/modules/incidence_1.json b/src/main/resources/templates/modules/incidence_1.json new file mode 100644 index 0000000000..253c9b9def --- /dev/null +++ b/src/main/resources/templates/modules/incidence_1.json @@ -0,0 +1,150 @@ +{ + "name": "Incidence 1", + "remarks": [ + "This template demonstrates an incidence-based approach for modeling condition onset in Synthea." + ], + "states": { + "Initial": { + "type": "Initial", + "direct_transition": "No_Infection" + }, + "No_Infection": { + "type": "Delay", + "exact": { + "quantity": 1, + "unit": "months" + }, + "complex_transition": [ + { + "condition": { + "condition_type": "Age", + "operator": "<", + "quantity": 3, + "unit": "years" + }, + "distributions": [ + { + "distribution": 0.02010556, + "transition": "Gets_Ear_Infection" + }, + { + "distribution": 0.97989444, + "transition": "No_Infection" + } + ] + }, + { + "condition": { + "condition_type": "Age", + "operator": "<", + "quantity": 6, + "unit": "years" + }, + "distributions": [ + { + "distribution": 0.0131625, + "transition": "Gets_Ear_Infection" + }, + { + "distribution": 0.9868375, + "transition": "No_Infection" + } + ] + }, + { + "condition": { + "condition_type": "Age", + "operator": "<", + "quantity": 18, + "unit": "years" + }, + "distributions": [ + { + "distribution": 0.0007444, + "transition": "Gets_Ear_Infection" + }, + { + "distribution": 0.99925556, + "transition": "No_Infection" + } + ] + }, + { + "condition": { + "condition_type": "Age", + "operator": ">=", + "quantity": 18, + "unit": "years" + }, + "distributions": [ + { + "distribution": 0.00020833, + "transition": "Gets_Ear_Infection" + }, + { + "distribution": 0.99979167, + "transition": "No_Infection" + } + ] + } + ] + }, + "Gets_Ear_Infection": { + "type": "ConditionOnset", + "target_encounter": "Ear_Infection_Encounter", + "codes": [ + { + "system": "SNOMED-CT", + "code": "65363002", + "display": "Otitis media" + } + ], + "direct_transition": "Ear_Infection_Encounter" + }, + "Ear_Infection_Encounter": { + "type": "Encounter", + "encounter_class": "outpatient", + "reason": "Gets_Ear_Infection", + "codes": [ + { + "system": "SNOMED-CT", + "code": "185345009", + "display": "Encounter for symptom" + } + ], + "direct_transition": "Ear_Infection_Prescribed_OTC_Painkiller" + }, + "Ear_Infection_Prescribed_OTC_Painkiller": { + "type": "MedicationOrder", + "codes": [ + { + "system": "RxNorm", + "code": "1234", + "display": "RxNorm Code" + } + ], + "direct_transition": "End_Encounter" + }, + "End_Encounter": { + "type": "EncounterEnd", + "direct_transition": "New_State" + }, + "New_State": { + "type": "Delay", + "distribution": { + "kind": "EXACT", + "parameters": { + "value": 2 + } + }, + "unit": "weeks", + "direct_transition": "New_State_2" + }, + "New_State_2": { + "type": "MedicationEnd", + "direct_transition": "No_Infection", + "medication_order": "Ear_Infection_Prescribed_OTC_Painkiller" + } + }, + "gmf_version": 1 +} \ No newline at end of file diff --git a/src/main/resources/templates/modules/incidence_2.json b/src/main/resources/templates/modules/incidence_2.json new file mode 100644 index 0000000000..ba5c4bba76 --- /dev/null +++ b/src/main/resources/templates/modules/incidence_2.json @@ -0,0 +1,196 @@ +{ + "name": "Incidence 2", + "remarks": [ + "This template demonstrates an incidence-based approach for modeling condition onset in Synthea." + ], + "states": { + "Initial": { + "type": "Initial", + "remarks": [ + "Initial impl == direct translation of ruby module" + ], + "direct_transition": "Age_Guard" + }, + "Age_Guard": { + "type": "Guard", + "allow": { + "condition_type": "Age", + "operator": ">=", + "quantity": 18, + "unit": "years", + "value": 0 + }, + "direct_transition": "Set_Yearly_Risk" + }, + "Set_Yearly_Risk": { + "type": "Simple", + "remarks": [ + "By age 55 years, cumulative incidence of hypertension was 75.5% in black men, 75.7% in black women, 54.5% in white men, and 40.0% in white women -- https://www.ahajournals.org/doi/full/10.1161/JAHA.117.007988", + "", + "", + "Cumulative Incidence = 1 - e(-IR x D)", + "e^(-IRxD) = 1 - CI", + "-IR x D = ln(1-CI)", + "IR = -ln(1-CI)/D", + "", + "Assuming 0% at age 18, and per the chart the increase is roughly linear, use the following yearly incidence rates:", + "", + "", + "black men - 3.8%", + "black women - 3.8%", + "white men - 2.1%", + "white women - 1.4%", + "others - 2.5% (just a value in the middle, no source)" + ], + "conditional_transition": [ + { + "transition": "Black", + "condition": { + "condition_type": "Race", + "race": "Black" + } + }, + { + "transition": "White", + "condition": { + "condition_type": "Race", + "race": "White" + } + }, + { + "transition": "Others" + } + ] + }, + "Chance_of_Hypertension": { + "type": "Simple", + "complex_transition": [ + { + "distributions": [ + { + "transition": "Onset_Hypertension", + "distribution": { + "attribute": "risk_of_hypertension", + "default": 0.05 + } + }, + { + "transition": "Wait_till_next_year", + "distribution": 0.95 + } + ] + } + ], + "remarks": [ + "Use the risk set above, but also check if some other module may have set hypertension == true" + ] + }, + "Wait_till_next_year": { + "type": "Delay", + "distribution": { + "kind": "EXACT", + "parameters": { + "value": 1 + } + }, + "unit": "years", + "direct_transition": "Chance_of_Hypertension" + }, + "Onset_Hypertension": { + "type": "SetAttribute", + "attribute": "hypertension", + "value": true, + "direct_transition": "Wellness_Encounter" + }, + "Black": { + "type": "Simple", + "conditional_transition": [ + { + "transition": "Black_Female", + "condition": { + "condition_type": "Gender", + "gender": "F" + } + }, + { + "transition": "Black_Male" + } + ] + }, + "White": { + "type": "Simple", + "conditional_transition": [ + { + "transition": "White_Female", + "condition": { + "condition_type": "Gender", + "gender": "F" + } + }, + { + "transition": "White_Male" + } + ] + }, + "Others": { + "type": "SetAttribute", + "attribute": "risk_of_hypertension", + "direct_transition": "Chance_of_Hypertension", + "value": 0.025 + }, + "Black_Female": { + "type": "SetAttribute", + "attribute": "risk_of_hypertension", + "direct_transition": "Chance_of_Hypertension", + "value": 0.038 + }, + "Black_Male": { + "type": "SetAttribute", + "attribute": "risk_of_hypertension", + "direct_transition": "Chance_of_Hypertension", + "value": 0.038 + }, + "White_Male": { + "type": "SetAttribute", + "attribute": "risk_of_hypertension", + "direct_transition": "Chance_of_Hypertension", + "value": 0.021 + }, + "White_Female": { + "type": "SetAttribute", + "attribute": "risk_of_hypertension", + "direct_transition": "Chance_of_Hypertension", + "value": 0.014 + }, + "Diagnose_Hypertension": { + "type": "ConditionOnset", + "codes": [ + { + "system": "SNOMED-CT", + "code": 59621000, + "display": "Essential hypertension (disorder)" + } + ], + "assign_to_attribute": "hypertension_dx", + "direct_transition": "Terminal" + }, + "Wellness_Encounter": { + "type": "Encounter", + "reason": "hypertension_screening_reason", + "direct_transition": "Diagnose_Hypertension", + "codes": [ + { + "system": "SNOMED-CT", + "code": "1234", + "display": "SNOMED Code" + } + ], + "encounter_class": "ambulatory", + "telemedicine_possibility": "none" + }, + "Terminal": { + "type": "Terminal" + } + }, + "gmf_version": 1 +} \ No newline at end of file diff --git a/src/main/resources/templates/modules/keep.json b/src/main/resources/templates/modules/keep.json new file mode 100644 index 0000000000..83bfb9cbc3 --- /dev/null +++ b/src/main/resources/templates/modules/keep.json @@ -0,0 +1,42 @@ +{ + "name": "Generated Keep Module", + "states": { + "Initial": { + "type": "Initial", + "name": "Initial", + "conditional_transition": [ + { + "transition": "Keep", + "condition": { + "condition_type": "And", + "conditions": [ + { + "condition_type": "Active Condition", + "codes": [ + { + "system": "SNOMED-CT", + "code": "26929004", + "display": "Alzheimer's disease (disorder)" + } + ] + } + ] + } + }, + { + "transition": "Terminal" + } + ] + }, + "Terminal": { + "type": "Terminal", + "name": "Terminal" + }, + "Keep": { + "type": "Terminal", + "name": "Keep" + } + }, + "gmf_version": 2 +} + \ No newline at end of file diff --git a/src/main/resources/templates/modules/onset_distribution.json b/src/main/resources/templates/modules/onset_distribution.json new file mode 100644 index 0000000000..c7cb938c7a --- /dev/null +++ b/src/main/resources/templates/modules/onset_distribution.json @@ -0,0 +1,158 @@ +{ + "name": "Onset Distribution", + "remarks": [ + "This template demonstrates a distribution-based approach for modeling age of condition onset in Synthea." + ], + "states": { + "Initial": { + "type": "Initial", + "direct_transition": "Age_Guard" + }, + "Age_Guard": { + "type": "Guard", + "allow": { + "condition_type": "Age", + "operator": ">=", + "quantity": 18, + "unit": "years" + }, + "direct_transition": "Veteran_Diabetes_Prevalence" + }, + "Eventual_Prediabetes": { + "type": "Delay", + "range": { + "low": 0, + "high": 37, + "unit": "years" + }, + "remarks": [ + "we assume that diabetes and prediabetes generally onset between the ages of 18-55" + ], + "direct_transition": "Onset_Prediabetes" + }, + "Eventual_Diabetes": { + "type": "SetAttribute", + "attribute": "time_until_diabetes_onset", + "direct_transition": "Already_age_18", + "remarks": [ + "we assume that diabetes and prediabetes generally onset between the ages of 18-55", + "this tracks a little lower so that we can diagnose prediabetes early and then diabetes later", + "there is little info on how many patients with prediabetes progress to diabetes", + "so we assume that 38% of patients with diabetes had a prediabetes diagnosis" + ], + "distribution": { + "kind": "GAUSSIAN", + "parameters": { + "mean": 55, + "standardDeviation": 15 + } + } + }, + "Onset_Prediabetes": { + "type": "SetAttribute", + "attribute": "prediabetes", + "value": true, + "direct_transition": "No_Diabetes" + }, + "No_Diabetes": { + "type": "Terminal" + }, + "Onset_Prediabetes_Towards_Diabetes": { + "type": "ConditionOnset", + "codes": [ + { + "system": "SNOMED-CT", + "code": "1234", + "display": "SNOMED Code" + } + ], + "direct_transition": "Terminal" + }, + "Veteran_Diabetes_Prevalence": { + "type": "Simple", + "distributed_transition": [ + { + "transition": "Eventual_Diabetes", + "distribution": 0.25 + }, + { + "transition": "Eventual_Prediabetes", + "distribution": 0.45 + }, + { + "transition": "No_Diabetes", + "distribution": 0.3 + } + ] + }, + "Onset_Prediabetes_2": { + "type": "SetAttribute", + "attribute": "prediabetes", + "direct_transition": "Delay_Another_Year", + "value": true + }, + "Countdown to Diabetes": { + "type": "Counter", + "attribute": "time_until_diabetes_onset", + "action": "decrement", + "distributed_transition": [ + { + "transition": "Onset_Prediabetes_2", + "distribution": 0.05 + }, + { + "transition": "Delay_Another_Year", + "distribution": 0.95 + } + ] + }, + "Already_age_18": { + "type": "Counter", + "attribute": "time_until_diabetes_onset", + "action": "decrement", + "conditional_transition": [ + { + "transition": "Onset_Prediabetes_Towards_Diabetes", + "condition": { + "condition_type": "Attribute", + "attribute": "time_until_diabetes_onset", + "operator": "<=", + "value": 0 + } + }, + { + "transition": "Countdown to Diabetes" + } + ], + "amount": 18 + }, + "Delay_Another_Year": { + "type": "Delay", + "distribution": { + "kind": "EXACT", + "parameters": { + "value": 1 + } + }, + "unit": "years", + "conditional_transition": [ + { + "transition": "Onset_Prediabetes_Towards_Diabetes", + "condition": { + "condition_type": "Attribute", + "attribute": "time_until_diabetes_onset", + "operator": "<=", + "value": 0 + } + }, + { + "transition": "Countdown to Diabetes" + } + ] + }, + "Terminal": { + "type": "Terminal" + } + }, + "gmf_version": 1 +} \ No newline at end of file diff --git a/src/main/resources/templates/modules/prevalence.json b/src/main/resources/templates/modules/prevalence.json new file mode 100644 index 0000000000..4a546b5a55 --- /dev/null +++ b/src/main/resources/templates/modules/prevalence.json @@ -0,0 +1,351 @@ +{ + "name": "Prevalence", + "remarks": [ + "This template demonstrates a 'Pure Prevalence' type approach of modeling condition onset in Synthea." + ], + "states": { + "Initial": { + "type": "Initial", + "conditional_transition": [ + { + "transition": "Female", + "condition": { + "condition_type": "Gender", + "gender": "F" + } + }, + { + "transition": "Male", + "condition": { + "condition_type": "Gender", + "gender": "M" + } + }, + { + "transition": "Terminal" + } + ] + }, + "Terminal": { + "type": "Terminal" + }, + "Female": { + "type": "Simple", + "remarks": [ + "North American Indians have the highest reported rates of cholelithiasis, afflicting 64.1% of women and 29.5% of men. White Americans have an overall prevalence of 16.6% in women and 8.6% in men. Intermediate prevalence rates occur in Asian populations and Black Americans (13.9% of women; 5.3% of men)", + "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3343155/", + "The age-adjusted prevalence of gallstone disease (gallstones + cholecystectomy) among Mexican American men (7.2%) was 1.7 times that of Cuban American men and 1.8 times that of Puerto Rican men. The prevalence for Mexican American women (23.2%) was 1.5 times that of Cuban American women and 1.7 times that of Puerto Rican women.", + "https://www.ncbi.nlm.nih.gov/pubmed/2642879" + ], + "complex_transition": [ + { + "condition": { + "condition_type": "Race", + "race": "Native" + }, + "distributions": [ + { + "transition": "Female_Gallstone_Formation", + "distribution": 0.641 + }, + { + "transition": "Terminal", + "distribution": 0.359 + } + ] + }, + { + "condition": { + "condition_type": "Race", + "race": "White" + }, + "distributions": [ + { + "transition": "Female_Gallstone_Formation", + "distribution": 0.166 + }, + { + "transition": "Terminal", + "distribution": 0.834 + } + ] + }, + { + "distributions": [ + { + "transition": "Female_Gallstone_Formation", + "distribution": 0.139 + }, + { + "transition": "Terminal", + "distribution": 0.861 + } + ], + "condition": { + "condition_type": "Race", + "race": "Black" + } + }, + { + "condition": { + "condition_type": "Race", + "race": "Asian" + }, + "distributions": [ + { + "transition": "Female_Gallstone_Formation", + "distribution": 0.139 + }, + { + "transition": "Terminal", + "distribution": 0.861 + } + ] + }, + { + "condition": { + "condition_type": "Race", + "race": "Hispanic" + }, + "distributions": [ + { + "transition": "Female_Gallstone_Formation", + "distribution": 0.232 + }, + { + "transition": "Terminal", + "distribution": 0.768 + } + ] + } + ] + }, + "Male": { + "type": "Simple", + "remarks": [ + "North American Indians have the highest reported rates of cholelithiasis, afflicting 64.1% of women and 29.5% of men. White Americans have an overall prevalence of 16.6% in women and 8.6% in men. Intermediate prevalence rates occur in Asian populations and Black Americans (13.9% of women; 5.3% of men)", + "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3343155/", + "The age-adjusted prevalence of gallstone disease (gallstones + cholecystectomy) among Mexican American men (7.2%) was 1.7 times that of Cuban American men and 1.8 times that of Puerto Rican men. The prevalence for Mexican American women (23.2%) was 1.5 times that of Cuban American women and 1.7 times that of Puerto Rican women.", + "https://www.ncbi.nlm.nih.gov/pubmed/2642879" + ], + "complex_transition": [ + { + "condition": { + "condition_type": "Race", + "race": "Native" + }, + "distributions": [ + { + "transition": "Male_Gallstone_Formation", + "distribution": 0.295 + }, + { + "transition": "Terminal", + "distribution": 0.705 + } + ] + }, + { + "distributions": [ + { + "transition": "Male_Gallstone_Formation", + "distribution": 0.0866 + }, + { + "transition": "Terminal", + "distribution": 0.9134 + } + ], + "condition": { + "condition_type": "Race", + "race": "White" + } + }, + { + "condition": { + "condition_type": "Race", + "race": "Black" + }, + "distributions": [ + { + "transition": "Male_Gallstone_Formation", + "distribution": 0.053 + }, + { + "transition": "Terminal", + "distribution": 0.947 + } + ] + }, + { + "condition": { + "condition_type": "Race", + "race": "Asian" + }, + "distributions": [ + { + "transition": "Male_Gallstone_Formation", + "distribution": 0.053 + }, + { + "transition": "Terminal", + "distribution": 0.947 + } + ] + }, + { + "condition": { + "condition_type": "Race", + "race": "Hispanic" + }, + "distributions": [ + { + "transition": "Male_Gallstone_Formation", + "distribution": 0.072 + }, + { + "transition": "Terminal", + "distribution": 0.928 + } + ] + } + ] + }, + "Female_Gallstone_Formation": { + "type": "Simple", + "distributed_transition": [ + { + "transition": "Age_20-29", + "distribution": 0.0718 + }, + { + "transition": "Age_30-39", + "distribution": 0.1127 + }, + { + "transition": "Age_40-49", + "distribution": 0.1735 + }, + { + "transition": "Age_50-59", + "distribution": 0.2763 + }, + { + "transition": "Age_60-74", + "distribution": 0.3657 + } + ], + "remarks": [ + "Distribution of gallbladder disease prevalence by sex and age from Table 1: https://www.gastrojournal.org/article/S0016-5085(99)70456-7/pdf", + "(Age prevalence percentages were converted to a proportion of the total prevalence, given the race and sex distribution already in place)" + ] + }, + "Male_Gallstone_Formation": { + "type": "Simple", + "distributed_transition": [ + { + "transition": "Age_20-29", + "distribution": 0.0274 + }, + { + "transition": "Age_30-39", + "distribution": 0.04 + }, + { + "transition": "Age_40-49", + "distribution": 0.1537 + }, + { + "transition": "Age_50-59", + "distribution": 0.2463 + }, + { + "transition": "Age_60-74", + "distribution": 0.5326 + } + ], + "remarks": [ + "Distribution of gallbladder disease prevalence by sex and age from Table 1: https://www.gastrojournal.org/article/S0016-5085(99)70456-7/pdf", + "(Age prevalence percentages were converted to a proportion of the total prevalence, given the race and sex distribution already in place)" + ] + }, + "Age_20-29": { + "type": "Delay", + "direct_transition": "Gallstones", + "range": { + "low": 20, + "high": 29, + "unit": "years" + } + }, + "Age_30-39": { + "type": "Delay", + "direct_transition": "Gallstones", + "range": { + "low": 30, + "high": 39, + "unit": "years" + } + }, + "Age_40-49": { + "type": "Delay", + "direct_transition": "Gallstones", + "range": { + "low": 40, + "high": 49, + "unit": "years" + } + }, + "Age_50-59": { + "type": "Delay", + "direct_transition": "Gallstones", + "range": { + "low": 50, + "high": 59, + "unit": "years" + } + }, + "Age_60-74": { + "type": "Delay", + "direct_transition": "Gallstones", + "range": { + "low": 60, + "high": 74, + "unit": "years" + } + }, + "Gallstones": { + "type": "ConditionOnset", + "target_encounter": "Cholecystitis_Encounter", + "codes": [ + { + "system": "SNOMED-CT", + "code": 235919008, + "display": "Cholelithiasis" + } + ], + "remarks": [ + "Up to 80% will never experience biliary pain or complications such as acute cholecystitis, cholangitis, or pancreatitis", + "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3343155/" + ], + "direct_transition": "Cholecystitis_Encounter" + }, + "Cholecystitis_Encounter": { + "type": "Encounter", + "encounter_class": "emergency", + "codes": [ + { + "system": "SNOMED-CT", + "code": "50849002", + "display": "Emergency room admission (procedure)" + } + ], + "remarks": [ + "The ratio of LC performed increased from 83% in 1998 to 93% in 2005; 12% of cases were attempted laparoscopically but converted to OC.", + "https://www.ncbi.nlm.nih.gov/pubmed/18656637" + ], + "direct_transition": "Terminal" + } + }, + "gmf_version": 1 +} \ No newline at end of file diff --git a/src/test/java/org/mitre/synthea/engine/GeneratorTest.java b/src/test/java/org/mitre/synthea/engine/GeneratorTest.java index 3ad894c596..9ca50684b5 100644 --- a/src/test/java/org/mitre/synthea/engine/GeneratorTest.java +++ b/src/test/java/org/mitre/synthea/engine/GeneratorTest.java @@ -10,7 +10,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; - +import java.nio.file.Path; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -358,7 +358,7 @@ public void testKeepPatientsModule() throws Exception { opts.minAge = 35; opts.maxAge = 75; opts.ageSpecified = true; - opts.keepPatientsModulePath = new File("src/test/resources/keep_patients_module/keep.json"); + opts.keepPatientsModulePath = Path.of("src/test/resources/keep_patients_module/keep.json"); // keep module checks that patients have attribute diabetes == true Generator generator = new Generator(opts); From 649eb82803192993b39d39cba03657d35bccdbf1 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Tue, 19 Dec 2023 08:21:08 -0500 Subject: [PATCH 2/2] rename utilities method --- src/main/java/App.java | 2 +- src/main/java/org/mitre/synthea/engine/Module.java | 2 +- src/main/java/org/mitre/synthea/helpers/Utilities.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/App.java b/src/main/java/App.java index 9fe0d6e752..4f71cf4cf6 100644 --- a/src/main/java/App.java +++ b/src/main/java/App.java @@ -206,7 +206,7 @@ public static void main(String[] args) throws Exception { } else { // look inside the src/main/resources/keep_modules folder URI keepModulesURI = App.class.getClassLoader().getResource("keep_modules").toURI(); - Utilities.fixPathFromJar(keepModulesURI); + Utilities.enableReadingURIFromJar(keepModulesURI); Path possibleLocation = Paths.get(keepModulesURI).resolve(value); if (Files.exists(possibleLocation)) { options.keepPatientsModulePath = possibleLocation; diff --git a/src/main/java/org/mitre/synthea/engine/Module.java b/src/main/java/org/mitre/synthea/engine/Module.java index 2663c37d8d..e41edc3440 100644 --- a/src/main/java/org/mitre/synthea/engine/Module.java +++ b/src/main/java/org/mitre/synthea/engine/Module.java @@ -100,7 +100,7 @@ private static Map loadModules() { */ public static Path getModulesPath() throws URISyntaxException, IOException { URI modulesURI = Module.class.getClassLoader().getResource("modules").toURI(); - Utilities.fixPathFromJar(modulesURI); + Utilities.enableReadingURIFromJar(modulesURI); return Paths.get(modulesURI); } diff --git a/src/main/java/org/mitre/synthea/helpers/Utilities.java b/src/main/java/org/mitre/synthea/helpers/Utilities.java index 983670be89..c3c913867e 100644 --- a/src/main/java/org/mitre/synthea/helpers/Utilities.java +++ b/src/main/java/org/mitre/synthea/helpers/Utilities.java @@ -651,7 +651,7 @@ public static Map cleanMap(Map input) { * path refers to an internal location. * @param uri URI to be accessed */ - public static void fixPathFromJar(URI uri) throws IOException { + public static void enableReadingURIFromJar(URI uri) throws IOException { // this function is a hack to enable reading modules from within a JAR file // see https://stackoverflow.com/a/48298758 if ("jar".equals(uri.getScheme())) {