diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java index d4409575a..274ebe74b 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistration.java @@ -38,6 +38,7 @@ import gov.hhs.cdc.trustedintermediary.external.database.DbDao; import gov.hhs.cdc.trustedintermediary.external.database.PostgresDao; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageConverterHelper; +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageHelper; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiOrderConverter; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiPartnerMetadataConverter; import gov.hhs.cdc.trustedintermediary.external.hapi.HapiResultConverter; @@ -120,6 +121,7 @@ public Map> domainRegistra HapiMessageConverterHelper.class, HapiMessageConverterHelper.getInstance()); ApplicationContext.register( ReportStreamSenderHelper.class, ReportStreamSenderHelper.getInstance()); + ApplicationContext.register(HapiMessageHelper.class, HapiMessageHelper.getInstance()); // Metadata ApplicationContext.register( PartnerMetadataOrchestrator.class, PartnerMetadataOrchestrator.getInstance()); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/Demographics.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/Demographics.java index 82d5b9d5d..662ff477a 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/Demographics.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/demographics/Demographics.java @@ -1,14 +1,12 @@ package gov.hhs.cdc.trustedintermediary.etor.demographics; +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; + /** * Interface to wrap a third-party demographics class (Ex: Hapi FHIR Bundle) * * @param The underlying FHIR demographics type. */ -public interface Demographics { - T getUnderlyingDemographics(); - - String getFhirResourceId(); - +public interface Demographics extends FhirResource { String getPatientId(); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/messages/Message.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/messages/Message.java new file mode 100644 index 000000000..67b24684e --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/messages/Message.java @@ -0,0 +1,20 @@ +package gov.hhs.cdc.trustedintermediary.etor.messages; + +import gov.hhs.cdc.trustedintermediary.etor.ruleengine.FhirResource; + +/** + * Defines the structure and operations for a message. This interface allows for the retrieval of + * various pieces of information related to a message, including details about the sending and + * receiving applications and facilities, as well as order numbers. + */ +public interface Message extends FhirResource { + String getPlacerOrderNumber(); + + MessageHdDataType getSendingApplicationDetails(); + + MessageHdDataType getSendingFacilityDetails(); + + MessageHdDataType getReceivingApplicationDetails(); + + MessageHdDataType getReceivingFacilityDetails(); +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/messages/MessageHdDataType.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/messages/MessageHdDataType.java new file mode 100644 index 000000000..9ff26d0ca --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/messages/MessageHdDataType.java @@ -0,0 +1,9 @@ +package gov.hhs.cdc.trustedintermediary.etor.messages; + +/** + * This record class represents the result of evaluating a FHIRPath expression, encapsulating + * specific details extracted from a FHIR resource. This class holds values for namespace, universal + * identifier (ID), and the type of the universal ID. HD reference: HD-DataType + */ +public record MessageHdDataType(String namespace, String universalId, String universalIdType) {} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/Order.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/Order.java index cc13ec4f3..4f338e48b 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/Order.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/orders/Order.java @@ -1,14 +1,8 @@ package gov.hhs.cdc.trustedintermediary.etor.orders; -/** - * Interface to wrap a third-party lab order class (Ex: Hapi FHIR Bundle) - * - * @param The underlying FHIR lab order type. - */ -public interface Order { - T getUnderlyingOrder(); - - String getFhirResourceId(); +import gov.hhs.cdc.trustedintermediary.etor.messages.Message; +/** Interface to wrap a third-party lab order class (Ex: Hapi FHIR Bundle) */ +public interface Order extends Message { String getPatientId(); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/Result.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/Result.java index 00199924c..b5ccd2df4 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/Result.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/results/Result.java @@ -1,12 +1,6 @@ package gov.hhs.cdc.trustedintermediary.etor.results; -/** - * Interface to wrap a third-party lab result class (Ex: Hapi FHIR Bundle) - * - * @param The underlying FHIR lab result type. - */ -public interface Result { - T getUnderlyingResult(); +import gov.hhs.cdc.trustedintermediary.etor.messages.Message; - String getFhirResourceId(); -} +/** Interface to wrap a third-party lab result class (Ex: Hapi FHIR Bundle) */ +public interface Result extends Message {} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/FhirResource.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/FhirResource.java index e3fa5bda9..0b15aa3a9 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/FhirResource.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/etor/ruleengine/FhirResource.java @@ -8,4 +8,6 @@ */ public interface FhirResource { T getUnderlyingResource(); + + String getFhirResourceId(); } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiDemographics.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiDemographics.java index b86a07970..4722ae76c 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiDemographics.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiDemographics.java @@ -2,44 +2,14 @@ import gov.hhs.cdc.trustedintermediary.etor.demographics.Demographics; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Identifier; -import org.hl7.fhir.r4.model.Patient; /** * A concrete implementation of a {@link Demographics} that uses the Hapi FHIR bundle as its * underlying type. */ -public class HapiDemographics implements Demographics { - - private final Bundle innerDemographics; +public class HapiDemographics extends HapiMessage implements Demographics { public HapiDemographics(Bundle innerDemographics) { - this.innerDemographics = innerDemographics; - } - - @Override - public Bundle getUnderlyingDemographics() { - return innerDemographics; - } - - @Override - public String getFhirResourceId() { - return innerDemographics.getId(); - } - - @Override - public String getPatientId() { - return HapiHelper.resourcesInBundle(innerDemographics, Patient.class) - .flatMap(patient -> patient.getIdentifier().stream()) - .filter( - identifier -> - identifier - .getType() - .hasCoding( - "http://terminology.hl7.org/CodeSystem/v2-0203", - "MR")) - .map(Identifier::getValue) - .findFirst() - .orElse(""); + super(innerDemographics); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirResource.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirResource.java index a0f41783e..697a07ecf 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirResource.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirResource.java @@ -16,4 +16,9 @@ public HapiFhirResource(IBaseResource innerResource) { public IBaseResource getUnderlyingResource() { return innerResource; } + + @Override + public String getFhirResourceId() { + return innerResource.getIdElement().getIdPart(); + } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessage.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessage.java new file mode 100644 index 000000000..3b29169b4 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessage.java @@ -0,0 +1,94 @@ +package gov.hhs.cdc.trustedintermediary.external.hapi; + +import gov.hhs.cdc.trustedintermediary.context.ApplicationContext; +import gov.hhs.cdc.trustedintermediary.etor.messages.Message; +import gov.hhs.cdc.trustedintermediary.etor.messages.MessageHdDataType; +import java.util.function.Supplier; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Patient; + +public class HapiMessage implements Message { + + protected final HapiMessageHelper MESSAGE_HELPER = + ApplicationContext.getImplementation(HapiMessageHelper.class); + + protected final Bundle innerResource; + + public HapiMessage(Bundle innerResource) { + this.innerResource = innerResource; + } + + @Override + public Bundle getUnderlyingResource() { + return innerResource; + } + + @Override + public String getFhirResourceId() { + return innerResource.getId(); + } + + @Override + public String getPlacerOrderNumber() { + return MESSAGE_HELPER.extractPlacerOrderNumber(innerResource); + } + + @Override + public MessageHdDataType getSendingApplicationDetails() { + return extractMessageHdDataType( + () -> MESSAGE_HELPER.extractSendingApplicationNamespace(innerResource), + () -> MESSAGE_HELPER.extractSendingApplicationUniversalId(innerResource), + () -> MESSAGE_HELPER.extractSendingApplicationUniversalIdType(innerResource)); + } + + @Override + public MessageHdDataType getSendingFacilityDetails() { + return extractMessageHdDataType( + () -> MESSAGE_HELPER.extractSendingFacilityNamespace(innerResource), + () -> MESSAGE_HELPER.extractSendingFacilityUniversalId(innerResource), + () -> MESSAGE_HELPER.extractSendingFacilityUniversalIdType(innerResource)); + } + + @Override + public MessageHdDataType getReceivingApplicationDetails() { + return extractMessageHdDataType( + () -> MESSAGE_HELPER.extractReceivingApplicationNamespace(innerResource), + () -> MESSAGE_HELPER.extractReceivingApplicationUniversalId(innerResource), + () -> MESSAGE_HELPER.extractReceivingApplicationUniversalIdType(innerResource)); + } + + @Override + public MessageHdDataType getReceivingFacilityDetails() { + return extractMessageHdDataType( + () -> MESSAGE_HELPER.extractReceivingFacilityNamespace(innerResource), + () -> MESSAGE_HELPER.extractReceivingFacilityUniversalId(innerResource), + () -> MESSAGE_HELPER.extractReceivingFacilityUniversalIdType(innerResource)); + } + + public String getPatientId() { + return HapiHelper.resourcesInBundle(innerResource, Patient.class) + .flatMap(patient -> patient.getIdentifier().stream()) + .filter( + identifier -> + identifier + .getType() + .hasCoding( + "http://terminology.hl7.org/CodeSystem/v2-0203", + "MR")) + .map(Identifier::getValue) + .findFirst() + .orElse(""); + } + + protected MessageHdDataType extractMessageHdDataType( + Supplier namespaceExtractor, + Supplier universalIdExtractor, + Supplier universalIdTypeExtractor) { + String namespace = namespaceExtractor.get(); + String universalId = universalIdExtractor.get(); + String universalIdType = universalIdTypeExtractor.get(); + + return new MessageHdDataType(namespace, universalId, universalIdType); + } +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageHelper.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageHelper.java new file mode 100644 index 000000000..d117e4d33 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiMessageHelper.java @@ -0,0 +1,89 @@ +package gov.hhs.cdc.trustedintermediary.external.hapi; + +import gov.hhs.cdc.trustedintermediary.plugin.path.FhirPath; +import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir; +import javax.inject.Inject; +import org.hl7.fhir.r4.model.Bundle; + +/** + * Helper class for extracting various pieces of information from FHIR messages using defined FHIR + * paths. This class utilizes the {@link HapiFhir} engine to execute FHIR path expressions against a + * given {@link Bundle}. + */ +public class HapiMessageHelper { + + private static final HapiMessageHelper INSTANCE = new HapiMessageHelper(); + + @Inject HapiFhir fhirEngine; + + public static HapiMessageHelper getInstance() { + return INSTANCE; + } + + private HapiMessageHelper() {} + + public String extractPlacerOrderNumber(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.PLACER_ORDER_NUMBER.getPath()); + } + + public String extractSendingApplicationNamespace(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.SENDING_APPLICATION_NAMESPACE.getPath()); + } + + public String extractSendingApplicationUniversalId(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.SENDING_APPLICATION_UNIVERSAL_ID.getPath()); + } + + public String extractSendingApplicationUniversalIdType(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.SENDING_APPLICATION_UNIVERSAL_ID_TYPE.getPath()); + } + + public String extractSendingFacilityNamespace(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.SENDING_FACILITY_NAMESPACE.getPath()); + } + + public String extractSendingFacilityUniversalId(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.SENDING_FACILITY_UNIVERSAL_ID.getPath()); + } + + public String extractSendingFacilityUniversalIdType(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.SENDING_FACILITY_UNIVERSAL_ID_TYPE.getPath()); + } + + public String extractReceivingApplicationNamespace(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.RECEIVING_APPLICATION_NAMESPACE.getPath()); + } + + public String extractReceivingApplicationUniversalId(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.RECEIVING_APPLICATION_UNIVERSAL_ID.getPath()); + } + + public String extractReceivingApplicationUniversalIdType(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.RECEIVING_APPLICATION_UNIVERSAL_ID_TYPE.getPath()); + } + + public String extractReceivingFacilityNamespace(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.RECEIVING_FACILITY_NAMESPACE.getPath()); + } + + public String extractReceivingFacilityUniversalId(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.RECEIVING_FACILITY_UNIVERSAL_ID.getPath()); + } + + public String extractReceivingFacilityUniversalIdType(Bundle messageBundle) { + return fhirEngine.getStringFromFhirPath( + messageBundle, FhirPath.RECEIVING_FACILITY_UNIVERSAL_ID_TYPE.getPath()); + } +} diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrder.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrder.java index 883a3046d..889ec25ee 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrder.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrder.java @@ -2,44 +2,14 @@ import gov.hhs.cdc.trustedintermediary.etor.orders.Order; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Identifier; -import org.hl7.fhir.r4.model.Patient; /** * A concrete implementation of a {@link Order} that uses the Hapi FHIR bundle as its underlying * type. */ -public class HapiOrder implements Order { - - private final Bundle innerOrder; +public class HapiOrder extends HapiMessage implements Order { public HapiOrder(Bundle innerOrder) { - this.innerOrder = innerOrder; - } - - @Override - public Bundle getUnderlyingOrder() { - return innerOrder; - } - - @Override - public String getFhirResourceId() { - return innerOrder.getId(); - } - - @Override - public String getPatientId() { - return HapiHelper.resourcesInBundle(innerOrder, Patient.class) - .flatMap(patient -> patient.getIdentifier().stream()) - .filter( - identifier -> - identifier - .getType() - .hasCoding( - "http://terminology.hl7.org/CodeSystem/v2-0203", - "MR")) - .map(Identifier::getValue) - .findFirst() - .orElse(""); + super(innerOrder); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java index fdb0ff7d9..0a2457d96 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverter.java @@ -54,7 +54,7 @@ public HapiOrder convertToOrder(final Demographics demographics) { logger.logInfo("Converting demographics to order"); var hapiDemographics = (Demographics) demographics; - var demographicsBundle = hapiDemographics.getUnderlyingDemographics(); + var demographicsBundle = hapiDemographics.getUnderlyingResource(); var overallId = UUID.randomUUID().toString(); if (!demographicsBundle.hasId()) { @@ -96,7 +96,7 @@ public Order convertToOmlOrder(Order order) { logger.logInfo("Converting order to have OML metadata"); var hapiOrder = (Order) order; - var orderBundle = hapiOrder.getUnderlyingOrder(); + var orderBundle = hapiOrder.getUnderlyingResource(); var messageHeader = hapiMessageConverterHelper.findOrInitializeMessageHeader(orderBundle); messageHeader.setEvent(OML_CODING); @@ -109,7 +109,7 @@ public Order addContactSectionToPatientResource(Order order) { logger.logInfo("Adding contact section in Patient resource"); var hapiOrder = (Order) order; - var orderBundle = hapiOrder.getUnderlyingOrder(); + var orderBundle = hapiOrder.getUnderlyingResource(); HapiHelper.resourcesInBundle(orderBundle, Patient.class) .forEach( @@ -159,7 +159,7 @@ private MessageHeader createMessageHeader() { @Override public Order addEtorProcessingTag(Order message) { var hapiOrder = (Order) message; - var messageBundle = hapiOrder.getUnderlyingOrder(); + var messageBundle = hapiOrder.getUnderlyingResource(); hapiMessageConverterHelper.addEtorTagToBundle(messageBundle); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResult.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResult.java index 56b6cec0f..20d690e69 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResult.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResult.java @@ -4,21 +4,9 @@ import org.hl7.fhir.r4.model.Bundle; /** Filler concrete implementation of a {@link Result} using the Hapi FHIR library */ -public class HapiResult implements Result { - - private final Bundle innerResult; +public class HapiResult extends HapiMessage implements Result { public HapiResult(Bundle innerResult) { - this.innerResult = innerResult; - } - - @Override - public Bundle getUnderlyingResult() { - return innerResult; - } - - @Override - public String getFhirResourceId() { - return innerResult.getId(); + super(innerResult); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverter.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverter.java index ed7c9a21e..e7067a567 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverter.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverter.java @@ -27,7 +27,7 @@ private HapiResultConverter() {} @Override public Result addEtorProcessingTag(final Result message) { var hapiResult = (Result) message; - var messageBundle = hapiResult.getUnderlyingResult(); + var messageBundle = hapiResult.getUnderlyingResource(); hapiMessageConverterHelper.addEtorTagToBundle(messageBundle); diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamOrderSender.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamOrderSender.java index 63b20bd3d..e001ab287 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamOrderSender.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamOrderSender.java @@ -26,7 +26,7 @@ private ReportStreamOrderSender() {} @Override public Optional send(final Order order) throws UnableToSendMessageException { logger.logInfo("Sending the order to ReportStream"); - String json = fhir.encodeResourceToJson(order.getUnderlyingOrder()); + String json = fhir.encodeResourceToJson(order.getUnderlyingResource()); return sender.sendOrderToReportStream(json, order.getFhirResourceId()); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamResultSender.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamResultSender.java index 38b22c452..418791064 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamResultSender.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamResultSender.java @@ -29,7 +29,7 @@ private ReportStreamResultSender() {} @Override public Optional send(Result result) throws UnableToSendMessageException { logger.logInfo("Sending results to ReportStream"); - String json = fhir.encodeResourceToJson(result.getUnderlyingResult()); + String json = fhir.encodeResourceToJson(result.getUnderlyingResource()); return sender.sendResultToReportStream(json, result.getFhirResourceId()); } } diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/plugin/path/FhirPath.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/plugin/path/FhirPath.java new file mode 100644 index 000000000..c28bbf6e8 --- /dev/null +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/plugin/path/FhirPath.java @@ -0,0 +1,89 @@ +package gov.hhs.cdc.trustedintermediary.plugin.path; + +/** + * Enumerates FHIR path expressions for various data elements within a FHIR message. These paths can + * be used to extract specific pieces of data from a FHIR message, such as identifiers, namespaces, + * and codes related to sending and receiving facilities and applications. + */ +public enum FhirPath { + PLACER_ORDER_NUMBER( + """ + Bundle.entry.resource.ofType(ServiceRequest).identifier.where(type.coding.code = 'PLAC').value + """), + SENDING_FACILITY_NAMESPACE( + """ + Bundle.entry.resource.ofType(MessageHeader).sender.resolve().identifier.where( + extension.url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field' and + extension.value = 'HD.1' + ).value + """), + SENDING_FACILITY_UNIVERSAL_ID( + """ + Bundle.entry.resource.ofType(MessageHeader).sender.resolve().identifier.where( + extension.url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field' and + extension.value = 'HD.2,HD.3' + ).value + """), + SENDING_FACILITY_UNIVERSAL_ID_TYPE( + """ + Bundle.entry.resource.ofType(MessageHeader).sender.resolve().identifier.where( + extension.url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field' and + extension.value = 'HD.2,HD.3' + ).type.coding.code + """), + SENDING_APPLICATION_NAMESPACE( + """ + Bundle.entry.resource.ofType(MessageHeader).source.extension.where(url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id').value + """), + SENDING_APPLICATION_UNIVERSAL_ID( + """ + Bundle.entry.resource.ofType(MessageHeader).source.extension.where(url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id').value + """), + SENDING_APPLICATION_UNIVERSAL_ID_TYPE( + """ + Bundle.entry.resource.ofType(MessageHeader).source.extension.where(url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type').value + """), + RECEIVING_FACILITY_NAMESPACE( + """ + Bundle.entry.resource.ofType(MessageHeader).destination.receiver.resolve().identifier.where( + extension.url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field' and + extension.value = 'HD.1' + ).value + """), + RECEIVING_FACILITY_UNIVERSAL_ID( + """ + Bundle.entry.resource.ofType(MessageHeader).destination.receiver.resolve().identifier.where( + extension.url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field' and + extension.value = 'HD.2,HD.3' + ).value + """), + RECEIVING_FACILITY_UNIVERSAL_ID_TYPE( + """ + Bundle.entry.resource.ofType(MessageHeader).destination.receiver.resolve().identifier.where( + extension.url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field' and + extension.value = 'HD.2,HD.3' + ).type.coding.code + """), + RECEIVING_APPLICATION_NAMESPACE( + """ + Bundle.entry.resource.ofType(MessageHeader).destination.name + """), + RECEIVING_APPLICATION_UNIVERSAL_ID( + """ + Bundle.entry.resource.ofType(MessageHeader).destination.extension.where(url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id').value + """), + RECEIVING_APPLICATION_UNIVERSAL_ID_TYPE( + """ + Bundle.entry.resource.ofType(MessageHeader).destination.extension.where(url = 'https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type').value + """); + + private final String path; + + FhirPath(String path) { + this.path = path; + } + + public String getPath() { + return path; + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/DemographicsMock.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/DemographicsMock.groovy index 5a84f1d43..86fd2c319 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/DemographicsMock.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/DemographicsMock.groovy @@ -19,7 +19,7 @@ class DemographicsMock implements Demographics { } @Override - T getUnderlyingDemographics() { + T getUnderlyingResource() { return underlyingDemographics } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirResourceMock.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirResourceMock.groovy index 07a000688..d6433a486 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirResourceMock.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/FhirResourceMock.groovy @@ -14,4 +14,9 @@ class FhirResourceMock implements FhirResource { public T getUnderlyingResource() { return innerResource } + + @Override + String getFhirResourceId() { + return null + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/OrderMock.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/OrderMock.groovy index 3ef6a9f0b..af74e1792 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/OrderMock.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/OrderMock.groovy @@ -1,5 +1,6 @@ package gov.hhs.cdc.trustedintermediary +import gov.hhs.cdc.trustedintermediary.etor.messages.MessageHdDataType import gov.hhs.cdc.trustedintermediary.etor.orders.Order /** @@ -10,25 +11,61 @@ class OrderMock implements Order { private String fhirResourceId private String patientId private T underlyingOrders + private String placerOrderNumber + private MessageHdDataType sendingApplicationDetails + private MessageHdDataType sendingFacilityDetails + private MessageHdDataType receivingApplicationDetails + private MessageHdDataType receivingFacilityDetails - OrderMock(String fhirResourceId, String patientId, T underlyingOrders) { + OrderMock(String fhirResourceId, String patientId, T underlyingOrders, String placerOrderNumber, MessageHdDataType sendingApplicationDetails, MessageHdDataType sendingFacilityDetails, + MessageHdDataType receivingApplicationDetails, MessageHdDataType receivingFacilityDetails) { this.fhirResourceId = fhirResourceId this.patientId = patientId this.underlyingOrders = underlyingOrders + this.placerOrderNumber = placerOrderNumber + this.sendingApplicationDetails = sendingApplicationDetails + this.sendingFacilityDetails = sendingFacilityDetails + this.receivingApplicationDetails = receivingApplicationDetails + this.receivingFacilityDetails = receivingFacilityDetails } @Override - T getUnderlyingOrder() { - return underlyingOrders + T getUnderlyingResource() { + return this.underlyingOrders } @Override String getFhirResourceId() { - return fhirResourceId + return this.fhirResourceId } @Override String getPatientId() { return patientId } + + @Override + String getPlacerOrderNumber() { + return this.placerOrderNumber + } + + @Override + MessageHdDataType getSendingApplicationDetails() { + return this.sendingApplicationDetails + } + + @Override + MessageHdDataType getSendingFacilityDetails() { + return this.sendingFacilityDetails + } + + @Override + MessageHdDataType getReceivingApplicationDetails() { + return this.receivingApplicationDetails + } + + @Override + MessageHdDataType getReceivingFacilityDetails() { + return this.receivingFacilityDetails + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ResultMock.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ResultMock.groovy index d925f02f3..46609bbc6 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ResultMock.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/ResultMock.groovy @@ -1,5 +1,6 @@ package gov.hhs.cdc.trustedintermediary +import gov.hhs.cdc.trustedintermediary.etor.messages.MessageHdDataType import gov.hhs.cdc.trustedintermediary.etor.results.Result /** @@ -9,19 +10,56 @@ class ResultMock implements Result { private String fhirResourceId private T underlyingResult + private String placerOrderNumber + private MessageHdDataType sendingApplicationDetails + private MessageHdDataType sendingFacilityDetails + private MessageHdDataType receivingApplicationDetails + private MessageHdDataType receivingFacilityDetails - ResultMock(String fhirResourceId, T underlyingResult) { + + ResultMock(String fhirResourceId, T underlyingResult, String placerOrderNumber, MessageHdDataType sendingApplicationDetails, MessageHdDataType sendingFacilityDetails, + MessageHdDataType receivingApplicationDetails, MessageHdDataType receivingFacilityDetails) { this.fhirResourceId = fhirResourceId this.underlyingResult = underlyingResult + this.placerOrderNumber = placerOrderNumber + this.sendingApplicationDetails = sendingApplicationDetails + this.sendingFacilityDetails = sendingFacilityDetails + this.receivingApplicationDetails = receivingApplicationDetails + this.receivingFacilityDetails = receivingFacilityDetails } @Override - T getUnderlyingResult() { - return underlyingResult + T getUnderlyingResource() { + return this.underlyingResult } @Override String getFhirResourceId() { - return fhirResourceId + return this.fhirResourceId + } + + @Override + String getPlacerOrderNumber() { + return this.placerOrderNumber + } + + @Override + MessageHdDataType getSendingApplicationDetails() { + return this.sendingApplicationDetails + } + + @Override + MessageHdDataType getSendingFacilityDetails() { + return this.sendingFacilityDetails + } + + @Override + MessageHdDataType getReceivingApplicationDetails() { + return this.receivingApplicationDetails + } + + @Override + MessageHdDataType getReceivingFacilityDetails() { + return this.receivingFacilityDetails } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistrationTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistrationTest.groovy index d8992342a..8d6f6d9e3 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistrationTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/EtorDomainRegistrationTest.groovy @@ -169,7 +169,7 @@ class EtorDomainRegistrationTest extends Specification { def "handleOrders happy path"() { given: - def orderMock = new OrderMock("resource id", "a patient ID", "orders") + def orderMock = new OrderMock("resource id", "a patient ID", "orders", null, null, null, null, null) def request = new DomainRequest(headers: ["recordid": "recordId"]) def connector = new EtorDomainRegistration() @@ -197,7 +197,7 @@ class EtorDomainRegistrationTest extends Specification { def "handleResults happy path"() { given: - def resultMock = new ResultMock("resource id", "lab result") + def resultMock = new ResultMock("resource id", "lab result", null, null, null, null, null) def request = new DomainRequest(headers: ["recordid": "recordId"]) def connector = new EtorDomainRegistration() diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy index c40ddb829..c1908bdbc 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/ConvertAndSendDemographicsUsecaseTest.groovy @@ -18,7 +18,7 @@ class ConvertAndSendDemographicsUsecaseTest extends Specification { def "ConvertAndSend"() { given: - def mockOrder = new OrderMock(null, null, null) + def mockOrder = new OrderMock(null, null, null, null, null, null, null, null) def mockConverter = Mock(OrderConverter) def mockSender = Mock(OrderSender) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/PatientDemographicsControllerTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/PatientDemographicsControllerTest.groovy index d6b39accc..e2bcddb57 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/PatientDemographicsControllerTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/demographics/PatientDemographicsControllerTest.groovy @@ -2,6 +2,7 @@ package gov.hhs.cdc.trustedintermediary.etor.demographics import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.domainconnector.DomainRequest +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageHelper import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import org.hl7.fhir.r4.model.Bundle @@ -12,6 +13,7 @@ class PatientDemographicsControllerTest extends Specification { def setup() { TestApplicationContext.reset() TestApplicationContext.init() + TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) TestApplicationContext.register(PatientDemographicsController, PatientDemographicsController.getInstance()) } @@ -27,7 +29,7 @@ class PatientDemographicsControllerTest extends Specification { def patientDemographics = PatientDemographicsController.getInstance().parseDemographics(new DomainRequest()) then: - patientDemographics.getUnderlyingDemographics() == expectedBundle + patientDemographics.getUnderlyingResource() == expectedBundle } def "parseDemographics throws an exception when unable to parse de request"() { diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/messages/MessageHdDataTypeTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/messages/MessageHdDataTypeTest.groovy new file mode 100644 index 000000000..0b06bac69 --- /dev/null +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/messages/MessageHdDataTypeTest.groovy @@ -0,0 +1,14 @@ +package gov.hhs.cdc.trustedintermediary.etor.messages + +import gov.hhs.cdc.trustedintermediary.PojoTestUtils +import spock.lang.Specification + +class MessageHdDataTypeTest extends Specification { + def "test getters and setters"() { + when: + PojoTestUtils.validateGettersAndSetters(MessageHdDataType) + + then: + noExceptionThrown() + } +} diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy index 2ad6e6a5a..75bdc1bc1 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderControllerTest.groovy @@ -4,6 +4,7 @@ import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.domainconnector.DomainRequest import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep import gov.hhs.cdc.trustedintermediary.etor.ruleengine.RuleEngine +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageHelper import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata @@ -19,6 +20,7 @@ class OrderControllerTest extends Specification { TestApplicationContext.register(OrderController, OrderController.getInstance()) TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) TestApplicationContext.register(RuleEngine, ruleEngine) + TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) } def "parseOrders happy path works"() { @@ -32,7 +34,7 @@ class OrderControllerTest extends Specification { TestApplicationContext.injectRegisteredImplementations() when: - def actualBundle = controller.parseOrders(new DomainRequest()).underlyingOrder + def actualBundle = controller.parseOrders(new DomainRequest()).underlyingResource then: actualBundle == expectedBundle diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderResponseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderResponseTest.groovy index 5f58494b0..6abfaf907 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderResponseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/OrderResponseTest.groovy @@ -19,7 +19,7 @@ class OrderResponseTest extends Specification { def expectedResourceId = "67890asdfg" def expectedPatientId = "fthgyu687" - def orders = new OrderMock(expectedResourceId, expectedPatientId, null) + def orders = new OrderMock(expectedResourceId, expectedPatientId, null, null, null, null, null, null) when: def actual = new OrderResponse(orders) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy index 536956a72..28a77cfb9 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/orders/SendOrderUseCaseTest.groovy @@ -38,7 +38,7 @@ class SendOrderUseCaseTest extends Specification { def messageType = PartnerMetadataMessageType.ORDER def sendOrder = SendOrderUseCase.getInstance() - def mockOrder = new OrderMock(null, null, null) + def mockOrder = new OrderMock(null, null, null, null, null, null, null, null) def mockOmlOrder = Mock(Order) TestApplicationContext.injectRegisteredImplementations() diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/ResultControllerTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/ResultControllerTest.groovy index d6845e054..1b35e32ca 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/ResultControllerTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/ResultControllerTest.groovy @@ -2,8 +2,7 @@ package gov.hhs.cdc.trustedintermediary.etor.results import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.domainconnector.DomainRequest -import gov.hhs.cdc.trustedintermediary.etor.orders.OrderController -import gov.hhs.cdc.trustedintermediary.etor.results.ResultController +import gov.hhs.cdc.trustedintermediary.external.hapi.HapiMessageHelper import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata @@ -18,6 +17,8 @@ class ResultControllerTest extends Specification { TestApplicationContext.init() TestApplicationContext.register(ResultController, ResultController.getInstance()) TestApplicationContext.register(MetricMetadata, Mock(MetricMetadata)) + TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) + TestApplicationContext.injectRegisteredImplementations() } def "parseResults Happy path works"() { @@ -31,7 +32,7 @@ class ResultControllerTest extends Specification { TestApplicationContext.injectRegisteredImplementations() when: - def actualBundle = controller.parseResults(new DomainRequest()).underlyingResult + def actualBundle = controller.parseResults(new DomainRequest()).underlyingResource then: actualBundle == expectedBundle diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/ResultResponseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/ResultResponseTest.groovy index 95cfbd048..e8ed53867 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/ResultResponseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/ResultResponseTest.groovy @@ -18,7 +18,7 @@ class ResultResponseTest extends Specification { given: def expectedResourceId = "12345678" - def result = new ResultMock(expectedResourceId, null) + def result = new ResultMock(expectedResourceId, null, null, null, null, null, null) when: def actual = new ResultResponse(result) diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy index 2a961ac51..0e7430aff 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/etor/results/SendResultUseCaseTest.groovy @@ -34,7 +34,7 @@ class SendResultUseCaseTest extends Specification { def "convertAndSend works"() { given: - def mockResult = new ResultMock(null, "Mock result") + def mockResult = new ResultMock(null, "Mock result", null, null, null, null, null) def receivedSubmissionId = "receivedId" when: diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiDemographicsTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiDemographicsTest.groovy index 44d97d5b3..cedd255c3 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiDemographicsTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiDemographicsTest.groovy @@ -1,5 +1,7 @@ package gov.hhs.cdc.trustedintermediary.external.hapi +import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding @@ -9,13 +11,20 @@ import spock.lang.Specification class HapiDemographicsTest extends Specification { - def "getUnderlyingDemographics works"() { + def setup() { + TestApplicationContext.reset() + TestApplicationContext.init() + TestApplicationContext.register(HapiMessageHelper.class, HapiMessageHelper.getInstance()) + TestApplicationContext.injectRegisteredImplementations() + } + + def "getUnderlyingResource works"() { given: def expectedInnerDemographics = new Bundle() def demographics = new HapiDemographics(expectedInnerDemographics) when: - def actualInnerDemographics = demographics.getUnderlyingDemographics() + def actualInnerDemographics = demographics.getUnderlyingResource() then: actualInnerDemographics == expectedInnerDemographics diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy index 3c2a8d3a2..bbac342cb 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderConverterTest.groovy @@ -3,15 +3,11 @@ package gov.hhs.cdc.trustedintermediary.external.hapi import gov.hhs.cdc.trustedintermediary.DemographicsMock import gov.hhs.cdc.trustedintermediary.OrderMock import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext -import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadata -import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataMessageType import gov.hhs.cdc.trustedintermediary.etor.orders.OrderConverter -import gov.hhs.cdc.trustedintermediary.etor.metadata.partner.PartnerMetadataStatus import org.hl7.fhir.r4.model.Address import org.hl7.fhir.r4.model.ContactPoint import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.HumanName -import org.hl7.fhir.r4.model.OperationOutcome import org.hl7.fhir.r4.model.StringType import java.time.Instant @@ -37,6 +33,7 @@ class HapiOrderConverterTest extends Specification { TestApplicationContext.init() TestApplicationContext.register(OrderConverter, HapiOrderConverter.getInstance()) TestApplicationContext.register(HapiMessageConverterHelper, HapiMessageConverterHelper.getInstance()) + TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) TestApplicationContext.injectRegisteredImplementations() mockPatient = new Patient() @@ -44,13 +41,13 @@ class HapiOrderConverterTest extends Specification { mockDemographics = new DemographicsMock("fhirResourceId", "patientId", mockDemographicsBundle) mockOrderBundle = new Bundle().addEntry(new Bundle.BundleEntryComponent().setResource(mockPatient)) - mockOrder = new OrderMock("fhirResourceId", "patientId", mockOrderBundle) + mockOrder = new OrderMock("fhirResourceId", "patientId", mockOrderBundle, null, null, null, null, null) } def "the converter fills in gaps of any missing data in the Bundle"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingOrder() + def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() then: orderBundle.hasId() @@ -70,7 +67,7 @@ class HapiOrderConverterTest extends Specification { mockDemographicsBundle.setTimestamp(mockTimestamp) when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingOrder() + def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() then: orderBundle.getId() == mockId @@ -84,7 +81,7 @@ class HapiOrderConverterTest extends Specification { mockDemographicsBundle.setType(Bundle.BundleType.COLLECTION) when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingOrder() + def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() then: orderBundle.getType() == Bundle.BundleType.MESSAGE @@ -93,7 +90,7 @@ class HapiOrderConverterTest extends Specification { def "the demographics correctly constructs a message header in the lab order"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingOrder() + def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() then: def messageHeader = orderBundle.getEntry().get(0).getResource() as MessageHeader @@ -111,7 +108,7 @@ class HapiOrderConverterTest extends Specification { def "the converter correctly reuses the patient from the passed in demographics"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingOrder() + def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() then: def patient = orderBundle.getEntry().get(1).getResource() as Patient @@ -122,7 +119,7 @@ class HapiOrderConverterTest extends Specification { def "the converter correctly constructs a service request in the lab order"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingOrder() + def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() then: def serviceRequest = orderBundle.getEntry().get(2).getResource() as ServiceRequest @@ -137,7 +134,7 @@ class HapiOrderConverterTest extends Specification { def "the order datetime should match for bundle, service request, and provenance resources"() { when: - def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingOrder() + def orderBundle = HapiOrderConverter.getInstance().convertToOrder(mockDemographics).getUnderlyingResource() def bundleDateTime = orderBundle.getTimestamp() def serviceRequest = orderBundle.getEntry().get(2).getResource() as ServiceRequest def provenance = orderBundle.getEntry().get(3).getResource() as Provenance @@ -161,7 +158,7 @@ class HapiOrderConverterTest extends Specification { "ORM")))) when: - def convertedOrderBundle = HapiOrderConverter.getInstance().convertToOmlOrder(mockOrder).getUnderlyingOrder() as Bundle + def convertedOrderBundle = HapiOrderConverter.getInstance().convertToOmlOrder(mockOrder).getUnderlyingResource() as Bundle then: def convertedMessageHeader = convertedOrderBundle.getEntry().get(1).getResource() as MessageHeader @@ -172,7 +169,7 @@ class HapiOrderConverterTest extends Specification { def "adds the message header to specify OML"() { when: - def convertedOrderBundle = HapiOrderConverter.getInstance().convertToOmlOrder(mockOrder).getUnderlyingOrder() as Bundle + def convertedOrderBundle = HapiOrderConverter.getInstance().convertToOmlOrder(mockOrder).getUnderlyingResource() as Bundle then: def convertedMessageHeader = convertedOrderBundle.getEntry().get(1).getResource() as MessageHeader @@ -195,7 +192,7 @@ class HapiOrderConverterTest extends Specification { mockOrderBundle.setEntry(entryList) when: - def convertedOrderBundle = HapiOrderConverter.getInstance().addContactSectionToPatientResource(mockOrder).getUnderlyingOrder() as Bundle + def convertedOrderBundle = HapiOrderConverter.getInstance().addContactSectionToPatientResource(mockOrder).getUnderlyingResource() as Bundle then: def convertedPatient = convertedOrderBundle.getEntry().get(0).getResource() as Patient @@ -239,10 +236,10 @@ class HapiOrderConverterTest extends Specification { messageHeader.setId(UUID.randomUUID().toString()) def messageHeaderEntry = new Bundle.BundleEntryComponent().setResource(messageHeader) mockOrderBundle.getEntry().add(1, messageHeaderEntry) - mockOrder.getUnderlyingOrder() >> mockOrderBundle + mockOrder.getUnderlyingResource() >> mockOrderBundle when: - def convertedOrderBundle = HapiOrderConverter.getInstance().addEtorProcessingTag(mockOrder).getUnderlyingOrder() as Bundle + def convertedOrderBundle = HapiOrderConverter.getInstance().addEtorProcessingTag(mockOrder).getUnderlyingResource() as Bundle then: def messageHeaders = convertedOrderBundle.getEntry().get(1).getResource() as MessageHeader @@ -264,7 +261,7 @@ class HapiOrderConverterTest extends Specification { entryList.add(patientEntry) mockOrderBundle.setEntry(entryList) when: - def convertedOrderBundle = HapiOrderConverter.getInstance().addContactSectionToPatientResource(mockOrder).getUnderlyingOrder() as Bundle + def convertedOrderBundle = HapiOrderConverter.getInstance().addContactSectionToPatientResource(mockOrder).getUnderlyingResource() as Bundle then: def convertedPatient = convertedOrderBundle.getEntry().get(0).getResource() as Patient diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderTest.groovy index 96ed3d99d..dab5cd610 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiOrderTest.groovy @@ -1,20 +1,43 @@ package gov.hhs.cdc.trustedintermediary.external.hapi +import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.etor.messages.MessageHdDataType +import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.Extension import org.hl7.fhir.r4.model.Identifier +import org.hl7.fhir.r4.model.MessageHeader +import org.hl7.fhir.r4.model.Organization import org.hl7.fhir.r4.model.Patient +import org.hl7.fhir.r4.model.Reference +import org.hl7.fhir.r4.model.ServiceRequest +import org.hl7.fhir.r4.model.StringType +import org.hl7.fhir.r4.model.UrlType import spock.lang.Specification +import javax.print.attribute.standard.Destination + class HapiOrderTest extends Specification { - def "getUnderlyingOrder Works"() { + + def fhirEngine = HapiFhirImplementation.getInstance() + + def setup() { + TestApplicationContext.reset() + TestApplicationContext.init() + TestApplicationContext.register(HapiFhir.class, fhirEngine) + TestApplicationContext.register(HapiMessageHelper.class, HapiMessageHelper.getInstance()) + TestApplicationContext.injectRegisteredImplementations() + } + + def "getUnderlyingResource Works"() { given: def expectedInnerOrder = new Bundle() def order = new HapiOrder(expectedInnerOrder) when: - def actualInnerOrder = order.getUnderlyingOrder() + def actualInnerOrder = order.getUnderlyingResource() then: actualInnerOrder == expectedInnerOrder @@ -64,4 +87,284 @@ class HapiOrderTest extends Specification { then: orders.getPatientId() == expectedPatientId } + + def "getPlacerOrderNumber works"() { + given: + def expectedPlacerOrderNumber = "mock-placer-order-number" + def bundle = new Bundle() + def serviceRequest = new ServiceRequest().addIdentifier(new Identifier() + .setValue(expectedPlacerOrderNumber) + .setType(new CodeableConcept().addCoding(new Coding("http://terminology.hl7.org/CodeSystem/v2-0203", "PLAC", "Placer Identifier")))) + bundle.addEntry(new Bundle.BundleEntryComponent().setResource(serviceRequest)) + def order = new HapiOrder(bundle) + + when: + def actualPlacerOrderNumber = order.getPlacerOrderNumber() + + then: + actualPlacerOrderNumber == expectedPlacerOrderNumber + } + + def "getPlacerOrderNumber unhappy path"() { + given: + def orders = setupOrderWithEmptyMessageHeader() + def expectedPlacerOrderNumber = "" + + when: + def actualPlacerOrderNumber = orders.getPlacerOrderNumber() + + then: + actualPlacerOrderNumber == expectedPlacerOrderNumber + } + + def "getSendingApplicationDetails happy path works"() { + given: + def nameSpaceId = "Natus" + def universalId = "natus.health.state.mn.us" + def universalIdType = "DNS" + def orders = setupOrderWithSendingApplicationDetails(nameSpaceId, universalId, universalIdType) + def expectedApplicationDetails = new MessageHdDataType(nameSpaceId, universalId, universalIdType) + + when: + def actualApplicationDetails = orders.getSendingApplicationDetails() + + then: + actualApplicationDetails.namespace() == expectedApplicationDetails.namespace() + actualApplicationDetails.universalId() == expectedApplicationDetails.universalId() + actualApplicationDetails.universalIdType() == expectedApplicationDetails.universalIdType() + } + + def "getSendingApplicationDetails unhappy path works"() { + given: + def expectedApplicationDetails = new MessageHdDataType("", "", "") + def orders = setupOrderWithEmptyMessageHeader() + + when: + def actualApplicationDetails = orders.getSendingApplicationDetails() + + then: + actualApplicationDetails.namespace() == expectedApplicationDetails.namespace() + actualApplicationDetails.universalId() == expectedApplicationDetails.universalId() + actualApplicationDetails.universalIdType() == expectedApplicationDetails.universalIdType() + } + + def "getSendingFacilityDetails happy path works"() { + given: + def facilityName = "MN Public Health Lab" + def universalId = "2.16.840.1.114222.4.1.10080" + def universalIdType = "ISO" + def orders = setupOrderWithSendingFacilityDetails(facilityName, universalId, universalIdType) + def expectedFacilityDetails = new MessageHdDataType(facilityName, universalId, universalIdType) + + when: + def actualFacilityDetails = orders.getSendingFacilityDetails() + + then: + actualFacilityDetails.namespace() == expectedFacilityDetails.namespace() + actualFacilityDetails.universalId() == expectedFacilityDetails.universalId() + actualFacilityDetails.universalIdType() == expectedFacilityDetails.universalIdType() + } + + def "getSendingFacilityDetails unhappy path works"() { + given: + def expectedFacilityDetails = new MessageHdDataType("", "", "") + def orders = setupOrderWithEmptyMessageHeader() + + when: + def actualFacilityDetails = orders.getSendingFacilityDetails() + + then: + actualFacilityDetails.namespace() == expectedFacilityDetails.namespace() + actualFacilityDetails.universalId() == expectedFacilityDetails.universalId() + actualFacilityDetails.universalIdType() == expectedFacilityDetails.universalIdType() + } + + def "getReceivingApplicationDetails happy path works"() { + given: + def universalId = "1.2.840.114350.1.13.145.2.7.2.695071" + def namespaceId = "Epic" + def universalIdType = "ISO" + def expectedApplicationDetails = new MessageHdDataType(namespaceId, universalId, universalIdType) + def orders = setupOrderWithReceivingApplicationDetails(namespaceId, universalId, universalIdType) + + when: + def actualApplicationDetails = orders.getReceivingApplicationDetails() + + then: + actualApplicationDetails.namespace() == expectedApplicationDetails.namespace() + actualApplicationDetails.universalId() == expectedApplicationDetails.universalId() + actualApplicationDetails.universalIdType() == expectedApplicationDetails.universalIdType() + } + + def "getReceivingApplicationDetails unhappy path works"() { + given: + def orders = setupOrderWithEmptyMessageHeader() + def expectedApplicationDetails = new MessageHdDataType("", "", "") + + when: + def actualApplicationDetails = orders.getReceivingApplicationDetails() + + then: + actualApplicationDetails.namespace() == expectedApplicationDetails.namespace() + actualApplicationDetails.universalId() == expectedApplicationDetails.universalId() + actualApplicationDetails.universalIdType() == expectedApplicationDetails.universalIdType() + } + + def "getReceivingFacilityDetails happy path works"() { + given: + def facilityName = "Central Hospital" + def universalId = "2.16.840.1.113883.3.4.5" + def universalIdType = "ISO" + def expectedFacilityDetails = new MessageHdDataType(facilityName, universalId, universalIdType) + def orders = setupOrderWithReceivingFacilityDetails(facilityName, universalId, universalIdType) + + when: + def actualFacilityDetails = orders.getReceivingFacilityDetails() + + then: + actualFacilityDetails.namespace() == expectedFacilityDetails.namespace() + actualFacilityDetails.universalId() == expectedFacilityDetails.universalId() + actualFacilityDetails.universalIdType() == expectedFacilityDetails.universalIdType() + } + + def "getReceivingFacilityDetails unhappy path works"() { + given: + def expectedFacilityDetails = new MessageHdDataType("", "", "") + def orders = setupOrderWithEmptyMessageHeader() + + when: + def actualFacilityDetails = orders.getReceivingFacilityDetails() + + then: + actualFacilityDetails.namespace() == expectedFacilityDetails.namespace() + actualFacilityDetails.universalId() == expectedFacilityDetails.universalId() + actualFacilityDetails.universalIdType() == expectedFacilityDetails.universalIdType() + } + + def "extractMessageHdDataType works" () { + given: + def namespace = "Central Hospital" + def universalId = "2.16.842.1.113883.4.5" + def universalIdType = "ISO" + def expectedDetails = new MessageHdDataType(namespace, universalId, universalIdType) + def hapiResult = new HapiResult(null) + + when: + def actualDetails = hapiResult.extractMessageHdDataType( + {namespace}, + {universalId}, + {universalIdType}) + + then: + actualDetails.namespace() == expectedDetails.namespace() + actualDetails.universalId() == expectedDetails.universalId() + actualDetails.universalIdType() == expectedDetails.universalIdType() + } + + protected HapiOrder setupOrderWithSendingApplicationDetails(String nameSpaceId, String universalId, String universalIdType) { + def innerOrders = new Bundle() + def messageHeader = new MessageHeader() + def endpoint = "urn:dns:natus.health.state.mn.us" + messageHeader.setSource(new MessageHeader.MessageSourceComponent(new UrlType(endpoint))) + def nameSpaceIdExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", new StringType(nameSpaceId)) + messageHeader.getSource().addExtension(nameSpaceIdExtension) + def universalIdExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", new StringType(universalId)) + messageHeader.getSource().addExtension(universalIdExtension) + def universalIdTypeExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type", new StringType(universalIdType)) + messageHeader.getSource().addExtension(universalIdTypeExtension) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + return new HapiOrder(innerOrders) + } + + protected HapiOrder setupOrderWithSendingFacilityDetails(String facilityName, String universalId, String universalIdType) { + def innerOrders = new Bundle() + def messageHeader = new MessageHeader() + def orgReference = "Organization/1708034743302204787.82104dfb-e854-47de-b7ce-19a2b71e61db" + messageHeader.setSender(new Reference(orgReference)) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + def organization = new Organization() + organization.setId("1708034743302204787.82104dfb-e854-47de-b7ce-19a2b71e61db") + // facility name + def facilityIdentifier = new Identifier() + def facilityNameExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", new StringType("HD.1")) + facilityIdentifier.addExtension(facilityNameExtension) + facilityIdentifier.setValue(facilityName) + // universal id + def universalIdIdentifier = new Identifier() + def universalIdExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", new StringType("HD.2,HD.3")) + universalIdIdentifier.addExtension(universalIdExtension) + universalIdIdentifier.setValue(universalId) + // Type + def typeConcept = new CodeableConcept() + def coding = new Coding("http://terminology.hl7.org/CodeSystem/v2-0301", universalIdType, null) + typeConcept.addCoding(coding) + universalIdIdentifier.setType(typeConcept) + + organization.addIdentifier(facilityIdentifier) + organization.addIdentifier(universalIdIdentifier) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(organization)) + + // Convert orders to json so the reference is added as part of the bundle so we can use .resolve() + // as part of the fhir path. + def jsonOrders = fhirEngine.parseResource(fhirEngine.encodeResourceToJson(innerOrders), Bundle) + return new HapiOrder(jsonOrders) + } + + protected HapiOrder setupOrderWithReceivingApplicationDetails(String namespaceId, String universalId, String universalIdType) { + def innerOrders = new Bundle() + def messageHeader = new MessageHeader() + def destination = new MessageHeader.MessageDestinationComponent() + def universalIdExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", new StringType(universalId)) + def universalIdTypeExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type", new StringType(universalIdType)) + + destination.setName(namespaceId) + destination.addExtension(universalIdExtension) + destination.addExtension(universalIdTypeExtension) + messageHeader.setDestination([destination]) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + return new HapiOrder(innerOrders) + } + + protected HapiOrder setupOrderWithReceivingFacilityDetails(String facilityName, String universalId, String universalIdType) { + def innerOrders = new Bundle() + def messageHeader = new MessageHeader() + def organizationReference = "Organization/1708034743312390878.b61e734a-4d65-4e25-b423-cdb19018d84a" + def destination = new MessageHeader.MessageDestinationComponent() + destination.setReceiver(new Reference(organizationReference)) + messageHeader.addDestination(destination) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + def organization = new Organization() + organization.setId("1708034743312390878.b61e734a-4d65-4e25-b423-cdb19018d84a") + // Facility + def identifierFacilityName = new Identifier() + def extensionFacilityName = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", new StringType("HD.1")) + identifierFacilityName.addExtension(extensionFacilityName) + identifierFacilityName.setValue(facilityName) + // Universal ID + def identifierUniversalId = new Identifier() + def extensionUniversalId = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", new StringType("HD.2,HD.3")) + identifierUniversalId.addExtension(extensionUniversalId) + identifierUniversalId.setValue(universalId) + // Universal ID type + def coding = new Coding("http://terminology.hl7.org/CodeSystem/v2-0203", universalIdType, null) + def typeCodeableConcept = new CodeableConcept() + typeCodeableConcept.addCoding(coding) + identifierUniversalId.setType(typeCodeableConcept) + + organization.addIdentifier(identifierFacilityName) + organization.addIdentifier(identifierUniversalId) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(organization)) + + // Convert orders to json so the reference is added as part of the bundle so we can use .resolve() + // as part of the fhir path. + def jsonOrders = fhirEngine.parseResource(fhirEngine.encodeResourceToJson(innerOrders), Bundle) + return new HapiOrder(jsonOrders) + } + + protected HapiOrder setupOrderWithEmptyMessageHeader() { + def innerOrders = new Bundle() + MessageHeader messageHeader = new MessageHeader() + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + return new HapiOrder(innerOrders) + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy index a143d6631..42285b7f5 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultConverterTest.groovy @@ -20,11 +20,12 @@ class HapiResultConverterTest extends Specification { TestApplicationContext.init() TestApplicationContext.register(ResultConverter, HapiResultConverter.getInstance()) TestApplicationContext.register(HapiMessageConverterHelper, HapiMessageConverterHelper.getInstance()) + TestApplicationContext.register(HapiMessageHelper, HapiMessageHelper.getInstance()) TestApplicationContext.injectRegisteredImplementations() mockPatient = new Patient() mockResultBundle = new Bundle().addEntry(new Bundle.BundleEntryComponent().setResource(mockPatient)) - mockResult = new ResultMock("mockFhirResourceId", mockResultBundle) + mockResult = new ResultMock("mockFhirResourceId", mockResultBundle, null, null, null, null, null) } def "add etor processing tag to messageHeader resource"() { @@ -37,10 +38,10 @@ class HapiResultConverterTest extends Specification { messageHeader.setId(UUID.randomUUID().toString()) def messageHeaderEntry = new Bundle.BundleEntryComponent().setResource(messageHeader) mockResultBundle.getEntry().add(1, messageHeaderEntry) - mockResult.getUnderlyingResult() >> mockResultBundle + mockResult.getUnderlyingResource() >> mockResultBundle when: - def convertedResultBundle = HapiResultConverter.getInstance().addEtorProcessingTag(mockResult).getUnderlyingResult() as Bundle + def convertedResultBundle = HapiResultConverter.getInstance().addEtorProcessingTag(mockResult).getUnderlyingResource() as Bundle then: def messageHeaders = convertedResultBundle.getEntry().get(1).getResource() as MessageHeader diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultTest.groovy index f40ada0ba..5997a313b 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiResultTest.groovy @@ -1,16 +1,40 @@ package gov.hhs.cdc.trustedintermediary.external.hapi +import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext +import gov.hhs.cdc.trustedintermediary.etor.messages.MessageHdDataType +import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir import org.hl7.fhir.r4.model.Bundle +import org.hl7.fhir.r4.model.CodeableConcept +import org.hl7.fhir.r4.model.Coding +import org.hl7.fhir.r4.model.Extension +import org.hl7.fhir.r4.model.Identifier +import org.hl7.fhir.r4.model.MessageHeader +import org.hl7.fhir.r4.model.Organization +import org.hl7.fhir.r4.model.Reference +import org.hl7.fhir.r4.model.ServiceRequest +import org.hl7.fhir.r4.model.StringType +import org.hl7.fhir.r4.model.UrlType import spock.lang.Specification -class HapiResultTest extends Specification{ - def "getUnderlyingResult works"() { +class HapiResultTest extends Specification { + + def fhirEngine = HapiFhirImplementation.getInstance() + + def setup() { + TestApplicationContext.reset() + TestApplicationContext.init() + TestApplicationContext.register(HapiFhir.class, fhirEngine) + TestApplicationContext.register(HapiMessageHelper.class, HapiMessageHelper.getInstance()) + TestApplicationContext.injectRegisteredImplementations() + } + + def "getUnderlyingResource works"() { given: def expectedResult = new Bundle() def result = new HapiResult(expectedResult) when: - def actualResult = result.getUnderlyingResult() + def actualResult = result.getUnderlyingResource() then: actualResult == expectedResult @@ -28,4 +52,284 @@ class HapiResultTest extends Specification{ then: actualResult.getFhirResourceId() == expectId } + + def "getPlacerOrderNumber works"() { + given: + def expectedPlacerOrderNumber = "mock-placer-order-number" + def bundle = new Bundle() + def serviceRequest = new ServiceRequest().addIdentifier(new Identifier() + .setValue(expectedPlacerOrderNumber) + .setType(new CodeableConcept().addCoding(new Coding("http://terminology.hl7.org/CodeSystem/v2-0203", "PLAC", "Placer Identifier")))) + bundle.addEntry(new Bundle.BundleEntryComponent().setResource(serviceRequest)) + def result = new HapiResult(bundle) + + when: + def actualPlacerOrderNumber = result.getPlacerOrderNumber() + + then: + actualPlacerOrderNumber == expectedPlacerOrderNumber + } + + def "getPlacerOrderNumber unhappy path"() { + given: + def expectedPlacerOrderNumber = "" + def result = setupResultWithEmptyMessageHeader() + + when: + def actualPlacerOrderNumber = result.getPlacerOrderNumber() + + then: + actualPlacerOrderNumber == expectedPlacerOrderNumber + } + + def "getSendingApplicationDetails works"() { + given: + def nameSpaceId = "Natus" + def universalId = "natus.health.state.mn.us" + def universalIdType = "DNS" + def expectedApplicationDetails = new MessageHdDataType(nameSpaceId, universalId, universalIdType) + def results = setupResultWithSendingApplicationDetails(nameSpaceId, universalId, universalIdType) + + when: + def actualApplicationDetails = results.getSendingApplicationDetails() + + then: + actualApplicationDetails.namespace() == expectedApplicationDetails.namespace() + actualApplicationDetails.universalId() == expectedApplicationDetails.universalId() + actualApplicationDetails.universalIdType() == expectedApplicationDetails.universalIdType() + } + + def "getSendingApplicationDetails unhappy path"() { + given: + def expectedApplicationDetails = new MessageHdDataType("", "", "") + def results = setupResultWithEmptyMessageHeader() + + when: + def actualApplicationDetails = results.getSendingApplicationDetails() + + then: + actualApplicationDetails.namespace() == expectedApplicationDetails.namespace() + actualApplicationDetails.universalId() == expectedApplicationDetails.universalId() + actualApplicationDetails.universalIdType() == expectedApplicationDetails.universalIdType() + } + + def "getSendingFacilityDetails happy path works"() { + given: + def facilityName = "MN Public Health Lab" + def universalId = "2.16.840.1.114222.4.1.10080" + def universalIdType = "ISO" + def results = setupResultWithSendingFacilityDetails(facilityName, universalId, universalIdType) + def expectedFacilityDetails = new MessageHdDataType(facilityName, universalId, universalIdType) + + when: + def actualFacilityDetails = results.getSendingFacilityDetails() + + then: + actualFacilityDetails.namespace() == expectedFacilityDetails.namespace() + actualFacilityDetails.universalId() == expectedFacilityDetails.universalId() + actualFacilityDetails.universalIdType() == expectedFacilityDetails.universalIdType() + } + + def "getSendingFacilityDetails unhappy path works"() { + given: + def expectedFacilityDetails = new MessageHdDataType("", "", "") + def results = setupResultWithEmptyMessageHeader() + + when: + def actualFacilityDetails = results.getSendingFacilityDetails() + + then: + actualFacilityDetails.namespace() == expectedFacilityDetails.namespace() + actualFacilityDetails.universalId() == expectedFacilityDetails.universalId() + actualFacilityDetails.universalIdType() == expectedFacilityDetails.universalIdType() + } + + def "getReceivingApplicationDetails works"() { + given: + def namespaceId = "Epic" + def universalId = "1.2.840.114350.1.13.145.2.7.2.695071" + def universalIdType = "ISO" + def expectedApplicationDetails = new MessageHdDataType(namespaceId, universalId, universalIdType) + def results = setupResultWithReceivingApplicationDetails(namespaceId, universalId, universalIdType) + + when: + def actualApplicationDetails = results.getReceivingApplicationDetails() + + then: + actualApplicationDetails.namespace() == expectedApplicationDetails.namespace() + actualApplicationDetails.universalId() == expectedApplicationDetails.universalId() + actualApplicationDetails.universalIdType() == expectedApplicationDetails.universalIdType() + } + + def "getReceivingApplicationDetails unhappy path"() { + given: + def results = setupResultWithEmptyMessageHeader() + def expectedApplicationDetails = new MessageHdDataType("", "", "") + + when: + def actualApplicationDetails = results.getReceivingApplicationDetails() + + then: + actualApplicationDetails.namespace() == expectedApplicationDetails.namespace() + actualApplicationDetails.universalId() == expectedApplicationDetails.universalId() + actualApplicationDetails.universalIdType() == expectedApplicationDetails.universalIdType() + } + + def "getReceivingFacilityDetails works"() { + given: + def facilityName = "Samtracare" + def universalId = "Samtracare.com" + def universalIdType = "DNS" + def expectedFacilityDetails = new MessageHdDataType(facilityName, universalId, universalIdType) + def results = setupResultWithReceivingFacilityDetails(facilityName, universalId, universalIdType) + + when: + def actualFacilityDetails = results.getReceivingFacilityDetails() + + then: + actualFacilityDetails.namespace() == expectedFacilityDetails.namespace() + actualFacilityDetails.universalId() == expectedFacilityDetails.universalId() + actualFacilityDetails.universalIdType() == expectedFacilityDetails.universalIdType() + } + + def "getReceivingFacilityDetails unhappy path"() { + given: + def results = setupResultWithEmptyMessageHeader() + def expectedApplicationDetails = new MessageHdDataType("", "", "") + + when: + def actualApplicationDetails = results.getReceivingFacilityDetails() + + then: + actualApplicationDetails.namespace() == expectedApplicationDetails.namespace() + actualApplicationDetails.universalId() == expectedApplicationDetails.universalId() + actualApplicationDetails.universalIdType() == expectedApplicationDetails.universalIdType() + } + + def "extractMessageHdDataType works" () { + given: + def namespace = "Central Hospital" + def universalId = "2.16.842.1.113883.4.5" + def universalIdType = "ISO" + def expectedDetails = new MessageHdDataType(namespace, universalId, universalIdType) + def hapiResult = new HapiResult(null) + + when: + def actualDetails = hapiResult.extractMessageHdDataType( + {namespace}, + {universalId}, + {universalIdType}) + + then: + actualDetails.namespace() == expectedDetails.namespace() + actualDetails.universalId() == expectedDetails.universalId() + actualDetails.universalIdType() == expectedDetails.universalIdType() + } + + protected HapiResult setupResultWithSendingApplicationDetails(String nameSpaceId, String universalId, String universalIdType) { + def innerOrders = new Bundle() + def messageHeader = new MessageHeader() + def endpoint = "urn:dns:natus.health.state.mn.us" + messageHeader.setSource(new MessageHeader.MessageSourceComponent(new UrlType(endpoint))) + def nameSpaceIdExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", new StringType(nameSpaceId)) + messageHeader.getSource().addExtension(nameSpaceIdExtension) + def universalIdExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", new StringType(universalId)) + messageHeader.getSource().addExtension(universalIdExtension) + def universalIdTypeExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type", new StringType(universalIdType)) + messageHeader.getSource().addExtension(universalIdTypeExtension) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + return new HapiResult(innerOrders) + } + + protected HapiResult setupResultWithSendingFacilityDetails(String facilityName, String universalId, String universalIdType) { + def innerOrders = new Bundle() + def messageHeader = new MessageHeader() + def orgReference = "Organization/1708034743302204787.82104dfb-e854-47de-b7ce-19a2b71e61db" + messageHeader.setSender(new Reference(orgReference) as Reference) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + def organization = new Organization() + organization.setId("1708034743302204787.82104dfb-e854-47de-b7ce-19a2b71e61db") + // facility name + def facilityIdentifier = new Identifier() + def facilityNameExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", new StringType("HD.1")) + facilityIdentifier.addExtension(facilityNameExtension) + facilityIdentifier.setValue(facilityName) + // universal id + def universalIdIdentifier = new Identifier() + def universalIdExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", new StringType("HD.2,HD.3")) + universalIdIdentifier.addExtension(universalIdExtension) + universalIdIdentifier.setValue(universalId) + // Type + def typeConcept = new CodeableConcept() + def coding = new Coding("http://terminology.hl7.org/CodeSystem/v2-0301", universalIdType, null) + typeConcept.addCoding(coding) + universalIdIdentifier.setType(typeConcept) + + organization.addIdentifier(facilityIdentifier) + organization.addIdentifier(universalIdIdentifier) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(organization)) + + // Convert orders to json so the reference is added as part of the bundle so we can use .resolve() + // as part of the fhir path. + def jsonOrders = fhirEngine.parseResource(fhirEngine.encodeResourceToJson(innerOrders), Bundle) + return new HapiResult(jsonOrders) + } + + protected HapiResult setupResultWithReceivingApplicationDetails(String namespaceId, String universalId, String universalIdType) { + def innerOrders = new Bundle() + def messageHeader = new MessageHeader() + def destination = new MessageHeader.MessageDestinationComponent() + def universalIdExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", new StringType(universalId)) + def universalIdTypeExtension = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type", new StringType(universalIdType)) + + destination.setName(namespaceId) + destination.addExtension(universalIdExtension) + destination.addExtension(universalIdTypeExtension) + messageHeader.setDestination([destination]) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + return new HapiResult(innerOrders) + } + + protected HapiResult setupResultWithReceivingFacilityDetails(String facilityName, String universalId, String universalIdType) { + def innerOrders = new Bundle() + def messageHeader = new MessageHeader() + def organizationReference = "Organization/1708034743312390878.b61e734a-4d65-4e25-b423-cdb19018d84a" + def destination = new MessageHeader.MessageDestinationComponent() + destination.setReceiver(new Reference(organizationReference)) + messageHeader.addDestination(destination) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + def organization = new Organization() + organization.setId("1708034743312390878.b61e734a-4d65-4e25-b423-cdb19018d84a") + // Facility + def identifierFacilityName = new Identifier() + def extensionFacilityName = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", new StringType("HD.1")) + identifierFacilityName.addExtension(extensionFacilityName) + identifierFacilityName.setValue(facilityName) + // Universal ID + def identifierUniversalId = new Identifier() + def extensionUniversalId = new Extension("https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", new StringType("HD.2,HD.3")) + identifierUniversalId.addExtension(extensionUniversalId) + identifierUniversalId.setValue(universalId) + // Universal ID type + def coding = new Coding("http://terminology.hl7.org/CodeSystem/v2-0203", universalIdType, null) + def typeCodeableConcept = new CodeableConcept() + typeCodeableConcept.addCoding(coding) + identifierUniversalId.setType(typeCodeableConcept) + + organization.addIdentifier(identifierFacilityName) + organization.addIdentifier(identifierUniversalId) + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(organization)) + + // Convert orders to json so the reference is added as part of the bundle so we can use .resolve() + // as part of the fhir path. + def jsonOrders = fhirEngine.parseResource(fhirEngine.encodeResourceToJson(innerOrders), Bundle) + return new HapiResult(jsonOrders) + } + + protected HapiResult setupResultWithEmptyMessageHeader() { + def innerOrders = new Bundle() + MessageHeader messageHeader = new MessageHeader() + innerOrders.addEntry(new Bundle.BundleEntryComponent().setResource(messageHeader)) + return new HapiResult(innerOrders) + } } diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamOrderSenderTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamOrderSenderTest.groovy index 803fdf685..a08cec1b9 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamOrderSenderTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamOrderSenderTest.groovy @@ -23,7 +23,7 @@ class ReportStreamOrderSenderTest extends Specification { given: def fhirResourceId = null def underlyingOrder = "Mock order" - def mockOrder = new OrderMock(fhirResourceId, "patient-id", underlyingOrder) + def mockOrder = new OrderMock(fhirResourceId, "patient-id", underlyingOrder, null, null, null, null, null) def senderHelper = Mock(ReportStreamSenderHelper) senderHelper.sendOrderToReportStream(underlyingOrder, fhirResourceId) >> Optional.of("fake-id") diff --git a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamResultSenderTest.groovy b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamResultSenderTest.groovy index c6428150d..29a8a6772 100644 --- a/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamResultSenderTest.groovy +++ b/etor/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/reportstream/ReportStreamResultSenderTest.groovy @@ -18,7 +18,7 @@ class ReportStreamResultSenderTest extends Specification { given: def fhirResourceId = null def underlyingResult = "Mock result" - def mockResult = new ResultMock(fhirResourceId, underlyingResult) + def mockResult = new ResultMock(fhirResourceId, underlyingResult, null, null, null, null, null) def senderHelper = Mock(ReportStreamSenderHelper) senderHelper.sendResultToReportStream(underlyingResult, fhirResourceId) >> Optional.of("fake-id") diff --git a/shared/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirImplementation.java b/shared/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirImplementation.java index d0ae43926..e37c2a108 100644 --- a/shared/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirImplementation.java +++ b/shared/src/main/java/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirImplementation.java @@ -6,6 +6,7 @@ import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException; import gov.hhs.cdc.trustedintermediary.wrappers.HapiFhir; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Base; import org.hl7.fhir.r4.model.BooleanType; /** Concrete implementation that calls the Hapi FHIR library. */ @@ -72,4 +73,26 @@ public Boolean evaluateCondition(Object resource, String expression) { PATH_ENGINE.evaluateFirst((IBaseResource) resource, expression, BooleanType.class); return result.map(BooleanType::booleanValue).orElse(false); } + + /** + * Retrieves a string result by evaluating a specified FHIRPath expression against a given FHIR + * resource. This method simplifies accessing textual data within FHIR resources by directly + * returning the string representation of the first matching element found by the FHIRPath + * expression. If no match is found, or the first result cannot be represented as a string, an + * empty string is returned. + * + * @param resource The FHIR resource upon which the FHIRPath expression will be evaluated. This + * resource acts as the context for the FHIRPath evaluation. + * @param expression The FHIRPath expression to be evaluated against the resource. The + * expression should be crafted to select textual data or elements that can be represented + * as text. + * @return The string representation of the first matching result of the FHIRPath expression + * evaluation. Returns an empty string if no matching element is found, or if the first + * result cannot be represented as a string. + */ + @Override + public String getStringFromFhirPath(Object resource, String expression) { + var result = PATH_ENGINE.evaluateFirst((IBaseResource) resource, expression, Base.class); + return result.map(Base::primitiveValue).orElse(""); + } } diff --git a/shared/src/main/java/gov/hhs/cdc/trustedintermediary/wrappers/HapiFhir.java b/shared/src/main/java/gov/hhs/cdc/trustedintermediary/wrappers/HapiFhir.java index f2537bb45..bc09b2cbf 100644 --- a/shared/src/main/java/gov/hhs/cdc/trustedintermediary/wrappers/HapiFhir.java +++ b/shared/src/main/java/gov/hhs/cdc/trustedintermediary/wrappers/HapiFhir.java @@ -15,4 +15,6 @@ T parseResource(String fhirResource, Class clazz) String encodeResourceToJson(Object resource); Boolean evaluateCondition(Object resource, String expression); + + String getStringFromFhirPath(Object resource, String expression); } diff --git a/shared/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirImplementationTest.groovy b/shared/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirImplementationTest.groovy index d388ab047..afa6cc165 100644 --- a/shared/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirImplementationTest.groovy +++ b/shared/src/test/groovy/gov/hhs/cdc/trustedintermediary/external/hapi/HapiFhirImplementationTest.groovy @@ -1,12 +1,12 @@ package gov.hhs.cdc.trustedintermediary.external.hapi -import ca.uhn.fhir.fhirpath.FhirPathExecutionException import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext import gov.hhs.cdc.trustedintermediary.wrappers.FhirParseException import org.hl7.fhir.instance.model.api.IBaseResource import org.hl7.fhir.r4.model.Bundle import org.hl7.fhir.r4.model.DiagnosticReport import org.hl7.fhir.r4.model.ServiceRequest +import org.hl7.fhir.r4.model.StringType import spock.lang.Specification import java.nio.file.Files @@ -97,6 +97,45 @@ class HapiFhirImplementationTest extends Specification { thrown(Exception) } + def "getStringFromFhirPath returns correct string value for existing path"() { + given: + def path = "Bundle.entry[0].resource.id" + def expected = diaReport.id + + when: + def actual = fhir.getStringFromFhirPath(bundle as IBaseResource, path) + + then: + actual == expected + } + + def "getStringFromFhirPath returns empty string fro non-existing path"() { + given: + def path = "Bundle.entry[0].resource.nonExistingProperty" + def expected = "" + + when: + def actual = fhir.getStringFromFhirPath(bundle as IBaseResource, path) + + then: + actual == expected + } + + def "getStringFromFhirPath handles complex paths correctly"() { + given: + def extensionUrl = "http://example.org/fhir/StructureDefinition/testExtension" + def extensionValue = "DogCow" + servRequest.addExtension(extensionUrl, new StringType(extensionValue)) + def path = "Bundle.entry.resource.ofType(ServiceRequest).extension('http://example.org/fhir/StructureDefinition/testExtension').value" + def expected = extensionValue + + when: + def actual = fhir.getStringFromFhirPath(bundle as IBaseResource, path) + + then: + actual == expected + } + def "parseResource can convert a valid string to Bundle"() { given: def fhirBody = Files.readString(Path.of("../examples/Test/Orders/001_OML_O21_short.fhir"))