From 0f5505508bde94927cd72cfc528def837c5d5e4d Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 17 Oct 2024 11:33:56 -0400 Subject: [PATCH 1/4] support for 'if' for conditionally mapping single fields --- .../synthea/export/flexporter/Actions.java | 6 +++++ .../export/flexporter/ActionsTest.java | 26 +++++++++++++++++++ .../resources/flexporter/test_mapping.yaml | 14 ++++++++++ 3 files changed, 46 insertions(+) diff --git a/src/main/java/org/mitre/synthea/export/flexporter/Actions.java b/src/main/java/org/mitre/synthea/export/flexporter/Actions.java index 7cdba247c9..a1df0eec7c 100644 --- a/src/main/java/org/mitre/synthea/export/flexporter/Actions.java +++ b/src/main/java/org/mitre/synthea/export/flexporter/Actions.java @@ -424,6 +424,12 @@ private static Map createFhirPathMapping(List 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 { diff --git a/src/test/resources/flexporter/test_mapping.yaml b/src/test/resources/flexporter/test_mapping.yaml index 470de64a5e..f1755de4eb 100644 --- a/src/test/resources/flexporter/test_mapping.yaml +++ b/src/test/resources/flexporter/test_mapping.yaml @@ -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 From 8f4024739644b364c6acde8f06913ab5fc5eae7b Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Tue, 29 Oct 2024 13:01:55 -0400 Subject: [PATCH 2/4] delete resources by fhirpath --- .../synthea/export/flexporter/Actions.java | 17 ++++----- .../export/flexporter/ActionsTest.java | 35 +++++++++++++------ .../resources/flexporter/test_mapping.yaml | 1 + 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/mitre/synthea/export/flexporter/Actions.java b/src/main/java/org/mitre/synthea/export/flexporter/Actions.java index a1df0eec7c..3773dc4946 100644 --- a/src/main/java/org/mitre/synthea/export/flexporter/Actions.java +++ b/src/main/java/org/mitre/synthea/export/flexporter/Actions.java @@ -655,13 +655,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 list) { - // TODO: make this FHIRPath instead of just straight resource types - - Set resourceTypesToDelete = new HashSet<>(list); - Set deletedResourceIDs = new HashSet<>(); Iterator itr = bundle.getEntry().iterator(); @@ -671,9 +667,14 @@ public static void deleteResources(Bundle bundle, List 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; + } } } diff --git a/src/test/java/org/mitre/synthea/export/flexporter/ActionsTest.java b/src/test/java/org/mitre/synthea/export/flexporter/ActionsTest.java index 4c71843872..ccef56e006 100644 --- a/src/test/java/org/mitre/synthea/export/flexporter/ActionsTest.java +++ b/src/test/java/org/mitre/synthea/export/flexporter/ActionsTest.java @@ -5,8 +5,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import ca.uhn.fhir.parser.IParser; - import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -30,6 +28,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; @@ -46,6 +45,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; @@ -60,6 +60,8 @@ import org.mitre.synthea.export.FhirR4; import org.mitre.synthea.world.agents.Person; +import ca.uhn.fhir.parser.IParser; + public class ActionsTest { private static Map buildApplyProfileAction(String profile, String applicability) { @@ -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); + + Condition c2 = new Condition(); + c2.setCode(new CodeableConcept(new Coding("http://snomed.info/sct", "444814009", "Viral sinusitis"))); + b.addEntry().setResource(c2); - long countProvenance = b.getEntry().stream() - .filter(bec -> bec.getResource().getResourceType() == ResourceType.Provenance).count(); + 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 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 cOut = (Condition)r; + assertEquals("49727002", cOut.getCode().getCodingFirstRep().getCode()); } @Test diff --git a/src/test/resources/flexporter/test_mapping.yaml b/src/test/resources/flexporter/test_mapping.yaml index f1755de4eb..3f1f292ec8 100644 --- a/src/test/resources/flexporter/test_mapping.yaml +++ b/src/test/resources/flexporter/test_mapping.yaml @@ -314,6 +314,7 @@ actions: - name: testDeleteResources delete_resources: - Provenance + - Condition.code.coding.where($this.code in ('15777000' | '444814009')) - name: testDeleteResourcesCascade delete_resources: From 814bdd202d2da9c52bf4a7f55053ec4338803851 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Tue, 29 Oct 2024 13:02:57 -0400 Subject: [PATCH 3/4] enable functions nested --- .../synthea/export/flexporter/Actions.java | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/mitre/synthea/export/flexporter/Actions.java b/src/main/java/org/mitre/synthea/export/flexporter/Actions.java index 3773dc4946..1977350b98 100644 --- a/src/main/java/org/mitre/synthea/export/flexporter/Actions.java +++ b/src/main/java/org/mitre/synthea/export/flexporter/Actions.java @@ -463,12 +463,15 @@ private static Map createFhirPathMapping(List) { Map valueMap = (Map) valueDef; - populateFhirPathMapping(fhirPathMapping, location, valueMap); + populateFhirPathMapping(fhirPathMapping, location, valueMap, sourceBundle, sourceResource, + person, fjContext); } else if (valueDef instanceof List) { List valueList = (List) 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(); @@ -480,7 +483,8 @@ private static Map createFhirPathMapping(List fhirPathMapping, String basePath, - Map valueMap) { + Map valueMap, Bundle sourceBundle, Resource sourceResource, Person person, + FlexporterJavascriptContext fjContext) { for (Map.Entry entry : valueMap.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); @@ -488,13 +492,23 @@ private static void populateFhirPathMapping(Map fhirPathMapping, 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) value); + populateFhirPathMapping(fhirPathMapping, path, (Map) value, sourceBundle, + sourceResource, person, fjContext); } else if (value instanceof List) { - populateFhirPathMapping(fhirPathMapping, path, (List) value); + populateFhirPathMapping(fhirPathMapping, path, (List) value, sourceBundle, + sourceResource, person, fjContext); } else if (value != null) { System.err .println("Unexpected class found in populateFhirPathMapping[map]: " + value.getClass()); @@ -503,20 +517,23 @@ private static void populateFhirPathMapping(Map fhirPathMapping, } private static void populateFhirPathMapping(Map fhirPathMapping, String basePath, - List valueList) { + List 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) value); + populateFhirPathMapping(fhirPathMapping, path, (Map) value, sourceBundle, + sourceResource, person, fjContext); } else if (value instanceof List) { - populateFhirPathMapping(fhirPathMapping, path, (List) value); + populateFhirPathMapping(fhirPathMapping, path, (List) value, sourceBundle, + sourceResource, person, fjContext); } else if (value != null) { System.err .println("Unexpected class found in populateFhirPathMapping[list]:" + value.getClass()); From 661207af4a86063c33ab5841ecff6bcedc43c169 Mon Sep 17 00:00:00 2001 From: Dylan Hall Date: Thu, 31 Oct 2024 09:11:32 -0400 Subject: [PATCH 4/4] fix checkstyle issues --- .../org/mitre/synthea/export/flexporter/ActionsTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/mitre/synthea/export/flexporter/ActionsTest.java b/src/test/java/org/mitre/synthea/export/flexporter/ActionsTest.java index ccef56e006..951abcd1cc 100644 --- a/src/test/java/org/mitre/synthea/export/flexporter/ActionsTest.java +++ b/src/test/java/org/mitre/synthea/export/flexporter/ActionsTest.java @@ -5,6 +5,8 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import ca.uhn.fhir.parser.IParser; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -60,8 +62,6 @@ import org.mitre.synthea.export.FhirR4; import org.mitre.synthea.world.agents.Person; -import ca.uhn.fhir.parser.IParser; - public class ActionsTest { private static Map buildApplyProfileAction(String profile, String applicability) { @@ -514,8 +514,8 @@ public void testDeleteResources() throws Exception { // this would work as of today but not guaranteed // assertTrue(r == c3); assertTrue(r instanceof Condition); - Condition cOut = (Condition)r; - assertEquals("49727002", cOut.getCode().getCodingFirstRep().getCode()); + Condition conditionOut = (Condition)r; + assertEquals("49727002", conditionOut.getCode().getCodingFirstRep().getCode()); } @Test