Skip to content

Commit

Permalink
Merge pull request #1525 from synthetichealth/flexporter_updates_2024_10
Browse files Browse the repository at this point in the history
Flexporter updates - October 2024
  • Loading branch information
jawalonoski authored Oct 31, 2024
2 parents 9de0460 + 661207a commit e2ca05d
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 25 deletions.
58 changes: 41 additions & 17 deletions src/main/java/org/mitre/synthea/export/flexporter/Actions.java
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,12 @@ private static Map<String, Object> createFhirPathMapping(List<Map<String, Object
String location = (String)field.get("location");
Object valueDef = field.get("value");
String transform = (String)field.get("transform");
String ifDef = (String)field.get("if");

if (ifDef != null && !FhirPathUtils.appliesToResource(sourceResource, ifDef)) {
// The "if" condition returned falsy, so don't evaluate this value/set this field
continue;
}

if (valueDef == null) {
// do nothing, leave it null
Expand Down Expand Up @@ -457,12 +463,15 @@ private static Map<String, Object> createFhirPathMapping(List<Map<String, Object
} else if (valueDef instanceof Map<?,?>) {
Map<String,Object> valueMap = (Map<String, Object>) valueDef;

populateFhirPathMapping(fhirPathMapping, location, valueMap);
populateFhirPathMapping(fhirPathMapping, location, valueMap, sourceBundle, sourceResource,
person, fjContext);

} else if (valueDef instanceof List<?>) {
List<Object> valueList = (List<Object>) valueDef;

populateFhirPathMapping(fhirPathMapping, location, valueList);
populateFhirPathMapping(fhirPathMapping, location, valueList, sourceBundle, sourceResource,
person, fjContext);

} else {
// unexpected type here - is it even possible to get anything else?
String type = valueDef == null ? "null" : valueDef.getClass().toGenericString();
Expand All @@ -474,21 +483,32 @@ private static Map<String, Object> createFhirPathMapping(List<Map<String, Object
}

private static void populateFhirPathMapping(Map<String, Object> fhirPathMapping, String basePath,
Map<String, Object> valueMap) {
Map<String, Object> valueMap, Bundle sourceBundle, Resource sourceResource, Person person,
FlexporterJavascriptContext fjContext) {
for (Map.Entry<String,Object> entry : valueMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();

String path = basePath + "." + key;

if (value instanceof String) {
String valueString = (String)value;

if (valueString.startsWith("$")) {
value = getValue(sourceBundle, valueString, sourceResource, person, fjContext);
}
}

if (value instanceof String || value instanceof Base) {
fhirPathMapping.put(path, value);
} else if (value instanceof Number) {
fhirPathMapping.put(path, value.toString());
} else if (value instanceof Map<?,?>) {
populateFhirPathMapping(fhirPathMapping, path, (Map<String, Object>) value);
populateFhirPathMapping(fhirPathMapping, path, (Map<String, Object>) value, sourceBundle,
sourceResource, person, fjContext);
} else if (value instanceof List<?>) {
populateFhirPathMapping(fhirPathMapping, path, (List<Object>) value);
populateFhirPathMapping(fhirPathMapping, path, (List<Object>) value, sourceBundle,
sourceResource, person, fjContext);
} else if (value != null) {
System.err
.println("Unexpected class found in populateFhirPathMapping[map]: " + value.getClass());
Expand All @@ -497,20 +517,23 @@ private static void populateFhirPathMapping(Map<String, Object> fhirPathMapping,
}

private static void populateFhirPathMapping(Map<String, Object> fhirPathMapping, String basePath,
List<Object> valueList) {
List<Object> valueList, Bundle sourceBundle, Resource sourceResource, Person person,
FlexporterJavascriptContext fjContext) {
for (int i = 0; i < valueList.size(); i++) {
Object value = valueList.get(i);

String path = basePath + "[" + i + "]";

if (value instanceof String) {
if (value instanceof String || value instanceof Base) {
fhirPathMapping.put(path, value);
} else if (value instanceof Number) {
fhirPathMapping.put(path, value.toString());
} else if (value instanceof Map<?,?>) {
populateFhirPathMapping(fhirPathMapping, path, (Map<String, Object>) value);
populateFhirPathMapping(fhirPathMapping, path, (Map<String, Object>) value, sourceBundle,
sourceResource, person, fjContext);
} else if (value instanceof List<?>) {
populateFhirPathMapping(fhirPathMapping, path, (List<Object>) value);
populateFhirPathMapping(fhirPathMapping, path, (List<Object>) value, sourceBundle,
sourceResource, person, fjContext);
} else if (value != null) {
System.err
.println("Unexpected class found in populateFhirPathMapping[list]:" + value.getClass());
Expand Down Expand Up @@ -649,13 +672,9 @@ private static void dateFilter(Bundle bundle, String minDateStr, String maxDateS
* Cascade (current), Delete reference field but leave object, Do nothing
*
* @param bundle FHIR Bundle to filter
* @param list List of resource types to delete, other types not listed will be kept
* @param list List of resource types or FHIRPath to delete, other types not listed will be kept
*/
public static void deleteResources(Bundle bundle, List<String> list) {
// TODO: make this FHIRPath instead of just straight resource types

Set<String> resourceTypesToDelete = new HashSet<>(list);

Set<String> deletedResourceIDs = new HashSet<>();

Iterator<BundleEntryComponent> itr = bundle.getEntry().iterator();
Expand All @@ -665,9 +684,14 @@ public static void deleteResources(Bundle bundle, List<String> list) {

Resource resource = entry.getResource();
String resourceType = resource.getResourceType().toString();
if (resourceTypesToDelete.contains(resourceType)) {
deletedResourceIDs.add(resource.getId());
itr.remove();

for (String applicability : list) {
if (applicability.equals(resourceType)
|| FhirPathUtils.appliesToResource(resource, applicability)) {
deletedResourceIDs.add(resource.getId());
itr.remove();
break;
}
}
}

Expand Down
57 changes: 49 additions & 8 deletions src/test/java/org/mitre/synthea/export/flexporter/ActionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.hl7.fhir.r4.model.Claim;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.Encounter;
Expand All @@ -46,6 +47,7 @@
import org.hl7.fhir.r4.model.PractitionerRole;
import org.hl7.fhir.r4.model.PractitionerRole.DaysOfWeek;
import org.hl7.fhir.r4.model.Procedure;
import org.hl7.fhir.r4.model.Provenance;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.ResourceType;
Expand Down Expand Up @@ -486,21 +488,34 @@ public void testKeepResources() throws Exception {

@Test
public void testDeleteResources() throws Exception {
Bundle b = loadFixtureBundle("sample_complete_patient.json");
Bundle b = new Bundle();
b.addEntry().setResource(new Provenance());

Condition c1 = new Condition();
c1.setCode(new CodeableConcept(new Coding("http://snomed.info/sct", "15777000", "Prediabetes")));
b.addEntry().setResource(c1);

long countProvenance = b.getEntry().stream()
.filter(bec -> bec.getResource().getResourceType() == ResourceType.Provenance).count();
Condition c2 = new Condition();
c2.setCode(new CodeableConcept(new Coding("http://snomed.info/sct", "444814009", "Viral sinusitis")));
b.addEntry().setResource(c2);

Condition c3 = new Condition();
c3.setCode(new CodeableConcept(new Coding("http://snomed.info/sct", "49727002", "Cough (finding)")));
b.addEntry().setResource(c3);

// this is just making sure the fixture actually contains the thing we want to delete
assertEquals(1, countProvenance);
Map<String, Object> action = getActionByName("testDeleteResources");

Actions.applyAction(b, action, null, null);

countProvenance = b.getEntry().stream()
.filter(bec -> bec.getResource().getResourceType() == ResourceType.Provenance).count();
// provenance, c1, and c2 should be deleted by the rule. c3 should stay
assertEquals(1, b.getEntry().size());

assertEquals(0, countProvenance);
Resource r = b.getEntryFirstRep().getResource();
// this would work as of today but not guaranteed
// assertTrue(r == c3);
assertTrue(r instanceof Condition);
Condition conditionOut = (Condition)r;
assertEquals("49727002", conditionOut.getCode().getCodingFirstRep().getCode());
}

@Test
Expand Down Expand Up @@ -752,6 +767,32 @@ public void testCreateResources_createBasedOnState() throws Exception {
assertEquals(120_000L, mr.getAuthoredOn().toInstant().toEpochMilli());
}

@Test
public void testCreateResources_if() throws Exception {
Bundle b = new Bundle();
b.setType(BundleType.COLLECTION);

Procedure p1 = new Procedure();
p1.setPerformed(new Period().setStart(new Date(0)));
b.addEntry().setResource(p1);

Procedure p2 = new Procedure();
p2.setPerformed(new DateTimeType("2024-10-01T12:34:56"));
b.addEntry().setResource(p2);

Map<String, Object> action = getActionByName("testCreateResources_if");

Actions.applyAction(b, action, null, null);

assertEquals(4, b.getEntry().size());

ServiceRequest sr1 = (ServiceRequest)b.getEntry().get(2).getResource();
assertTrue(sr1.getAuthoredOn().getTime() == 0);

ServiceRequest sr2 = (ServiceRequest)b.getEntry().get(3).getResource();
DateTimeType sr2AuthoredOn = sr2.getAuthoredOnElement();
assertEquals("2024-10-01T12:34:56", sr2AuthoredOn.getValueAsString());
}

@Test
public void testGetAttribute() throws Exception {
Expand Down
15 changes: 15 additions & 0 deletions src/test/resources/flexporter/test_mapping.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,20 @@ actions:
- location: Patient.name.family
value: $getAttribute([last_name])

- name: testCreateResources_if
create_resource:
- resourceType: ServiceRequest
based_on:
resource: Procedure
fields:
- if: Procedure.performed.ofType(Period)
location: ServiceRequest.authoredOn
value: $getField([Procedure.performed.start]) # period choice type
- if: Procedure.performed.ofType(dateTime)
location: ServiceRequest.authoredOn
value: $getField([Procedure.performed]) # datetime choice type


- name: testExecuteScript
execute_script:
- apply_to: bundle
Expand Down Expand Up @@ -300,6 +314,7 @@ actions:
- name: testDeleteResources
delete_resources:
- Provenance
- Condition.code.coding.where($this.code in ('15777000' | '444814009'))

- name: testDeleteResourcesCascade
delete_resources:
Expand Down

0 comments on commit e2ca05d

Please sign in to comment.