001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.term.loinc; 021 022import ca.uhn.fhir.jpa.entity.TermConcept; 023import ca.uhn.fhir.jpa.term.IZipContentsHandlerCsv; 024import org.apache.commons.lang3.StringUtils; 025import org.hl7.fhir.r4.model.ConceptMap; 026import org.hl7.fhir.r4.model.ContactPoint; 027import org.hl7.fhir.r4.model.Enumerations; 028import org.hl7.fhir.r4.model.ValueSet; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Properties; 036 037import static ca.uhn.fhir.jpa.term.loinc.LoincUploadPropertiesEnum.LOINC_CODESYSTEM_VERSION; 038import static org.apache.commons.lang3.StringUtils.defaultIfBlank; 039import static org.apache.commons.lang3.StringUtils.defaultString; 040import static org.apache.commons.lang3.StringUtils.isBlank; 041import static org.apache.commons.lang3.StringUtils.isNotBlank; 042 043public abstract class BaseLoincHandler implements IZipContentsHandlerCsv { 044 private static final Logger ourLog = LoggerFactory.getLogger(BaseLoincHandler.class); 045 046 /** 047 * This is <b>NOT</b> the LOINC CodeSystem URI! It is just 048 * the website URL to LOINC. 049 */ 050 public static final String LOINC_WEBSITE_URL = "https://loinc.org"; 051 052 public static final String REGENSTRIEF_INSTITUTE_INC = "Regenstrief Institute, Inc."; 053 private final List<ConceptMap> myConceptMaps; 054 private final Map<String, ConceptMap> myIdToConceptMaps = new HashMap<>(); 055 private final List<ValueSet> myValueSets; 056 private final Map<String, ValueSet> myIdToValueSet = new HashMap<>(); 057 private final Map<String, TermConcept> myCode2Concept; 058 protected final Properties myUploadProperties; 059 protected String myLoincCopyrightStatement; 060 061 BaseLoincHandler( 062 Map<String, TermConcept> theCode2Concept, 063 List<ValueSet> theValueSets, 064 List<ConceptMap> theConceptMaps, 065 Properties theUploadProperties) { 066 this(theCode2Concept, theValueSets, theConceptMaps, theUploadProperties, null); 067 } 068 069 BaseLoincHandler( 070 Map<String, TermConcept> theCode2Concept, 071 List<ValueSet> theValueSets, 072 List<ConceptMap> theConceptMaps, 073 Properties theUploadProperties, 074 String theCopyrightStatement) { 075 myValueSets = theValueSets; 076 myValueSets.forEach(t -> myIdToValueSet.put(t.getId(), t)); 077 myCode2Concept = theCode2Concept; 078 myConceptMaps = theConceptMaps; 079 myConceptMaps.forEach(t -> myIdToConceptMaps.put(t.getId(), t)); 080 myUploadProperties = theUploadProperties; 081 myLoincCopyrightStatement = theCopyrightStatement; 082 } 083 084 void addCodeAsIncludeToValueSet(ValueSet theVs, String theCodeSystemUrl, String theCode, String theDisplayName) { 085 ValueSet.ConceptSetComponent include = null; 086 for (ValueSet.ConceptSetComponent next : theVs.getCompose().getInclude()) { 087 if (next.getSystem().equals(theCodeSystemUrl)) { 088 include = next; 089 break; 090 } 091 } 092 if (include == null) { 093 include = theVs.getCompose().addInclude(); 094 include.setSystem(theCodeSystemUrl); 095 if (StringUtils.isNotBlank(theVs.getVersion())) { 096 include.setVersion(theVs.getVersion()); 097 } 098 } 099 100 boolean found = false; 101 for (ValueSet.ConceptReferenceComponent next : include.getConcept()) { 102 if (next.getCode().equals(theCode)) { 103 found = true; 104 } 105 } 106 if (!found) { 107 108 String displayName = theDisplayName; 109 if (isBlank(displayName)) { 110 TermConcept concept = myCode2Concept.get(theCode); 111 if (concept != null) { 112 displayName = concept.getDisplay(); 113 } 114 } 115 116 include.addConcept().setCode(theCode).setDisplay(displayName); 117 } 118 } 119 120 void addConceptMapEntry(ConceptMapping theMapping, String theExternalCopyright) { 121 if (isBlank(theMapping.getSourceCode())) { 122 return; 123 } 124 if (isBlank(theMapping.getTargetCode())) { 125 return; 126 } 127 128 ConceptMap conceptMap; 129 if (!myIdToConceptMaps.containsKey(theMapping.getConceptMapId())) { 130 conceptMap = new ConceptMap(); 131 conceptMap.setId(theMapping.getConceptMapId()); 132 conceptMap.setUrl(theMapping.getConceptMapUri()); 133 conceptMap.setName(theMapping.getConceptMapName()); 134 conceptMap.setVersion(theMapping.getConceptMapVersion()); 135 conceptMap.setPublisher(REGENSTRIEF_INSTITUTE_INC); 136 conceptMap 137 .addContact() 138 .setName(REGENSTRIEF_INSTITUTE_INC) 139 .addTelecom() 140 .setSystem(ContactPoint.ContactPointSystem.URL) 141 .setValue(LOINC_WEBSITE_URL); 142 143 String copyright = theExternalCopyright; 144 if (!copyright.contains("LOINC")) { 145 copyright = 146 myLoincCopyrightStatement + (myLoincCopyrightStatement.endsWith(".") ? " " : ". ") + copyright; 147 } 148 conceptMap.setCopyright(copyright); 149 150 myIdToConceptMaps.put(theMapping.getConceptMapId(), conceptMap); 151 myConceptMaps.add(conceptMap); 152 } else { 153 conceptMap = myIdToConceptMaps.get(theMapping.getConceptMapId()); 154 } 155 156 if (isBlank(theMapping.getCopyright())) { 157 conceptMap.setCopyright(theMapping.getCopyright()); 158 } 159 160 ConceptMap.SourceElementComponent source = null; 161 ConceptMap.ConceptMapGroupComponent group = null; 162 163 for (ConceptMap.ConceptMapGroupComponent next : conceptMap.getGroup()) { 164 if (next.getSource().equals(theMapping.getSourceCodeSystem())) { 165 if (next.getTarget().equals(theMapping.getTargetCodeSystem())) { 166 if (!defaultString(theMapping.getTargetCodeSystemVersion()) 167 .equals(defaultString(next.getTargetVersion()))) { 168 continue; 169 } 170 group = next; 171 break; 172 } 173 } 174 } 175 if (group == null) { 176 group = conceptMap.addGroup(); 177 group.setSource(theMapping.getSourceCodeSystem()); 178 group.setSourceVersion(theMapping.getSourceCodeSystemVersion()); 179 group.setTarget(theMapping.getTargetCodeSystem()); 180 group.setTargetVersion(defaultIfBlank(theMapping.getTargetCodeSystemVersion(), null)); 181 } 182 183 for (ConceptMap.SourceElementComponent next : group.getElement()) { 184 if (next.getCode().equals(theMapping.getSourceCode())) { 185 source = next; 186 } 187 } 188 if (source == null) { 189 source = group.addElement(); 190 source.setCode(theMapping.getSourceCode()); 191 source.setDisplay(theMapping.getSourceDisplay()); 192 } 193 194 boolean found = false; 195 for (ConceptMap.TargetElementComponent next : source.getTarget()) { 196 if (next.getCode().equals(theMapping.getTargetCode())) { 197 found = true; 198 } 199 } 200 if (!found) { 201 source.addTarget() 202 .setCode(theMapping.getTargetCode()) 203 .setDisplay(theMapping.getTargetDisplay()) 204 .setEquivalence(theMapping.getEquivalence()); 205 } else { 206 ourLog.info( 207 "Not going to add a mapping from [{}/{}] to [{}/{}] because one already exists", 208 theMapping.getSourceCodeSystem(), 209 theMapping.getSourceCode(), 210 theMapping.getTargetCodeSystem(), 211 theMapping.getTargetCode()); 212 } 213 } 214 215 ValueSet getValueSet( 216 String theValueSetId, String theValueSetUri, String theValueSetName, String theVersionPropertyName) { 217 218 String version; 219 String codeSystemVersion = myUploadProperties.getProperty(LOINC_CODESYSTEM_VERSION.getCode()); 220 if (isNotBlank(theVersionPropertyName)) { 221 if (codeSystemVersion != null) { 222 version = myUploadProperties.getProperty(theVersionPropertyName) + "-" + codeSystemVersion; 223 } else { 224 version = myUploadProperties.getProperty(theVersionPropertyName); 225 } 226 } else { 227 version = codeSystemVersion; 228 } 229 230 ValueSet vs; 231 if (!myIdToValueSet.containsKey(theValueSetId)) { 232 vs = new ValueSet(); 233 vs.setUrl(theValueSetUri); 234 vs.setId(theValueSetId); 235 vs.setVersion(version); 236 vs.setStatus(Enumerations.PublicationStatus.ACTIVE); 237 vs.setPublisher(REGENSTRIEF_INSTITUTE_INC); 238 vs.addContact() 239 .setName(REGENSTRIEF_INSTITUTE_INC) 240 .addTelecom() 241 .setSystem(ContactPoint.ContactPointSystem.URL) 242 .setValue(LOINC_WEBSITE_URL); 243 vs.setCopyright(myLoincCopyrightStatement); 244 myIdToValueSet.put(theValueSetId, vs); 245 myValueSets.add(vs); 246 } else { 247 vs = myIdToValueSet.get(theValueSetId); 248 } 249 250 if (isBlank(vs.getName()) && isNotBlank(theValueSetName)) { 251 vs.setName(theValueSetName); 252 } 253 254 return vs; 255 } 256 257 static class ConceptMapping { 258 259 private String myCopyright; 260 private String myConceptMapId; 261 private String myConceptMapUri; 262 private String myConceptMapVersion; 263 private String myConceptMapName; 264 private String mySourceCodeSystem; 265 private String mySourceCodeSystemVersion; 266 private String mySourceCode; 267 private String mySourceDisplay; 268 private String myTargetCodeSystem; 269 private String myTargetCode; 270 private String myTargetDisplay; 271 private Enumerations.ConceptMapEquivalence myEquivalence; 272 private String myTargetCodeSystemVersion; 273 274 String getConceptMapId() { 275 return myConceptMapId; 276 } 277 278 ConceptMapping setConceptMapId(String theConceptMapId) { 279 myConceptMapId = theConceptMapId; 280 return this; 281 } 282 283 String getConceptMapName() { 284 return myConceptMapName; 285 } 286 287 ConceptMapping setConceptMapName(String theConceptMapName) { 288 myConceptMapName = theConceptMapName; 289 return this; 290 } 291 292 String getConceptMapUri() { 293 return myConceptMapUri; 294 } 295 296 ConceptMapping setConceptMapUri(String theConceptMapUri) { 297 myConceptMapUri = theConceptMapUri; 298 return this; 299 } 300 301 String getConceptMapVersion() { 302 return myConceptMapVersion; 303 } 304 305 ConceptMapping setConceptMapVersion(String theConceptMapVersion) { 306 myConceptMapVersion = theConceptMapVersion; 307 return this; 308 } 309 310 String getCopyright() { 311 return myCopyright; 312 } 313 314 ConceptMapping setCopyright(String theCopyright) { 315 myCopyright = theCopyright; 316 return this; 317 } 318 319 Enumerations.ConceptMapEquivalence getEquivalence() { 320 return myEquivalence; 321 } 322 323 ConceptMapping setEquivalence(Enumerations.ConceptMapEquivalence theEquivalence) { 324 myEquivalence = theEquivalence; 325 return this; 326 } 327 328 String getSourceCode() { 329 return mySourceCode; 330 } 331 332 ConceptMapping setSourceCode(String theSourceCode) { 333 mySourceCode = theSourceCode; 334 return this; 335 } 336 337 String getSourceCodeSystem() { 338 return mySourceCodeSystem; 339 } 340 341 ConceptMapping setSourceCodeSystem(String theSourceCodeSystem) { 342 mySourceCodeSystem = theSourceCodeSystem; 343 return this; 344 } 345 346 String getSourceCodeSystemVersion() { 347 return mySourceCodeSystemVersion; 348 } 349 350 ConceptMapping setSourceCodeSystemVersion(String theSourceCodeSystemVersion) { 351 mySourceCodeSystemVersion = theSourceCodeSystemVersion; 352 return this; 353 } 354 355 String getSourceDisplay() { 356 return mySourceDisplay; 357 } 358 359 ConceptMapping setSourceDisplay(String theSourceDisplay) { 360 mySourceDisplay = theSourceDisplay; 361 return this; 362 } 363 364 String getTargetCode() { 365 return myTargetCode; 366 } 367 368 ConceptMapping setTargetCode(String theTargetCode) { 369 myTargetCode = theTargetCode; 370 return this; 371 } 372 373 String getTargetCodeSystem() { 374 return myTargetCodeSystem; 375 } 376 377 ConceptMapping setTargetCodeSystem(String theTargetCodeSystem) { 378 myTargetCodeSystem = theTargetCodeSystem; 379 return this; 380 } 381 382 String getTargetCodeSystemVersion() { 383 return myTargetCodeSystemVersion; 384 } 385 386 ConceptMapping setTargetCodeSystemVersion(String theTargetCodeSystemVersion) { 387 myTargetCodeSystemVersion = theTargetCodeSystemVersion; 388 return this; 389 } 390 391 String getTargetDisplay() { 392 return myTargetDisplay; 393 } 394 395 ConceptMapping setTargetDisplay(String theTargetDisplay) { 396 myTargetDisplay = theTargetDisplay; 397 return this; 398 } 399 } 400}