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.TermConcept;
024import ca.uhn.fhir.jpa.term.IZipContentsHandlerCsv;
025import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
026import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
027import org.apache.commons.csv.CSVRecord;
028import org.hl7.fhir.r4.model.ConceptMap;
029import org.hl7.fhir.r4.model.Enumerations;
030import org.hl7.fhir.r4.model.ValueSet;
031
032import java.util.HashMap;
033import java.util.HashSet;
034import java.util.List;
035import java.util.Map;
036import java.util.Properties;
037import java.util.Set;
038
039import static ca.uhn.fhir.jpa.term.loinc.LoincUploadPropertiesEnum.LOINC_CODESYSTEM_VERSION;
040import static ca.uhn.fhir.jpa.term.loinc.LoincUploadPropertiesEnum.LOINC_CONCEPTMAP_VERSION;
041import static org.apache.commons.lang3.StringUtils.isNotBlank;
042import static org.apache.commons.lang3.StringUtils.trim;
043
044public class LoincRsnaPlaybookHandler extends BaseLoincHandler implements IZipContentsHandlerCsv {
045
046        public static final String RSNA_CODES_VS_ID = "loinc-rsna-radiology-playbook";
047        public static final String RSNA_CODES_VS_URI = "http://loinc.org/vs/loinc-rsna-radiology-playbook";
048        public static final String RSNA_CODES_VS_NAME = "LOINC/RSNA Radiology Playbook";
049        public static final String RID_CS_URI = "http://www.radlex.org";
050        /*
051         * About these being the same - Per Dan Vreeman:
052         * We had some discussion about this, and both
053         * RIDs (RadLex clinical terms) and RPIDs (Radlex Playbook Ids)
054         * belong to the same "code system" since they will never collide.
055         * The codesystem uri is "http://www.radlex.org". FYI, that's
056         * now listed on the FHIR page:
057         * https://www.hl7.org/fhir/terminologies-systems.html
058         * -ja
059         */
060        public static final String RPID_CS_URI = RID_CS_URI;
061        private static final String CM_COPYRIGHT =
062                        "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.";
063        private final Map<String, TermConcept> myCode2Concept;
064        private final List<ValueSet> myValueSets;
065        private final Map<String, ValueSet> myIdToValueSet = new HashMap<>();
066        private final Set<String> myCodesInRsnaPlaybookValueSet = new HashSet<>();
067
068        /**
069         * Constructor
070         */
071        public LoincRsnaPlaybookHandler(
072                        Map<String, TermConcept> theCode2concept,
073                        List<ValueSet> theValueSets,
074                        List<ConceptMap> theConceptMaps,
075                        Properties theUploadProperties,
076                        String theCopyrightStatement) {
077                super(theCode2concept, theValueSets, theConceptMaps, theUploadProperties, theCopyrightStatement);
078                myCode2Concept = theCode2concept;
079                myValueSets = theValueSets;
080        }
081
082        @Override
083        public void accept(CSVRecord theRecord) {
084
085                String loincNumber = trim(theRecord.get("LoincNumber"));
086                String longCommonName = trim(theRecord.get("LongCommonName"));
087                String partNumber = trim(theRecord.get("PartNumber"));
088                String partTypeName = trim(theRecord.get("PartTypeName"));
089                String partName = trim(theRecord.get("PartName"));
090                String partSequenceOrder = trim(theRecord.get("PartSequenceOrder"));
091                String rid = trim(theRecord.get("RID"));
092                String preferredName = trim(theRecord.get("PreferredName"));
093                String rpid = trim(theRecord.get("RPID"));
094                String longName = trim(theRecord.get("LongName"));
095
096                // CodeSystem version from properties file
097                String codeSystemVersionId = myUploadProperties.getProperty(LOINC_CODESYSTEM_VERSION.getCode());
098
099                // ConceptMap version from properties files
100                String loincRsnaCmVersion;
101                if (codeSystemVersionId != null) {
102                        loincRsnaCmVersion =
103                                        myUploadProperties.getProperty(LOINC_CONCEPTMAP_VERSION.getCode()) + "-" + codeSystemVersionId;
104                } else {
105                        loincRsnaCmVersion = myUploadProperties.getProperty(LOINC_CONCEPTMAP_VERSION.getCode());
106                }
107
108                // RSNA Codes VS
109                ValueSet vs;
110                String rsnaCodesValueSetId;
111                if (codeSystemVersionId != null) {
112                        rsnaCodesValueSetId = RSNA_CODES_VS_ID + "-" + codeSystemVersionId;
113                } else {
114                        rsnaCodesValueSetId = RSNA_CODES_VS_ID;
115                }
116                if (!myIdToValueSet.containsKey(rsnaCodesValueSetId)) {
117                        vs = new ValueSet();
118                        vs.setUrl(RSNA_CODES_VS_URI);
119                        vs.setId(rsnaCodesValueSetId);
120                        vs.setName(RSNA_CODES_VS_NAME);
121                        vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
122                        vs.setVersion(codeSystemVersionId);
123                        myIdToValueSet.put(rsnaCodesValueSetId, vs);
124                        myValueSets.add(vs);
125                } else {
126                        vs = myIdToValueSet.get(rsnaCodesValueSetId);
127                }
128
129                if (!myCodesInRsnaPlaybookValueSet.contains(loincNumber)) {
130                        vs.getCompose()
131                                        .getIncludeFirstRep()
132                                        .setSystem(ITermLoaderSvc.LOINC_URI)
133                                        .setVersion(codeSystemVersionId)
134                                        .addConcept()
135                                        .setCode(loincNumber)
136                                        .setDisplay(longCommonName);
137                        myCodesInRsnaPlaybookValueSet.add(loincNumber);
138                }
139
140                String loincCodePropName;
141                switch (partTypeName.toLowerCase()) {
142                        case "rad.anatomic location.region imaged":
143                                loincCodePropName = "rad-anatomic-location-region-imaged";
144                                break;
145                        case "rad.anatomic location.imaging focus":
146                                loincCodePropName = "rad-anatomic-location-imaging-focus";
147                                break;
148                        case "rad.modality.modality type":
149                                loincCodePropName = "rad-modality-modality-type";
150                                break;
151                        case "rad.modality.modality subtype":
152                                loincCodePropName = "rad-modality-modality-subtype";
153                                break;
154                        case "rad.anatomic location.laterality":
155                                loincCodePropName = "rad-anatomic-location-laterality";
156                                break;
157                        case "rad.anatomic location.laterality.presence":
158                                loincCodePropName = "rad-anatomic-location-laterality-presence";
159                                break;
160                        case "rad.guidance for.action":
161                                loincCodePropName = "rad-guidance-for-action";
162                                break;
163                        case "rad.guidance for.approach":
164                                loincCodePropName = "rad-guidance-for-approach";
165                                break;
166                        case "rad.guidance for.object":
167                                loincCodePropName = "rad-guidance-for-object";
168                                break;
169                        case "rad.guidance for.presence":
170                                loincCodePropName = "rad-guidance-for-presence";
171                                break;
172                        case "rad.maneuver.maneuver type":
173                                loincCodePropName = "rad-maneuver-maneuver-type";
174                                break;
175                        case "rad.pharmaceutical.route":
176                                loincCodePropName = "rad-pharmaceutical-route";
177                                break;
178                        case "rad.pharmaceutical.substance given":
179                                loincCodePropName = "rad-pharmaceutical-substance-given";
180                                break;
181                        case "rad.reason for exam":
182                                loincCodePropName = "rad-reason-for-exam";
183                                break;
184                        case "rad.subject":
185                                loincCodePropName = "rad-subject";
186                                break;
187                        case "rad.timing":
188                                loincCodePropName = "rad-timing";
189                                break;
190                        case "rad.view.aggregation":
191                                loincCodePropName = "rad-view-view-aggregation";
192                                break;
193                        case "rad.view.view type":
194                                loincCodePropName = "rad-view-view-type";
195                                break;
196                        default:
197                                throw new InternalErrorException(Msg.code(912) + "Unknown PartTypeName: " + partTypeName);
198                }
199
200                TermConcept code = myCode2Concept.get(loincNumber);
201                if (code != null) {
202                        code.addPropertyCoding(loincCodePropName, ITermLoaderSvc.LOINC_URI, partNumber, partName);
203                }
204
205                String partConceptMapId;
206                String termConceptMapId;
207                if (codeSystemVersionId != null) {
208                        partConceptMapId =
209                                        LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_ID + "-" + codeSystemVersionId;
210                        termConceptMapId =
211                                        LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_ID + "-" + codeSystemVersionId;
212                } else {
213                        partConceptMapId = LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_ID;
214                        termConceptMapId = LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_ID;
215                }
216
217                // LOINC Part -> Radlex RID code mappings
218                if (isNotBlank(rid)) {
219                        addConceptMapEntry(
220                                        new ConceptMapping()
221                                                        .setConceptMapId(partConceptMapId)
222                                                        .setConceptMapUri(LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_URI)
223                                                        .setConceptMapVersion(loincRsnaCmVersion)
224                                                        .setConceptMapName(LoincPartRelatedCodeMappingHandler.LOINC_PART_TO_RID_PART_MAP_NAME)
225                                                        .setSourceCodeSystem(ITermLoaderSvc.LOINC_URI)
226                                                        .setSourceCodeSystemVersion(codeSystemVersionId)
227                                                        .setSourceCode(partNumber)
228                                                        .setSourceDisplay(partName)
229                                                        .setTargetCodeSystem(RID_CS_URI)
230                                                        .setTargetCode(rid)
231                                                        .setTargetDisplay(preferredName)
232                                                        .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL),
233                                        myLoincCopyrightStatement + " " + CM_COPYRIGHT);
234                }
235
236                // LOINC Term -> Radlex RPID code mappings
237                if (isNotBlank(rpid)) {
238                        addConceptMapEntry(
239                                        new ConceptMapping()
240                                                        .setConceptMapId(termConceptMapId)
241                                                        .setConceptMapUri(LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_URI)
242                                                        .setConceptMapVersion(loincRsnaCmVersion)
243                                                        .setConceptMapName(LoincPartRelatedCodeMappingHandler.LOINC_TERM_TO_RPID_PART_MAP_NAME)
244                                                        .setSourceCodeSystem(ITermLoaderSvc.LOINC_URI)
245                                                        .setSourceCodeSystemVersion(codeSystemVersionId)
246                                                        .setSourceCode(loincNumber)
247                                                        .setSourceDisplay(longCommonName)
248                                                        .setTargetCodeSystem(RPID_CS_URI)
249                                                        .setTargetCode(rpid)
250                                                        .setTargetDisplay(longName)
251                                                        .setEquivalence(Enumerations.ConceptMapEquivalence.EQUAL),
252                                        myLoincCopyrightStatement + " " + CM_COPYRIGHT);
253                }
254        }
255}