From a2310ea4487178d6145025453beee16886acdf5d Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Thu, 7 Sep 2023 16:41:59 +1200 Subject: [PATCH 1/9] Added ignoreEmpty attribute to message templates; It allows us to sometimes ignore weird empty HL7 Segments Signed-off-by: Stuart McGrigor --- .../linuxforhealth/api/FHIRResourceTemplate.java | 8 ++++++-- .../core/message/AbstractFHIRResourceTemplate.java | 12 ++++++++++-- .../hl7/message/HL7FHIRResourceTemplate.java | 4 ++++ .../message/HL7FHIRResourceTemplateAttributes.java | 13 +++++++++++-- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/github/linuxforhealth/api/FHIRResourceTemplate.java b/src/main/java/io/github/linuxforhealth/api/FHIRResourceTemplate.java index 2c705d43..0902e73d 100644 --- a/src/main/java/io/github/linuxforhealth/api/FHIRResourceTemplate.java +++ b/src/main/java/io/github/linuxforhealth/api/FHIRResourceTemplate.java @@ -49,6 +49,10 @@ public interface FHIRResourceTemplate { */ boolean isReferenced(); - - + /** + * If this resource is to ignore empty source segments + * + * @return True/False + */ + boolean ignoreEmpty(); } diff --git a/src/main/java/io/github/linuxforhealth/core/message/AbstractFHIRResourceTemplate.java b/src/main/java/io/github/linuxforhealth/core/message/AbstractFHIRResourceTemplate.java index f46a089c..8a1287bf 100644 --- a/src/main/java/io/github/linuxforhealth/core/message/AbstractFHIRResourceTemplate.java +++ b/src/main/java/io/github/linuxforhealth/core/message/AbstractFHIRResourceTemplate.java @@ -17,22 +17,25 @@ public abstract class AbstractFHIRResourceTemplate implements FHIRResourceTempla private boolean repeats; private String resourcePath; private boolean isReferenced; + private boolean ignoreEmpty; @JsonCreator public AbstractFHIRResourceTemplate(@JsonProperty("resourceName") String resourceName, @JsonProperty("resourcePath") String resourcePath, @JsonProperty("isReferenced") boolean isReferenced, - @JsonProperty("repeats") boolean repeats) { + @JsonProperty("repeats") boolean repeats, + @JsonProperty("ignoreEmpty") boolean ignoreEmpty) { this.resourceName = resourceName; this.resourcePath = resourcePath; this.repeats = repeats; this.isReferenced = isReferenced; + this.ignoreEmpty = ignoreEmpty; } public AbstractFHIRResourceTemplate(String resourceName, String resourcePath) { - this(resourceName, resourcePath, false, false); + this(resourceName, resourcePath, false, false, false); } @Override @@ -64,6 +67,11 @@ public boolean isReferenced() { return isReferenced; } + @Override + public boolean ignoreEmpty() { + return ignoreEmpty; + } + diff --git a/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplate.java b/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplate.java index 8315ce01..6d85c9b9 100644 --- a/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplate.java +++ b/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplate.java @@ -52,6 +52,10 @@ public boolean isReferenced() { return this.attributes.isReferenced(); } + @Override + public boolean ignoreEmpty() { + return this.attributes.ignoreEmpty(); + } } diff --git a/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplateAttributes.java b/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplateAttributes.java index d3031dce..5c665478 100644 --- a/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplateAttributes.java +++ b/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplateAttributes.java @@ -20,13 +20,13 @@ public class HL7FHIRResourceTemplateAttributes { private boolean repeats; private String resourcePath; private boolean isReferenced; + private boolean ignoreEmpty; private HL7Segment segment;// primary segment private List additionalSegments; private ResourceModel resource; private List group; - public HL7FHIRResourceTemplateAttributes(Builder builder) { Preconditions.checkArgument(StringUtils.isNotBlank(builder.resourceName), "resourceName cannot be null"); @@ -35,6 +35,7 @@ public HL7FHIRResourceTemplateAttributes(Builder builder) { this.resourcePath = builder.resourcePath; this.repeats = builder.repeats; this.isReferenced = builder.isReferenced; + this.ignoreEmpty = builder.ignoreEmpty; additionalSegments = new ArrayList<>(); builder.rawAdditionalSegments .forEach(e -> additionalSegments.add(HL7Segment.parse(e, builder.group))); @@ -85,7 +86,9 @@ public boolean isReferenced() { return isReferenced; } - + public boolean ignoreEmpty() { + return ignoreEmpty; + } private static ResourceModel generateResourceModel(String resourcePath) { return ResourceReader.getInstance().generateResourceModel(resourcePath); @@ -102,6 +105,7 @@ public static class Builder { private String resourcePath; private String group; private boolean isReferenced; + private boolean ignoreEmpty; private boolean repeats; private ResourceModel resourceModel; @@ -156,6 +160,11 @@ public Builder withIsReferenced(boolean isReferenced) { return this; } + public Builder withignoreEmpty(boolean ignoreEmpty) { + this.ignoreEmpty = ignoreEmpty; + return this; + } + public Builder withResourceModel(ResourceModel resourceModel) { this.resourceModel = resourceModel; return this; From 1defc9fe76b55049af370b9f95bad8239db3fb7e Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Thu, 7 Sep 2023 17:06:35 +1200 Subject: [PATCH 2/9] Added ADT^A10 as a test message; ADT^A09 gains ability to ignore empty AL1 and ZAL segments; just for testing Signed-off-by: Stuart McGrigor --- .../hl7/message/ADT_A09.yml | 16 ++++++ .../hl7/message/ADT_A10.yml | 57 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/test/resources/additional_resources/hl7/message/ADT_A10.yml diff --git a/src/test/resources/additional_resources/hl7/message/ADT_A09.yml b/src/test/resources/additional_resources/hl7/message/ADT_A09.yml index 282a9361..88f6b489 100644 --- a/src/test/resources/additional_resources/hl7/message/ADT_A09.yml +++ b/src/test/resources/additional_resources/hl7/message/ADT_A09.yml @@ -39,3 +39,19 @@ resources: - EVN - MSH - DG1 + + - resourceName: AllergyIntolerance + segment: AL1 + resourcePath: resource/AllergyIntolerance + repeats: true + ignoreEmpty: true ## We want to test our new ignoreEmpty flag + additionalSegments: + - MSH + + - resourceName: AllergyIntolerance + segment: ZAL + resourcePath: resource/AllergyIntolerance + repeats: true + ignoreEmpty: true ## We want to test our new ignoreEmpty flag on 'Z' segments + additionalSegments: + - MSH diff --git a/src/test/resources/additional_resources/hl7/message/ADT_A10.yml b/src/test/resources/additional_resources/hl7/message/ADT_A10.yml new file mode 100644 index 00000000..b8c90cca --- /dev/null +++ b/src/test/resources/additional_resources/hl7/message/ADT_A10.yml @@ -0,0 +1,57 @@ +# +# (C) Te Whatu Ora, Health New Zealand, 2023 +# +# SPDX-License-Identifier: Apache-2.0 +# +# FHIR Resources to extract from (pretend) ADT_A10 message +# + +######################################################################## +# Used for testing only. Not used in production. +######################################################################## + +--- +resources: + - resourceName: MessageHeader + segment: MSH + resourcePath: resource/MessageHeader + repeats: false + isReferenced: false + additionalSegments: + - EVN + + - resourceName: Patient + segment: PID + resourcePath: resource/customPatient + repeats: false + isReferenced: true + additionalSegments: + - PD1 + - MSH + + - resourceName: Encounter + segment: PV1 + resourcePath: resource/Encounter + repeats: false + isReferenced: true + additionalSegments: + - PV2 + - EVN + - MSH + - DG1 + + - resourceName: AllergyIntolerance + segment: AL1 + resourcePath: resource/AllergyIntolerance + repeats: true + ignoreEmpty: false ## We want to test our new ignoreEmpty flag + additionalSegments: + - MSH + + - resourceName: AllergyIntolerance + segment: ZAL + resourcePath: resource/AllergyIntolerance + repeats: true + ignoreEmpty: false ## We want to test our new ignoreEmpty flag on 'Z' segments + additionalSegments: + - MSH From 68877538bb3d179f66cb4c5127d590819186bb9b Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Thu, 7 Sep 2023 17:08:35 +1200 Subject: [PATCH 3/9] Action the new ignoreEmpty FHIR Resource Attribute; if a segment is marked as ignoreEmpty and is actually empty then don't attempt to make a matching Resource Signed-off-by: Stuart McGrigor --- .../hl7/message/HL7MessageEngine.java | 28 +++++---- .../hl7/message/Hl7CustomMessageTest.java | 58 +++++++++++++++++-- 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/github/linuxforhealth/hl7/message/HL7MessageEngine.java b/src/main/java/io/github/linuxforhealth/hl7/message/HL7MessageEngine.java index f83e1bcd..7c5add36 100644 --- a/src/main/java/io/github/linuxforhealth/hl7/message/HL7MessageEngine.java +++ b/src/main/java/io/github/linuxforhealth/hl7/message/HL7MessageEngine.java @@ -27,7 +27,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; +import ca.uhn.hl7v2.HL7Exception; import ca.uhn.hl7v2.model.Structure; +import ca.uhn.hl7v2.model.Visitable; import io.github.linuxforhealth.api.EvaluationResult; import io.github.linuxforhealth.api.FHIRResourceTemplate; import io.github.linuxforhealth.api.InputDataExtractor; @@ -189,7 +191,7 @@ private List generateResources(HL7MessageData hl7DataInput, if (!multipleSegments.isEmpty()) { resourceResults = generateMultipleResources(hl7DataInput, resourceModel, contextValues, - multipleSegments, template.isGenerateMultiple()); + multipleSegments, template.isGenerateMultiple(), template.ignoreEmpty()); } return resourceResults; } @@ -284,7 +286,7 @@ private static List getMultipleSegments(final HL7MessageData hl7Da private static List generateMultipleResources(final HL7MessageData hl7DataInput, final ResourceModel rs, final Map contextValues, - final List multipleSegments, boolean generateMultiple) { + final List multipleSegments, boolean generateMultiple, boolean ignoreEmpty) { List resourceResults = new ArrayList<>(); for (SegmentGroup currentGroup : multipleSegments) { @@ -300,16 +302,22 @@ private static List generateMultipleResources(final HL7MessageDa for (EvaluationResult baseValue : baseValues) { try { - ResourceResult result = rs.evaluate(hl7DataInput, ImmutableMap.copyOf(localContextValues), - baseValue); - if (result != null && result.getValue() != null) { - resourceResults.add(result); - if (!generateMultiple) { - // If only single resource needs to be generated then return. - return resourceResults; + // We might need to check if the baseValue is empty + Visitable vs = baseValue.getValue(); + if((vs != null && ! vs.isEmpty()) || ! ignoreEmpty) { + + // baseValue is either not empty or we're ignoring empty segments + ResourceResult result = rs.evaluate(hl7DataInput, ImmutableMap.copyOf(localContextValues), + baseValue); + if (result != null && result.getValue() != null) { + resourceResults.add(result); + if (!generateMultiple) { + // If only single resource needs to be generated then return. + return resourceResults; + } } } - } catch (RequiredConstraintFailureException | IllegalArgumentException + } catch (RequiredConstraintFailureException | IllegalArgumentException | HL7Exception | IllegalStateException e) { LOGGER.warn("generateMultipleResources - Exception encountered"); LOGGER.debug("generateMultipleResources - Exception encountered", e); diff --git a/src/test/java/io/github/linuxforhealth/hl7/message/Hl7CustomMessageTest.java b/src/test/java/io/github/linuxforhealth/hl7/message/Hl7CustomMessageTest.java index 6b8b2c0e..964c1dd5 100644 --- a/src/test/java/io/github/linuxforhealth/hl7/message/Hl7CustomMessageTest.java +++ b/src/test/java/io/github/linuxforhealth/hl7/message/Hl7CustomMessageTest.java @@ -15,6 +15,7 @@ import java.util.Properties; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.AllergyIntolerance; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Resource; @@ -24,6 +25,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import io.github.linuxforhealth.core.config.ConverterConfiguration; import io.github.linuxforhealth.fhir.FHIRContext; @@ -82,7 +85,7 @@ static void reloadPreviousConfigurations() { void testCustomPatMessage() throws IOException { // Set up the config file - commonConfigFileSetup(); + commonConfigFileSetup(true); String hl7message = "MSH|^~\\&|||||20211005105125||CUSTOM^PAT|1a3952f1-38fe-4d55-95c6-ce58ebfc7f10|P|2.6\n" + "PID|1|100009^^^FAC^MR|100009^^^FAC^MR||DOE^JANE||195001010000|M|||||5734421788|||U\n" @@ -107,13 +110,60 @@ void testCustomPatMessage() throws IOException { assertThat(e.size()).isEqualTo(5); } - private static void commonConfigFileSetup() throws IOException { + @ParameterizedTest + @ValueSource(strings = { "ADT^A09", "ADT^A10" }) // ADT_A09 has ignoreEmpty = true; ADT_A10 doesn't + void testIgnoreEmptySegment(String messageType) throws IOException { + + // ADT^A09 can ignore empty AL1 and ZAL segments; ADT^A01 can ignore empty AL1, and always ignores ZAL segments + int alCount = messageType.equals("ADT^A09") ? 0 : 2; + + // Set up the config file + commonConfigFileSetup(false); + + // An empty AL1 Segment... + String hl7message = "MSH|^~\\&|TestSystem||TestTransformationAgent||20150502090000||" + messageType + "|controlID|P|2.6\r" + + "EVN|A01|20150502090000|\r" + + "PID|||1234^^^^MR||DOE^JANE^|||F||||||||||||||||||||||\r" + + "PV1||I||||||||SUR||||||||S|VisitNumber^^^ACME|A||||||||||||||||||||||||20150502090000|\r" + + "AL1|\r" + + "ZAL|\r"; + + List e = getBundleEntryFromHL7Message(hl7message); + + List patientResource = ResourceUtils.getResourceList(e, ResourceType.Patient); + assertThat(patientResource).hasSize(1); // from PID + + List encounterResource = ResourceUtils.getResourceList(e, ResourceType.Encounter); + assertThat(encounterResource).hasSize(1); // from EVN, PV1 + + List allergyIntoleranceResource = ResourceUtils.getResourceList(e, ResourceType.AllergyIntolerance); + assertThat(allergyIntoleranceResource).hasSize(alCount); // empty AL1 and ZAL Segments + + // Confirm that there are no extra resources + assertThat(e).hasSize(2 + alCount); + + // We might be done + if(alCount == 0) + return; + + // Let's take a peek at the 'empty' AL1 Segment + assertThat(allergyIntoleranceResource).allSatisfy(rs -> { + + AllergyIntolerance ai = (AllergyIntolerance) rs; + assert(ai.hasId()); + assert(ai.hasClinicalStatus()); + assert(ai.hasVerificationStatus()); + }); + } + + private static void commonConfigFileSetup(boolean customResources) throws IOException { File configFile = new File(folder, "config.properties"); Properties prop = new Properties(); prop.put("base.path.resource", "src/main/resources"); - prop.put("supported.hl7.messages", "*"); // Must use wild card so the custom resources are found. + prop.put("supported.hl7.messages", customResources ? "*" : "ADT_A09, ADT_A10"); // Must use wild card so the custom resources are found. prop.put("default.zoneid", "+08:00"); - prop.put("additional.resources.location", "src/test/resources/additional_custom_resources"); // Location of custom resources + // Location of custom (or merely additional) resources + prop.put("additional.resources.location", customResources ? "src/test/resources/additional_custom_resources" : "src/test/resources/additional_resources"); prop.store(new FileOutputStream(configFile), null); System.setProperty(CONF_PROP_HOME, configFile.getParent()); } From f8c29aabfde43a4e78dbff029099ce1ed38d4354 Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Thu, 7 Sep 2023 17:19:43 +1200 Subject: [PATCH 4/9] Tidied up comments Signed-off-by: Stuart McGrigor --- .../github/linuxforhealth/hl7/message/HL7MessageEngine.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/github/linuxforhealth/hl7/message/HL7MessageEngine.java b/src/main/java/io/github/linuxforhealth/hl7/message/HL7MessageEngine.java index 7c5add36..9938bc68 100644 --- a/src/main/java/io/github/linuxforhealth/hl7/message/HL7MessageEngine.java +++ b/src/main/java/io/github/linuxforhealth/hl7/message/HL7MessageEngine.java @@ -306,9 +306,11 @@ private static List generateMultipleResources(final HL7MessageDa Visitable vs = baseValue.getValue(); if((vs != null && ! vs.isEmpty()) || ! ignoreEmpty) { - // baseValue is either not empty or we're ignoring empty segments + // baseValue is either not empty or we're not allowed to ignore empty segments ResourceResult result = rs.evaluate(hl7DataInput, ImmutableMap.copyOf(localContextValues), baseValue); + + // We can't rely on empty segment giving us an empty resource; as common templates populate Resource.meta fields if (result != null && result.getValue() != null) { resourceResults.add(result); if (!generateMultiple) { From 6e35e8a0c86f8ffa3aec19a3526223793b06abec Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Thu, 7 Sep 2023 17:40:37 +1200 Subject: [PATCH 5/9] Added documentation on ignoreEntry flag on Resource Templates Signed-off-by: Stuart McGrigor --- TEMPLATING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TEMPLATING.md b/TEMPLATING.md index 5405fb48..b726fad7 100644 --- a/TEMPLATING.md +++ b/TEMPLATING.md @@ -14,6 +14,7 @@ A HL7 message template maps one or more HL7 segments to a FHIR resource using th resourcePath: [REQUIRED] repeats: [DEFAULT false] isReferenced: [DEFAULT false] + ignoreEmpty: [DEFAULT false] additionalSegments: [DEFAULT empty] ``` @@ -24,6 +25,7 @@ A HL7 message template maps one or more HL7 segments to a FHIR resource using th | resourcePath | Required | Relative path to the resource template. Example: resource/Patient | | repeats | Default: false | Indicates if a repeating HL7 segment will generate multiple FHIR resources. | | isReferenced | Default: false | Indicates if the FHIR Resource is referenced by other FHIR resources. | +| ignoreEmpty | Default: false | Indicates if an empty HL7 segment will NOT generate the matching (almost empty) FHIR resource | | group | Default: empty | Base group from which the segment and additionalSegments are specified. | additionalSegments | Default: empty | List of additional HL7 segment names required to complete the FHIR resource mapping. | @@ -61,6 +63,7 @@ resources: segment: AL1 resourcePath: resource/AllergyIntolerance repeats: true + ignoreEmpty: true ## Sometimes AL1 segments arrive empty additionalSegments: From d32153a2695a144421290ce8dadb2ba4ff75acc0 Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Fri, 8 Sep 2023 09:40:45 +1200 Subject: [PATCH 6/9] Fixup - Test A09 message needs the right Patient Resource Signed-off-by: Stuart McGrigor --- src/test/resources/additional_resources/hl7/message/ADT_A10.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/additional_resources/hl7/message/ADT_A10.yml b/src/test/resources/additional_resources/hl7/message/ADT_A10.yml index b8c90cca..7c9db1d3 100644 --- a/src/test/resources/additional_resources/hl7/message/ADT_A10.yml +++ b/src/test/resources/additional_resources/hl7/message/ADT_A10.yml @@ -22,7 +22,7 @@ resources: - resourceName: Patient segment: PID - resourcePath: resource/customPatient + resourcePath: resource/Patient repeats: false isReferenced: true additionalSegments: From 74e6fb28db50cd561b0b4139e1faf51f74108e47 Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Tue, 12 Sep 2023 12:08:07 +1200 Subject: [PATCH 7/9] Move IgnoreEmpty tests out of CustomMessageTest to their own file Signed-off-by: Stuart McGrigor --- .../hl7/message/Hl7CustomMessageTest.java | 58 +------ .../hl7/message/Hl7IgnoreEmptyTest.java | 155 ++++++++++++++++++ 2 files changed, 159 insertions(+), 54 deletions(-) create mode 100644 src/test/java/io/github/linuxforhealth/hl7/message/Hl7IgnoreEmptyTest.java diff --git a/src/test/java/io/github/linuxforhealth/hl7/message/Hl7CustomMessageTest.java b/src/test/java/io/github/linuxforhealth/hl7/message/Hl7CustomMessageTest.java index 964c1dd5..2d36d9c8 100644 --- a/src/test/java/io/github/linuxforhealth/hl7/message/Hl7CustomMessageTest.java +++ b/src/test/java/io/github/linuxforhealth/hl7/message/Hl7CustomMessageTest.java @@ -15,7 +15,6 @@ import java.util.Properties; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.AllergyIntolerance; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Resource; @@ -25,8 +24,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import io.github.linuxforhealth.core.config.ConverterConfiguration; import io.github.linuxforhealth.fhir.FHIRContext; @@ -84,8 +81,7 @@ static void reloadPreviousConfigurations() { @Test void testCustomPatMessage() throws IOException { - // Set up the config file - commonConfigFileSetup(true); + commonConfigFileSetup(); String hl7message = "MSH|^~\\&|||||20211005105125||CUSTOM^PAT|1a3952f1-38fe-4d55-95c6-ce58ebfc7f10|P|2.6\n" + "PID|1|100009^^^FAC^MR|100009^^^FAC^MR||DOE^JANE||195001010000|M|||||5734421788|||U\n" @@ -110,60 +106,14 @@ void testCustomPatMessage() throws IOException { assertThat(e.size()).isEqualTo(5); } - @ParameterizedTest - @ValueSource(strings = { "ADT^A09", "ADT^A10" }) // ADT_A09 has ignoreEmpty = true; ADT_A10 doesn't - void testIgnoreEmptySegment(String messageType) throws IOException { - - // ADT^A09 can ignore empty AL1 and ZAL segments; ADT^A01 can ignore empty AL1, and always ignores ZAL segments - int alCount = messageType.equals("ADT^A09") ? 0 : 2; - - // Set up the config file - commonConfigFileSetup(false); - - // An empty AL1 Segment... - String hl7message = "MSH|^~\\&|TestSystem||TestTransformationAgent||20150502090000||" + messageType + "|controlID|P|2.6\r" - + "EVN|A01|20150502090000|\r" - + "PID|||1234^^^^MR||DOE^JANE^|||F||||||||||||||||||||||\r" - + "PV1||I||||||||SUR||||||||S|VisitNumber^^^ACME|A||||||||||||||||||||||||20150502090000|\r" - + "AL1|\r" - + "ZAL|\r"; - - List e = getBundleEntryFromHL7Message(hl7message); - - List patientResource = ResourceUtils.getResourceList(e, ResourceType.Patient); - assertThat(patientResource).hasSize(1); // from PID - - List encounterResource = ResourceUtils.getResourceList(e, ResourceType.Encounter); - assertThat(encounterResource).hasSize(1); // from EVN, PV1 - - List allergyIntoleranceResource = ResourceUtils.getResourceList(e, ResourceType.AllergyIntolerance); - assertThat(allergyIntoleranceResource).hasSize(alCount); // empty AL1 and ZAL Segments - - // Confirm that there are no extra resources - assertThat(e).hasSize(2 + alCount); - - // We might be done - if(alCount == 0) - return; - - // Let's take a peek at the 'empty' AL1 Segment - assertThat(allergyIntoleranceResource).allSatisfy(rs -> { - - AllergyIntolerance ai = (AllergyIntolerance) rs; - assert(ai.hasId()); - assert(ai.hasClinicalStatus()); - assert(ai.hasVerificationStatus()); - }); - } - - private static void commonConfigFileSetup(boolean customResources) throws IOException { + private static void commonConfigFileSetup() throws IOException { File configFile = new File(folder, "config.properties"); Properties prop = new Properties(); prop.put("base.path.resource", "src/main/resources"); - prop.put("supported.hl7.messages", customResources ? "*" : "ADT_A09, ADT_A10"); // Must use wild card so the custom resources are found. + prop.put("supported.hl7.messages", "*"); // Must use wild card so the custom resources are found. prop.put("default.zoneid", "+08:00"); // Location of custom (or merely additional) resources - prop.put("additional.resources.location", customResources ? "src/test/resources/additional_custom_resources" : "src/test/resources/additional_resources"); + prop.put("additional.resources.location", "src/test/resources/additional_custom_resources"); prop.store(new FileOutputStream(configFile), null); System.setProperty(CONF_PROP_HOME, configFile.getParent()); } diff --git a/src/test/java/io/github/linuxforhealth/hl7/message/Hl7IgnoreEmptyTest.java b/src/test/java/io/github/linuxforhealth/hl7/message/Hl7IgnoreEmptyTest.java new file mode 100644 index 00000000..40f86631 --- /dev/null +++ b/src/test/java/io/github/linuxforhealth/hl7/message/Hl7IgnoreEmptyTest.java @@ -0,0 +1,155 @@ +/* + * (c) Te Whatu Ora, Health New Zealand, 2023 + * + * SPDX-License-Identifier: Apache-2.0 + * + * @author Stuart McGrigor + */ + +package io.github.linuxforhealth.hl7.message; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Properties; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.AllergyIntolerance; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import io.github.linuxforhealth.core.config.ConverterConfiguration; +import io.github.linuxforhealth.fhir.FHIRContext; +import io.github.linuxforhealth.hl7.ConverterOptions; +import io.github.linuxforhealth.hl7.ConverterOptions.Builder; +import io.github.linuxforhealth.hl7.HL7ToFHIRConverter; +import io.github.linuxforhealth.hl7.resource.ResourceReader; +import io.github.linuxforhealth.hl7.segments.util.ResourceUtils; + +// This class uses the ability to create ADDITIONAL HL7 messages to convert weird HL7 messages +// that exercise the new ignoreEmpty Resource Template functionality +// +// In these tests, the additional message definitions for (entirely ficticious) ADT^A09 and ^A10 messages +// are placed in src/test/resources/additional_resources/hl7/message/ADT_A09.yml and ADT_A10.yml + +class Hl7IgnoreEmptyTest { + + // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + // NOTE VALIDATION IS INTENTIONALLY NOT USED BECAUSE WE ARE CREATING RESOURCES THAT ARE NOT STANDARD + private static final ConverterOptions OPTIONS = new Builder().withPrettyPrint().build(); + // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + private static final String CONF_PROP_HOME = "hl7converter.config.home"; + + @TempDir + static File folder; + + static String originalConfigHome; + + @BeforeAll + static void saveConfigHomeProperty() { + originalConfigHome = System.getProperty(CONF_PROP_HOME); + ConverterConfiguration.reset(); + ResourceReader.reset(); + folder.setWritable(true); + } + + @AfterEach + void reset() { + System.clearProperty(CONF_PROP_HOME); + ConverterConfiguration.reset(); + ResourceReader.reset(); + } + + @AfterAll + static void reloadPreviousConfigurations() { + if (originalConfigHome != null) + System.setProperty(CONF_PROP_HOME, originalConfigHome); + else + System.clearProperty(CONF_PROP_HOME); + folder.setWritable(true); + } + + @ParameterizedTest + @ValueSource(strings = { "ADT^A09", "ADT^A10" }) // ADT_A09 has ignoreEmpty = true; ADT_A10 doesn't + void testIgnoreEmptySegment(String messageType) throws IOException { + + // ADT^A09 can ignore empty AL1 and ZAL segments; ADT^A01 can ignore empty AL1, and always ignores ZAL segments + int alCount = messageType.equals("ADT^A09") ? 0 : 2; + + // Set up the config file + commonConfigFileSetup(); + + // An empty AL1 and ZAL Segment... + String hl7message = "MSH|^~\\&|TestSystem||TestTransformationAgent||20150502090000||" + messageType + "|controlID|P|2.6\r" + + "EVN|A01|20150502090000|\r" + + "PID|||1234^^^^MR||DOE^JANE^|||F||||||||||||||||||||||\r" + + "PV1||I||||||||SUR||||||||S|VisitNumber^^^ACME|A||||||||||||||||||||||||20150502090000|\r" + + "AL1|\r" + + "ZAL|\r"; + + List e = getBundleEntryFromHL7Message(hl7message); + + List patientResource = ResourceUtils.getResourceList(e, ResourceType.Patient); + assertThat(patientResource).hasSize(1); // from PID + + List encounterResource = ResourceUtils.getResourceList(e, ResourceType.Encounter); + assertThat(encounterResource).hasSize(1); // from EVN, PV1 + + List allergyIntoleranceResource = ResourceUtils.getResourceList(e, ResourceType.AllergyIntolerance); + assertThat(allergyIntoleranceResource).hasSize(alCount); // empty AL1 and ZAL Segments + + // Confirm that there are no extra resources + assertThat(e).hasSize(2 + alCount); + + // We might be done + if(alCount == 0) + return; + + // Let's take a peek at the 'empty' AL1 Segment + assertThat(allergyIntoleranceResource).allSatisfy(rs -> { + + AllergyIntolerance ai = (AllergyIntolerance) rs; + assert(ai.hasId()); + assert(ai.hasClinicalStatus()); + assert(ai.hasVerificationStatus()); + }); + } + + + private static void commonConfigFileSetup() throws IOException { + File configFile = new File(folder, "config.properties"); + Properties prop = new Properties(); + prop.put("base.path.resource", "src/main/resources"); + prop.put("supported.hl7.messages", "ADT_A09, ADT_A10, ADT_A11"); // We're using weird ADT messages + prop.put("default.zoneid", "+08:00"); + // Location of additional resources + prop.put("additional.resources.location", "src/test/resources/additional_resources"); + prop.store(new FileOutputStream(configFile), null); + System.setProperty(CONF_PROP_HOME, configFile.getParent()); + } + + // Need custom convert sequence with options that turn off FHIR validation. + private static List getBundleEntryFromHL7Message(String hl7message) { + HL7ToFHIRConverter ftv = new HL7ToFHIRConverter(); // Testing loading of config which happens once per instantiation + String json = ftv.convert(hl7message, OPTIONS); // Need custom options that turn off FHIR validation. + assertThat(json).isNotNull(); + FHIRContext context = new FHIRContext(); + IBaseResource bundleResource = context.getParser().parseResource(json); + assertThat(bundleResource).isNotNull(); + Bundle b = (Bundle) bundleResource; + return b.getEntry(); + } + +} From 6d1dcac7d4bf83b06069eb42e3b4d1878341881d Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Tue, 12 Sep 2023 12:31:43 +1200 Subject: [PATCH 8/9] Added an extra pair of so-called empty segment tests; "" as a field value doesn't qualify as empty Signed-off-by: Stuart McGrigor --- .../hl7/message/Hl7IgnoreEmptyTest.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/test/java/io/github/linuxforhealth/hl7/message/Hl7IgnoreEmptyTest.java b/src/test/java/io/github/linuxforhealth/hl7/message/Hl7IgnoreEmptyTest.java index 40f86631..52bea791 100644 --- a/src/test/java/io/github/linuxforhealth/hl7/message/Hl7IgnoreEmptyTest.java +++ b/src/test/java/io/github/linuxforhealth/hl7/message/Hl7IgnoreEmptyTest.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.CsvSource; import io.github.linuxforhealth.core.config.ConverterConfiguration; import io.github.linuxforhealth.fhir.FHIRContext; @@ -81,12 +81,14 @@ static void reloadPreviousConfigurations() { folder.setWritable(true); } + // ADT_A09 has ignoreEmpty = true; ADT_A10 doesn't @ParameterizedTest - @ValueSource(strings = { "ADT^A09", "ADT^A10" }) // ADT_A09 has ignoreEmpty = true; ADT_A10 doesn't - void testIgnoreEmptySegment(String messageType) throws IOException { - - // ADT^A09 can ignore empty AL1 and ZAL segments; ADT^A01 can ignore empty AL1, and always ignores ZAL segments - int alCount = messageType.equals("ADT^A09") ? 0 : 2; + @CsvSource({ "ADT^A09,ZAL|\r,0", "ADT^A10,ZAL|\r,1", // Custom ZAL segment + "ADT^A09,AL1|\r,0", "ADT^A10,AL1|\r,1", // Standard AL1 segment + "ADT^A09,ZAL|||||||||||||||\r,0", // ZAL segment with all fields being empty + "ADT^A09,ZAL|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"|\"\"\r,1" // So-called EMPTY ZAL segment is not actually empty + }) + void testIgnoreEmptySegment(String messageType, String emptySegment, int aiCount) throws IOException { // Set up the config file commonConfigFileSetup(); @@ -96,8 +98,7 @@ void testIgnoreEmptySegment(String messageType) throws IOException { + "EVN|A01|20150502090000|\r" + "PID|||1234^^^^MR||DOE^JANE^|||F||||||||||||||||||||||\r" + "PV1||I||||||||SUR||||||||S|VisitNumber^^^ACME|A||||||||||||||||||||||||20150502090000|\r" - + "AL1|\r" - + "ZAL|\r"; + + emptySegment; List e = getBundleEntryFromHL7Message(hl7message); @@ -108,18 +109,19 @@ void testIgnoreEmptySegment(String messageType) throws IOException { assertThat(encounterResource).hasSize(1); // from EVN, PV1 List allergyIntoleranceResource = ResourceUtils.getResourceList(e, ResourceType.AllergyIntolerance); - assertThat(allergyIntoleranceResource).hasSize(alCount); // empty AL1 and ZAL Segments + assertThat(allergyIntoleranceResource).hasSize(aiCount); // empty AL1 and ZAL Segments // Confirm that there are no extra resources - assertThat(e).hasSize(2 + alCount); + assertThat(e).hasSize(2 + aiCount); // We might be done - if(alCount == 0) + if(aiCount == 0) return; - // Let's take a peek at the 'empty' AL1 Segment + // Let's take a peek at the 'empty' ZAL Segment assertThat(allergyIntoleranceResource).allSatisfy(rs -> { + // Only some of the fields AllergyIntolerance ai = (AllergyIntolerance) rs; assert(ai.hasId()); assert(ai.hasClinicalStatus()); From 269869eb120194e665dbcf9acde826adaca31ce3 Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Thu, 28 Sep 2023 09:18:10 +1300 Subject: [PATCH 9/9] Update HL7FHIRResourceTemplateAttributes.java Fixed camel case name of Builder.withIgnoreEmpty() --- .../hl7/message/HL7FHIRResourceTemplateAttributes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplateAttributes.java b/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplateAttributes.java index 5c665478..8e0c6eca 100644 --- a/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplateAttributes.java +++ b/src/main/java/io/github/linuxforhealth/hl7/message/HL7FHIRResourceTemplateAttributes.java @@ -160,7 +160,7 @@ public Builder withIsReferenced(boolean isReferenced) { return this; } - public Builder withignoreEmpty(boolean ignoreEmpty) { + public Builder withIgnoreEmpty(boolean ignoreEmpty) { this.ignoreEmpty = ignoreEmpty; return this; }