diff --git a/LOINC_NOTES.txt b/LOINC_NOTES.txt index 49026d20b3a..fa9a988eb52 100644 --- a/LOINC_NOTES.txt +++ b/LOINC_NOTES.txt @@ -6,6 +6,10 @@ TODO: Comments for Loinc: +Overall +- ValueSet and ConceptMap resources have a spot for copyright and + contact information. Are there official values for these? + 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 @@ -18,5 +22,31 @@ Parts - The PartTypeName (e.g. "ADJUSTMENT") is ignored as there is no corresponding property in loinc.xml - PartDisplayName is not mapped +- Part links are not currently processed (it's not clear to me how to model + these in FHIR, as CodeSystem.hierarchyMeaning has to be only one of 'is-a' + or 'part-of' and presumably the 'is-a' relationship is more important. +Part Mappings +- I have made LOINC the source and SCT the target for the mappings in the + ConceptMap resource. Does this seem like the appropriate orientation? +- A canonical URI should be defined for the LOINC->SCT mapping ConceptMap + resource. I have hardcoded "http://loinc.org/loinc-to-snomed" for now, but + we should discuss what is appropriate. + +RSNA Playbook +- A canonical URI should be defined for the "all RSNA playbook codes" ValueSet. + I have hardcoded "http://loinc.org/rsna-codes" for now but we should discuss + what is appropriate. +- A name for the "RSNA Playbook" ValueSet is needed. +- Just to confirm, the "all RSNA playbook codes" ValueSet should contain the + loinc codes (e.g. "17787-3") and not the part codes (e.g. "LP199995-4")? +- A codesystem URI for radlex RID and RPID codes is needed (currently + "http://rid" and "http://rpid" are used as placeholders since I'm assuming + these exist somewhere. +- For mappings from loinc part codes to RadLex RIDs, are the codes considered + equivalent (or would they be wider/narrower). They look equivalent to me. + +Document Ontology +- Need to define a URI for the document ontology ValueSet. Currently I am + using "http://loinc.org/document-ontology-codes" 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 59ba177a0c9..ab882885774 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 @@ -106,7 +106,7 @@ public class BaseDstu3Config extends BaseConfig { @Bean(autowire = Autowire.BY_TYPE) public IHapiTerminologyLoaderSvc terminologyLoaderService() { - return new TerminologyLoaderSvc(); + return new TerminologyLoaderSvcImpl(); } @Bean(autowire = Autowire.BY_TYPE) 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 8215cc4a44f..c9ff4f94920 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 @@ -14,7 +14,7 @@ import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4; import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvcR4; -import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; import ca.uhn.fhir.validation.IValidatorModule; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; @@ -124,7 +124,7 @@ public class BaseR4Config extends BaseConfig { @Bean(autowire = Autowire.BY_TYPE) public IHapiTerminologyLoaderSvc terminologyLoaderService() { - return new TerminologyLoaderSvc(); + return new TerminologyLoaderSvcImpl(); } @Bean(autowire = Autowire.BY_TYPE) 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 02c3b772012..8a39a5178cf 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 @@ -8,6 +8,7 @@ import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.hibernate.search.annotations.*; +import org.hl7.fhir.r4.model.Coding; import javax.annotation.Nonnull; import javax.persistence.*; @@ -121,14 +122,27 @@ public class TermConcept implements Serializable { } } - public void addProperty(@Nonnull String thePropertyName, @Nonnull String thePropertyValue) { + private TermConceptProperty addProperty(@Nonnull TermConceptPropertyTypeEnum thePropertyType, @Nonnull String thePropertyName, @Nonnull String thePropertyValue) { Validate.notBlank(thePropertyName); TermConceptProperty property = new TermConceptProperty(); property.setConcept(this); + property.setType(thePropertyType); property.setKey(thePropertyName); property.setValue(thePropertyValue); - getProperties().add(property); + getStringProperties().add(property); + + return property; + } + + public TermConceptProperty addPropertyCoding(@Nonnull String thePropertyName, @Nonnull String thePropertyCodeSystem, @Nonnull String thePropertyCode, String theDisplayName) { + return addProperty(TermConceptPropertyTypeEnum.CODING, thePropertyName, thePropertyCode) + .setCodeSystem(thePropertyCodeSystem) + .setDisplay(theDisplayName); + } + + public TermConceptProperty addPropertyString(@Nonnull String thePropertyName, @Nonnull String thePropertyValue) { + return addProperty(TermConceptPropertyTypeEnum.STRING, thePropertyName, thePropertyValue); } @Override @@ -159,10 +173,6 @@ public class TermConcept implements Serializable { return myCode; } - public Integer getSequence() { - return mySequence; - } - public void setCode(String theCode) { myCode = theCode; } @@ -213,32 +223,57 @@ public class TermConcept implements Serializable { return myParents; } - public Collection getProperties() { + public Collection getStringProperties() { if (myProperties == null) { myProperties = new ArrayList<>(); } return myProperties; } - public String getProperty(String thePropertyName) { - for (TermConceptProperty next : getProperties()) { - if (thePropertyName.equals(next.getKey())) { - return next.getValue(); - } - } - return null; - } - - public List getProperties(String thePropertyName) { + public List getStringProperties(String thePropertyName) { List retVal = new ArrayList<>(); - for (TermConceptProperty next : getProperties()) { + for (TermConceptProperty next : getStringProperties()) { if (thePropertyName.equals(next.getKey())) { - retVal.add(next.getValue()); + if (next.getType() == TermConceptPropertyTypeEnum.STRING) { + retVal.add(next.getValue()); + } } } return retVal; } + public List getCodingProperties(String thePropertyName) { + List retVal = new ArrayList<>(); + for (TermConceptProperty next : getStringProperties()) { + if (thePropertyName.equals(next.getKey())) { + if (next.getType() == TermConceptPropertyTypeEnum.CODING) { + Coding coding = new Coding(); + coding.setSystem(next.getCodeSystem()); + coding.setCode(next.getValue()); + coding.setDisplay(next.getDisplay()); + retVal.add(coding); + } + } + } + return retVal; + } + + public Integer getSequence() { + return mySequence; + } + + public void setSequence(Integer theSequence) { + mySequence = theSequence; + } + + public String getStringProperty(String thePropertyName) { + List properties = getStringProperties(thePropertyName); + if (properties.size() > 0) { + return properties.get(0); + } + return null; + } + @Override public int hashCode() { HashCodeBuilder b = new HashCodeBuilder(); @@ -294,10 +329,6 @@ public class TermConcept implements Serializable { myParentPids = theParentPids; } - public void setSequence(Integer theSequence) { - mySequence = theSequence; - } - @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("code", myCode).append("display", myDisplay).build(); 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 5ab572f4df0..b3a5bbbe2c3 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 @@ -33,8 +33,8 @@ import java.io.Serializable; }) public class TermConceptProperty implements Serializable { + static final int MAX_PROPTYPE_ENUM_LENGTH = 6; private static final long serialVersionUID = 1L; - @ManyToOne @JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT")) private TermConcept myConcept; @@ -48,6 +48,48 @@ public class TermConceptProperty implements Serializable { private String myKey; @Column(name = "PROP_VAL", length = 200, nullable = true) private String myValue; + @Column(name = "PROP_TYPE", length = MAX_PROPTYPE_ENUM_LENGTH, nullable = false) + private TermConceptPropertyTypeEnum myType; + /** + * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} + */ + @Column(name = "PROP_CODESYSTEM", length = 200, nullable = true) + private String myCodeSystem; + /** + * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} + */ + @Column(name = "PROP_DISPLAY", length = 200, nullable = true) + private String myDisplay; + + /** + * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} + */ + public String getCodeSystem() { + return myCodeSystem; + } + + /** + * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} + */ + public TermConceptProperty setCodeSystem(String theCodeSystem) { + myCodeSystem = theCodeSystem; + return this; + } + + /** + * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} + */ + public String getDisplay() { + return myDisplay; + } + + /** + * Relevant only for properties of type {@link TermConceptPropertyTypeEnum#CODING} + */ + public TermConceptProperty setDisplay(String theDisplay) { + myDisplay = theDisplay; + return this; + } public String getKey() { return myKey; @@ -57,10 +99,27 @@ public class TermConceptProperty implements Serializable { myKey = theKey; } + public TermConceptPropertyTypeEnum getType() { + return myType; + } + + public TermConceptProperty setType(TermConceptPropertyTypeEnum theType) { + myType = theType; + return this; + } + + /** + * This will contain the value for a {@link TermConceptPropertyTypeEnum#STRING string} + * property, and the code for a {@link TermConceptPropertyTypeEnum#CODING coding} property. + */ public String getValue() { return myValue; } + /** + * This will contain the value for a {@link TermConceptPropertyTypeEnum#STRING string} + * property, and the code for a {@link TermConceptPropertyTypeEnum#CODING coding} property. + */ public void setValue(String theValue) { myValue = theValue; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptPropertyTypeEnum.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptPropertyTypeEnum.java new file mode 100644 index 00000000000..14762936e45 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptPropertyTypeEnum.java @@ -0,0 +1,22 @@ +package ca.uhn.fhir.jpa.entity; + +/** + * @see TermConceptProperty#getType() + * @see TermConceptProperty#MAX_PROPTYPE_ENUM_LENGTH + */ +public enum TermConceptPropertyTypeEnum { + /* + * VALUES SHOULD BE <= 6 CHARS LONG! + * + * We store this in a DB column of that length + */ + + /** + * String + */ + STRING, + /** + * Coding + */ + CODING +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java index 7def6f12b2b..f8933053eae 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseHapiTerminologySvcImpl.java @@ -48,6 +48,7 @@ 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.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -91,20 +92,18 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc @Autowired private DaoConfig myDaoConfig; private long myNextReindexPass; - private boolean myProcessDeferred = true; - @Autowired private PlatformTransactionManager myTransactionMgr; @Autowired private IFhirResourceDaoCodeSystem myCodeSystemResourceDao; - private void addCodeIfNotAlreadyAdded(String system, ValueSet.ValueSetExpansionComponent retVal, Set addedCodes, TermConcept nextConcept) { - if (addedCodes.add(nextConcept.getCode())) { - ValueSet.ValueSetExpansionContainsComponent contains = retVal.addContains(); - contains.setCode(nextConcept.getCode()); - contains.setSystem(system); - contains.setDisplay(nextConcept.getDisplay()); + private void addCodeIfNotAlreadyAdded(String theCodeSystem, ValueSet.ValueSetExpansionComponent theExpansionComponent, Set theAddedCodes, TermConcept theConcept) { + if (theAddedCodes.add(theConcept.getCode())) { + ValueSet.ValueSetExpansionContainsComponent contains = theExpansionComponent.addContains(); + contains.setCode(theConcept.getCode()); + contains.setSystem(theCodeSystem); + contains.setDisplay(theConcept.getDisplay()); } } @@ -137,6 +136,8 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc protected abstract IIdType createOrUpdateCodeSystem(CodeSystem theCodeSystemResource, RequestDetails theRequestDetails); + protected abstract void createOrUpdateConceptMap(ConceptMap theNextConceptMap, RequestDetails theRequestDetails); + abstract void createOrUpdateValueSet(ValueSet theValueSet, RequestDetails theRequestDetails); @Override @@ -248,7 +249,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc } else { -// bool.must(qb.keyword().onField("myProperties").matching(nextFilter.getProperty()+"="+nextFilter.getValue()).createQuery()); +// bool.must(qb.keyword().onField("myProperties").matching(nextFilter.getStringProperty()+"="+nextFilter.getValue()).createQuery()); bool.must(qb.phrase().onField("myProperties").sentence(nextFilter.getProperty() + "=" + nextFilter.getValue()).createQuery()); } @@ -300,8 +301,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc private TermConcept fetchLoadedCode(Long theCodeSystemResourcePid, Long theCodeSystemVersionPid, String theCode) { TermCodeSystemVersion codeSystem = myCodeSystemVersionDao.findByCodeSystemResourceAndVersion(theCodeSystemResourcePid, theCodeSystemVersionPid); - TermConcept concept = myConceptDao.findByCodeSystemAndCode(codeSystem, theCode); - return concept; + return myConceptDao.findByCodeSystemAndCode(codeSystem, theCode); } private void fetchParents(TermConcept theConcept, Set theSetToPopulate) { @@ -353,8 +353,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc TermCodeSystemVersion csv = cs.getCurrentVersion(); Set codes = findCodesAbove(cs.getResource().getId(), csv.getPid(), theCode); - ArrayList retVal = toVersionIndependentConcepts(theSystem, codes); - return retVal; + return toVersionIndependentConcepts(theSystem, codes); } @Transactional(propagation = Propagation.REQUIRED) @@ -711,7 +710,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc @Override @Transactional(propagation = Propagation.REQUIRED) - public void storeNewCodeSystemVersion(CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets) { + public void storeNewCodeSystemVersion(CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets, List theConceptMaps) { Validate.notBlank(theCodeSystemResource.getUrl(), "theCodeSystemResource must have a URL"); IIdType csId = createOrUpdateCodeSystem(theCodeSystemResource, theRequestDetails); @@ -728,6 +727,10 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc createOrUpdateValueSet(nextValueSet, theRequestDetails); } + for (ConceptMap nextConceptMap : theConceptMaps) { + createOrUpdateConceptMap(nextConceptMap, theRequestDetails); + } + } @Override 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 b78f2050a3c..d7259cba057 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 @@ -20,11 +20,11 @@ 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.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; import org.springframework.beans.factory.annotation.Autowired; @@ -66,6 +66,16 @@ public class HapiTerminologySvcDstu2 extends BaseHapiTerminologySvcImpl { throw new UnsupportedOperationException(); } + @Override + protected void createOrUpdateConceptMap(ConceptMap theNextConceptMap, RequestDetails theRequestDetails) { + throw new UnsupportedOperationException(); + } + + @Override + protected void createOrUpdateValueSet(ValueSet theValueSet, RequestDetails theRequestDetails) { + throw new UnsupportedOperationException(); + } + @Override public List expandValueSet(String theValueSet) { throw new UnsupportedOperationException(); @@ -113,14 +123,4 @@ public class HapiTerminologySvcDstu2 extends BaseHapiTerminologySvcImpl { 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 cf65d3090f3..fd0a629de7e 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 @@ -63,6 +63,9 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen @Qualifier("myValueSetDaoDstu3") private IFhirResourceDao myValueSetResourceDao; @Autowired + @Qualifier("myConceptMapDaoDstu3") + private IFhirResourceDao myConceptMapResourceDao; + @Autowired private IFhirResourceDaoCodeSystem myCodeSystemResourceDao; @Autowired private IValidationSupport myValidationSupport; @@ -76,19 +79,6 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen super(); } - @Override - 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); - } - 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())); @@ -124,6 +114,30 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen return myCodeSystemResourceDao.update(resourceToStore, matchUrl, theRequestDetails).getId(); } + @Override + protected void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap, RequestDetails theRequestDetails) { + String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); + ConceptMap resourceToStore; + try { + resourceToStore = VersionConvertor_30_40.convertConceptMap(theConceptMap); + } catch (FHIRException e) { + throw new InternalErrorException(e); + } + myConceptMapResourceDao.update(resourceToStore, matchUrl, theRequestDetails).getId(); + } + + @Override + 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); + } + myValueSetResourceDao.update(valueSetDstu3, matchUrl, theRequestDetails); + } + @Override public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { ValueSet valueSetToExpand = new ValueSet(); 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 8c566259f5b..e66a0c2acee 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 @@ -12,6 +12,7 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; @@ -55,6 +56,9 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; @Autowired + @Qualifier("myConceptMapDaoR4") + private IFhirResourceDao myConceptMapResourceDao; + @Autowired @Qualifier("myCodeSystemDaoR4") private IFhirResourceDao myCodeSystemResourceDao; @Autowired @@ -76,7 +80,6 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements } } - private boolean addTreeIfItContainsCode(String theSystemString, ConceptDefinitionComponent theNext, String theCode, List theListToPopulate) { boolean foundCodeInChild = false; for (ConceptDefinitionComponent nextChild : theNext.getConcept()) { @@ -97,6 +100,12 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements return myCodeSystemResourceDao.update(theCodeSystemResource, matchUrl, theRequestDetails).getId(); } + @Override + protected void createOrUpdateConceptMap(org.hl7.fhir.r4.model.ConceptMap theConceptMap, RequestDetails theRequestDetails) { + String matchUrl = "ConceptMap?url=" + UrlUtil.escapeUrlParam(theConceptMap.getUrl()); + myConceptMapResourceDao.update(theConceptMap, matchUrl, theRequestDetails).getId(); + } + @Override protected void createOrUpdateValueSet(ValueSet theValueSet, RequestDetails theRequestDetails) { String matchUrl = "CodeSystem?url=" + UrlUtil.escapeUrlParam(theValueSet.getUrl()); 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 9d57cda6924..f3dbfc64cac 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 @@ -65,6 +65,6 @@ public interface IHapiTerminologySvc { List findCodesBelowUsingBuiltInSystems(String theSystem, String theCode); - void storeNewCodeSystemVersion(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets); + void storeNewCodeSystemVersion(org.hl7.fhir.r4.model.CodeSystem theCodeSystemResource, TermCodeSystemVersion theCodeSystemVersion, RequestDetails theRequestDetails, List theValueSets, List theConceptMaps); } 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/TerminologyLoaderSvcImpl.java similarity index 87% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvc.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcImpl.java index 45b9c4720ff..6338ca30401 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/TerminologyLoaderSvcImpl.java @@ -24,6 +24,7 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; import org.springframework.beans.factory.annotation.Autowired; @@ -55,20 +56,22 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ -public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { - public static final String LOINC_FILE = "loinc.csv"; - 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 class TerminologyLoaderSvcImpl implements IHapiTerminologyLoaderSvc { 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"; private static final int LOG_INCREMENT = 100000; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyLoaderSvc.class); + 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_DOCUMENT_ONTOLOGY_FILE = "DocumentOntology.csv"; + public static final String LOINC_FILE = "loinc.csv"; + public static final String LOINC_HIERARCHY_FILE = "MULTI-AXIAL_HIERARCHY.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 LOINC_RSNA_PLAYBOOK_FILE = "LoincRsnaRadiologyPlaybook.csv"; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyLoaderSvcImpl.class); @Autowired private IHapiTerminologySvc myTermSvc; @Autowired(required = false) @@ -191,6 +194,7 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { final TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion(); final Map code2concept = new HashMap<>(); final List valueSets = new ArrayList<>(); + final List conceptMaps = new ArrayList<>(); CodeSystem loincCs; try { @@ -233,6 +237,18 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { handler = new LoincPartLinkHandler(codeSystemVersion, code2concept); iterateOverZipFile(theZipBytes, LOINC_PART_LINK_FILE, handler, ',', QuoteMode.NON_NUMERIC); + // Part related code mapping + handler = new LoincPartRelatedCodeMappingHandler(codeSystemVersion, code2concept, conceptMaps); + iterateOverZipFile(theZipBytes, LOINC_PART_RELATED_CODE_MAPPING_FILE, handler, ',', QuoteMode.NON_NUMERIC); + + // Document Ontology File + handler = new LoincDocumentOntologyHandler(codeSystemVersion, code2concept, propertyNames, valueSets); + iterateOverZipFile(theZipBytes, LOINC_DOCUMENT_ONTOLOGY_FILE, handler, ',', QuoteMode.NON_NUMERIC); + + // RSNA Playbook file + handler = new LoincRsnaPlaybookHandler(codeSystemVersion, code2concept, propertyNames, valueSets, conceptMaps); + iterateOverZipFile(theZipBytes, LOINC_RSNA_PLAYBOOK_FILE, handler, ',', QuoteMode.NON_NUMERIC); + theZipBytes.clear(); for (Entry next : code2concept.entrySet()) { @@ -242,18 +258,21 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { } } - ourLog.info("Have {} total concepts, {} root concepts", code2concept.size(), codeSystemVersion.getConcepts().size()); + int valueSetCount = valueSets.size(); + int rootConceptCount = codeSystemVersion.getConcepts().size(); + int conceptCount = code2concept.size(); + ourLog.info("Have {} total concepts, {} root concepts, {} ValueSets", conceptCount, rootConceptCount, valueSetCount); - storeCodeSystem(theRequestDetails, codeSystemVersion, loincCs, valueSets); + storeCodeSystem(theRequestDetails, codeSystemVersion, loincCs, valueSets, conceptMaps); - return new UploadStatistics(code2concept.size()); + return new UploadStatistics(conceptCount); } UploadStatistics processSnomedCtFiles(List theZipBytes, RequestDetails theRequestDetails) { final TermCodeSystemVersion codeSystemVersion = new TermCodeSystemVersion(); - final Map id2concept = new HashMap(); - final Map code2concept = new HashMap(); - final Set validConceptIds = new HashSet(); + final Map id2concept = new HashMap<>(); + final Map code2concept = new HashMap<>(); + final Set validConceptIds = new HashSet<>(); IRecordHandler handler = new SctHandlerConcept(validConceptIds); iterateOverZipFile(theZipBytes, SCT_FILE_CONCEPT, handler, '\t', null); @@ -264,7 +283,7 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { iterateOverZipFile(theZipBytes, SCT_FILE_DESCRIPTION, handler, '\t', null); ourLog.info("Got {} concepts, cloning map", code2concept.size()); - final HashMap rootConcepts = new HashMap(code2concept); + final HashMap rootConcepts = new HashMap<>(code2concept); handler = new SctHandlerRelationship(codeSystemVersion, rootConcepts, code2concept); iterateOverZipFile(theZipBytes, SCT_FILE_RELATIONSHIP, handler, '\t', null); @@ -293,7 +312,7 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { CodeSystem cs = new org.hl7.fhir.r4.model.CodeSystem(); cs.setUrl(SCT_URL); cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT); - storeCodeSystem(theRequestDetails, codeSystemVersion, cs, null); + storeCodeSystem(theRequestDetails, codeSystemVersion, cs, null, null); return new UploadStatistics(code2concept.size()); } @@ -308,16 +327,17 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc { myTermSvc = theTermSvc; } - private void storeCodeSystem(RequestDetails theRequestDetails, final TermCodeSystemVersion theCodeSystemVersion, CodeSystem theCodeSystem, List theValueSets) { + private void storeCodeSystem(RequestDetails theRequestDetails, final TermCodeSystemVersion theCodeSystemVersion, CodeSystem theCodeSystem, List theValueSets, List theConceptMaps) { Validate.isTrue(theCodeSystem.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT); List valueSets = ObjectUtils.defaultIfNull(theValueSets, Collections.emptyList()); + List conceptMaps = ObjectUtils.defaultIfNull(theConceptMaps, Collections.emptyList()); myTermSvc.setProcessDeferred(false); if (myTermSvcDstu3 != null) { - myTermSvcDstu3.storeNewCodeSystemVersion(theCodeSystem, theCodeSystemVersion, theRequestDetails, valueSets); + myTermSvcDstu3.storeNewCodeSystemVersion(theCodeSystem, theCodeSystemVersion, theRequestDetails, valueSets, conceptMaps); } else { - myTermSvcR4.storeNewCodeSystemVersion(theCodeSystem, theCodeSystemVersion, theRequestDetails, valueSets); + myTermSvcR4.storeNewCodeSystemVersion(theCodeSystem, theCodeSystemVersion, theRequestDetails, valueSets, conceptMaps); } myTermSvc.setProcessDeferred(true); } 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 index c3feb412e0d..cda7bdce357 100644 --- 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 @@ -59,12 +59,12 @@ public class LoincAnswerListLinkHandler implements IRecordHandler { TermConcept loincCode = myCode2Concept.get(loincNumber); if (loincCode != null) { - loincCode.addProperty("answer-list", answerListId); + loincCode.addPropertyString("answer-list", answerListId); } TermConcept answerListCode = myCode2Concept.get(answerListId); if (answerListCode != null) { - answerListCode.addProperty("answers-for", loincNumber); + answerListCode.addPropertyString("answers-for", loincNumber); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java new file mode 100644 index 00000000000..a0cd72fe4bb --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincDocumentOntologyHandler.java @@ -0,0 +1,95 @@ +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 ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.apache.commons.csv.CSVRecord; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.ValueSet; + +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.trim; + +public class LoincDocumentOntologyHandler implements IRecordHandler { + + public static final String DOCUMENT_ONTOLOGY_CODES_VS_ID = "DOCUMENT_ONTOLOGY_CODES_VS"; + public static final String DOCUMENT_ONTOLOGY_CODES_VS_URI = "http://loinc.org/document-ontology-codes"; + public static final String DOCUMENT_ONTOLOGY_CODES_VS_NAME = "LOINC Document Ontology Codes"; + private final Map myCode2Concept; + private final TermCodeSystemVersion myCodeSystemVersion; + private final Set myPropertyNames; + private final List myValueSets; + private final Map myIdToValueSet = new HashMap<>(); + private final Set myCodesInRsnaPlaybookValueSet = new HashSet<>(); + + public LoincDocumentOntologyHandler(TermCodeSystemVersion theCodeSystemVersion, Map theCode2concept, Set thePropertyNames, List theValueSets) { + myCodeSystemVersion = theCodeSystemVersion; + myCode2Concept = theCode2concept; + myPropertyNames = thePropertyNames; + myValueSets = theValueSets; + } + + @Override + public void accept(CSVRecord theRecord) { + + String loincNumber = trim(theRecord.get("LoincNumber")); + String partNumber = trim(theRecord.get("PartNumber")); + String partTypeName = trim(theRecord.get("PartTypeName")); + String partSequenceOrder = trim(theRecord.get("PartSequenceOrder")); + String partName = trim(theRecord.get("PartName")); + + // RSNA Codes VS + ValueSet vs; + if (!myIdToValueSet.containsKey(RSNA_CODES_VS_ID)) { + vs = new ValueSet(); + vs.setUrl(RSNA_CODES_VS_URI); + vs.setId(RSNA_CODES_VS_ID); + vs.setName(RSNA_CODES_VS_NAME); + vs.setStatus(Enumerations.PublicationStatus.ACTIVE); + myIdToValueSet.put(RSNA_CODES_VS_ID, vs); + myValueSets.add(vs); + } else { + vs = myIdToValueSet.get(RSNA_CODES_VS_ID); + } + + if (!myCodesInRsnaPlaybookValueSet.contains(loincNumber)) { + vs + .getCompose() + .getIncludeFirstRep() + .setSystem(IHapiTerminologyLoaderSvc.LOINC_URL) + .addConcept() + .setCode(loincNumber) + .setDisplay(longCommonName); + myCodesInRsnaPlaybookValueSet.add(loincNumber); + } + + String loincCodePropName; + switch (partTypeName) { + case "Rad.Anatomic Location.Region Imaged": + loincCodePropName = "rad-anatomic-location-region-imaged"; + break; + case "Rad.Anatomic Location.Imaging Focus": + loincCodePropName = "rad-anatomic-location-imaging-focus"; + break; + case "Rad.Modality.Modality type": + loincCodePropName = "rad-modality-modality-type"; + break; + default: + throw new InternalErrorException("Unknown PartTypeName: " + partTypeName); + } + + TermConcept code = myCode2Concept.get(loincNumber); + if (code != null) { + code.addPropertyCoding(loincCodePropName, IHapiTerminologyLoaderSvc.LOINC_URL, partNumber, partName); + } + + + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java index 93703000668..b2d95452c9b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java @@ -3,7 +3,7 @@ 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 ca.uhn.fhir.jpa.term.TerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl; import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang3.Validate; @@ -32,7 +32,7 @@ public class LoincHandler implements IRecordHandler { String longCommonName = trim(theRecord.get("LONG_COMMON_NAME")); String shortName = trim(theRecord.get("SHORTNAME")); String consumerName = trim(theRecord.get("CONSUMER_NAME")); - String display = TerminologyLoaderSvc.firstNonBlank(longCommonName, shortName, consumerName); + String display = TerminologyLoaderSvcImpl.firstNonBlank(longCommonName, shortName, consumerName); TermConcept concept = new TermConcept(myCodeSystemVersion, code); concept.setDisplay(display); @@ -43,7 +43,7 @@ public class LoincHandler implements IRecordHandler { } String nextPropertyValue = theRecord.get(nextPropertyName); if (isNotBlank(nextPropertyValue)) { - concept.addProperty(nextPropertyName, nextPropertyValue); + concept.addPropertyString(nextPropertyName, nextPropertyValue); } } 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 index e27c55c403e..fe92e66b347 100644 --- 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 @@ -29,8 +29,6 @@ public class LoincPartLinkHandler implements IRecordHandler { 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); @@ -44,7 +42,7 @@ public class LoincPartLinkHandler implements IRecordHandler { return; } - partConcept.addProperty(); + // For now we're ignoring these } private static final Logger ourLog = LoggerFactory.getLogger(LoincPartLinkHandler.class); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java new file mode 100644 index 00000000000..8ccf7f8fb5d --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincPartRelatedCodeMappingHandler.java @@ -0,0 +1,130 @@ +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 ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.apache.commons.csv.CSVRecord; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.Enumerations; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; + +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; +import static org.apache.commons.lang3.StringUtils.trim; + +public class LoincPartRelatedCodeMappingHandler implements IRecordHandler { + + public static final String LOINC_TO_SNOMED_CM_ID = "LOINC_TO_SNOMED_CM"; + private static final Logger ourLog = LoggerFactory.getLogger(LoincPartRelatedCodeMappingHandler.class); + private final Map myCode2Concept; + private final TermCodeSystemVersion myCodeSystemVersion; + private final List myConceptMaps; + + public LoincPartRelatedCodeMappingHandler(TermCodeSystemVersion theCodeSystemVersion, Map theCode2concept, List theConceptMaps) { + myCodeSystemVersion = theCodeSystemVersion; + myCode2Concept = theCode2concept; + myConceptMaps = theConceptMaps; + } + + @Override + public void accept(CSVRecord theRecord) { + + String partNumber = trim(theRecord.get("PartNumber")); + String partName = trim(theRecord.get("PartName")); + String partTypeName = trim(theRecord.get("PartTypeName")); + String extCodeId = trim(theRecord.get("ExtCodeId")); + // TODO: use hex code for ascii 160 + extCodeId = extCodeId.replace(" ", ""); + String extCodeDisplayName = trim(theRecord.get("ExtCodeDisplayName")); + String extCodeSystem = trim(theRecord.get("ExtCodeSystem")); + String mapType = trim(theRecord.get("MapType")); + String contentOrigin = trim(theRecord.get("ContentOrigin")); + String extCodeSystemVersion = trim(theRecord.get("ExtCodeSystemVersion")); + String extCodeSystemCopyrightNotice = trim(theRecord.get("ExtCodeSystemCopyrightNotice")); + + ConceptMap conceptMap; + if (extCodeSystem.equals(IHapiTerminologyLoaderSvc.SCT_URL)) { + conceptMap = findOrAddCodeSystem(LOINC_TO_SNOMED_CM_ID, "http://loinc.org/loinc-to-snomed", extCodeSystem, extCodeSystemCopyrightNotice); + } else { + throw new InternalErrorException("Unknown external code system ID: " + extCodeSystem); + } + + + ConceptMap.ConceptMapGroupComponent group = null; + for (ConceptMap.ConceptMapGroupComponent next : conceptMap.getGroup()) { + if (next.getTarget().equals(extCodeSystem)) { + if (defaultIfBlank(next.getTargetVersion(), "").equals(defaultIfBlank(extCodeSystemVersion, ""))) { + group = next; + break; + } + } + } + + if (group == null) { + group = conceptMap.addGroup(); + group.setSource(IHapiTerminologyLoaderSvc.LOINC_URL); + group.setTarget(extCodeSystem); + group.setTargetVersion(defaultIfBlank(extCodeSystemVersion, null)); + } + + ConceptMap.SourceElementComponent element = null; + for (ConceptMap.SourceElementComponent next : group.getElement()) { + if (next.getCode().equals(partNumber)) { + element = next; + break; + } + } + + if (element == null) { + element = group + .addElement() + .setCode(partNumber) + .setDisplay(partName); + } + + ConceptMap.TargetElementComponent target = element + .addTarget() + .setCode(extCodeId) + .setDisplay(extCodeDisplayName); + + switch (mapType) { + case "Exact": + // 'equal' is more exact than 'equivalent' in the equivalence codes + target.setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL); + break; + case "LOINC broader": + target.setEquivalence(Enumerations.ConceptMapEquivalence.NARROWER); + break; + case "LOINC narrower": + target.setEquivalence(Enumerations.ConceptMapEquivalence.WIDER); + break; + default: + throw new InternalErrorException("Unknown MapType: " + mapType); + } + + + } + + private ConceptMap findOrAddCodeSystem(String theId, String theUri, String theTargetCodeSystem, String theTargetCopyright) { + for (ConceptMap next : myConceptMaps) { + if (next.getId().equals(theId)) { + return next; + } + } + + ConceptMap cm = new ConceptMap(); + cm.setId(theId); + cm.setUrl(theUri); + cm.setSource(new CanonicalType(IHapiTerminologyLoaderSvc.LOINC_URL)); + cm.setTarget(new CanonicalType(theTargetCodeSystem)); + cm.setCopyright(theTargetCopyright); + myConceptMaps.add(cm); + return cm; + } +} 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 new file mode 100644 index 00000000000..917586a0a0c --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincRsnaPlaybookHandler.java @@ -0,0 +1,165 @@ +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 ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import org.apache.commons.csv.CSVRecord; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.ValueSet; + +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.trim; + +public class LoincRsnaPlaybookHandler implements IRecordHandler { + + public static final String RSNA_CODES_VS_ID = "RSNA_LOINC_CODES_VS"; + public static final String RSNA_CODES_VS_URI = "http://loinc.org/rsna-codes"; + public static final String RSNA_CODES_VS_NAME = "RSNA Playbook"; + public static final String RID_MAPPING_CM_ID = "LOINC_TO_RID_CODES_CM"; + public static final String RID_MAPPING_CM_URI = "http://loinc.org/rid-codes"; + public static final String RID_MAPPING_CM_NAME = "RSNA Playbook RID Codes Mapping"; + public static final String RID_CS_URI = "http://rid"; + public static final String RPID_MAPPING_CM_ID = "LOINC_TO_RPID_CODES_CM"; + public static final String RPID_MAPPING_CM_URI = "http://loinc.org/rpid-codes"; + public static final String RPID_MAPPING_CM_NAME = "RSNA Playbook RPID Codes Mapping"; + public static final String RPID_CS_URI = "http://rpid"; + private final Map myCode2Concept; + private final TermCodeSystemVersion myCodeSystemVersion; + private final Set myPropertyNames; + private final List myValueSets; + private final Map myIdToValueSet = new HashMap<>(); + private final List myConceptMaps; + private final Set myCodesInRsnaPlaybookValueSet = new HashSet<>(); + private final Map myIdToConceptMaps = new HashMap<>(); + + /** + * Constructor + */ + public LoincRsnaPlaybookHandler(TermCodeSystemVersion theCodeSystemVersion, Map theCode2concept, Set thePropertyNames, List theValueSets, List theConceptMaps) { + myCodeSystemVersion = theCodeSystemVersion; + myCode2Concept = theCode2concept; + myPropertyNames = thePropertyNames; + myValueSets = theValueSets; + myConceptMaps = theConceptMaps; + } + + @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 partTypeName = trim(theRecord.get("PartTypeName")); + String partName = trim(theRecord.get("PartName")); + String partSequenceOrder = trim(theRecord.get("PartSequenceOrder")); + String rid = trim(theRecord.get("RID")); + String preferredName = trim(theRecord.get("PreferredName")); + String rpid = trim(theRecord.get("RPID")); + String longName = trim(theRecord.get("LongName")); + + // RSNA Codes VS + ValueSet vs; + if (!myIdToValueSet.containsKey(RSNA_CODES_VS_ID)) { + vs = new ValueSet(); + vs.setUrl(RSNA_CODES_VS_URI); + vs.setId(RSNA_CODES_VS_ID); + vs.setName(RSNA_CODES_VS_NAME); + vs.setStatus(Enumerations.PublicationStatus.ACTIVE); + myIdToValueSet.put(RSNA_CODES_VS_ID, vs); + myValueSets.add(vs); + } else { + vs = myIdToValueSet.get(RSNA_CODES_VS_ID); + } + + if (!myCodesInRsnaPlaybookValueSet.contains(loincNumber)) { + vs + .getCompose() + .getIncludeFirstRep() + .setSystem(IHapiTerminologyLoaderSvc.LOINC_URL) + .addConcept() + .setCode(loincNumber) + .setDisplay(longCommonName); + myCodesInRsnaPlaybookValueSet.add(loincNumber); + } + + String loincCodePropName; + switch (partTypeName) { + case "Rad.Anatomic Location.Region Imaged": + loincCodePropName = "rad-anatomic-location-region-imaged"; + break; + case "Rad.Anatomic Location.Imaging Focus": + loincCodePropName = "rad-anatomic-location-imaging-focus"; + break; + case "Rad.Modality.Modality type": + loincCodePropName = "rad-modality-modality-type"; + break; + default: + throw new InternalErrorException("Unknown PartTypeName: " + partTypeName); + } + + TermConcept code = myCode2Concept.get(loincNumber); + if (code != null) { + code.addPropertyCoding(loincCodePropName, IHapiTerminologyLoaderSvc.LOINC_URL, partNumber, partName); + } + + // LOINC Part -> Radlex RID code mappings + addMapping(partNumber, partName, RID_MAPPING_CM_ID, RID_MAPPING_CM_URI, RID_MAPPING_CM_NAME, RID_CS_URI, rid, preferredName, Enumerations.ConceptMapEquivalence.EQUAL); + + // LOINC Term -> Radlex RPID code mappings + addMapping(loincNumber, longCommonName, RPID_MAPPING_CM_ID, RPID_MAPPING_CM_URI, RPID_MAPPING_CM_NAME, RPID_CS_URI, rpid, longName, Enumerations.ConceptMapEquivalence.EQUAL); + + } + + private void addMapping(String theLoincNumber, String theLongCommonName, String theConceptMapId, String theConceptMapUri, String theConceptMapName, String theTargetCodeSystemUri, String theTargetCode, String theTargetDisplay, Enumerations.ConceptMapEquivalence theEquivalence) { + if (isNotBlank(theTargetCode)) { + + ConceptMap conceptMap; + if (!myIdToConceptMaps.containsKey(theConceptMapId)) { + conceptMap = new ConceptMap(); + conceptMap.setId(theConceptMapId); + conceptMap.setUrl(theConceptMapUri); + conceptMap.setName(theConceptMapName); + conceptMap.setSource(new CanonicalType(IHapiTerminologyLoaderSvc.LOINC_URL)); + conceptMap.setTarget(new CanonicalType(theTargetCodeSystemUri)); + myIdToConceptMaps.put(theConceptMapId, conceptMap); + myConceptMaps.add(conceptMap); + } else { + conceptMap = myIdToConceptMaps.get(theConceptMapId); + } + + ConceptMap.SourceElementComponent source = null; + ConceptMap.ConceptMapGroupComponent group = conceptMap.getGroupFirstRep(); + for (ConceptMap.SourceElementComponent next : group.getElement()) { + if (next.getCode().equals(theLoincNumber)) { + source = next; + } + } + if (source == null) { + source = group.addElement(); + source.setCode(theLoincNumber); + source.setDisplay(theLongCommonName); + } + + boolean found = false; + for (ConceptMap.TargetElementComponent next : source.getTarget()) { + if (next.getCode().equals(theTargetCode)) { + found = true; + } + } + if (!found) { + source + .addTarget() + .setCode(theTargetCode) + .setDisplay(theTargetDisplay) + .setEquivalence(theEquivalence); + } + } + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/snomedct/SctHandlerDescription.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/snomedct/SctHandlerDescription.java index db9ad6531cc..e7ef13c41db 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/snomedct/SctHandlerDescription.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/snomedct/SctHandlerDescription.java @@ -3,7 +3,7 @@ package ca.uhn.fhir.jpa.term.snomedct; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.term.IRecordHandler; -import ca.uhn.fhir.jpa.term.TerminologyLoaderSvc; +import ca.uhn.fhir.jpa.term.TerminologyLoaderSvcImpl; import org.apache.commons.csv.CSVRecord; import java.util.Map; @@ -36,7 +36,7 @@ public final class SctHandlerDescription implements IRecordHandler { String term = theRecord.get("term"); - TermConcept concept = TerminologyLoaderSvc.getOrCreateConcept(myCodeSystemVersion, myId2concept, id); + TermConcept concept = TerminologyLoaderSvcImpl.getOrCreateConcept(myCodeSystemVersion, myId2concept, id); concept.setCode(conceptId); concept.setDisplay(term); myCode2concept.put(conceptId, concept); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java index 01de5a78efa..587a74d8f90 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java @@ -54,7 +54,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { } catch (Exception e) { ourLog.error("Exceeded maximum wait for connection", e); logGetConnectionStackTrace(); -// if ("true".equals(System.getProperty("ci"))) { +// if ("true".equals(System.getStringProperty("ci"))) { fail("Exceeded maximum wait for connection: " + e.toString()); // } // System.exit(1); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index 0722d3b97c4..4d886041433 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -60,7 +60,7 @@ public class TestR4Config extends BaseJavaConfigR4 { } catch (Exception e) { ourLog.error("Exceeded maximum wait for connection", e); logGetConnectionStackTrace(); -// if ("true".equals(System.getProperty("ci"))) { +// if ("true".equals(System.getStringProperty("ci"))) { fail("Exceeded maximum wait for connection: " + e.toString()); // } // System.exit(1); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java similarity index 64% rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java index b4e41aa2c3c..9b12611608b 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcIntegrationDstu3Test.java @@ -1,20 +1,17 @@ package ca.uhn.fhir.jpa.term; -import java.io.File; -import java.util.HashMap; -import java.util.Map; - +import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; +import ca.uhn.fhir.util.TestUtil; import org.junit.AfterClass; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test; -import ca.uhn.fhir.util.TestUtil; +import java.util.List; -public class TerminologyLoaderSvcIntegrationTest extends BaseJpaDstu3Test { +public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test { - private TerminologyLoaderSvc myLoader; + private TerminologyLoaderSvcImpl myLoader; @AfterClass public static void afterClassClearContext() { @@ -23,13 +20,14 @@ public class TerminologyLoaderSvcIntegrationTest extends BaseJpaDstu3Test { @Before public void beforeInitTest() { - myLoader = new TerminologyLoaderSvc(); + myLoader = new TerminologyLoaderSvcImpl(); myLoader.setTermSvcForUnitTests(myTermSvc); } @Test @Ignore - public void testLoadAndStoreSnomedCt() { + public void testLoadAndStoreLoinc() { + List files; // myLoader.processSnomedCtFiles(files, mySrd); } 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 b4037581ebc..bc7cf9554d4 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 @@ -2,12 +2,14 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; import ca.uhn.fhir.jpa.entity.TermConcept; +import ca.uhn.fhir.jpa.term.loinc.LoincPartHandler; +import ca.uhn.fhir.jpa.term.loinc.LoincPartRelatedCodeMappingHandler; +import ca.uhn.fhir.jpa.term.loinc.LoincRsnaPlaybookHandler; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.TestUtil; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.Validate; import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.ValueSet; import org.junit.AfterClass; import org.junit.Before; @@ -18,27 +20,20 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; 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; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class TerminologyLoaderSvcLoincTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyLoaderSvcLoincTest.class); - private TerminologyLoaderSvc mySvc; + private TerminologyLoaderSvcImpl mySvc; @Mock private IHapiTerminologySvc myTermSvc; @@ -48,53 +43,42 @@ public class TerminologyLoaderSvcLoincTest { @Captor private ArgumentCaptor myCsvCaptor; - private ArrayList myFiles; @Captor private ArgumentCaptor mySystemCaptor; @Mock private RequestDetails details; @Captor private ArgumentCaptor> myValueSetsCaptor; + @Captor + private ArgumentCaptor> myConceptMapCaptor; + private ZipCollectionBuilder myFiles; - private void addFile(String theClasspathPrefix, String theClasspathFileName, String theOutputFilename) throws IOException { - ByteArrayOutputStream bos; - bos = new ByteArrayOutputStream(); - ZipOutputStream zos = new ZipOutputStream(bos); - ourLog.info("Adding {} to test zip", theClasspathFileName); - zos.putNextEntry(new ZipEntry("SnomedCT_Release_INT_20160131_Full/Terminology/" + theOutputFilename)); - String classpathName = theClasspathPrefix + theClasspathFileName; - InputStream stream = getClass().getResourceAsStream(classpathName); - Validate.notNull(stream, "Couldn't load " + classpathName); - byte[] byteArray = IOUtils.toByteArray(stream); - Validate.notNull(byteArray); - zos.write(byteArray); - zos.closeEntry(); - zos.close(); - ourLog.info("ZIP file has {} bytes", bos.toByteArray().length); - myFiles.add(bos.toByteArray()); - } - @Before public void before() { - mySvc = new TerminologyLoaderSvc(); + mySvc = new TerminologyLoaderSvcImpl(); mySvc.setTermSvcForUnitTests(myTermSvc); mySvc.setTermSvcDstu3ForUnitTest(myTermSvcDstu3); - - myFiles = new ArrayList<>(); + + myFiles = new ZipCollectionBuilder(); } @Test public void testLoadLoinc() throws Exception { - 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); + myFiles.addFile("/loinc/", "loinc.csv", TerminologyLoaderSvcImpl.LOINC_FILE); + myFiles.addFile("/loinc/", "hierarchy.csv", TerminologyLoaderSvcImpl.LOINC_HIERARCHY_FILE); + myFiles.addFile("/loinc/", "AnswerList_Beta_1.csv", TerminologyLoaderSvcImpl.LOINC_ANSWERLIST_FILE); + myFiles.addFile("/loinc/", TerminologyLoaderSvcImpl.LOINC_ANSWERLIST_LINK_FILE, TerminologyLoaderSvcImpl.LOINC_ANSWERLIST_LINK_FILE); + myFiles.addFile("/loinc/", TerminologyLoaderSvcImpl.LOINC_PART_FILE, TerminologyLoaderSvcImpl.LOINC_PART_FILE); + myFiles.addFile("/loinc/", TerminologyLoaderSvcImpl.LOINC_PART_LINK_FILE, TerminologyLoaderSvcImpl.LOINC_PART_LINK_FILE); + myFiles.addFile("/loinc/", TerminologyLoaderSvcImpl.LOINC_PART_RELATED_CODE_MAPPING_FILE); + myFiles.addFile("/loinc/", TerminologyLoaderSvcImpl.LOINC_DOCUMENT_ONTOLOGY_FILE); + myFiles.addFile("/loinc/", TerminologyLoaderSvcImpl.LOINC_RSNA_PLAYBOOK_FILE); // Actually do the load - mySvc.loadLoinc(myFiles, details); + mySvc.loadLoinc(myFiles.getFiles(), details); - verify(myTermSvcDstu3, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture()); + verify(myTermSvcDstu3, times(1)).storeNewCodeSystemVersion(mySystemCaptor.capture(), myCsvCaptor.capture(), any(RequestDetails.class), myValueSetsCaptor.capture(), myConceptMapCaptor.capture()); TermCodeSystemVersion ver = myCsvCaptor.getValue(); @@ -102,17 +86,29 @@ public class TerminologyLoaderSvcLoincTest { for (TermConcept next : ver.getConcepts()) { concepts.put(next.getCode(), next); } + Map valueSets = new HashMap<>(); + for (ValueSet next : myValueSetsCaptor.getValue()) { + valueSets.put(next.getId(), next); + } + Map conceptMaps = new HashMap<>(); + for (ConceptMap next : myConceptMapCaptor.getAllValues().get(0)) { + conceptMaps.put(next.getId(), next); + } + ConceptMap conceptMap; + TermConcept code; + ValueSet vs; + ConceptMap.ConceptMapGroupComponent group; // Normal loinc code - TermConcept code = concepts.get("10013-1"); + code = concepts.get("10013-1"); assertEquals("10013-1", code.getCode()); - assertEquals("Elpot", code.getProperty("PROPERTY")); - assertEquals("Pt", code.getProperty("TIME_ASPCT")); + assertEquals("Elpot", code.getStringProperty("PROPERTY")); + assertEquals("Pt", code.getStringProperty("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")); + assertThat(code.getStringProperties("answer-list"), contains("LL1000-0")); // Answer list code = concepts.get("LL1001-8"); @@ -127,15 +123,10 @@ public class TerminologyLoaderSvcLoincTest { // Answer list code with link to answers-for code = concepts.get("LL1000-0"); - assertThat(code.getProperties("answers-for"), contains("61438-8")); - + assertThat(code.getStringProperties("answers-for"), contains("61438-8")); // AnswerList valueSet - Map valueSets = new HashMap<>(); - for (ValueSet next : myValueSetsCaptor.getValue()) { - valueSets.put(next.getId(), next); - } - ValueSet vs = valueSets.get("LL1001-8"); + vs = valueSets.get("LL1001-8"); assertEquals(IHapiTerminologyLoaderSvc.LOINC_URL, vs.getIdentifier().get(0).getSystem()); assertEquals("LL1001-8", vs.getIdentifier().get(0).getValue()); assertEquals("PhenX05_14_30D freq amts", vs.getName()); @@ -151,9 +142,94 @@ public class TerminologyLoaderSvcLoincTest { assertEquals("LP101394-7", code.getCode()); assertEquals("adjusted for maternal weight", code.getDisplay()); + // Part Mappings + conceptMap = conceptMaps.get(LoincPartRelatedCodeMappingHandler.LOINC_TO_SNOMED_CM_ID); + assertEquals(IHapiTerminologyLoaderSvc.LOINC_URL, conceptMap.getSourceCanonicalType().getValueAsString()); + assertEquals(IHapiTerminologyLoaderSvc.SCT_URL, conceptMap.getTargetCanonicalType().getValueAsString()); + assertEquals("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 of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO.This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. This may incur a fee in SNOMED International non-Member countries.", conceptMap.getCopyright()); + assertEquals(1, conceptMap.getGroup().size()); + group = conceptMap.getGroup().get(0); + assertEquals(IHapiTerminologyLoaderSvc.LOINC_URL, group.getSource()); + assertEquals(IHapiTerminologyLoaderSvc.SCT_URL, group.getTarget()); + assertEquals("http://snomed.info/sct/900000000000207008/version/20170731", group.getTargetVersion()); + assertEquals("LP18172-4", group.getElement().get(0).getCode()); + assertEquals("Interferon.beta", group.getElement().get(0).getDisplay()); + assertEquals(1, group.getElement().get(0).getTarget().size()); + assertEquals("420710006", group.getElement().get(0).getTarget().get(0).getCode()); + assertEquals("Interferon beta (substance)", group.getElement().get(0).getTarget().get(0).getDisplay()); + + // Document Ontology Parts + + + // RSNA Playbook ValueSet + vs = valueSets.get(LoincRsnaPlaybookHandler.RSNA_CODES_VS_ID); + assertEquals(LoincRsnaPlaybookHandler.RSNA_CODES_VS_NAME, vs.getName()); + assertEquals(LoincRsnaPlaybookHandler.RSNA_CODES_VS_URI, vs.getUrl()); + assertEquals(1, vs.getCompose().getInclude().size()); + assertEquals(3, vs.getCompose().getInclude().get(0).getConcept().size()); + assertEquals(IHapiTerminologyLoaderSvc.LOINC_URL, vs.getCompose().getInclude().get(0).getSystem()); + assertEquals("17787-3", vs.getCompose().getInclude().get(0).getConcept().get(0).getCode()); + assertEquals("NM Thyroid gland Study report", vs.getCompose().getInclude().get(0).getConcept().get(0).getDisplay()); + + // RSNA Playbook Code Parts - Region Imaged + code = concepts.get("17787-3"); + String propertyName = "rad-anatomic-location-region-imaged"; + assertEquals(1, code.getCodingProperties(propertyName).size()); + assertEquals(IHapiTerminologyLoaderSvc.LOINC_URL, code.getCodingProperties(propertyName).get(0).getSystem()); + assertEquals("LP199995-4", code.getCodingProperties(propertyName).get(0).getCode()); + assertEquals("Neck", code.getCodingProperties(propertyName).get(0).getDisplay()); + // RSNA Playbook Code Parts - Imaging Focus + code = concepts.get("17787-3"); + propertyName = "rad-anatomic-location-imaging-focus"; + assertEquals(1, code.getCodingProperties(propertyName).size()); + assertEquals(IHapiTerminologyLoaderSvc.LOINC_URL, code.getCodingProperties(propertyName).get(0).getSystem()); + assertEquals("LP206648-0", code.getCodingProperties(propertyName).get(0).getCode()); + assertEquals("Thyroid gland", code.getCodingProperties(propertyName).get(0).getDisplay()); + // RSNA Playbook Code Parts - Modality Type + code = concepts.get("17787-3"); + propertyName = "rad-modality-modality-type"; + assertEquals(1, code.getCodingProperties(propertyName).size()); + assertEquals(IHapiTerminologyLoaderSvc.LOINC_URL, code.getCodingProperties(propertyName).get(0).getSystem()); + assertEquals("LP208891-4", code.getCodingProperties(propertyName).get(0).getCode()); + assertEquals("NM", code.getCodingProperties(propertyName).get(0).getDisplay()); + + // RSNA Playbook - LOINC Part -> RadLex RID Mappings + conceptMap = conceptMaps.get(LoincRsnaPlaybookHandler.RID_MAPPING_CM_ID); + assertEquals(LoincRsnaPlaybookHandler.RID_MAPPING_CM_URI, conceptMap.getUrl()); + assertEquals(LoincRsnaPlaybookHandler.RID_MAPPING_CM_NAME, conceptMap.getName()); + assertEquals(1, conceptMap.getGroup().size()); + group = conceptMap.getGroupFirstRep(); + // all entries have the same source and target so these should be null + assertEquals(null, group.getSource()); + assertEquals(null, group.getTarget()); + assertEquals("LP199995-4", group.getElement().get(0).getCode()); + assertEquals("Neck", group.getElement().get(0).getDisplay()); + assertEquals(1, group.getElement().get(0).getTarget().size()); + assertEquals("RID7488", group.getElement().get(0).getTarget().get(0).getCode()); + assertEquals("neck", group.getElement().get(0).getTarget().get(0).getDisplay()); + assertEquals(Enumerations.ConceptMapEquivalence.EQUAL, group.getElement().get(0).getTarget().get(0).getEquivalence()); + + // RSNA Playbook - LOINC Term -> RadLex RPID Mappings + conceptMap = conceptMaps.get(LoincRsnaPlaybookHandler.RPID_MAPPING_CM_ID); + assertEquals(LoincRsnaPlaybookHandler.RPID_MAPPING_CM_URI, conceptMap.getUrl()); + assertEquals(LoincRsnaPlaybookHandler.RPID_MAPPING_CM_NAME, conceptMap.getName()); + assertEquals(1, conceptMap.getGroup().size()); + group = conceptMap.getGroupFirstRep(); + // all entries have the same source and target so these should be null + assertEquals(null, group.getSource()); + assertEquals(null, group.getTarget()); + assertEquals("24531-6", group.getElement().get(0).getCode()); + assertEquals("US Retroperitoneum", group.getElement().get(0).getDisplay()); + assertEquals(1, group.getElement().get(0).getTarget().size()); + assertEquals("RPID2142", group.getElement().get(0).getTarget().get(0).getCode()); + assertEquals("US Retroperitoneum", group.getElement().get(0).getTarget().get(0).getDisplay()); + assertEquals(Enumerations.ConceptMapEquivalence.EQUAL, group.getElement().get(0).getTarget().get(0).getEquivalence()); + + } + @AfterClass public static void afterClassClearContext() { TestUtil.clearAllStaticFieldsForUnitTest(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcSnomedCtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcSnomedCtTest.java index 43d17839871..50453809d98 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcSnomedCtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologyLoaderSvcSnomedCtTest.java @@ -7,8 +7,8 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.TestUtil; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.Validate; import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; import org.junit.AfterClass; import org.junit.Before; @@ -22,9 +22,7 @@ import org.mockito.runners.MockitoJUnitRunner; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; -import java.io.IOException; import java.util.*; -import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import static org.hamcrest.Matchers.*; @@ -37,7 +35,7 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class TerminologyLoaderSvcSnomedCtTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TerminologyLoaderSvcSnomedCtTest.class); - private TerminologyLoaderSvc mySvc; + private TerminologyLoaderSvcImpl mySvc; @Mock private IHapiTerminologySvc myTermSvc; @@ -45,21 +43,15 @@ public class TerminologyLoaderSvcSnomedCtTest { private ArgumentCaptor myCsvCaptor; @Mock private IHapiTerminologySvcDstu3 myTermSvcDstu3; - - private void addEntry(ZipOutputStream zos, String theClasspathPrefix, String theFileName) throws IOException { - ourLog.info("Adding {} to test zip", theFileName); - zos.putNextEntry(new ZipEntry("SnomedCT_Release_INT_20160131_Full/Terminology/" + theFileName)); - byte[] byteArray = IOUtils.toByteArray(getClass().getResourceAsStream(theClasspathPrefix + theFileName)); - Validate.notNull(byteArray); - zos.write(byteArray); - zos.closeEntry(); - } + private ZipCollectionBuilder myFiles; @Before public void before() { - mySvc = new TerminologyLoaderSvc(); + mySvc = new TerminologyLoaderSvcImpl(); mySvc.setTermSvcForUnitTests(myTermSvc); mySvc.setTermSvcDstu3ForUnitTest(myTermSvcDstu3); + + myFiles = new ZipCollectionBuilder(); } private List list(byte[]... theByteArray) { @@ -68,23 +60,18 @@ public class TerminologyLoaderSvcSnomedCtTest { @Test public void testLoadSnomedCt() throws Exception { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ZipOutputStream zos = new ZipOutputStream(bos); - addEntry(zos, "/sct/", "sct2_Concept_Full_INT_20160131.txt"); - addEntry(zos, "/sct/", "sct2_Concept_Full-en_INT_20160131.txt"); - addEntry(zos, "/sct/", "sct2_Description_Full-en_INT_20160131.txt"); - addEntry(zos, "/sct/", "sct2_Identifier_Full_INT_20160131.txt"); - addEntry(zos, "/sct/", "sct2_Relationship_Full_INT_20160131.txt"); - addEntry(zos, "/sct/", "sct2_StatedRelationship_Full_INT_20160131.txt"); - addEntry(zos, "/sct/", "sct2_TextDefinition_Full-en_INT_20160131.txt"); - zos.close(); - - ourLog.info("ZIP file has {} bytes", bos.toByteArray().length); + myFiles.addFile("/sct/", "sct2_Concept_Full_INT_20160131.txt"); + myFiles.addFile("/sct/", "sct2_Concept_Full-en_INT_20160131.txt"); + myFiles.addFile("/sct/", "sct2_Description_Full-en_INT_20160131.txt"); + myFiles.addFile("/sct/", "sct2_Identifier_Full_INT_20160131.txt"); + myFiles.addFile("/sct/", "sct2_Relationship_Full_INT_20160131.txt"); + myFiles.addFile("/sct/", "sct2_StatedRelationship_Full_INT_20160131.txt"); + myFiles.addFile("/sct/", "sct2_TextDefinition_Full-en_INT_20160131.txt"); RequestDetails details = mock(RequestDetails.class); - mySvc.loadSnomedCt(list(bos.toByteArray()), details); + mySvc.loadSnomedCt(myFiles.getFiles(), details); - verify(myTermSvcDstu3).storeNewCodeSystemVersion(any(CodeSystem.class), myCsvCaptor.capture(), any(RequestDetails.class), anyListOf(ValueSet.class)); + verify(myTermSvcDstu3).storeNewCodeSystemVersion(any(CodeSystem.class), myCsvCaptor.capture(), any(RequestDetails.class), anyListOf(ValueSet.class), anyListOf(ConceptMap.class)); TermCodeSystemVersion csv = myCsvCaptor.getValue(); TreeSet allCodes = toCodes(csv, true); @@ -115,7 +102,7 @@ public class TerminologyLoaderSvcSnomedCtTest { public void testLoadSnomedCtBadInput() throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ZipOutputStream zos = new ZipOutputStream(bos); - addEntry(zos, "/sct/", "sct2_StatedRelationship_Full_INT_20160131.txt"); + myFiles.addFile("/sct/", "sct2_StatedRelationship_Full_INT_20160131.txt"); zos.close(); ourLog.info("ZIP file has {} bytes", bos.toByteArray().length); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java index 6ab36fb836b..25170572101 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java @@ -179,13 +179,13 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test { parentA.addChild(childAA, RelationshipTypeEnum.ISA); TermConcept childAAA = new TermConcept(cs, "childAAA"); - childAAA.addProperty("propA", "valueAAA"); - childAAA.addProperty("propB", "foo"); + childAAA.addPropertyString("propA", "valueAAA"); + childAAA.addPropertyString("propB", "foo"); childAA.addChild(childAAA, RelationshipTypeEnum.ISA); TermConcept childAAB = new TermConcept(cs, "childAAB"); - childAAB.addProperty("propA", "valueAAB"); - childAAB.addProperty("propB", "foo"); + childAAB.addPropertyString("propA", "valueAAB"); + childAAB.addPropertyString("propB", "foo"); childAA.addChild(childAAB, RelationshipTypeEnum.ISA); TermConcept childAB = new TermConcept(cs, "childAB"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ZipCollectionBuilder.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ZipCollectionBuilder.java new file mode 100644 index 00000000000..d77fb7c55ba --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ZipCollectionBuilder.java @@ -0,0 +1,50 @@ +package ca.uhn.fhir.jpa.term; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class ZipCollectionBuilder { + + private static final Logger ourLog = LoggerFactory.getLogger(ZipCollectionBuilder.class); + private final ArrayList myFiles; + + public ZipCollectionBuilder() { + myFiles = new ArrayList<>(); + } + + public void addFile(String theClasspathPrefix, String theClasspathFileName) throws IOException { + addFile(theClasspathPrefix, theClasspathFileName, theClasspathFileName); + } + + public void addFile(String theClasspathPrefix, String theClasspathFileName, String theOutputFilename) throws IOException { + ByteArrayOutputStream bos; + bos = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(bos); + ourLog.info("Adding {} to test zip", theClasspathFileName); + zos.putNextEntry(new ZipEntry("SnomedCT_Release_INT_20160131_Full/Terminology/" + theOutputFilename)); + String classpathName = theClasspathPrefix + theClasspathFileName; + InputStream stream = getClass().getResourceAsStream(classpathName); + Validate.notNull(stream, "Couldn't load " + classpathName); + byte[] byteArray = IOUtils.toByteArray(stream); + Validate.notNull(byteArray); + zos.write(byteArray); + zos.closeEntry(); + zos.close(); + ourLog.info("ZIP file has {} bytes", bos.toByteArray().length); + myFiles.add(bos.toByteArray()); + } + + public ArrayList getFiles() { + return myFiles; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/test/resources/loinc/DocumentOntology.csv b/hapi-fhir-jpaserver-base/src/test/resources/loinc/DocumentOntology.csv new file mode 100644 index 00000000000..4b90bf3046d --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/DocumentOntology.csv @@ -0,0 +1,10 @@ +"LoincNumber","PartNumber","PartTypeName","PartSequenceOrder","PartName" +"11488-4","LP173418-7","Document.Kind","1","Note" +"11488-4","LP173110-0","Document.TypeOfService","1","Consultation" +"11488-4","LP173061-5","Document.Setting","1","{Setting}" +"11488-4","LP187187-2","Document.Role","1","{Role}" +"11490-0","LP173418-7","Document.Kind","1","Note" +"11490-0","LP173221-5","Document.TypeOfService","1","Discharge summary" +"11490-0","LP173061-5","Document.Setting","1","{Setting}" +"11490-0","LP173084-7","Document.Role","1","Physician" +"11492-6","LP173418-7","Document.Kind","1","Note" diff --git a/hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincRsnaRadiologyPlaybook.csv b/hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincRsnaRadiologyPlaybook.csv new file mode 100644 index 00000000000..1acba586623 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/LoincRsnaRadiologyPlaybook.csv @@ -0,0 +1,10 @@ +"LoincNumber","LongCommonName" ,"PartNumber","PartTypeName" ,"PartName" ,"PartSequenceOrder","RID" ,"PreferredName" ,"RPID" ,"LongName" +"17787-3" ,"NM Thyroid gland Study report","LP199995-4","Rad.Anatomic Location.Region Imaged","Neck" ,"A" ,"RID7488" ,"neck" ,"" ,"" +"17787-3" ,"NM Thyroid gland Study report","LP206648-0","Rad.Anatomic Location.Imaging Focus","Thyroid gland" ,"A" ,"RID7578" ,"thyroid gland" ,"" ,"" +"17787-3" ,"NM Thyroid gland Study report","LP208891-4","Rad.Modality.Modality type" ,"NM" ,"A" ,"RID10330","nuclear medicine imaging","" ,"" +"24531-6" ,"US Retroperitoneum" ,"LP207608-3","Rad.Modality.Modality type" ,"US" ,"A" ,"RID10326","Ultrasound" ,"RPID2142","US Retroperitoneum" +"24531-6" ,"US Retroperitoneum" ,"LP199943-4","Rad.Anatomic Location.Imaging Focus","Retroperitoneum" ,"A" ,"RID431" ,"RETROPERITONEUM" ,"RPID2142","US Retroperitoneum" +"24531-6" ,"US Retroperitoneum" ,"LP199956-6","Rad.Anatomic Location.Region Imaged","Abdomen" ,"A" ,"RID56" ,"Abdomen" ,"RPID2142","US Retroperitoneum" +"24532-4" ,"US Abdomen RUQ" ,"LP199956-6","Rad.Anatomic Location.Region Imaged","Abdomen" ,"A" ,"RID56" ,"Abdomen" ,"" ,"" +"24532-4" ,"US Abdomen RUQ" ,"LP207608-3","Rad.Modality.Modality type" ,"US" ,"A" ,"RID10326","Ultrasound" ,"" ,"" +"24532-4" ,"US Abdomen RUQ" ,"LP208105-9","Rad.Anatomic Location.Imaging Focus","Right upper quadrant","A" ,"RID29994","Right upper quadrant" ,"" ,"" diff --git a/hapi-fhir-jpaserver-base/src/test/resources/loinc/PartRelatedCodeMapping_Beta_1.csv b/hapi-fhir-jpaserver-base/src/test/resources/loinc/PartRelatedCodeMapping_Beta_1.csv new file mode 100644 index 00000000000..fec510bd3f0 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/PartRelatedCodeMapping_Beta_1.csv @@ -0,0 +1,10 @@ +"PartNumber","PartName","PartTypeName","ExtCodeId","ExtCodeDisplayName","ExtCodeSystem","MapType","ContentOrigin","ExtCodeSystemVersion","ExtCodeSystemCopyrightNotice" +"LP18172-4","Interferon.beta","COMPONENT"," 420710006","Interferon beta (substance)","http://snomed.info/sct","Exact","Both","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 of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO.This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. This may incur a fee in SNOMED International non-Member countries." +"LP31706-2","Nornicotine","COMPONENT","1018001","Nornicotine (substance)","http://snomed.info/sct","Exact","Both","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 of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO.This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. This may incur a fee in SNOMED International non-Member countries." +"LP15826-8","Prostaglandin F2","COMPONENT","10192006","Prostaglandin PGF2 (substance)","http://snomed.info/sct","Exact","Both","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 of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO.This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. This may incur a fee in SNOMED International non-Member countries." +"LP7400-7","Liver","SYSTEM","10200004","Liver structure (body structure)","http://snomed.info/sct","LOINC broader","Both","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 of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO.This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. This may incur a fee in SNOMED International non-Member countries." +"LP29165-5","Liver.FNA","SYSTEM","10200004","Liver structure (body structure)","http://snomed.info/sct","LOINC broader","Both","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 of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO.This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. This may incur a fee in SNOMED International non-Member countries." +"LP15666-8","Inosine","COMPONENT","102640000","Inosine (substance)","http://snomed.info/sct","Exact","Both","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 of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO.This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. This may incur a fee in SNOMED International non-Member countries." +"LP15943-1","Uronate","COMPONENT","102641001","Uronic acid (substance)","http://snomed.info/sct","Exact","Both","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 of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO.This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. This may incur a fee in SNOMED International non-Member countries." +"LP15791-4","Phenylketones","COMPONENT","102642008","Phenylketones (substance)","http://snomed.info/sct","Exact","Both","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 of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO.This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. This may incur a fee in SNOMED International non-Member countries." +"LP15721-1","Malonate","COMPONENT","102648007","Malonic acid (substance)","http://snomed.info/sct","Exact","Both","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 of American Pathologists. “SNOMED” and “SNOMED CT” are registered trademarks of the IHTSDO.This material includes content from the US Edition to SNOMED CT, which is developed and maintained by the U.S. National Library of Medicine and is available to authorized UMLS Metathesaurus Licensees from the UTS Downloads site at https://uts.nlm.nih.gov.Use of SNOMED CT content is subject to the terms and conditions set forth in the SNOMED CT Affiliate License Agreement. It is the responsibility of those implementing this product to ensure they are appropriately licensed and for more information on the license, including how to register as an Affiliate Licensee, please refer to http://www.snomed.org/snomed-ct/get-snomed-ct or info@snomed.org. This may incur a fee in SNOMED International non-Member countries." 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 index 9da3ad5770d..d1d4f60fb7b 100644 --- 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 @@ -7,3 +7,4 @@ "LP173483-1","ADJUSTMENT","post cyanocobalamin",,"ACTIVE" "LP173484-9","ADJUSTMENT","W hyperextension)",,"ACTIVE" "LP6244-0","METHOD","EKG","Electrocardiogram (EKG)","ACTIVE" +"LP18172-4","COMPONENT","Interferon.beta","Interferon beta","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 e46b1881a7d..8cd626ab856 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/loinc/loinc.csv +++ b/hapi-fhir-jpaserver-base/src/test/resources/loinc/loinc.csv @@ -1,12 +1,15 @@ -"LOINC_NUM","COMPONENT" ,"PROPERTY","TIME_ASPCT","SYSTEM" ,"SCALE_TYP","METHOD_TYP","CLASS" ,"SOURCE","VersionLastChanged","CHNG_TYPE","DefinitionDescription","STATUS","CONSUMER_NAME","CLASSTYPE","FORMULA","SPECIES","EXMPL_ANSWERS","SURVEY_QUEST_TEXT" ,"SURVEY_QUEST_SRC" ,"UNITSREQUIRED","SUBMITTED_UNITS","RELATEDNAMES2" ,"SHORTNAME" ,"ORDER_OBS" ,"CDISC_COMMON_TESTS","HL7_FIELD_SUBFIELD_ID","EXTERNAL_COPYRIGHT_NOTICE","EXAMPLE_UNITS","LONG_COMMON_NAME" ,"UnitsAndRange","DOCUMENT_SECTION","EXAMPLE_UCUM_UNITS","EXAMPLE_SI_UCUM_UNITS","STATUS_REASON","STATUS_TEXT","CHANGE_REASON_PUBLIC" ,"COMMON_TEST_RANK","COMMON_ORDER_RANK","COMMON_SI_TEST_RANK","HL7_ATTACHMENT_STRUCTURE","EXTERNAL_COPYRIGHT_LINK","PanelType","AskAtOrderEntry","AssociatedObservations" -"10013-1" ,"R' wave amplitude.lead I" ,"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-I; R wave Amp L-I; Random; Right; Voltage" ,"R' wave Amp L-I" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead I" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , -"10014-9" ,"R' wave amplitude.lead II" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS","CH" ,"2.48" ,"MIN" , ,"ACTIVE", ,2 , , , , , ,"Y" , ,"2; 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-II; R wave Amp L-II; Random; Right; Voltage" ,"R' wave Amp L-II" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead II" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , -"10015-6" ,"R' wave amplitude.lead III" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS","CH" ,"2.48" ,"MIN" , ,"ACTIVE", ,2 , , , , , ,"Y" , ,"3; 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-III; R wave Amp L-III; Random; Right; Voltage" ,"R' wave Amp L-III" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead III" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , -"10016-4" ,"R' wave amplitude.lead V1" ,"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-V1; R wave Amp L-V1; Random; Right; Voltage" ,"R' wave Amp L-V1" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead V1" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , -"1001-7" ,"DBG Ab" ,"Pr" ,"Pt" ,"Ser/Plas^donor","Ord" , ,"BLDBK" ,"FS" ,"2.44" ,"MIN" , ,"ACTIVE", ,1 , , , , , , , ,"ABS; Aby; Antby; Anti; Antibodies; Antibody; Autoantibodies; Autoantibody; BLOOD BANK; Donna Bennett-Goodspeed; Donr; Ordinal; Pl; Plasma; Plsm; Point in time; QL; Qual; Qualitative; Random; Screen; SerP; SerPl; SerPl^donor; SerPlas; Serum; Serum or plasma; SR","DBG Ab SerPl Donr Ql" ,"Observation", , , , ,"DBG Ab [Presence] in Serum or Plasma from donor" , , , , , , ,"The Property has been changed from ACnc to Pr (Presence) to reflect the new model for ordinal terms where results are based on presence or absence.",0 ,0 ,0 , , , , , -"10017-2" ,"R' wave amplitude.lead V2" ,"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-V2; R wave Amp L-V2; Random; Right; Voltage" ,"R' wave Amp L-V2" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead V2" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , -"10018-0" ,"R' wave amplitude.lead V3" ,"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-V3; R wave Amp L-V3; Random; Right; Voltage" ,"R' wave Amp L-V3" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead V3" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , -"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 , , , , , +"LOINC_NUM","COMPONENT" ,"PROPERTY","TIME_ASPCT","SYSTEM" ,"SCALE_TYP","METHOD_TYP" ,"CLASS" ,"VersionLastChanged","CHNG_TYPE","DefinitionDescription" ,"STATUS","CONSUMER_NAME","CLASSTYPE","FORMULA","SPECIES","EXMPL_ANSWERS","SURVEY_QUEST_TEXT" ,"SURVEY_QUEST_SRC" ,"UNITSREQUIRED","SUBMITTED_UNITS","RELATEDNAMES2" ,"SHORTNAME" ,"ORDER_OBS" ,"CDISC_COMMON_TESTS","HL7_FIELD_SUBFIELD_ID","EXTERNAL_COPYRIGHT_NOTICE","EXAMPLE_UNITS","LONG_COMMON_NAME" ,"UnitsAndRange","DOCUMENT_SECTION","EXAMPLE_UCUM_UNITS","EXAMPLE_SI_UCUM_UNITS","STATUS_REASON","STATUS_TEXT","CHANGE_REASON_PUBLIC" ,"COMMON_TEST_RANK","COMMON_ORDER_RANK","COMMON_SI_TEST_RANK","HL7_ATTACHMENT_STRUCTURE","EXTERNAL_COPYRIGHT_LINK","PanelType","AskAtOrderEntry","AssociatedObservations" ,"VersionFirstReleased","ValidHL7AttachmentRequest" +"10013-1" ,"R' wave amplitude.lead I" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS" ,"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-I; R wave Amp L-I; Random; Right; Voltage" ,"R' wave Amp L-I" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead I" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , , , +"10014-9" ,"R' wave amplitude.lead II" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS" ,"2.48" ,"MIN" , ,"ACTIVE", ,2 , , , , , ,"Y" , ,"2; 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-II; R wave Amp L-II; Random; Right; Voltage" ,"R' wave Amp L-II" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead II" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , , , +"10015-6" ,"R' wave amplitude.lead III" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS" ,"2.48" ,"MIN" , ,"ACTIVE", ,2 , , , , , ,"Y" , ,"3; 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-III; R wave Amp L-III; Random; Right; Voltage" ,"R' wave Amp L-III" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead III" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , , , +"10016-4" ,"R' wave amplitude.lead V1" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS" ,"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-V1; R wave Amp L-V1; Random; Right; Voltage" ,"R' wave Amp L-V1" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead V1" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , , , +"1001-7" ,"DBG Ab" ,"Pr" ,"Pt" ,"Ser/Plas^donor" ,"Ord" , ,"BLDBK" ,"2.44" ,"MIN" , ,"ACTIVE", ,1 , , , , , , , ,"ABS; Aby; Antby; Anti; Antibodies; Antibody; Autoantibodies; Autoantibody; BLOOD BANK; Donna Bennett-Goodspeed; Donr; Ordinal; Pl; Plasma; Plsm; Point in time; QL; Qual; Qualitative; Random; Screen; SerP; SerPl; SerPl^donor; SerPlas; Serum; Serum or plasma; SR" ,"DBG Ab SerPl Donr Ql" ,"Observation", , , , ,"DBG Ab [Presence] in Serum or Plasma from donor" , , , , , , ,"The Property has been changed from ACnc to Pr (Presence) to reflect the new model for ordinal terms where results are based on presence or absence." ,0 ,0 ,0 , , , , , , , +"10017-2" ,"R' wave amplitude.lead V2" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS" ,"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-V2; R wave Amp L-V2; Random; Right; Voltage" ,"R' wave Amp L-V2" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead V2" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , , , +"10018-0" ,"R' wave amplitude.lead V3" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS" ,"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-V3; R wave Amp L-V3; Random; Right; Voltage" ,"R' wave Amp L-V3" ,"Observation", , , ,"mV" ,"R' wave amplitude in lead V3" , , ,"mV" , , , , ,0 ,0 ,0 , , , , , , , +"10019-8" ,"R' wave amplitude.lead V4" ,"Elpot" ,"Pt" ,"Heart" ,"Qn" ,"EKG" ,"EKG.MEAS" ,"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" ,"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" ,"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" ,"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 , , , , , , , +"17787-3" ,"Study report" ,"Find" ,"Pt" ,"Neck>Thyroid gland","Doc" ,"NM" ,"RAD" ,"2.61" ,"MIN" , ,"ACTIVE", ,2 , , , , , , , ,"Document; Finding; Findings; Imaging; Point in time; Radiology; Random; Study report; Thy" ,"NM Thyroid Study report" ,"Both" , , , , ,"NM Thyroid gland Study report" , , , , , , ,"Changed System from ""Thyroid"" for conformance with the LOINC/RadLex unified model.; Method of ""Radnuc"" was changed to ""NM"". The LOINC/RadLex Committee agreed to use a subset of the two-letter DICOM modality codes as the primary modality identifier." ,0 ,0 ,0 ,"IG exists" , , , ,"81220-6;72230-6" ,"1.0l" , +"17788-1" ,"Large unstained cells/100 leukocytes" ,"NFr" ,"Pt" ,"Bld" ,"Qn" ,"Automated count","HEM/BC" ,"2.50" ,"MIN" ,"Part of auto diff output of Bayer H*3S; peroxidase negative cells too large to be classified as lymph or basophil","ACTIVE", ,1 , , , , , ,"Y" ,"%" ,"100WBC; Auto; Automated detection; Blood; Cell; Cellularity; Elec; Elect; Electr; HEMATOLOGY/CELL COUNTS; Leuc; Leuk; Leukocyte; Lkcs; LUC; Number Fraction; Percent; Point in time; QNT; Quan; Quant; Quantitative; Random; WB; WBC; WBCs; White blood cell; White blood cells; Whole blood","LUC/leuk NFr Bld Auto" ,"Observation", , , ,"%" ,"Large unstained cells/100 leukocytes in Blood by Automated count" , , ,"%" , , , , ,1894 ,0 ,1894 , , , , , ,"1.0l" , +"11488-4" ,"Consultation note" ,"Find" ,"Pt" ,"{Setting}" ,"Doc" ,"{Role}" ,"DOC.ONTOLOGY","2.63" ,"MIN" , ,"ACTIVE", ,2 , , , , , , , ,"Consult note; DOC.ONT; Document; Encounter; Evaluation and management; Evaluation and management note; Finding; Findings; notes; Point in time; Random; Visit note" ,"Consult note" ,"Both" , , , , ,"Consult note" , , , , , , ,"Edit made because this term is conformant to the Document Ontology axis values and therefore are being placed in this class.; Based on Clinical LOINC Committee decision during the September 2014 meeting, {Provider} was changed to {Author Type} to emphasize a greater breadth of potential document authors. At the September 2015 Clinical LOINC Committee meeting, the Committee decided to change {Author Type} to {Role} to align with the 'Role' axis name in the LOINC Document Ontology.; Because it is too difficult to maintain and because the distinction between documents and sections is not clear-cut nor necessary in most cases, the DOCUMENT_SECTION field has been deemed to have little value. The field has been set to null in the December 2017 release in preparation for removal in the December 2018 release. These changes were approved by the Clinical LOINC Committee.",0 ,0 ,0 ,"IG exists" , , , ,"81222-2;72231-4;81243-8","1.0j-a" ,"Y"