Skip to content

Commit

Permalink
add custom ValueSets to flexporter mapping file
Browse files Browse the repository at this point in the history
  • Loading branch information
dehall committed Nov 12, 2024
1 parent e83ae2f commit 4926b46
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 35 deletions.
2 changes: 2 additions & 0 deletions src/main/java/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ public static void main(String[] args) throws Exception {
if (flexporterMappingFile.exists()) {
Mapping mapping = Mapping.parseMapping(flexporterMappingFile);
exportOptions.addFlexporterMapping(mapping);
mapping.loadValueSets();

// disable the graalVM warning when FlexporterJavascriptContext is instantiated
System.getProperties().setProperty("polyglot.engine.WarnInterpreterOnly", "false");
} else {
Expand Down
5 changes: 1 addition & 4 deletions src/main/java/RunFlexporter.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
Expand All @@ -14,7 +11,6 @@
import java.nio.file.StandardOpenOption;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Map;
import java.util.Queue;

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

Mapping mapping = Mapping.parseMapping(mappingFile);
mapping.loadValueSets();

if (igDirectory != null) {
loadIG(igDirectory);
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/org/mitre/synthea/export/flexporter/Mapping.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package org.mitre.synthea.export.flexporter;

import com.fasterxml.jackson.core.JsonProcessingException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

import org.hl7.fhir.r4.model.ValueSet;
import org.mitre.synthea.helpers.RandomCodeGenerator;
import org.mitre.synthea.helpers.Utilities;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

Expand All @@ -15,6 +20,7 @@ public class Mapping {
public String applicability;

public Map<String, Object> variables;
public List<Map<String, Object>> customValueSets;

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

return yaml.loadAs(selectorInputSteam, Mapping.class);
}

/**
* Load the custom ValueSets that this mapping defines, so that the codes can be selected
* in RandomCodeGenerator.
*/
public void loadValueSets() {
try {
if (this.customValueSets != null) {
List<ValueSet> valueSets =
Utilities.parseYamlToResources(this.customValueSets, ValueSet.class);
valueSets.forEach(vs -> RandomCodeGenerator.loadValueSet(null, vs));
}
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
38 changes: 38 additions & 0 deletions src/main/java/org/mitre/synthea/helpers/Utilities.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package org.mitre.synthea.helpers;

import ca.uhn.fhir.parser.IParser;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import com.google.gson.FieldNamingPolicy;
Expand Down Expand Up @@ -36,9 +40,11 @@
import java.util.regex.Pattern;

import org.apache.commons.lang3.Range;
import org.hl7.fhir.r4.model.Resource;
import org.mitre.synthea.engine.Logic;
import org.mitre.synthea.engine.Module;
import org.mitre.synthea.engine.State;
import org.mitre.synthea.export.FhirR4;
import org.mitre.synthea.world.concepts.HealthRecord.Code;

public class Utilities {
Expand Down Expand Up @@ -669,4 +675,36 @@ public static void enableReadingURIFromJar(URI uri) throws IOException {
}
}
}

/**
* Helper method to parse FHIR resources from YAML.
* This is a workaround since the FHIR model classes don't work with our YAML parser.
*
* @param <T> Resource type contained in the YAML
* @param yaml List of pre-parsed YAML as Map&lt;String, Object&gt;
* @param resourceClass Specific resource class, must not be Resource
* @return List of parsed resources
* @throws JsonProcessingException (should never happen)
*/
public static <T extends Resource> List<T> parseYamlToResources(
List<Map<String, Object>> yaml, Class<T> resourceClass)
throws JsonProcessingException {
if (yaml.isEmpty()) {
return Collections.emptyList();
}
ObjectMapper jsonMapper = new ObjectMapper();
IParser jsonParser = FhirR4.getContext().newJsonParser();
List<T> results = new ArrayList<>();
for (Map<String, Object> singleYaml : yaml) {
if (!singleYaml.containsKey("resourceType")) {
// allows the YAML to be cleaner by letting the resourceType be implied
singleYaml.put("resourceType", resourceClass.getSimpleName());
}
String resourceJson = jsonMapper.writeValueAsString(singleYaml);
@SuppressWarnings("unchecked")
T resource = (T) jsonParser.parseResource(resourceJson);
results.add(resource);
}
return results;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public static void setupClass() throws FileNotFoundException {
File file = new File(classLoader.getResource("flexporter/test_mapping.yaml").getFile());

testMapping = Mapping.parseMapping(file);
testMapping.loadValueSets();
}

@AfterClass
Expand Down Expand Up @@ -825,21 +826,6 @@ public void testRandomCode() {
Bundle b = new Bundle();
b.setType(BundleType.COLLECTION);

ValueSet statusVs = constructValueSet(
"http://hl7.org/fhir/encounter-status",
"planned", "finished", "cancelled");
RandomCodeGenerator.loadValueSet("http://example.org/encounterStatus", statusVs);

ValueSet classVs = constructValueSet(
"http://terminology.hl7.org/CodeSystem/v3-ActCode",
"AMB", "EMER", "ACUTE");
RandomCodeGenerator.loadValueSet("http://example.org/encounterClass", classVs);

ValueSet typeVs = constructValueSet(
"http://terminology.hl7.org/CodeSystem/encounter-type",
"ADMS", "OKI");
RandomCodeGenerator.loadValueSet("http://example.org/encounterType", typeVs);

Map<String, Object> action = getActionByName("testRandomCode");
Actions.applyAction(b, action, null, null);

Expand All @@ -865,20 +851,4 @@ public void testRandomCode() {
code = typeCoding.getCode();
assertTrue(code.equals("ADMS") || code.equals("OKI"));
}

private ValueSet constructValueSet(String system, String... codes) {
ValueSet vs = new ValueSet();

// populates the codes so that they can be read in RandomCodeGenerator.loadValueSet
ConceptSetComponent csc = new ConceptSetComponent();
csc.setSystem(system);
for (String code : codes) {
csc.addConcept().setCode(code).setDisplay(code);
}

vs.getCompose().getInclude().add(csc);

return vs;
}

}
43 changes: 43 additions & 0 deletions src/test/resources/flexporter/test_mapping.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,49 @@ name: Random Testing
# for now the assumption is 1 file = 1 synthea patient bundle.
applicability: true

# Not a huge fan of this format, but it's better than defining yet another custom syntax
customValueSets:
- url: whats-for-dinner
compose:
include:
- system: http://snomed.info/sct
concept:
- code: 227360002
display: Pinto beans (substance)
- code: 227319009
display: Baked beans canned in tomato sauce with burgers (substance)
- url: http://example.org/encounterStatus
compose:
include:
- system: http://hl7.org/fhir/encounter-status
concept:
- code: planned
display: Planned
- code: finished
display: Finished
- code: cancelled
display: Cancelled
- url: http://example.org/encounterClass
compose:
include:
- system: http://terminology.hl7.org/CodeSystem/v3-ActCode
concept:
- code: AMB
display: ambulatory
- code: EMER
display: emergency
- code: ACUTE
display: inpatient acute
- url: http://example.org/encounterType
compose:
include:
- system: http://terminology.hl7.org/CodeSystem/encounter-type
concept:
- code: ADMS
display: Annual diabetes mellitus screening
- code: OKI
display: Outpatient Kenacort injection

actions:
- name: Apply Profiles
# v1: define specific profiles and an applicability statement on when to apply them
Expand Down

0 comments on commit 4926b46

Please sign in to comment.