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