From 16e329e40c7374f4485c49cdf784e5de8c48a018 Mon Sep 17 00:00:00 2001 From: "juan.marchionatto" Date: Wed, 15 Sep 2021 10:29:30 -0400 Subject: [PATCH 1/2] Add version to ValueSet.compose.include when uploading terminology --- .../uhn/fhir/jpa/term/TermLoaderSvcImpl.java | 2 +- .../fhir/jpa/term/loinc/BaseLoincHandler.java | 4 + .../term/loinc/LoincAnswerListHandler.java | 1 + .../term/loinc/LoincRsnaPlaybookHandler.java | 1 + .../jpa/term/loinc/BaseLoincHandlerTest.java | 304 ++++++++++++++++++ 5 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincHandlerTest.java diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java index 60f2952754c..be31eb190c3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermLoaderSvcImpl.java @@ -723,7 +723,7 @@ public class TermLoaderSvcImpl implements ITermLoaderSvc { retVal.setPublisher("Regenstrief Institute, Inc."); retVal.setDescription("A value set that includes all LOINC codes"); retVal.setCopyright("This content from LOINC® is copyright © 1995 Regenstrief Institute, Inc. and the LOINC Committee, and available at no cost under the license at https://loinc.org/license/"); - retVal.getCompose().addInclude().setSystem(ITermLoaderSvc.LOINC_URI); + retVal.getCompose().addInclude().setSystem(ITermLoaderSvc.LOINC_URI).setVersion(codeSystemVersionId); return retVal; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincHandler.java index cba3609fa8b..7cd49e97c45 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincHandler.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.term.loinc; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.term.IZipContentsHandlerCsv; +import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ContactPoint; import org.hl7.fhir.r4.model.Enumerations; @@ -74,6 +75,9 @@ public abstract class BaseLoincHandler implements IZipContentsHandlerCsv { if (include == null) { include = theVs.getCompose().addInclude(); include.setSystem(theCodeSystemUrl); + if (StringUtils.isNotBlank(theVs.getVersion())) { + include.setVersion(theVs.getVersion()); + } } boolean found = false; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListHandler.java index cdb97ca48d8..037a2fb0e6b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListHandler.java @@ -104,6 +104,7 @@ public class LoincAnswerListHandler extends BaseLoincHandler { .getCompose() .getIncludeFirstRep() .setSystem(ITermLoaderSvc.LOINC_URI) + .setVersion(codeSystemVersionId) .addConcept() .setCode(answerString) .setDisplay(displayText); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java index d0b03f0acc9..d81466f5380 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java @@ -120,6 +120,7 @@ public class LoincRsnaPlaybookHandler extends BaseLoincHandler implements IZipCo .getCompose() .getIncludeFirstRep() .setSystem(ITermLoaderSvc.LOINC_URI) + .setVersion(codeSystemVersionId) .addConcept() .setCode(loincNumber) .setDisplay(longCommonName); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincHandlerTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincHandlerTest.java new file mode 100644 index 00000000000..9a304b33bbb --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/loinc/BaseLoincHandlerTest.java @@ -0,0 +1,304 @@ +package ca.uhn.fhir.jpa.term.loinc; + +import com.google.common.collect.Maps; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.r4.model.ValueSet; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.StringReader; +import java.util.Map; +import java.util.Properties; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class BaseLoincHandlerTest { + + public static final String LOINC_NUMBER = "test-loinc-number"; + public static final String DISPLAY_NAME = "test-display-name"; + public static final String TEST_VERSION = "2.69"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ValueSet myValueSet; + + @Mock + private ValueSet.ConceptSetComponent myInclude; + + @Mock + private ValueSet.ConceptReferenceComponent myConceptReferenceComponent; + + private Map headerMap; + private CSVRecord recordWithHeader; + + + /** + * Need to setup a real parser because we can't mock final classes (without PowerMockito) + */ + private void setupCSVParser(Map headerValues) throws Exception { + final String[] headers = headerValues.keySet().toArray(new String[0]); + final String[] values = headerValues.values().toArray(new String[0]); + final String rowData = StringUtils.join(values, ','); + + try (final CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(rowData))) { + parser.iterator().next(); + } + try (final CSVParser parser = CSVFormat.DEFAULT.withHeader(headers).parse(new StringReader(rowData))) { + recordWithHeader = parser.iterator().next(); + headerMap = parser.getHeaderMap(); + } + } + + + @Nested + public class WithVersion { + + @Test + void Top2000LabResultsHandlerTest() throws Exception { + Map recordDataMap = Maps.newHashMap(); + recordDataMap.put("LOINC #", "test-loinc-number"); + recordDataMap.put("Long Common Name", "test-Long-Common-Names"); + setupCSVParser(recordDataMap); + + BaseLoincHandler loincHandler = new BaseLoincTop2000LabResultsHandler( + null, Lists.newArrayList(), null, null, null, Lists.newArrayList(), new Properties()); + BaseLoincHandler spiedLoincHandler = spy(loincHandler); + + when(spiedLoincHandler.getValueSet(any(), any(), any(), any())).thenReturn(myValueSet); + when(myValueSet.getVersion()).thenReturn(TEST_VERSION); + when(myValueSet.getCompose().addInclude()).thenReturn(myInclude); + when(myInclude.addConcept()).thenReturn(myConceptReferenceComponent); + when(myConceptReferenceComponent.setCode(any())).thenReturn(myConceptReferenceComponent); + + spiedLoincHandler.accept(recordWithHeader); + + Mockito.verify(myInclude).setVersion(Mockito.notNull()); + } + + @Test + void LoincDocumentOntologyHandlerTest() throws Exception { + Map recordDataMap = Maps.newHashMap(); + recordDataMap.put("LoincNumber", "test-LoincNumber"); + recordDataMap.put("PartNumber", "test-PartNumber"); + recordDataMap.put("PartTypeName", "Document.Role"); + recordDataMap.put("PartSequenceOrder", "test-PartSequenceOrder"); + recordDataMap.put("PartName", "test-PartName"); + setupCSVParser(recordDataMap); + + BaseLoincHandler loincHandler = new LoincDocumentOntologyHandler( + Maps.newHashMap(), null, Lists.newArrayList(), Lists.newArrayList(), new Properties()); + BaseLoincHandler spiedLoincHandler = spy(loincHandler); + + when(spiedLoincHandler.getValueSet(any(), any(), any(), any())).thenReturn(myValueSet); + when(myValueSet.getVersion()).thenReturn(TEST_VERSION); + when(myValueSet.getCompose().addInclude()).thenReturn(myInclude); + when(myInclude.addConcept()).thenReturn(myConceptReferenceComponent); + when(myConceptReferenceComponent.setCode(any())).thenReturn(myConceptReferenceComponent); + + spiedLoincHandler.accept(recordWithHeader); + + Mockito.verify(myInclude).setVersion(Mockito.notNull()); + } + + + @Test + void LoincGroupTermsFileHandlerTest() throws Exception { + Map recordDataMap = Maps.newHashMap(); + recordDataMap.put("LoincNumber", "test-LoincNumber"); + recordDataMap.put("GroupId", "test-GroupId"); + setupCSVParser(recordDataMap); + + BaseLoincHandler loincHandler = new LoincGroupTermsFileHandler( + Maps.newHashMap(), Lists.newArrayList(), Lists.newArrayList(), new Properties()); + BaseLoincHandler spiedLoincHandler = spy(loincHandler); + + when(spiedLoincHandler.getValueSet(any(), any(), any(), any())).thenReturn(myValueSet); + when(myValueSet.getVersion()).thenReturn(TEST_VERSION); + when(myValueSet.getCompose().addInclude()).thenReturn(myInclude); + when(myInclude.addConcept()).thenReturn(myConceptReferenceComponent); + when(myConceptReferenceComponent.setCode(any())).thenReturn(myConceptReferenceComponent); + + spiedLoincHandler.accept(recordWithHeader); + + Mockito.verify(myInclude).setVersion(Mockito.notNull()); + } + + + @Test + void LoincImagingDocumentCodeHandlerTest() throws Exception { + Map recordDataMap = Maps.newHashMap(); + recordDataMap.put("LOINC_NUM", "test-loinc-number"); + recordDataMap.put("LONG_COMMON_NAME", "test-Long-Common-Names"); + setupCSVParser(recordDataMap); + + BaseLoincHandler loincHandler = new LoincImagingDocumentCodeHandler( + Maps.newHashMap(), Lists.newArrayList(), Lists.newArrayList(), new Properties()); + BaseLoincHandler spiedLoincHandler = spy(loincHandler); + + when(spiedLoincHandler.getValueSet(any(), any(), any(), any())).thenReturn(myValueSet); + when(myValueSet.getVersion()).thenReturn(TEST_VERSION); + when(myValueSet.getCompose().addInclude()).thenReturn(myInclude); + when(myInclude.addConcept()).thenReturn(myConceptReferenceComponent); + when(myConceptReferenceComponent.setCode(any())).thenReturn(myConceptReferenceComponent); + + spiedLoincHandler.accept(recordWithHeader); + + Mockito.verify(myInclude).setVersion(Mockito.notNull()); + } + + + @Test + void LoincUniversalOrderSetHandlerTest() throws Exception { + Map recordDataMap = Maps.newHashMap(); + recordDataMap.put("LOINC_NUM", "test-loinc-number"); + recordDataMap.put("LONG_COMMON_NAME", "test-Long-Common-Names"); + recordDataMap.put("ORDER_OBS", "test-ORDER_OBS"); + setupCSVParser(recordDataMap); + + BaseLoincHandler loincHandler = new LoincUniversalOrderSetHandler( + Maps.newHashMap(), Lists.newArrayList(), Lists.newArrayList(), new Properties()); + BaseLoincHandler spiedLoincHandler = spy(loincHandler); + + when(spiedLoincHandler.getValueSet(any(), any(), any(), any())).thenReturn(myValueSet); + when(myValueSet.getVersion()).thenReturn(TEST_VERSION); + when(myValueSet.getCompose().addInclude()).thenReturn(myInclude); + when(myInclude.addConcept()).thenReturn(myConceptReferenceComponent); + when(myConceptReferenceComponent.setCode(any())).thenReturn(myConceptReferenceComponent); + + spiedLoincHandler.accept(recordWithHeader); + + Mockito.verify(myInclude).setVersion(Mockito.notNull()); + } } + + @Nested + public class WithoutVersion { + + @Test + void Top2000LabResultsHandlerTest() throws Exception { + Map recordDataMap = Maps.newHashMap(); + recordDataMap.put("LOINC #", "test-loinc-number"); + recordDataMap.put("Long Common Name", "test-Long-Common-Names"); + setupCSVParser(recordDataMap); + + BaseLoincHandler loincHandler = new BaseLoincTop2000LabResultsHandler( + Maps.newHashMap(), Lists.newArrayList(), null, null, null, Lists.newArrayList(), new Properties()); + BaseLoincHandler spiedLoincHandler = spy(loincHandler); + + when(spiedLoincHandler.getValueSet(any(), any(), any(), any())).thenReturn(myValueSet); + when(myValueSet.getCompose().addInclude()).thenReturn(myInclude); + when(myInclude.addConcept()).thenReturn(myConceptReferenceComponent); + when(myConceptReferenceComponent.setCode(any())).thenReturn(myConceptReferenceComponent); + + spiedLoincHandler.accept(recordWithHeader); + + Mockito.verify(myInclude, never()).setVersion(any()); + } + + @Test + void LoincDocumentOntologyHandlerTest() throws Exception { + Map recordDataMap = Maps.newHashMap(); + recordDataMap.put("LoincNumber", "test-LoincNumber"); + recordDataMap.put("PartNumber", "test-PartNumber"); + recordDataMap.put("PartTypeName", "Document.Role"); + recordDataMap.put("PartSequenceOrder", "test-PartSequenceOrder"); + recordDataMap.put("PartName", "test-PartName"); + setupCSVParser(recordDataMap); + + BaseLoincHandler loincHandler = new LoincDocumentOntologyHandler( + Maps.newHashMap(), null, Lists.newArrayList(), Lists.newArrayList(), new Properties()); + BaseLoincHandler spiedLoincHandler = spy(loincHandler); + + when(spiedLoincHandler.getValueSet(any(), any(), any(), any())).thenReturn(myValueSet); + when(myValueSet.getCompose().addInclude()).thenReturn(myInclude); + when(myInclude.addConcept()).thenReturn(myConceptReferenceComponent); + when(myConceptReferenceComponent.setCode(any())).thenReturn(myConceptReferenceComponent); + + spiedLoincHandler.accept(recordWithHeader); + + Mockito.verify(myInclude, never()).setVersion(any()); + } + + @Test + void LoincGroupTermsFileHandlerTest() throws Exception { + Map recordDataMap = Maps.newHashMap(); + recordDataMap.put("LoincNumber", "test-LoincNumber"); + recordDataMap.put("GroupId", "test-GroupId"); + setupCSVParser(recordDataMap); + + BaseLoincHandler loincHandler = new LoincGroupTermsFileHandler( + Maps.newHashMap(), Lists.newArrayList(), Lists.newArrayList(), new Properties()); + BaseLoincHandler spiedLoincHandler = spy(loincHandler); + + when(spiedLoincHandler.getValueSet(any(), any(), any(), any())).thenReturn(myValueSet); + when(myValueSet.getCompose().addInclude()).thenReturn(myInclude); + when(myInclude.addConcept()).thenReturn(myConceptReferenceComponent); + when(myConceptReferenceComponent.setCode(any())).thenReturn(myConceptReferenceComponent); + + spiedLoincHandler.accept(recordWithHeader); + + Mockito.verify(myInclude, never()).setVersion(any()); + } + + + @Test + void LoincImagingDocumentCodeHandlerTest() throws Exception { + Map recordDataMap = Maps.newHashMap(); + recordDataMap.put("LOINC_NUM", "test-loinc-number"); + recordDataMap.put("LONG_COMMON_NAME", "test-Long-Common-Names"); + setupCSVParser(recordDataMap); + + BaseLoincHandler loincHandler = new LoincImagingDocumentCodeHandler( + Maps.newHashMap(), Lists.newArrayList(), Lists.newArrayList(), new Properties()); + BaseLoincHandler spiedLoincHandler = spy(loincHandler); + + when(spiedLoincHandler.getValueSet(any(), any(), any(), any())).thenReturn(myValueSet); + when(myValueSet.getCompose().addInclude()).thenReturn(myInclude); + when(myInclude.addConcept()).thenReturn(myConceptReferenceComponent); + when(myConceptReferenceComponent.setCode(any())).thenReturn(myConceptReferenceComponent); + + spiedLoincHandler.accept(recordWithHeader); + + Mockito.verify(myInclude, never()).setVersion(any()); + } + + + @Test + void LoincUniversalOrderSetHandlerTest() throws Exception { + Map recordDataMap = Maps.newHashMap(); + recordDataMap.put("LOINC_NUM", "test-loinc-number"); + recordDataMap.put("LONG_COMMON_NAME", "test-Long-Common-Names"); + recordDataMap.put("ORDER_OBS", "test-ORDER_OBS"); + setupCSVParser(recordDataMap); + + BaseLoincHandler loincHandler = new LoincUniversalOrderSetHandler( + Maps.newHashMap(), Lists.newArrayList(), Lists.newArrayList(), new Properties()); + BaseLoincHandler spiedLoincHandler = spy(loincHandler); + + when(spiedLoincHandler.getValueSet(any(), any(), any(), any())).thenReturn(myValueSet); + when(myValueSet.getCompose().addInclude()).thenReturn(myInclude); + when(myInclude.addConcept()).thenReturn(myConceptReferenceComponent); + when(myConceptReferenceComponent.setCode(any())).thenReturn(myConceptReferenceComponent); + + spiedLoincHandler.accept(recordWithHeader); + + Mockito.verify(myInclude, never()).setVersion(any()); + } + + } + + + + +} From 2435d770898c1cfe618e210701b2657e6058e107 Mon Sep 17 00:00:00 2001 From: "juan.marchionatto" Date: Wed, 15 Sep 2021 14:46:33 -0400 Subject: [PATCH 2/2] Add changelog --- ...nclude-version-is-not-set-when-uploading-terminology.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/2995-valueset-compose-include-version-is-not-set-when-uploading-terminology.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/2995-valueset-compose-include-version-is-not-set-when-uploading-terminology.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/2995-valueset-compose-include-version-is-not-set-when-uploading-terminology.yaml new file mode 100644 index 00000000000..8377273cf8c --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/2995-valueset-compose-include-version-is-not-set-when-uploading-terminology.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 2995 +title: "CodeSystem version is copied to ValueSet.compose.include.version on loinc terminology upload + to support versioned ValueSet expansion."