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.i18n.Msg;
023import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
024import ca.uhn.fhir.jpa.entity.TermConcept;
025import ca.uhn.fhir.jpa.term.IZipContentsHandlerCsv;
026import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl;
027import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
028import org.apache.commons.csv.CSVRecord;
029import org.apache.commons.lang3.Validate;
030import org.hl7.fhir.r4.model.CodeSystem;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import java.util.Map;
035
036import static org.apache.commons.lang3.StringUtils.isNotBlank;
037import static org.apache.commons.lang3.StringUtils.trim;
038
039public class LoincHandler implements IZipContentsHandlerCsv {
040
041        private static final Logger ourLog = LoggerFactory.getLogger(LoincHandler.class);
042        private final Map<String, TermConcept> myCode2Concept;
043        private final TermCodeSystemVersion myCodeSystemVersion;
044        private final Map<String, CodeSystem.PropertyType> myPropertyNames;
045        private final Map<PartTypeAndPartName, String> myPartTypeAndPartNameToPartNumber;
046
047        public LoincHandler(
048                        TermCodeSystemVersion theCodeSystemVersion,
049                        Map<String, TermConcept> theCode2concept,
050                        Map<String, CodeSystem.PropertyType> thePropertyNames,
051                        Map<PartTypeAndPartName, String> thePartTypeAndPartNameToPartNumber) {
052                myCodeSystemVersion = theCodeSystemVersion;
053                myCode2Concept = theCode2concept;
054                myPropertyNames = thePropertyNames;
055                myPartTypeAndPartNameToPartNumber = thePartTypeAndPartNameToPartNumber;
056        }
057
058        @Override
059        public void accept(CSVRecord theRecord) {
060                String code = trim(theRecord.get("LOINC_NUM"));
061                if (isNotBlank(code)) {
062                        String longCommonName = trim(theRecord.get("LONG_COMMON_NAME"));
063                        String shortName = trim(theRecord.get("SHORTNAME"));
064                        String consumerName = trim(theRecord.get("CONSUMER_NAME"));
065                        String display = TermLoaderSvcImpl.firstNonBlank(longCommonName, shortName, consumerName);
066
067                        TermConcept concept = new TermConcept(myCodeSystemVersion, code);
068                        concept.setDisplay(display);
069
070                        if (isNotBlank(shortName) && !display.equalsIgnoreCase(shortName)) {
071                                concept.addDesignation().setUseDisplay("ShortName").setValue(shortName);
072                        }
073
074                        for (String nextPropertyName : myPropertyNames.keySet()) {
075                                if (!theRecord.toMap().containsKey(nextPropertyName)) {
076                                        continue;
077                                }
078
079                                CodeSystem.PropertyType nextPropertyType = myPropertyNames.get(nextPropertyName);
080
081                                String nextPropertyValue = theRecord.get(nextPropertyName);
082                                if (isNotBlank(nextPropertyValue)) {
083                                        nextPropertyValue = trim(nextPropertyValue);
084
085                                        switch (nextPropertyType) {
086                                                case STRING:
087                                                        concept.addPropertyString(nextPropertyName, nextPropertyValue);
088                                                        ourLog.trace(
089                                                                        "Adding string property: {} to concept.code {}",
090                                                                        nextPropertyName,
091                                                                        concept.getCode());
092                                                        break;
093
094                                                case CODING:
095                                                        // "Coding" property types are handled by loincCodingProperties, partlink, hierarchy,
096                                                        // RsnaPlaybook or DocumentOntology handlers
097                                                        break;
098
099                                                case DECIMAL:
100                                                case CODE:
101                                                case INTEGER:
102                                                case BOOLEAN:
103                                                case DATETIME:
104                                                case NULL:
105                                                        throw new InternalErrorException(Msg.code(915)
106                                                                        + "Don't know how to handle LOINC property of type: " + nextPropertyType);
107                                        }
108                                }
109                        }
110
111                        Validate.isTrue(!myCode2Concept.containsKey(code), "The code %s has appeared more than once", code);
112                        myCode2Concept.put(code, concept);
113                }
114        }
115}