From 5a653aeb206b50eefc421663361df9b822651ff5 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Sat, 10 Mar 2018 20:31:27 -0300 Subject: [PATCH] Work on loinc mapping --- LOINC_NOTES.txt | 18 ++ .../ca/uhn/fhir/i18n/hapi-messages.properties | 6 +- .../jpa/config/dstu3/BaseDstu3Config.java | 7 +- .../uhn/fhir/jpa/config/r4/BaseR4Config.java | 30 +- .../ca/uhn/fhir/jpa/entity/TermConcept.java | 10 + .../fhir/jpa/entity/TermConceptProperty.java | 34 ++- ...l.java => BaseHapiTerminologySvcImpl.java} | 45 ++- .../jpa/term/HapiTerminologySvcDstu2.java | 90 +++++- .../jpa/term/HapiTerminologySvcDstu3.java | 270 ++++-------------- .../fhir/jpa/term/HapiTerminologySvcR4.java | 204 ++----------- .../fhir/jpa/term/IHapiTerminologySvc.java | 9 +- .../jpa/term/IHapiTerminologySvcDstu3.java | 4 +- .../fhir/jpa/term/IHapiTerminologySvcR4.java | 4 +- .../IVersionSpecificValidationSupport.java | 18 -- .../fhir/jpa/term/TerminologyLoaderSvc.java | 21 +- .../loinc/LoincAnswerListLinkHandler.java | 72 +++++ .../fhir/jpa/term/loinc/LoincPartHandler.java | 45 +++ .../jpa/term/loinc/LoincPartLinkHandler.java | 51 ++++ .../FhirResourceDaoDstu3CodeSystemTest.java | 6 +- .../FhirResourceDaoDstu3TerminologyTest.java | 6 +- .../r4/FhirResourceDaoR4CodeSystemTest.java | 6 +- .../r4/FhirResourceDaoR4TerminologyTest.java | 6 +- .../term/TerminologyLoaderSvcLoincTest.java | 18 +- .../resources/loinc/AnswerList_Beta_1.csv | 21 +- .../loinc/LoincAnswerListLink_Beta_1.csv | 2 +- .../resources/loinc/LoincPartLink_Beta_1.csv | 10 + .../src/test/resources/loinc/Part_Beta_1.csv | 9 + .../src/test/resources/loinc/loinc.csv | 1 + 28 files changed, 535 insertions(+), 488 deletions(-) rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/{HapiTerminologySvcImpl.java => BaseHapiTerminologySvcImpl.java} (93%) delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IVersionSpecificValidationSupport.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListLinkHandler.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartHandler.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartLinkHandler.java create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincPartLink_Beta_1.csv create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/loinc/Part_Beta_1.csv diff --git a/LOINC_NOTES.txt b/LOINC_NOTES.txt index 265ffe858e3..49026d20b3a 100644 --- a/LOINC_NOTES.txt +++ b/LOINC_NOTES.txt @@ -1,4 +1,22 @@ Database migration: update table TRM_CODESYSTEM_VER drop column RES_VERSION_ID; +TODO: + In answer lists, figure out what to do with externally defined lists + +Comments for Loinc: + +Answer Lists +- Per the notes, there is no way in FHIR currently to map answer lists to + codes based on context. For this reason, I am ignoring any entries in + LoincAnswerListLink_Beta_1.csv where the "ApplicableContext" context is + not empty. Is this correct? + +Parts +- Only parts with a status of "ACTIVE" are being imported, any others are + ignored. +- The PartTypeName (e.g. "ADJUSTMENT") is ignored as there is no corresponding + property in loinc.xml +- PartDisplayName is not mapped + diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index c88d379d6eb..db8bc3910d4 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -91,7 +91,7 @@ ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number pr ca.uhn.fhir.jpa.provider.BaseJpaProvider.cantCombintAtAndSince=Unable to combine _at and _since parameters for history operation -ca.uhn.fhir.jpa.term.HapiTerminologySvcImpl.cannotCreateDuplicateCodeSystemUri=Can not create multiple code systems with URI "{0}", already have one with resource ID: {1} -ca.uhn.fhir.jpa.term.HapiTerminologySvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted! -ca.uhn.fhir.jpa.term.HapiTerminologySvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted! +ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateCodeSystemUri=Can not create multiple code systems with URI "{0}", already have one with resource ID: {1} +ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted! +ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted! diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java index 50b7683f7ad..59ba177a0c9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java @@ -110,12 +110,7 @@ public class BaseDstu3Config extends BaseConfig { } @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologySvc terminologyService() { - return new HapiTerminologySvcImpl(); - } - - @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologySvcDstu3 terminologyServiceDstu3() { + public IHapiTerminologySvcDstu3 terminologyService() { return new HapiTerminologySvcDstu3(); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java index 8d301267ba3..8215cc4a44f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java @@ -38,9 +38,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -65,6 +65,18 @@ public class BaseR4Config extends BaseConfig { return retVal; } + @Bean(name = "myGraphQLProvider") + @Lazy + public GraphQLProvider graphQLProvider() { + return new GraphQLProvider(fhirContextR4(), validationSupportChainR4(), graphqlStorageServices()); + } + + @Bean + @Lazy + public GraphQLEngine.IGraphQLStorageServices graphqlStorageServices() { + return new JpaStorageServices(); + } + @Bean(name = "myInstanceValidatorR4") @Lazy public IValidatorModule instanceValidatorR4() { @@ -86,12 +98,6 @@ public class BaseR4Config extends BaseConfig { return searchDao; } - @Bean(name = "myGraphQLProvider") - @Lazy - public GraphQLProvider graphQLProvider() { - return new GraphQLProvider(fhirContextR4(), validationSupportChainR4(), graphqlStorageServices()); - } - @Bean(autowire = Autowire.BY_TYPE) public SearchParamExtractorR4 searchParamExtractor() { return new SearchParamExtractorR4(); @@ -122,7 +128,7 @@ public class BaseR4Config extends BaseConfig { } @Bean(autowire = Autowire.BY_TYPE) - public IHapiTerminologySvcR4 terminologyServiceR4() { + public IHapiTerminologySvcR4 terminologyService() { return new HapiTerminologySvcR4(); } @@ -139,10 +145,4 @@ public class BaseR4Config extends BaseConfig { return new JpaValidationSupportChainR4(); } - @Bean - @Lazy - public GraphQLEngine.IGraphQLStorageServices graphqlStorageServices() { - return new JpaStorageServices(); - } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java index 1ffa0f17be3..02c3b772012 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConcept.java @@ -229,6 +229,16 @@ public class TermConcept implements Serializable { return null; } + public List getProperties(String thePropertyName) { + List retVal = new ArrayList<>(); + for (TermConceptProperty next : getProperties()) { + if (thePropertyName.equals(next.getKey())) { + retVal.add(next.getValue()); + } + } + return retVal; + } + @Override public int hashCode() { HashCodeBuilder b = new HashCodeBuilder(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java index 19ba37d467a..5ab572f4df0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.entity; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,7 +20,8 @@ package ca.uhn.fhir.jpa.entity; * #L% */ -import org.hibernate.search.annotations.Field; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; import org.hibernate.validator.constraints.NotBlank; import javax.persistence.*; @@ -37,38 +38,43 @@ public class TermConceptProperty implements Serializable { @ManyToOne @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT")) private TermConcept myConcept; - @Id() @SequenceGenerator(name = "SEQ_CONCEPT_PROP_PID", sequenceName = "SEQ_CONCEPT_PROP_PID") @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_CONCEPT_PROP_PID") @Column(name = "PID") private Long myId; - - @Column(name="PROP_KEY", length=200, nullable=false) + @Column(name = "PROP_KEY", length = 200, nullable = false) @NotBlank private String myKey; - - @Column(name="PROP_VAL", length=200, nullable=true) + @Column(name = "PROP_VAL", length = 200, nullable = true) private String myValue; public String getKey() { return myKey; } + public void setKey(String theKey) { + myKey = theKey; + } + public String getValue() { return myValue; } + public void setValue(String theValue) { + myValue = theValue; + } + public void setConcept(TermConcept theConcept) { myConcept = theConcept; } - public void setKey(String theKey) { - myKey = theKey; - } - - public void setValue(String theValue) { - myValue = theValue; + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("key", myKey) + .append("value", myValue) + .toString(); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java similarity index 93% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcImpl.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java index be31755b3a3..7def6f12b2b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java @@ -23,12 +23,14 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptParentChildLinkDao; import ca.uhn.fhir.jpa.entity.*; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; +import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.ObjectUtil; @@ -44,6 +46,8 @@ import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.FullTextQuery; import org.hibernate.search.query.dsl.BooleanJunction; import org.hibernate.search.query.dsl.QueryBuilder; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.ValueSet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -65,8 +69,8 @@ import java.util.concurrent.TimeUnit; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -public class HapiTerminologySvcImpl implements IHapiTerminologySvc { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HapiTerminologySvcImpl.class); +public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiTerminologySvcImpl.class); private static final Object PLACEHOLDER_OBJECT = new Object(); private static boolean ourForceSaveDeferredAlwaysForUnitTest; @Autowired @@ -93,7 +97,7 @@ public class HapiTerminologySvcImpl implements IHapiTerminologySvc { @Autowired private PlatformTransactionManager myTransactionMgr; @Autowired - private IVersionSpecificValidationSupport myVersionSpecificValidationSupport; + private IFhirResourceDaoCodeSystem myCodeSystemResourceDao; private void addCodeIfNotAlreadyAdded(String system, ValueSet.ValueSetExpansionComponent retVal, Set addedCodes, TermConcept nextConcept) { if (addedCodes.add(nextConcept.getCode())) { @@ -124,13 +128,17 @@ public class HapiTerminologySvcImpl implements IHapiTerminologySvc { boolean retVal = theSetToPopulate.add(theConcept); if (retVal) { if (theSetToPopulate.size() >= myDaoConfig.getMaximumExpansionSize()) { - String msg = myContext.getLocalizer().getMessage(HapiTerminologySvcImpl.class, "expansionTooLarge", myDaoConfig.getMaximumExpansionSize()); + String msg = myContext.getLocalizer().getMessage(BaseHapiTerminologySvcImpl.class, "expansionTooLarge", myDaoConfig.getMaximumExpansionSize()); throw new InvalidRequestException(msg); } } return retVal; } + protected abstract IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource, RequestDetails theRequestDetails); + + abstract void createOrUpdateValueSet(ValueSet theValueSet, RequestDetails theRequestDetails); + @Override public void deleteCodeSystem(TermCodeSystem theCodeSystem) { ourLog.info(" * Deleting code system {}", theCodeSystem.getPid()); @@ -241,7 +249,7 @@ public class HapiTerminologySvcImpl implements IHapiTerminologySvc { } else { // bool.must(qb.keyword().onField("myProperties").matching(nextFilter.getProperty()+"="+nextFilter.getValue()).createQuery()); - bool.must(qb.phrase().onField("myProperties").sentence(nextFilter.getProperty()+"="+nextFilter.getValue()).createQuery()); + bool.must(qb.phrase().onField("myProperties").sentence(nextFilter.getProperty() + "=" + nextFilter.getValue()).createQuery()); } } @@ -340,7 +348,7 @@ public class HapiTerminologySvcImpl implements IHapiTerminologySvc { public List findCodesAbove(String theSystem, String theCode) { TermCodeSystem cs = getCodeSystem(theSystem); if (cs == null) { - return myVersionSpecificValidationSupport.findCodesAboveUsingBuiltInSystems(theSystem, theCode); + return findCodesAboveUsingBuiltInSystems(theSystem, theCode); } TermCodeSystemVersion csv = cs.getCurrentVersion(); @@ -372,7 +380,7 @@ public class HapiTerminologySvcImpl implements IHapiTerminologySvc { public List findCodesBelow(String theSystem, String theCode) { TermCodeSystem cs = getCodeSystem(theSystem); if (cs == null) { - return myVersionSpecificValidationSupport.findCodesBelowUsingBuiltInSystems(theSystem, theCode); + return findCodesBelowUsingBuiltInSystems(theSystem, theCode); } TermCodeSystemVersion csv = cs.getCurrentVersion(); @@ -651,7 +659,7 @@ public class HapiTerminologySvcImpl implements IHapiTerminologySvc { myCodeSystemDao.save(codeSystem); } else { if (!ObjectUtil.equals(codeSystem.getResource().getId(), theCodeSystemVersion.getResource().getId())) { - String msg = myContext.getLocalizer().getMessage(HapiTerminologySvcImpl.class, "cannotCreateDuplicateCodeSystemUri", theSystemUri, + String msg = myContext.getLocalizer().getMessage(BaseHapiTerminologySvcImpl.class, "cannotCreateDuplicateCodeSystemUri", theSystemUri, codeSystem.getResource().getIdDt().toUnqualifiedVersionless().getValue()); throw new UnprocessableEntityException(msg); } @@ -701,6 +709,27 @@ public class HapiTerminologySvcImpl implements IHapiTerminologySvc { } } + @Override + @Transactional(propagation = Propagation.REQUIRED) + public void storeNewCodeSystemVersion(CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets) { + Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL"); + + IIdType csId = createOrUpdateCodeSystem(theCodeSystemResource, theRequestDetails); + + ResourceTable resource = (ResourceTable) myCodeSystemResourceDao.readEntity(csId); + Long codeSystemResourcePid = resource.getId(); + + ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); + + theCodeSystemVersion.setResource(resource); + storeNewCodeSystemVersion(codeSystemResourcePid, theCodeSystemResource.getUrl(), theCodeSystemVersion); + + for (ValueSet nextValueSet : theValueSets) { + createOrUpdateValueSet(nextValueSet, theRequestDetails); + } + + } + @Override public boolean supportsSystem(String theSystem) { TermCodeSystem cs = getCodeSystem(theSystem); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java index 4a3a1723808..b78f2050a3c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu2.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.term; * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,23 +20,107 @@ package ca.uhn.fhir.jpa.term; * #L% */ +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.instance.hapi.validation.IValidationSupport; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ValueSet; import org.springframework.beans.factory.annotation.Autowired; +import java.util.ArrayList; import java.util.List; -public class HapiTerminologySvcDstu2 extends HapiTerminologySvcImpl { +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +public class HapiTerminologySvcDstu2 extends BaseHapiTerminologySvcImpl { @Autowired private IValidationSupport myValidationSupport; + private void addAllChildren(String theSystemString, org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent theCode, List theListToPopulate) { + if (isNotBlank(theCode.getCode())) { + theListToPopulate.add(new VersionIndependentConcept(theSystemString, theCode.getCode())); + } + for (org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent nextChild : theCode.getConcept()) { + addAllChildren(theSystemString, nextChild, theListToPopulate); + } + } + + private boolean addTreeIfItContainsCode(String theSystemString, org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent theNext, String theCode, List theListToPopulate) { + boolean foundCodeInChild = false; + for (org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent nextChild : theNext.getConcept()) { + foundCodeInChild |= addTreeIfItContainsCode(theSystemString, nextChild, theCode, theListToPopulate); + } + + if (theCode.equals(theNext.getCode()) || foundCodeInChild) { + theListToPopulate.add(new VersionIndependentConcept(theSystemString, theNext.getCode())); + return true; + } + + return false; + } + + @Override + protected IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource, RequestDetails theRequestDetails) { + throw new UnsupportedOperationException(); + } @Override public List expandValueSet(String theValueSet) { throw new UnsupportedOperationException(); } + private void findCodesAbove(org.hl7.fhir.instance.model.ValueSet theSystem, String theSystemString, String theCode, List theListToPopulate) { + List conceptList = theSystem.getCodeSystem().getConcept(); + for (org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent next : conceptList) { + addTreeIfItContainsCode(theSystemString, next, theCode, theListToPopulate); + } + } + @Override + public List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) { + ArrayList retVal = new ArrayList<>(); + org.hl7.fhir.instance.model.ValueSet system = myValidationSupport.fetchCodeSystem(myContext, theSystem); + if (system != null) { + findCodesAbove(system, theSystem, theCode, retVal); + } + return retVal; + } + private void findCodesBelow(org.hl7.fhir.instance.model.ValueSet theSystem, String theSystemString, String theCode, List theListToPopulate) { + List conceptList = theSystem.getCodeSystem().getConcept(); + findCodesBelow(theSystemString, theCode, theListToPopulate, conceptList); + } + + private void findCodesBelow(String theSystemString, String theCode, List theListToPopulate, List conceptList) { + for (org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent next : conceptList) { + if (theCode.equals(next.getCode())) { + addAllChildren(theSystemString, next, theListToPopulate); + } else { + findCodesBelow(theSystemString, theCode, theListToPopulate, next.getConcept()); + } + } + } + + @Override + public List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) { + ArrayList retVal = new ArrayList<>(); + org.hl7.fhir.instance.model.ValueSet system = myValidationSupport.fetchCodeSystem(myContext, theSystem); + if (system != null) { + findCodesBelow(system, theSystem, theCode, retVal); + } + return retVal; + } + + @Override + public void storeNewCodeSystemVersion(CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets) { + throw new UnsupportedOperationException(); + } + + @Override + protected void createOrUpdateValueSet(ValueSet theValueSet, RequestDetails theRequestDetails) { + throw new UnsupportedOperationException(); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java index 1b50e30a8bd..cf65d3090f3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java @@ -1,44 +1,34 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; -import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.TermCodeSystem; -import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.CoverageIgnore; -import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.UrlUtil; -import org.apache.lucene.search.Query; -import org.hibernate.search.jpa.FullTextEntityManager; -import org.hibernate.search.jpa.FullTextQuery; -import org.hibernate.search.query.dsl.BooleanJunction; -import org.hibernate.search.query.dsl.QueryBuilder; import org.hl7.fhir.convertors.VersionConvertor_30_40; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; -import org.hl7.fhir.dstu3.model.CodeableConcept; -import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.StructureDefinition; -import org.hl7.fhir.dstu3.model.ValueSet.*; +import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; +import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Propagation; +import org.springframework.beans.factory.annotation.Qualifier; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; -import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; /* @@ -61,9 +51,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ -public class HapiTerminologySvcDstu3 implements IValidationSupport, IHapiTerminologySvcDstu3 { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HapiTerminologySvcDstu3.class); - private final VersionConvertor_30_40 myConverter; +public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implements IValidationSupport, IHapiTerminologySvcDstu3 { @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; @@ -71,10 +59,11 @@ public class HapiTerminologySvcDstu3 implements IValidationSupport, IHapiTermino protected FhirContext myContext; @Autowired protected ITermCodeSystemDao myCodeSystemDao; - + @Autowired + @Qualifier("myValueSetDaoDstu3") + private IFhirResourceDao myValueSetResourceDao; @Autowired private IFhirResourceDaoCodeSystem myCodeSystemResourceDao; - @Autowired private IValidationSupport myValidationSupport; @Autowired @@ -84,19 +73,22 @@ public class HapiTerminologySvcDstu3 implements IValidationSupport, IHapiTermino * Constructor */ public HapiTerminologySvcDstu3() { - myConverter = new VersionConvertor_30_40(); + super(); } @Override - public List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) { - ArrayList retVal = new ArrayList(); - CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); - if (system != null) { - findCodesBelow(system, theSystem, theCode, retVal); + protected void createOrUpdateValueSet(org.hl7.fhir.r4.model.ValueSet theValueSet, RequestDetails theRequestDetails) { + String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); + ValueSet valueSetDstu3; + try { + valueSetDstu3 = VersionConvertor_30_40.convertValueSet(theValueSet); + } catch (FHIRException e) { + throw new InternalErrorException(e); } - return retVal; + myValueSetResourceDao.update(valueSetDstu3, matchUrl, theRequestDetails); } + private void addAllChildren(String theSystemString, ConceptDefinitionComponent theCode, List theListToPopulate) { if (isNotBlank(theCode.getCode())) { theListToPopulate.add(new VersionIndependentConcept(theSystemString, theCode.getCode())); @@ -106,31 +98,6 @@ public class HapiTerminologySvcDstu3 implements IValidationSupport, IHapiTermino } } - private void addCodeIfNotAlreadyAdded(String system, ValueSetExpansionComponent retVal, Set addedCodes, TermConcept nextConcept) { - if (addedCodes.add(nextConcept.getCode())) { - ValueSetExpansionContainsComponent contains = retVal.addContains(); - contains.setCode(nextConcept.getCode()); - contains.setSystem(system); - contains.setDisplay(nextConcept.getDisplay()); - } - } - - private void addDisplayFilterExact(QueryBuilder qb, BooleanJunction bool, ConceptSetFilterComponent nextFilter) { - bool.must(qb.phrase().onField("myDisplay").sentence(nextFilter.getValue()).createQuery()); - } - - private void addDisplayFilterInexact(QueryBuilder qb, BooleanJunction bool, ConceptSetFilterComponent nextFilter) { - Query textQuery = qb - .phrase() - .withSlop(2) - .onField("myDisplay").boostedTo(4.0f) - .andField("myDisplayEdgeNGram").boostedTo(2.0f) - // .andField("myDisplayNGram").boostedTo(1.0f) - // .andField("myDisplayPhonetic").boostedTo(0.5f) - .sentence(nextFilter.getValue().toLowerCase()).createQuery(); - bool.must(textQuery); - } - private boolean addTreeIfItContainsCode(String theSystemString, ConceptDefinitionComponent theNext, String theCode, List theListToPopulate) { boolean foundCodeInChild = false; for (ConceptDefinitionComponent nextChild : theNext.getConcept()) { @@ -146,115 +113,30 @@ public class HapiTerminologySvcDstu3 implements IValidationSupport, IHapiTermino } @Override - public List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) { - ArrayList retVal = new ArrayList<>(); - CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); - if (system != null) { - findCodesAbove(system, theSystem, theCode, retVal); + protected IIdType createOrUpdateCodeSystem(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource, RequestDetails theRequestDetails) { + String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); + CodeSystem resourceToStore; + try { + resourceToStore = VersionConvertor_30_40.convertCodeSystem(theCodeSystemResource); + } catch (FHIRException e) { + throw new InternalErrorException(e); } - return retVal; + return myCodeSystemResourceDao.update(resourceToStore, matchUrl, theRequestDetails).getId(); } @Override public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - String system = theInclude.getSystem(); - ourLog.info("Starting expansion around code system: {}", system); + ValueSet valueSetToExpand = new ValueSet(); + valueSetToExpand.getCompose().addInclude(theInclude); - TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system); - TermCodeSystemVersion csv = cs.getCurrentVersion(); - - ValueSetExpansionComponent retVal = new ValueSetExpansionComponent(); - Set addedCodes = new HashSet<>(); - boolean haveIncludeCriteria = false; - - /* - * Include Concepts - */ - for (ConceptReferenceComponent next : theInclude.getConcept()) { - String nextCode = next.getCode(); - if (isNotBlank(nextCode) && !addedCodes.contains(nextCode)) { - haveIncludeCriteria = true; - TermConcept code = myTerminologySvc.findCode(system, nextCode); - if (code != null) { - addedCodes.add(nextCode); - ValueSetExpansionContainsComponent contains = retVal.addContains(); - contains.setCode(nextCode); - contains.setSystem(system); - contains.setDisplay(code.getDisplay()); - } - } + try { + org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; + valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(valueSetToExpand); + org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = super.expandValueSet(valueSetToExpandR4).getExpansion(); + return VersionConvertor_30_40.convertValueSetExpansionComponent(expandedR4); + } catch (FHIRException e) { + throw new InternalErrorException(e); } - - /* - * Filters - */ - - if (theInclude.getFilter().size() > 0) { - haveIncludeCriteria = true; - - FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); - QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(TermConcept.class).get(); - BooleanJunction bool = qb.bool(); - - bool.must(qb.keyword().onField("myCodeSystemVersionPid").matching(csv.getPid()).createQuery()); - - for (ConceptSetFilterComponent nextFilter : theInclude.getFilter()) { - if (isBlank(nextFilter.getValue()) && nextFilter.getOp() == null && isBlank(nextFilter.getProperty())) { - continue; - } - - if (isBlank(nextFilter.getValue()) || nextFilter.getOp() == null || isBlank(nextFilter.getProperty())) { - throw new InvalidRequestException("Invalid filter, must have fields populated: property op value"); - } - - - if (nextFilter.getProperty().equals("display:exact") && nextFilter.getOp() == FilterOperator.EQUAL) { - addDisplayFilterExact(qb, bool, nextFilter); - } else if ("display".equals(nextFilter.getProperty()) && nextFilter.getOp() == FilterOperator.EQUAL) { - if (nextFilter.getValue().trim().contains(" ")) { - addDisplayFilterExact(qb, bool, nextFilter); - } else { - addDisplayFilterInexact(qb, bool, nextFilter); - } - } else if ((nextFilter.getProperty().equals("concept") || nextFilter.getProperty().equals("code")) && nextFilter.getOp() == FilterOperator.ISA) { - TermConcept code = myTerminologySvc.findCode(system, nextFilter.getValue()); - if (code == null) { - throw new InvalidRequestException("Invalid filter criteria - code does not exist: {" + system + "}" + nextFilter.getValue()); - } - - ourLog.info(" * Filtering on codes with a parent of {}/{}/{}", code.getId(), code.getCode(), code.getDisplay()); - bool.must(qb.keyword().onField("myParentPids").matching("" + code.getId()).createQuery()); - } else { - throw new InvalidRequestException("Unknown filter property[" + nextFilter + "] + op[" + nextFilter.getOpElement().getValueAsString() + "]"); - } - } - - Query luceneQuery = bool.createQuery(); - FullTextQuery jpaQuery = em.createFullTextQuery(luceneQuery, TermConcept.class); - jpaQuery.setMaxResults(1000); - - StopWatch sw = new StopWatch(); - - @SuppressWarnings("unchecked") - List result = jpaQuery.getResultList(); - - ourLog.info("Expansion completed in {}ms", sw.getMillis()); - - for (TermConcept nextConcept : result) { - addCodeIfNotAlreadyAdded(system, retVal, addedCodes, nextConcept); - } - - retVal.setTotal(jpaQuery.getResultSize()); - } - - if (!haveIncludeCriteria) { - List allCodes = myTerminologySvc.findCodes(system); - for (TermConcept nextConcept : allCodes) { - addCodeIfNotAlreadyAdded(system, retVal, addedCodes, nextConcept); - } - } - - return retVal; } @Override @@ -278,29 +160,6 @@ public class HapiTerminologySvcDstu3 implements IValidationSupport, IHapiTermino return null; } -// @Override -// public List expandValueSet(String theValueSet) { -// ValueSet source = new ValueSet(); -// source.getCompose().addInclude().addValueSet(theValueSet); -// try { -// ArrayList retVal = new ArrayList(); -// -// HapiWorkerContext worker = new HapiWorkerContext(myContext, myValidationSupport); -// ValueSetExpansionOutcome outcome = worker.expand(source, null); -// for (ValueSetExpansionContainsComponent next : outcome.getValueset().getExpansion().getContains()) { -// retVal.add(new VersionIndependentConcept(next.getSystem(), next.getCode())); -// } -// -// return retVal; -// -// } catch (BaseServerResponseException e) { -// throw e; -// } catch (Exception e) { -// throw new InternalErrorException(e); -// } -// -// } - @CoverageIgnore @Override public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) { @@ -314,6 +173,16 @@ public class HapiTerminologySvcDstu3 implements IValidationSupport, IHapiTermino } } + @Override + public List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode) { + ArrayList retVal = new ArrayList<>(); + CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); + if (system != null) { + findCodesAbove(system, theSystem, theCode, retVal); + } + return retVal; + } + private void findCodesBelow(CodeSystem theSystem, String theSystemString, String theCode, List theListToPopulate) { List conceptList = theSystem.getConcept(); findCodesBelow(theSystemString, theCode, theListToPopulate, conceptList); @@ -330,34 +199,18 @@ public class HapiTerminologySvcDstu3 implements IValidationSupport, IHapiTermino } @Override - public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { - return myTerminologySvc.supportsSystem(theSystem); + public List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode) { + ArrayList retVal = new ArrayList<>(); + CodeSystem system = myValidationSupport.fetchCodeSystem(myContext, theSystem); + if (system != null) { + findCodesBelow(system, theSystem, theCode, retVal); + } + return retVal; } @Override - @org.springframework.transaction.annotation.Transactional(propagation = Propagation.REQUIRED) - public void storeNewCodeSystemVersion(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets) { - CodeSystem cs = new org.hl7.fhir.dstu3.model.CodeSystem(); - cs.setUrl(theCodeSystemResource.getUrl()); - cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - - DaoMethodOutcome createOutcome = myCodeSystemResourceDao.create(cs, "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()), theRequestDetails); - IIdType csId = createOutcome.getId().toUnqualifiedVersionless(); - if (createOutcome.getCreated() != Boolean.TRUE) { - CodeSystem existing = myCodeSystemResourceDao.read(csId, theRequestDetails); - csId = myCodeSystemResourceDao.update(existing, null, false, true, theRequestDetails).getId(); - - ourLog.info("Created new version of CodeSystem, got ID: {}", csId.toUnqualified().getValue()); - } - - ResourceTable resource = (ResourceTable) myCodeSystemResourceDao.readEntity(csId); - Long codeSystemResourcePid = resource.getId(); - - ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); - - theCodeSystemVersion.setResource(resource); - myTerminologySvc.storeNewCodeSystemVersion(codeSystemResourcePid, theCodeSystemResource.getUrl(), theCodeSystemVersion); - + public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { + return myTerminologySvc.supportsSystem(theSystem); } @CoverageIgnore @@ -371,7 +224,8 @@ public class HapiTerminologySvcDstu3 implements IValidationSupport, IHapiTermino return new CodeValidationResult(def); } - return new CodeValidationResult(IssueSeverity.ERROR, "Unkonwn code {" + theCodeSystem + "}" + theCode); + return new CodeValidationResult(IssueSeverity.ERROR, "Unknown code {" + theCodeSystem + "}" + theCode); } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java index 1dd0bf1380b..8c566259f5b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java @@ -3,21 +3,10 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; -import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.entity.TermCodeSystem; -import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.CoverageIgnore; -import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.UrlUtil; -import org.apache.commons.lang3.Validate; -import org.apache.lucene.search.Query; -import org.hibernate.search.jpa.FullTextEntityManager; -import org.hibernate.search.jpa.FullTextQuery; -import org.hibernate.search.query.dsl.BooleanJunction; -import org.hibernate.search.query.dsl.QueryBuilder; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; @@ -25,19 +14,19 @@ import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r4.model.ValueSet.*; +import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; -import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; /* @@ -60,8 +49,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ -public class HapiTerminologySvcR4 implements IHapiTerminologySvcR4 { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HapiTerminologySvcR4.class); +public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements IHapiTerminologySvcR4 { @Autowired protected ITermCodeSystemDao myCodeSystemDao; @PersistenceContext(type = PersistenceContextType.TRANSACTION) @@ -70,6 +58,9 @@ public class HapiTerminologySvcR4 implements IHapiTerminologySvcR4 { @Qualifier("myCodeSystemDaoR4") private IFhirResourceDao myCodeSystemResourceDao; @Autowired + @Qualifier("myValueSetDaoR4") + private IFhirResourceDao myValueSetResourceDao; + @Autowired private IValidationSupport myValidationSupport; @Autowired private IHapiTerminologySvc myTerminologySvc; @@ -85,30 +76,6 @@ public class HapiTerminologySvcR4 implements IHapiTerminologySvcR4 { } } - private void addCodeIfNotAlreadyAdded(String system, ValueSetExpansionComponent retVal, Set addedCodes, TermConcept nextConcept) { - if (addedCodes.add(nextConcept.getCode())) { - ValueSetExpansionContainsComponent contains = retVal.addContains(); - contains.setCode(nextConcept.getCode()); - contains.setSystem(system); - contains.setDisplay(nextConcept.getDisplay()); - } - } - - private void addDisplayFilterExact(QueryBuilder qb, BooleanJunction bool, ConceptSetFilterComponent nextFilter) { - bool.must(qb.phrase().onField("myDisplay").sentence(nextFilter.getValue()).createQuery()); - } - - private void addDisplayFilterInexact(QueryBuilder qb, BooleanJunction bool, ConceptSetFilterComponent nextFilter) { - Query textQuery = qb - .phrase() - .withSlop(2) - .onField("myDisplay").boostedTo(4.0f) - .andField("myDisplayEdgeNGram").boostedTo(2.0f) - // .andField("myDisplayNGram").boostedTo(1.0f) - // .andField("myDisplayPhonetic").boostedTo(0.5f) - .sentence(nextFilter.getValue().toLowerCase()).createQuery(); - bool.must(textQuery); - } private boolean addTreeIfItContainsCode(String theSystemString, ConceptDefinitionComponent theNext, String theCode, List theListToPopulate) { boolean foundCodeInChild = false; @@ -125,129 +92,23 @@ public class HapiTerminologySvcR4 implements IHapiTerminologySvcR4 { } @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - String system = theInclude.getSystem(); - ourLog.info("Starting expansion around code system: {}", system); - - TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system); - TermCodeSystemVersion csv = cs.getCurrentVersion(); - - ValueSetExpansionComponent retVal = new ValueSetExpansionComponent(); - Set addedCodes = new HashSet<>(); - boolean haveIncludeCriteria = false; - - /* - * Include Concepts - */ - for (ConceptReferenceComponent next : theInclude.getConcept()) { - String nextCode = next.getCode(); - if (isNotBlank(nextCode) && !addedCodes.contains(nextCode)) { - haveIncludeCriteria = true; - TermConcept code = myTerminologySvc.findCode(system, nextCode); - if (code != null) { - addedCodes.add(nextCode); - ValueSetExpansionContainsComponent contains = retVal.addContains(); - contains.setCode(nextCode); - contains.setSystem(system); - contains.setDisplay(code.getDisplay()); - } - } - } - - /* - * Filters - */ - - if (theInclude.getFilter().size() > 0) { - haveIncludeCriteria = true; - - FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); - QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(TermConcept.class).get(); - BooleanJunction bool = qb.bool(); - - bool.must(qb.keyword().onField("myCodeSystemVersionPid").matching(csv.getPid()).createQuery()); - - for (ConceptSetFilterComponent nextFilter : theInclude.getFilter()) { - if (isBlank(nextFilter.getValue()) && nextFilter.getOp() == null && isBlank(nextFilter.getProperty())) { - continue; - } - - if (isBlank(nextFilter.getValue()) || nextFilter.getOp() == null || isBlank(nextFilter.getProperty())) { - throw new InvalidRequestException("Invalid filter, must have fields populated: property op value"); - } - - - if (nextFilter.getProperty().equals("display:exact") && nextFilter.getOp() == FilterOperator.EQUAL) { - addDisplayFilterExact(qb, bool, nextFilter); - } else if ("display".equals(nextFilter.getProperty()) && nextFilter.getOp() == FilterOperator.EQUAL) { - if (nextFilter.getValue().trim().contains(" ")) { - addDisplayFilterExact(qb, bool, nextFilter); - } else { - addDisplayFilterInexact(qb, bool, nextFilter); - } - } else if ((nextFilter.getProperty().equals("concept") || nextFilter.getProperty().equals("code")) && nextFilter.getOp() == FilterOperator.ISA) { - TermConcept code = myTerminologySvc.findCode(system, nextFilter.getValue()); - if (code == null) { - throw new InvalidRequestException("Invalid filter criteria - code does not exist: {" + system + "}" + nextFilter.getValue()); - } - - ourLog.info(" * Filtering on codes with a parent of {}/{}/{}", code.getId(), code.getCode(), code.getDisplay()); - bool.must(qb.keyword().onField("myParentPids").matching("" + code.getId()).createQuery()); - } else { - throw new InvalidRequestException("Unknown filter property[" + nextFilter + "] + op[" + nextFilter.getOpElement().getValueAsString() + "]"); - } - } - - Query luceneQuery = bool.createQuery(); - FullTextQuery jpaQuery = em.createFullTextQuery(luceneQuery, TermConcept.class); - jpaQuery.setMaxResults(1000); - - StopWatch sw = new StopWatch(); - - @SuppressWarnings("unchecked") - List result = jpaQuery.getResultList(); - - ourLog.info("Expansion completed in {}ms", sw.getMillis()); - - for (TermConcept nextConcept : result) { - addCodeIfNotAlreadyAdded(system, retVal, addedCodes, nextConcept); - } - - retVal.setTotal(jpaQuery.getResultSize()); - } - - if (!haveIncludeCriteria) { - List allCodes = myTerminologySvc.findCodes(system); - for (TermConcept nextConcept : allCodes) { - addCodeIfNotAlreadyAdded(system, retVal, addedCodes, nextConcept); - } - } - - return retVal; + protected IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource, RequestDetails theRequestDetails) { + String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()); + return myCodeSystemResourceDao.update(theCodeSystemResource, matchUrl, theRequestDetails).getId(); } -// @Override -// public List expandValueSet(String theValueSet) { -// ValueSet source = new ValueSet(); -// source.getCompose().addInclude().addValueSet(theValueSet); -// try { -// ArrayList retVal = new ArrayList(); -// -// HapiWorkerContext worker = new HapiWorkerContext(myContext, myValidationSupport); -// ValueSetExpansionOutcome outcome = worker.expand(source, null); -// for (ValueSetExpansionContainsComponent next : outcome.getValueset().getExpansion().getContains()) { -// retVal.add(new VersionIndependentConcept(next.getSystem(), next.getCode())); -// } -// -// return retVal; -// -// } catch (BaseServerResponseException e) { -// throw e; -// } catch (Exception e) { -// throw new InternalErrorException(e); -// } -// -// } + @Override + protected void createOrUpdateValueSet(ValueSet theValueSet, RequestDetails theRequestDetails) { + String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); + myValueSetResourceDao.update(theValueSet, matchUrl, theRequestDetails); + } + + @Override + public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { + ValueSet valueSetToExpand = new ValueSet(); + valueSetToExpand.getCompose().addInclude(theInclude); + return super.expandValueSet(valueSetToExpand).getExpansion(); + } @Override public List fetchAllConformanceResources(FhirContext theContext) { @@ -323,23 +184,6 @@ public class HapiTerminologySvcR4 implements IHapiTerminologySvcR4 { return myTerminologySvc.supportsSystem(theSystem); } - @Override - @Transactional(propagation = Propagation.REQUIRED) - public void storeNewCodeSystemVersion(CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets) { - Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL"); - - IIdType csId = myCodeSystemResourceDao.update(theCodeSystemResource, "CodeSystem?url=" + UrlUtil.escapeUrlParam(theCodeSystemResource.getUrl()), theRequestDetails).getId(); - - ResourceTable resource = (ResourceTable) myCodeSystemResourceDao.readEntity(csId); - Long codeSystemResourcePid = resource.getId(); - - ourLog.info("CodeSystem resource has ID: {}", csId.getValue()); - - theCodeSystemVersion.setResource(resource); - myTerminologySvc.storeNewCodeSystemVersion(codeSystemResourcePid, theCodeSystemResource.getUrl(), theCodeSystemVersion); - - } - @CoverageIgnore @Override public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java index 20f839f17de..9d57cda6924 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvc.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.rest.api.server.RequestDetails; import org.hl7.fhir.r4.model.ValueSet; import java.util.List; @@ -60,4 +61,10 @@ public interface IHapiTerminologySvc { boolean supportsSystem(String theCodeSystem); -} + List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode); + + List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode); + + void storeNewCodeSystemVersion(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets); + + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java index 4f208fd6518..354ca79b0a3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcDstu3.java @@ -22,6 +22,6 @@ package ca.uhn.fhir.jpa.term; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; -public interface IHapiTerminologySvcDstu3 extends IValidationSupport, IVersionSpecificValidationSupport { - +public interface IHapiTerminologySvcDstu3 extends IHapiTerminologySvc, IValidationSupport { + // nothing } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcR4.java index 8f26c6424f2..818d758126f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IHapiTerminologySvcR4.java @@ -22,6 +22,6 @@ package ca.uhn.fhir.jpa.term; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; -public interface IHapiTerminologySvcR4 extends IValidationSupport { - +public interface IHapiTerminologySvcR4 extends IHapiTerminologySvc, IValidationSupport { + // nothing } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IVersionSpecificValidationSupport.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IVersionSpecificValidationSupport.java deleted file mode 100644 index 69c26e448c1..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/IVersionSpecificValidationSupport.java +++ /dev/null @@ -1,18 +0,0 @@ -package ca.uhn.fhir.jpa.term; - -import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.ValueSet; - -import java.util.List; - -public interface IVersionSpecificValidationSupport { - - List findCodesAboveUsingBuiltInSystems(String theSystem, String theCode); - - List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode); - - void storeNewCodeSystemVersion(CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets); - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java index 1fe5712d89b..45b9c4720ff 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java @@ -4,9 +4,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; -import ca.uhn.fhir.jpa.term.loinc.LoincAnswerListHandler; -import ca.uhn.fhir.jpa.term.loinc.LoincHandler; -import ca.uhn.fhir.jpa.term.loinc.LoincHierarchyHandler; +import ca.uhn.fhir.jpa.term.loinc.*; import ca.uhn.fhir.jpa.term.snomedct.SctHandlerConcept; import ca.uhn.fhir.jpa.term.snomedct.SctHandlerDescription; import ca.uhn.fhir.jpa.term.snomedct.SctHandlerRelationship; @@ -62,6 +60,9 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { public static final String LOINC_HIERARCHY_FILE = "MULTI-AXIAL_HIERARCHY.CSV"; public static final String LOINC_ANSWERLIST_FILE = "AnswerList_Beta_1.csv"; public static final String LOINC_ANSWERLIST_LINK_FILE = "LoincAnswerListLink_Beta_1.csv"; + public static final String LOINC_PART_FILE = "Part_Beta_1.csv"; + public static final String LOINC_PART_LINK_FILE = "LoincPartLink_Beta_1.csv"; + public static final String LOINC_PART_RELATED_CODE_MAPPING_FILE = "PartRelatedCodeMapping_Beta_1.csv"; public static final String SCT_FILE_CONCEPT = "Terminology/sct2_Concept_Full_"; public static final String SCT_FILE_DESCRIPTION = "Terminology/sct2_Description_Full-en"; public static final String SCT_FILE_RELATIONSHIP = "Terminology/sct2_Relationship_Full"; @@ -193,7 +194,7 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { CodeSystem loincCs; try { - String loincCsString = IOUtils.toString(HapiTerminologySvcImpl.class.getResourceAsStream("/ca/uhn/fhir/jpa/term/loinc/loinc.xml"), Charsets.UTF_8); + String loincCsString = IOUtils.toString(BaseHapiTerminologySvcImpl.class.getResourceAsStream("/ca/uhn/fhir/jpa/term/loinc/loinc.xml"), Charsets.UTF_8); loincCs = FhirContext.forR4().newXmlParser().parseResource(CodeSystem.class, loincCsString); } catch (IOException e) { throw new InternalErrorException("Failed to load loinc.xml", e); @@ -220,6 +221,18 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { handler = new LoincAnswerListHandler(codeSystemVersion, code2concept, propertyNames, valueSets); iterateOverZipFile(theZipBytes, LOINC_ANSWERLIST_FILE, handler, ',', QuoteMode.NON_NUMERIC); + // Answer list links (connects loinc observation codes to answerlist codes) + handler = new LoincAnswerListLinkHandler(code2concept, valueSets); + iterateOverZipFile(theZipBytes, LOINC_ANSWERLIST_LINK_FILE, handler, ',', QuoteMode.NON_NUMERIC); + + // Part file + handler = new LoincPartHandler(codeSystemVersion, code2concept); + iterateOverZipFile(theZipBytes, LOINC_PART_FILE, handler, ',', QuoteMode.NON_NUMERIC); + + // Part link file + handler = new LoincPartLinkHandler(codeSystemVersion, code2concept); + iterateOverZipFile(theZipBytes, LOINC_PART_LINK_FILE, handler, ',', QuoteMode.NON_NUMERIC); + theZipBytes.clear(); for (Entry next : code2concept.entrySet()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListLinkHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListLinkHandler.java new file mode 100644 index 00000000000..c3feb412e0d --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincAnswerListLinkHandler.java @@ -0,0 +1,72 @@ +package ca.uhn.fhir.jpa.term.loinc; + +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.IRecordHandler; +import org.apache.commons.csv.CSVRecord; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.ValueSet; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.trim; + +public class LoincAnswerListLinkHandler implements IRecordHandler { + + private final Map myCode2Concept; + private final Map myIdToValueSet = new HashMap<>(); + + public LoincAnswerListLinkHandler(Map theCode2concept, List theValueSets) { + myCode2Concept = theCode2concept; + for (ValueSet next : theValueSets) { + myIdToValueSet.put(next.getId(), next); + } + } + + @Override + public void accept(CSVRecord theRecord) { + String applicableContext = trim(theRecord.get("ApplicableContext")); + + /* + * Per Dan V's Notes: + * + * Note: in our current format, we support binding of the same + * LOINC term to different answer lists depending on the panel + * context. I don’t believe there’s a way to handle that in + * the current FHIR spec, so I might suggest we discuss either + * only binding the “default” (non-context specific) list or + * if multiple bindings could be supported. + */ + if (isNotBlank(applicableContext)) { + return; + } + + String answerListId = trim(theRecord.get("AnswerListId")); + if (isBlank(answerListId)) { + return; + } + + String loincNumber = trim(theRecord.get("LoincNumber")); + if (isBlank(loincNumber)) { + return; + } + + TermConcept loincCode = myCode2Concept.get(loincNumber); + if (loincCode != null) { + loincCode.addProperty("answer-list", answerListId); + } + + TermConcept answerListCode = myCode2Concept.get(answerListId); + if (answerListCode != null) { + answerListCode.addProperty("answers-for", loincNumber); + } + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartHandler.java new file mode 100644 index 00000000000..3dadb4e0e04 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartHandler.java @@ -0,0 +1,45 @@ +package ca.uhn.fhir.jpa.term.loinc; + +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.term.IRecordHandler; +import org.apache.commons.csv.CSVRecord; +import org.hl7.fhir.r4.model.ValueSet; + +import java.util.HashMap; +import java.util.Map; + +import static org.apache.commons.lang3.StringUtils.trim; + +public class LoincPartHandler implements IRecordHandler { + + private final Map myCode2Concept; + private final TermCodeSystemVersion myCodeSystemVersion; + private final Map myIdToValueSet = new HashMap<>(); + + public LoincPartHandler(TermCodeSystemVersion theCodeSystemVersion, Map theCode2concept) { + myCodeSystemVersion = theCodeSystemVersion; + myCode2Concept = theCode2concept; + } + + @Override + public void accept(CSVRecord theRecord) { + + // this is the code for the list (will repeat) + String partNumber = trim(theRecord.get("PartNumber")); + String partTypeName = trim(theRecord.get("PartTypeName")); + String partName = trim(theRecord.get("PartName")); + String partDisplayName = trim(theRecord.get("PartDisplayName")); + String status = trim(theRecord.get("Status")); + + if (!"ACTIVE".equals(status)) { + return; + } + + TermConcept concept = new TermConcept(myCodeSystemVersion, partNumber); + concept.setDisplay(partName); + + myCode2Concept.put(partDisplayName, concept); + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartLinkHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartLinkHandler.java new file mode 100644 index 00000000000..e27c55c403e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartLinkHandler.java @@ -0,0 +1,51 @@ +package ca.uhn.fhir.jpa.term.loinc; + +import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; +import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.term.IRecordHandler; +import org.apache.commons.csv.CSVRecord; +import org.hl7.fhir.r4.model.ValueSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +import static org.apache.commons.lang3.StringUtils.trim; + +public class LoincPartLinkHandler implements IRecordHandler { + + private final Map myCode2Concept; + private final TermCodeSystemVersion myCodeSystemVersion; + + public LoincPartLinkHandler(TermCodeSystemVersion theCodeSystemVersion, Map theCode2concept) { + myCodeSystemVersion = theCodeSystemVersion; + myCode2Concept = theCode2concept; + } + + @Override + public void accept(CSVRecord theRecord) { + + String loincNumber = trim(theRecord.get("LoincNumber")); + String longCommonName = trim(theRecord.get("LongCommonName")); + String partNumber = trim(theRecord.get("PartNumber")); + String partDisplayName = trim(theRecord.get("PartDisplayName")); + String status = trim(theRecord.get("Status")); + + TermConcept loincConcept = myCode2Concept.get(loincNumber); + TermConcept partConcept = myCode2Concept.get(partNumber); + + if (loincConcept==null) { + ourLog.warn("No loinc code: {}", loincNumber); + return; + } + if (partConcept==null) { + ourLog.warn("No part code: {}", partNumber); + return; + } + + partConcept.addProperty(); + + } +private static final Logger ourLog = LoggerFactory.getLogger(LoincPartLinkHandler.class); +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3CodeSystemTest.java index 1f12bdb1486..36dd96b73d0 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3CodeSystemTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3CodeSystemTest.java @@ -4,7 +4,7 @@ import static org.junit.Assert.assertNotEquals; import java.nio.charset.StandardCharsets; -import ca.uhn.fhir.jpa.term.HapiTerminologySvcImpl; +import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; import org.apache.commons.io.IOUtils; import org.hl7.fhir.dstu3.model.CodeSystem; import org.junit.AfterClass; @@ -18,13 +18,13 @@ public class FhirResourceDaoDstu3CodeSystemTest extends BaseJpaDstu3Test { @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); - HapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); } @Test public void testIndexContained() throws Exception { - HapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); String input = IOUtils.toString(getClass().getResource("/dstu3_codesystem_complete.json"), StandardCharsets.UTF_8); CodeSystem cs = myFhirCtx.newJsonParser().parseResource(CodeSystem.class, input); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java index 5ffbb3f226f..2ec3bc3b1fa 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java @@ -9,7 +9,7 @@ import static org.junit.Assert.fail; import java.util.*; -import ca.uhn.fhir.jpa.term.HapiTerminologySvcImpl; +import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceCategory; import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus; @@ -48,7 +48,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { public void after() { myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); - HapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); } @Before @@ -483,7 +483,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test { @Test public void testExpandWithIsAInExternalValueSetReindex() { - HapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); createExternalCsAndLocalVs(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java index 2e3eb177f2a..f334e53d7ac 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CodeSystemTest.java @@ -4,12 +4,12 @@ import static org.junit.Assert.assertNotEquals; import java.nio.charset.StandardCharsets; +import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; import org.apache.commons.io.IOUtils; import org.hl7.fhir.r4.model.CodeSystem; import org.junit.AfterClass; import org.junit.Test; -import ca.uhn.fhir.jpa.term.HapiTerminologySvcImpl; import ca.uhn.fhir.util.TestUtil; public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test { @@ -18,13 +18,13 @@ public class FhirResourceDaoR4CodeSystemTest extends BaseJpaR4Test { @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); - HapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); } @Test public void testIndexContained() throws Exception { - HapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); String input = IOUtils.toString(getClass().getResource("/r4/codesystem_complete.json"), StandardCharsets.UTF_8); CodeSystem cs = myFhirCtx.newJsonParser().parseResource(CodeSystem.class, input); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java index 29fa21fe501..19f7e28e6ed 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java @@ -9,7 +9,7 @@ import static org.junit.Assert.fail; import java.util.*; -import ca.uhn.fhir.jpa.term.HapiTerminologySvcImpl; +import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory; import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus; @@ -48,7 +48,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { public void after() { myDaoConfig.setDeferIndexingForCodesystemsOfSize(new DaoConfig().getDeferIndexingForCodesystemsOfSize()); - HapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(false); } @Before @@ -483,7 +483,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test { @Test public void testExpandWithIsAInExternalValueSetReindex() { - HapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + BaseHapiTerminologySvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); createExternalCsAndLocalVs(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java index a226b02b347..b4037581ebc 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcLoincTest.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyListOf; @@ -88,6 +89,7 @@ public class TerminologyLoaderSvcLoincTest { addFile("/loinc/", "loinc.csv", TerminologyLoaderSvc.LOINC_FILE); addFile("/loinc/", "hierarchy.csv", TerminologyLoaderSvc.LOINC_HIERARCHY_FILE); addFile("/loinc/", "AnswerList_Beta_1.csv", TerminologyLoaderSvc.LOINC_ANSWERLIST_FILE); + addFile("/loinc/", "LoincAnswerListLink_Beta_1.csv", TerminologyLoaderSvc.LOINC_ANSWERLIST_LINK_FILE); // Actually do the load mySvc.loadLoinc(myFiles, details); @@ -108,6 +110,10 @@ public class TerminologyLoaderSvcLoincTest { assertEquals("Pt", code.getProperty("TIME_ASPCT")); assertEquals("R' wave amplitude in lead I", code.getDisplay()); + // Loinc code with answer + code = concepts.get("61438-8"); + assertThat(code.getProperties("answer-list"), contains("LL1000-0")); + // Answer list code = concepts.get("LL1001-8"); assertEquals("LL1001-8", code.getCode()); @@ -119,6 +125,11 @@ public class TerminologyLoaderSvcLoincTest { assertEquals("1-2 times per week", code.getDisplay()); assertEquals(3, code.getSequence().intValue()); + // Answer list code with link to answers-for + code = concepts.get("LL1000-0"); + assertThat(code.getProperties("answers-for"), contains("61438-8")); + + // AnswerList valueSet Map valueSets = new HashMap<>(); for (ValueSet next : myValueSetsCaptor.getValue()) { @@ -130,11 +141,16 @@ public class TerminologyLoaderSvcLoincTest { assertEquals("PhenX05_14_30D freq amts", vs.getName()); assertEquals("urn:oid:1.3.6.1.4.1.12009.10.1.166", vs.getUrl()); assertEquals(1, vs.getCompose().getInclude().size()); - assertEquals(6, vs.getCompose().getInclude().get(0).getConcept().size()); + assertEquals(7, vs.getCompose().getInclude().get(0).getConcept().size()); assertEquals(IHapiTerminologyLoaderSvc.LOINC_URL, vs.getCompose().getInclude().get(0).getSystem()); assertEquals("LA6270-8", vs.getCompose().getInclude().get(0).getConcept().get(0).getCode()); assertEquals("Never", vs.getCompose().getInclude().get(0).getConcept().get(0).getDisplay()); + // Part + code = concepts.get("LP101394-7"); + assertEquals("LP101394-7", code.getCode()); + assertEquals("adjusted for maternal weight", code.getDisplay()); + } diff --git a/hapi-fhir-jpaserver-base/src/test/resources/loinc/AnswerList_Beta_1.csv b/hapi-fhir-jpaserver-base/src/test/resources/loinc/AnswerList_Beta_1.csv index 1a778ef6aa0..8af66cca178 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/loinc/AnswerList_Beta_1.csv +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/AnswerList_Beta_1.csv @@ -1,10 +1,11 @@ -"AnswerListId","AnswerListName" ,"AnswerListOID" ,"ExtDefinedYN","ExtDefinedAnswerListCodeSystem","ExtDefinedAnswerListLink","AnswerStringId","LocalAnswerCode","LocalAnswerCodeSystem","SequenceNumber","DisplayText" ,"ExtCodeId","ExtCodeDisplayName","ExtCodeSystem","ExtCodeSystemVersion","ExtCodeSystemCopyrightNotice","SubsequentTextPrompt","Description","Score" -"LL1000-0" ,"PhenX05_13_30D bread amt","1.3.6.1.4.1.12009.10.1.165","N" , , ,"LA13825-7" ,"1" , ,1 ,"1 slice or 1 dinner roll" , , , , , , , , -"LL1000-0" ,"PhenX05_13_30D bread amt","1.3.6.1.4.1.12009.10.1.165","N" , , ,"LA13838-0" ,"2" , ,2 ,"2 slices or 2 dinner rolls" , , , , , , , , -"LL1000-0" ,"PhenX05_13_30D bread amt","1.3.6.1.4.1.12009.10.1.165","N" , , ,"LA13892-7" ,"3" , ,3 ,"More than 2 slices or 2 dinner rolls", , , , , , , , -"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA6270-8" ,"00" , ,1 ,"Never" , , , , , , , , -"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA13836-4" ,"01" , ,2 ,"1-3 times per month" , , , , , , , , -"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA13834-9" ,"02" , ,3 ,"1-2 times per week" , , , , , , , , -"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA13853-9" ,"03" , ,4 ,"3-4 times per week" , , , , , , , , -"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA13860-4" ,"04" , ,5 ,"5-6 times per week" , , , , , , , , -"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA13827-3" ,"05" , ,6 ,"1 time per day" , , , , , , , , +"AnswerListId","AnswerListName" ,"AnswerListOID" ,"ExtDefinedYN","ExtDefinedAnswerListCodeSystem","ExtDefinedAnswerListLink","AnswerStringId","LocalAnswerCode","LocalAnswerCodeSystem","SequenceNumber","DisplayText" ,"ExtCodeId","ExtCodeDisplayName" ,"ExtCodeSystem" ,"ExtCodeSystemVersion" ,"ExtCodeSystemCopyrightNotice" ,"SubsequentTextPrompt","Description","Score" +"LL1000-0" ,"PhenX05_13_30D bread amt","1.3.6.1.4.1.12009.10.1.165","N" , , ,"LA13825-7" ,"1" , ,1 ,"1 slice or 1 dinner roll" , , , , , , , , +"LL1000-0" ,"PhenX05_13_30D bread amt","1.3.6.1.4.1.12009.10.1.165","N" , , ,"LA13838-0" ,"2" , ,2 ,"2 slices or 2 dinner rolls" , , , , , , , , +"LL1000-0" ,"PhenX05_13_30D bread amt","1.3.6.1.4.1.12009.10.1.165","N" , , ,"LA13892-7" ,"3" , ,3 ,"More than 2 slices or 2 dinner rolls", , , , , , , , +"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA6270-8" ,"00" , ,1 ,"Never" , , , , , , , , +"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA13836-4" ,"01" , ,2 ,"1-3 times per month" , , , , , , , , +"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA13834-9" ,"02" , ,3 ,"1-2 times per week" , , , , , , , , +"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA13853-9" ,"03" , ,4 ,"3-4 times per week" , , , , , , , , +"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA13860-4" ,"04" , ,5 ,"5-6 times per week" , , , , , , , , +"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA13827-3" ,"05" , ,6 ,"1 time per day" , , , , , , , , +"LL1001-8" ,"PhenX05_14_30D freq amts","1.3.6.1.4.1.12009.10.1.166","N" , , ,"LA4389-8" ,"97" , ,11 ,"Refused" ,"443390004","Refused (qualifier value)","http://snomed.info/sct","http://snomed.info/sct/900000000000207008/version/20170731","This material includes SNOMED Clinical Terms® (SNOMED CT®) which is used by permission of the International Health Terminology Standards Development Organisation (IHTSDO) under license. All rights reserved. SNOMED CT® was originally created by The College", , , diff --git a/hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincAnswerListLink_Beta_1.csv b/hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincAnswerListLink_Beta_1.csv index a30efca859e..820602c77ad 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincAnswerListLink_Beta_1.csv +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincAnswerListLink_Beta_1.csv @@ -1,4 +1,5 @@ "LoincNumber","LongCommonName" ,"AnswerListId","AnswerListName" ,"AnswerListLinkType","ApplicableContext" +"61438-8" ,"Each time you ate bread, toast or dinner rolls, how much did you usually eat in the past 30 days [PhenX]","LL1000-0" ,"PhenX05_13_30D bread amt","NORMATIVE" , "10061-0" ,"S' wave amplitude in lead I" ,"LL1311-1" ,"PhenX12_44" ,"EXAMPLE" , "10331-7" ,"Rh [Type] in Blood" ,"LL360-9" ,"Pos|Neg" ,"EXAMPLE" , "10389-5" ,"Blood product.other [Type]" ,"LL2413-4" ,"Othr bld prod" ,"EXAMPLE" , @@ -8,4 +9,3 @@ "10401-8" ,"Immune serum globulin given [Type]" ,"LL2421-7" ,"IM/IV" ,"EXAMPLE" , "10410-9" ,"Plasma given [Type]" ,"LL2417-5" ,"Plasma type" ,"EXAMPLE" , "10568-4" ,"Clarity of Semen" ,"LL2427-4" ,"Clear/Opales/Milky" ,"EXAMPLE" , -"61438-8" ,"Each time you ate bread, toast or dinner rolls, how much did you usually eat in the past 30 days [PhenX]","LL1000-0" ,"PhenX05_13_30D bread amt","NORMATIVE" , diff --git a/hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincPartLink_Beta_1.csv b/hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincPartLink_Beta_1.csv new file mode 100644 index 00000000000..07277640bd7 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincPartLink_Beta_1.csv @@ -0,0 +1,10 @@ +"LoincNumber","LongCommonName","PartNumber","PartName","PartTypeName","LinkTypeName" +"10000-8","R wave duration in lead AVR","LP31088-5","R wave duration.lead AVR","COMPONENT","Primary" +"10000-8","R wave duration in lead AVR","LP6244-0","EKG","METHOD","Primary" +"10000-8","R wave duration in lead AVR","LP6879-3","Time","PROPERTY","Primary" +"10000-8","R wave duration in lead AVR","LP6960-1","Pt","TIME","Primary" +"10000-8","R wave duration in lead AVR","LP7289-4","Heart","SYSTEM","Primary" +"10000-8","R wave duration in lead AVR","LP7753-9","Qn","SCALE","Primary" +"10000-8","R wave duration in lead AVR","LP7795-0","EKG.MEAS","CLASS","Primary" +"10000-8","R wave duration in lead AVR","LP14259-3","Lead","COMPONENT","Search" +"10000-8","R wave duration in lead AVR","LP14744-4","Duration","COMPONENT","Search" diff --git a/hapi-fhir-jpaserver-base/src/test/resources/loinc/Part_Beta_1.csv b/hapi-fhir-jpaserver-base/src/test/resources/loinc/Part_Beta_1.csv new file mode 100644 index 00000000000..9da3ad5770d --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/Part_Beta_1.csv @@ -0,0 +1,9 @@ +"PartNumber","PartTypeName","PartName","PartDisplayName","Status" +"LP101394-7","ADJUSTMENT","adjusted for maternal weight","adjusted for maternal weight","ACTIVE" +"LP101907-6","ADJUSTMENT","corrected for age","corrected for age","ACTIVE" +"LP115711-6","ADJUSTMENT","corrected for background","corrected for background","ACTIVE" +"LP147359-6","ADJUSTMENT","adjusted for body weight","adjusted for body weight","ACTIVE" +"LP173482-3","ADJUSTMENT","1st specimen","1st specimen","DEPRECATED" +"LP173483-1","ADJUSTMENT","post cyanocobalamin",,"ACTIVE" +"LP173484-9","ADJUSTMENT","W hyperextension)",,"ACTIVE" +"LP6244-0","METHOD","EKG","Electrocardiogram (EKG)","ACTIVE" diff --git a/hapi-fhir-jpaserver-base/src/test/resources/loinc/loinc.csv b/hapi-fhir-jpaserver-base/src/test/resources/loinc/loinc.csv index ec93d6b415d..e46b1881a7d 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/loinc/loinc.csv +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/loinc.csv @@ -9,3 +9,4 @@ "10019-8" ,"R' wave amplitude.lead V4" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS","CH" ,"2.48" ,"MIN" , ,"ACTIVE", ,2 , , , , , ,"Y" , ,"Cardiac; ECG; EKG.MEASUREMENTS; Electrical potential; Electrocardiogram; Electrocardiograph; Hrt; Painter's colic; PB; Plumbism; Point in time; QNT; Quan; Quant; Quantitative; R prime; R' wave Amp L-V4; R wave Amp L-V4; Random; Right; Voltage" ,"R' wave Amp L-V4" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead V4" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , "10020-6" ,"R' wave amplitude.lead V5" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS","CH" ,"2.48" ,"MIN" , ,"ACTIVE", ,2 , , , , , ,"Y" , ,"Cardiac; ECG; EKG.MEASUREMENTS; Electrical potential; Electrocardiogram; Electrocardiograph; Hrt; Painter's colic; PB; Plumbism; Point in time; QNT; Quan; Quant; Quantitative; R prime; R' wave Amp L-V5; R wave Amp L-V5; Random; Right; Voltage" ,"R' wave Amp L-V5" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead V5" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , "61438-8" ,"Each time you ate bread, toast or dinner rolls, how much did you usually eat in the past 30D","Find" ,"Pt" ,"^Patient" ,"Ord" ,"PhenX" ,"PHENX" ,"PhenX" ,"2.44" ,"MIN" , ,"TRIAL" , ,2 , , , ,"Each time you eat bread, toast or dinner rolls, how much do you usually eat?","PhenX.050201100100","N" , ,"Finding; Findings; How much bread in 30D; Last; Ordinal; Point in time; QL; Qual; Qualitative; Random; Screen" ,"How much bread in 30D PhenX", , , , , ,"Each time you ate bread, toast or dinner rolls, how much did you usually eat in the past 30 days [PhenX]", , , , , , , ,0 ,0 ,0 , , , , , +"10000-8" ,"R wave duration.lead AVR" ,"Time" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS","CH" ,"2.48" ,"MIN" , ,"ACTIVE", ,2 , , , , , ,"Y" , ,"Cardiac; Durat; ECG; EKG.MEASUREMENTS; Electrocardiogram; Electrocardiograph; Hrt; Painter's colic; PB; Plumbism; Point in time; QNT; Quan; Quant; Quantitative; R prime; R' wave dur L-AVR; R wave dur L-AVR; Random; Right" ,"R wave dur L-AVR" ,"Observation", , , ,"s" ,"R wave duration in lead AVR" , , ,"s" , , , , ,0 ,0 ,0 , , , , ,