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