Skip to content

Commit 4926b46

Browse files
committed
add custom ValueSets to flexporter mapping file
1 parent e83ae2f commit 4926b46

File tree

6 files changed

+107
-35
lines changed

6 files changed

+107
-35
lines changed

src/main/java/App.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ public static void main(String[] args) throws Exception {
220220
if (flexporterMappingFile.exists()) {
221221
Mapping mapping = Mapping.parseMapping(flexporterMappingFile);
222222
exportOptions.addFlexporterMapping(mapping);
223+
mapping.loadValueSets();
224+
223225
// disable the graalVM warning when FlexporterJavascriptContext is instantiated
224226
System.getProperties().setProperty("polyglot.engine.WarnInterpreterOnly", "false");
225227
} else {

src/main/java/RunFlexporter.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import ca.uhn.fhir.parser.DataFormatException;
22
import ca.uhn.fhir.parser.IParser;
33

4-
import com.fasterxml.jackson.core.type.TypeReference;
5-
import com.fasterxml.jackson.databind.ObjectMapper;
6-
74
import java.io.File;
85
import java.io.FileNotFoundException;
96
import java.io.IOException;
@@ -14,7 +11,6 @@
1411
import java.nio.file.StandardOpenOption;
1512
import java.util.ArrayDeque;
1613
import java.util.Arrays;
17-
import java.util.Map;
1814
import java.util.Queue;
1915

2016
import org.apache.commons.io.FilenameUtils;
@@ -125,6 +121,7 @@ private static void convertFhir(File mappingFile, File igDirectory, File sourceF
125121
throws IOException {
126122

127123
Mapping mapping = Mapping.parseMapping(mappingFile);
124+
mapping.loadValueSets();
128125

129126
if (igDirectory != null) {
130127
loadIG(igDirectory);

src/main/java/org/mitre/synthea/export/flexporter/Mapping.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package org.mitre.synthea.export.flexporter;
22

3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
35
import java.io.File;
46
import java.io.FileInputStream;
57
import java.io.FileNotFoundException;
68
import java.io.InputStream;
79
import java.util.List;
810
import java.util.Map;
911

12+
import org.hl7.fhir.r4.model.ValueSet;
13+
import org.mitre.synthea.helpers.RandomCodeGenerator;
14+
import org.mitre.synthea.helpers.Utilities;
1015
import org.yaml.snakeyaml.Yaml;
1116
import org.yaml.snakeyaml.constructor.Constructor;
1217

@@ -15,6 +20,7 @@ public class Mapping {
1520
public String applicability;
1621

1722
public Map<String, Object> variables;
23+
public List<Map<String, Object>> customValueSets;
1824

1925
/**
2026
* Each action is a {@code Map>String,?>}. Nested fields within the YAML become ArrayLists and
@@ -34,4 +40,20 @@ public static Mapping parseMapping(File mappingFile) throws FileNotFoundExceptio
3440

3541
return yaml.loadAs(selectorInputSteam, Mapping.class);
3642
}
43+
44+
/**
45+
* Load the custom ValueSets that this mapping defines, so that the codes can be selected
46+
* in RandomCodeGenerator.
47+
*/
48+
public void loadValueSets() {
49+
try {
50+
if (this.customValueSets != null) {
51+
List<ValueSet> valueSets =
52+
Utilities.parseYamlToResources(this.customValueSets, ValueSet.class);
53+
valueSets.forEach(vs -> RandomCodeGenerator.loadValueSet(null, vs));
54+
}
55+
} catch (JsonProcessingException e) {
56+
throw new RuntimeException(e);
57+
}
58+
}
3759
}

src/main/java/org/mitre/synthea/helpers/Utilities.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package org.mitre.synthea.helpers;
22

3+
import ca.uhn.fhir.parser.IParser;
4+
5+
import com.fasterxml.jackson.core.JsonProcessingException;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
37
import com.google.common.base.Charsets;
48
import com.google.common.io.Resources;
59
import com.google.gson.FieldNamingPolicy;
@@ -36,9 +40,11 @@
3640
import java.util.regex.Pattern;
3741

3842
import org.apache.commons.lang3.Range;
43+
import org.hl7.fhir.r4.model.Resource;
3944
import org.mitre.synthea.engine.Logic;
4045
import org.mitre.synthea.engine.Module;
4146
import org.mitre.synthea.engine.State;
47+
import org.mitre.synthea.export.FhirR4;
4248
import org.mitre.synthea.world.concepts.HealthRecord.Code;
4349

4450
public class Utilities {
@@ -669,4 +675,36 @@ public static void enableReadingURIFromJar(URI uri) throws IOException {
669675
}
670676
}
671677
}
678+
679+
/**
680+
* Helper method to parse FHIR resources from YAML.
681+
* This is a workaround since the FHIR model classes don't work with our YAML parser.
682+
*
683+
* @param <T> Resource type contained in the YAML
684+
* @param yaml List of pre-parsed YAML as Map&lt;String, Object&gt;
685+
* @param resourceClass Specific resource class, must not be Resource
686+
* @return List of parsed resources
687+
* @throws JsonProcessingException (should never happen)
688+
*/
689+
public static <T extends Resource> List<T> parseYamlToResources(
690+
List<Map<String, Object>> yaml, Class<T> resourceClass)
691+
throws JsonProcessingException {
692+
if (yaml.isEmpty()) {
693+
return Collections.emptyList();
694+
}
695+
ObjectMapper jsonMapper = new ObjectMapper();
696+
IParser jsonParser = FhirR4.getContext().newJsonParser();
697+
List<T> results = new ArrayList<>();
698+
for (Map<String, Object> singleYaml : yaml) {
699+
if (!singleYaml.containsKey("resourceType")) {
700+
// allows the YAML to be cleaner by letting the resourceType be implied
701+
singleYaml.put("resourceType", resourceClass.getSimpleName());
702+
}
703+
String resourceJson = jsonMapper.writeValueAsString(singleYaml);
704+
@SuppressWarnings("unchecked")
705+
T resource = (T) jsonParser.parseResource(resourceJson);
706+
results.add(resource);
707+
}
708+
return results;
709+
}
672710
}

src/test/java/org/mitre/synthea/export/flexporter/ActionsTest.java

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public static void setupClass() throws FileNotFoundException {
8585
File file = new File(classLoader.getResource("flexporter/test_mapping.yaml").getFile());
8686

8787
testMapping = Mapping.parseMapping(file);
88+
testMapping.loadValueSets();
8889
}
8990

9091
@AfterClass
@@ -825,21 +826,6 @@ public void testRandomCode() {
825826
Bundle b = new Bundle();
826827
b.setType(BundleType.COLLECTION);
827828

828-
ValueSet statusVs = constructValueSet(
829-
"http://hl7.org/fhir/encounter-status",
830-
"planned", "finished", "cancelled");
831-
RandomCodeGenerator.loadValueSet("http://example.org/encounterStatus", statusVs);
832-
833-
ValueSet classVs = constructValueSet(
834-
"http://terminology.hl7.org/CodeSystem/v3-ActCode",
835-
"AMB", "EMER", "ACUTE");
836-
RandomCodeGenerator.loadValueSet("http://example.org/encounterClass", classVs);
837-
838-
ValueSet typeVs = constructValueSet(
839-
"http://terminology.hl7.org/CodeSystem/encounter-type",
840-
"ADMS", "OKI");
841-
RandomCodeGenerator.loadValueSet("http://example.org/encounterType", typeVs);
842-
843829
Map<String, Object> action = getActionByName("testRandomCode");
844830
Actions.applyAction(b, action, null, null);
845831

@@ -865,20 +851,4 @@ public void testRandomCode() {
865851
code = typeCoding.getCode();
866852
assertTrue(code.equals("ADMS") || code.equals("OKI"));
867853
}
868-
869-
private ValueSet constructValueSet(String system, String... codes) {
870-
ValueSet vs = new ValueSet();
871-
872-
// populates the codes so that they can be read in RandomCodeGenerator.loadValueSet
873-
ConceptSetComponent csc = new ConceptSetComponent();
874-
csc.setSystem(system);
875-
for (String code : codes) {
876-
csc.addConcept().setCode(code).setDisplay(code);
877-
}
878-
879-
vs.getCompose().getInclude().add(csc);
880-
881-
return vs;
882-
}
883-
884854
}

src/test/resources/flexporter/test_mapping.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,49 @@ name: Random Testing
66
# for now the assumption is 1 file = 1 synthea patient bundle.
77
applicability: true
88

9+
# Not a huge fan of this format, but it's better than defining yet another custom syntax
10+
customValueSets:
11+
- url: whats-for-dinner
12+
compose:
13+
include:
14+
- system: http://snomed.info/sct
15+
concept:
16+
- code: 227360002
17+
display: Pinto beans (substance)
18+
- code: 227319009
19+
display: Baked beans canned in tomato sauce with burgers (substance)
20+
- url: http://example.org/encounterStatus
21+
compose:
22+
include:
23+
- system: http://hl7.org/fhir/encounter-status
24+
concept:
25+
- code: planned
26+
display: Planned
27+
- code: finished
28+
display: Finished
29+
- code: cancelled
30+
display: Cancelled
31+
- url: http://example.org/encounterClass
32+
compose:
33+
include:
34+
- system: http://terminology.hl7.org/CodeSystem/v3-ActCode
35+
concept:
36+
- code: AMB
37+
display: ambulatory
38+
- code: EMER
39+
display: emergency
40+
- code: ACUTE
41+
display: inpatient acute
42+
- url: http://example.org/encounterType
43+
compose:
44+
include:
45+
- system: http://terminology.hl7.org/CodeSystem/encounter-type
46+
concept:
47+
- code: ADMS
48+
display: Annual diabetes mellitus screening
49+
- code: OKI
50+
display: Outpatient Kenacort injection
51+
952
actions:
1053
- name: Apply Profiles
1154
# v1: define specific profiles and an applicability statement on when to apply them

0 commit comments

Comments
 (0)