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}