
001package ca.uhn.fhir.jpa.term.loinc; 002 003/*- 004 * #%L 005 * HAPI FHIR JPA Server 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.jpa.entity.TermConcept; 025import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; 026import ca.uhn.fhir.jpa.term.IZipContentsHandlerCsv; 027import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 028import org.apache.commons.csv.CSVRecord; 029import org.hl7.fhir.r4.model.ConceptMap; 030import org.hl7.fhir.r4.model.Enumerations; 031import org.hl7.fhir.r4.model.ValueSet; 032 033import java.util.*; 034 035import static ca.uhn.fhir.jpa.term.loinc.LoincUploadPropertiesEnum.LOINC_CODESYSTEM_VERSION; 036import static ca.uhn.fhir.jpa.term.loinc.LoincUploadPropertiesEnum.LOINC_CONCEPTMAP_VERSION; 037import static org.apache.commons.lang3.StringUtils.isNotBlank; 038import static org.apache.commons.lang3.StringUtils.trim; 039 040public class LoincRsnaPlaybookHandler extends BaseLoincHandler implements IZipContentsHandlerCsv { 041 042 public static final String RSNA_CODES_VS_ID = "loinc-rsna-radiology-playbook"; 043 public static final String RSNA_CODES_VS_URI = "http://loinc.org/vs/loinc-rsna-radiology-playbook"; 044 public static final String RSNA_CODES_VS_NAME = "LOINC/RSNA Radiology Playbook"; 045 public static final String RID_CS_URI = "http://www.radlex.org"; 046 /* 047 * About these being the same - Per Dan Vreeman: 048 * We had some discussion about this, and both 049 * RIDs (RadLex clinical terms) and RPIDs (Radlex Playbook Ids) 050 * belong to the same "code system" since they will never collide. 051 * The codesystem uri is "http://www.radlex.org". FYI, that's 052 * now listed on the FHIR page: 053 * https://www.hl7.org/fhir/terminologies-systems.html 054 * -ja 055 */ 056 public static final String RPID_CS_URI = RID_CS_URI; 057 private static final String CM_COPYRIGHT = "The LOINC/RSNA Radiology Playbook and the LOINC Part File contain content from RadLex® (http://rsna.org/RadLex.aspx), copyright © 2005-2017, The Radiological Society of North America, Inc., available at no cost under the license at http://www.rsna.org/uploadedFiles/RSNA/Content/Informatics/RadLex_License_Agreement_and_Terms_of_Use_V2_Final.pdf."; 058 private final Map<String, TermConcept> myCode2Concept; 059 private final List<ValueSet> myValueSets; 060 private final Map<String, ValueSet> myIdToValueSet = new HashMap<>(); 061 private final Set<String> myCodesInRsnaPlaybookValueSet = new HashSet<>(); 062 063 /** 064 * Constructor 065 */ 066 public LoincRsnaPlaybookHandler(Map<String, TermConcept> theCode2concept, List<ValueSet> theValueSets, 067 List<ConceptMap> theConceptMaps, Properties theUploadProperties, String theCopyrightStatement) { 068 super(theCode2concept, theValueSets, theConceptMaps, theUploadProperties, theCopyrightStatement); 069 myCode2Concept = theCode2concept; 070 myValueSets = theValueSets; 071 } 072 073 @Override 074 public void accept(CSVRecord theRecord) { 075 076 String loincNumber = trim(theRecord.get("LoincNumber")); 077 String longCommonName = trim(theRecord.get("LongCommonName")); 078 String partNumber = trim(theRecord.get("PartNumber")); 079 String partTypeName = trim(theRecord.get("PartTypeName")); 080 String partName = trim(theRecord.get("PartName")); 081 String partSequenceOrder = trim(theRecord.get("PartSequenceOrder")); 082 String rid = trim(theRecord.get("RID")); 083 String preferredName = trim(theRecord.get("PreferredName")); 084 String rpid = trim(theRecord.get("RPID")); 085 String longName = trim(theRecord.get("LongName")); 086 087 // CodeSystem version from properties file 088 String codeSystemVersionId = myUploadProperties.getProperty(LOINC_CODESYSTEM_VERSION.getCode()); 089 090 // ConceptMap version from properties files 091 String loincRsnaCmVersion; 092 if (codeSystemVersionId != null) { 093 loincRsnaCmVersion = myUploadProperties.getProperty(LOINC_CONCEPTMAP_VERSION.getCode()) + "-" + codeSystemVersionId; 094 } else { 095 loincRsnaCmVersion = myUploadProperties.getProperty(LOINC_CONCEPTMAP_VERSION.getCode()); 096 } 097 098 099 // RSNA Codes VS 100 ValueSet vs; 101 String rsnaCodesValueSetId; 102 if (codeSystemVersionId != null) { 103 rsnaCodesValueSetId = RSNA_CODES_VS_ID + "-" + codeSystemVersionId; 104 } else { 105 rsnaCodesValueSetId = RSNA_CODES_VS_ID; 106 } 107 if (!myIdToValueSet.containsKey(rsnaCodesValueSetId)) { 108 vs = new ValueSet(); 109 vs.setUrl(RSNA_CODES_VS_URI); 110 vs.setId(rsnaCodesValueSetId); 111 vs.setName(RSNA_CODES_VS_NAME); 112 vs.setStatus(Enumerations.PublicationStatus.ACTIVE); 113 vs.setVersion(codeSystemVersionId); 114 myIdToValueSet.put(rsnaCodesValueSetId, vs); 115 myValueSets.add(vs); 116 } else { 117 vs = myIdToValueSet.get(rsnaCodesValueSetId); 118 } 119 120 if (!myCodesInRsnaPlaybookValueSet.contains(loincNumber)) { 121 vs 122 .getCompose() 123 .getIncludeFirstRep() 124 .setSystem(ITermLoaderSvc.LOINC_URI) 125 .setVersion(codeSystemVersionId) 126 .addConcept() 127 .setCode(loincNumber) 128 .setDisplay(longCommonName); 129 myCodesInRsnaPlaybookValueSet.add(loincNumber); 130 } 131 132 String loincCodePropName; 133 switch (partTypeName.toLowerCase()) { 134 case "rad.anatomic location.region imaged": 135 loincCodePropName = "rad-anatomic-location-region-imaged"; 136 break; 137 case "rad.anatomic location.imaging focus": 138 loincCodePropName = "rad-anatomic-location-imaging-focus"; 139 break; 140 case "rad.modality.modality type": 141 loincCodePropName = "rad-modality-modality-type"; 142 break; 143 case "rad.modality.modality subtype": 144 loincCodePropName = "rad-modality-modality-subtype"; 145 break; 146 case "rad.anatomic location.laterality": 147 loincCodePropName = "rad-anatomic-location-laterality"; 148 break; 149 case "rad.anatomic location.laterality.presence": 150 loincCodePropName = "rad-anatomic-location-laterality-presence"; 151 break; 152 case "rad.guidance for.action": 153 loincCodePropName = "rad-guidance-for-action"; 154 break; 155 case "rad.guidance for.approach": 156 loincCodePropName = "rad-guidance-for-approach"; 157 break; 158 case "rad.guidance for.object": 159 loincCodePropName = "rad-guidance-for-object"; 160 break; 161 case "rad.guidance for.presence": 162 loincCodePropName = "rad-guidance-for-presence"; 163 break; 164 case "rad.maneuver.maneuver type": 165 loincCodePropName = "rad-maneuver-maneuver-type"; 166 break; 167 case "rad.pharmaceutical.route": 168 loincCodePropName = "rad-pharmaceutical-route"; 169 break; 170 case "rad.pharmaceutical.substance given": 171 loincCodePropName = "rad-pharmaceutical-substance-given"; 172 break; 173 case "rad.reason for exam": 174 loincCodePropName = "rad-reason-for-exam"; 175 break; 176 case "rad.subject": 177 loincCodePropName = "rad-subject"; 178 break; 179 case "rad.timing": 180 loincCodePropName = "rad-timing"; 181 break; 182 case "rad.view.aggregation": 183 loincCodePropName = "rad-view-view-aggregation"; 184 break; 185 case "rad.view.view type": 186 loincCodePropName = "rad-view-view-type"; 187 break; 188 default: 189 throw new InternalErrorException(Msg.code(912) + "Unknown PartTypeName: " + partTypeName); 190 } 191 192 TermConcept code = myCode2Concept.get(loincNumber); 193 if (code != null) { 194 code.addPropertyCoding(loincCodePropName, ITermLoaderSvc.LOINC_URI, partNumber, partName); 195 } 196 197 String partConceptMapId; 198 String termConceptMapId; 199 if (codeSystemVersionId != null) { 200 partConceptMapId = LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_ID + "-" + codeSystemVersionId; 201 termConceptMapId = LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_ID + "-" + codeSystemVersionId; 202 } else { 203 partConceptMapId = LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_ID; 204 termConceptMapId = LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_ID; 205 } 206 207 // LOINC Part -> Radlex RID code mappings 208 if (isNotBlank(rid)) { 209 addConceptMapEntry( 210 new ConceptMapping() 211 .setConceptMapId(partConceptMapId) 212 .setConceptMapUri(LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_URI) 213 .setConceptMapVersion(loincRsnaCmVersion) 214 .setConceptMapName(LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_NAME) 215 .setSourceCodeSystem(ITermLoaderSvc.LOINC_URI) 216 .setSourceCodeSystemVersion(codeSystemVersionId) 217 .setSourceCode(partNumber) 218 .setSourceDisplay(partName) 219 .setTargetCodeSystem(RID_CS_URI) 220 .setTargetCode(rid) 221 .setTargetDisplay(preferredName) 222 .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL) 223 ,myLoincCopyrightStatement + " " + CM_COPYRIGHT); 224 } 225 226 // LOINC Term -> Radlex RPID code mappings 227 if (isNotBlank(rpid)) { 228 addConceptMapEntry( 229 new ConceptMapping() 230 .setConceptMapId(termConceptMapId) 231 .setConceptMapUri(LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_URI) 232 .setConceptMapVersion(loincRsnaCmVersion) 233 .setConceptMapName(LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_NAME) 234 .setSourceCodeSystem(ITermLoaderSvc.LOINC_URI) 235 .setSourceCodeSystemVersion(codeSystemVersionId) 236 .setSourceCode(loincNumber) 237 .setSourceDisplay(longCommonName) 238 .setTargetCodeSystem(RPID_CS_URI) 239 .setTargetCode(rpid) 240 .setTargetDisplay(longName) 241 .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL), 242 myLoincCopyrightStatement + " " + CM_COPYRIGHT); 243 } 244 245 } 246 247 248}