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 ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; 025import org.apache.commons.csv.CSVRecord; 026import org.hl7.fhir.r4.model.CodeSystem; 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029 030import java.util.Arrays; 031import java.util.List; 032import java.util.Map; 033import java.util.stream.Collectors; 034 035import static org.apache.commons.lang3.StringUtils.isBlank; 036import static org.apache.commons.lang3.StringUtils.isNotBlank; 037import static org.apache.commons.lang3.StringUtils.trim; 038 039/** 040 * Handler to process coding type properties 'AskAtOrderEntry' and 'AssociatedObservations'. 041 * 042 * These properties are added in a specific handler which is involved after all TermConcepts 043 * are created, because they require a 'display' value associated to other TermConcept (pointed by the 'code' 044 * property value), which require that concept to have been created. 045 */ 046public class LoincCodingPropertiesHandler implements IZipContentsHandlerCsv { 047 private static final Logger ourLog = LoggerFactory.getLogger(LoincCodingPropertiesHandler.class); 048 049 public static final String ASK_AT_ORDER_ENTRY_PROP_NAME = "AskAtOrderEntry"; 050 public static final String ASSOCIATED_OBSERVATIONS_PROP_NAME = "AssociatedObservations"; 051 public static final String LOINC_NUM = "LOINC_NUM"; 052 053 private final Map<String, TermConcept> myCode2Concept; 054 private final Map<String, CodeSystem.PropertyType> myPropertyNameTypeMap; 055 056 public LoincCodingPropertiesHandler( 057 Map<String, TermConcept> theCode2concept, Map<String, CodeSystem.PropertyType> thePropertyNameTypeMap) { 058 myCode2Concept = theCode2concept; 059 myPropertyNameTypeMap = thePropertyNameTypeMap; 060 } 061 062 @Override 063 public void accept(CSVRecord theRecord) { 064 if (!anyValidProperty()) { 065 return; 066 } 067 068 String code = trim(theRecord.get(LOINC_NUM)); 069 if (isBlank(code)) { 070 return; 071 } 072 073 String askAtOrderEntryValue = trim(theRecord.get(ASK_AT_ORDER_ENTRY_PROP_NAME)); 074 String associatedObservationsValue = trim(theRecord.get(ASSOCIATED_OBSERVATIONS_PROP_NAME)); 075 076 // any of the record properties have a valid value? 077 if (isBlank(askAtOrderEntryValue) && isBlank(associatedObservationsValue)) { 078 return; 079 } 080 081 TermConcept srcTermConcept = myCode2Concept.get(code); 082 083 if (isNotBlank(askAtOrderEntryValue)) { 084 addCodingProperties(srcTermConcept, ASK_AT_ORDER_ENTRY_PROP_NAME, askAtOrderEntryValue); 085 } 086 087 if (isNotBlank(associatedObservationsValue)) { 088 addCodingProperties(srcTermConcept, ASSOCIATED_OBSERVATIONS_PROP_NAME, associatedObservationsValue); 089 } 090 } 091 092 /** 093 * Validates that at least one ot target properties is defined in loinc.xml file and is of type "CODING" 094 */ 095 private boolean anyValidProperty() { 096 CodeSystem.PropertyType askAtOrderEntryPropType = myPropertyNameTypeMap.get(ASK_AT_ORDER_ENTRY_PROP_NAME); 097 CodeSystem.PropertyType associatedObservationsPropType = 098 myPropertyNameTypeMap.get(ASSOCIATED_OBSERVATIONS_PROP_NAME); 099 100 return askAtOrderEntryPropType == CodeSystem.PropertyType.CODING 101 || associatedObservationsPropType == CodeSystem.PropertyType.CODING; 102 } 103 104 private void addCodingProperties(TermConcept theSrcTermConcept, String thePropertyName, String thePropertyValue) { 105 List<String> propertyCodeValues = parsePropertyCodeValues(thePropertyValue); 106 for (String propertyCodeValue : propertyCodeValues) { 107 TermConcept targetTermConcept = myCode2Concept.get(propertyCodeValue); 108 if (targetTermConcept == null) { 109 ourLog.error( 110 "Couldn't find TermConcept for code: '{}'. Display property set to blank for property: '{}'", 111 propertyCodeValue, 112 thePropertyName); 113 continue; 114 } 115 theSrcTermConcept.addPropertyCoding( 116 thePropertyName, ITermLoaderSvc.LOINC_URI, propertyCodeValue, targetTermConcept.getDisplay()); 117 ourLog.trace("Adding coding property: {} to concept.code {}", thePropertyName, theSrcTermConcept.getCode()); 118 } 119 } 120 121 private List<String> parsePropertyCodeValues(String theValue) { 122 return Arrays.stream(theValue.split(";")).map(String::trim).collect(Collectors.toList()); 123 } 124}