001package ca.uhn.fhir.jpa.term.loinc;
002
003/*-
004 * #%L
005 * HAPI FHIR JPA Server
006 * %%
007 * Copyright (C) 2014 - 2023 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.jpa.entity.TermConcept;
024import ca.uhn.fhir.jpa.term.IZipContentsHandlerCsv;
025import org.apache.commons.lang3.StringUtils;
026import org.hl7.fhir.r4.model.ConceptMap;
027import org.hl7.fhir.r4.model.ContactPoint;
028import org.hl7.fhir.r4.model.Enumerations;
029import org.hl7.fhir.r4.model.ValueSet;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036import java.util.Properties;
037
038import static ca.uhn.fhir.jpa.term.loinc.LoincUploadPropertiesEnum.*;
039import static org.apache.commons.lang3.StringUtils.*;
040
041public abstract class BaseLoincHandler implements IZipContentsHandlerCsv {
042        private static final Logger ourLog = LoggerFactory.getLogger(BaseLoincHandler.class);
043
044        /**
045         * This is <b>NOT</b> the LOINC CodeSystem URI! It is just
046         * the website URL to LOINC.
047         */
048        public static final String LOINC_WEBSITE_URL = "https://loinc.org";
049        public static final String REGENSTRIEF_INSTITUTE_INC = "Regenstrief Institute, Inc.";
050        private final List<ConceptMap> myConceptMaps;
051        private final Map<String, ConceptMap> myIdToConceptMaps = new HashMap<>();
052        private final List<ValueSet> myValueSets;
053        private final Map<String, ValueSet> myIdToValueSet = new HashMap<>();
054        private final Map<String, TermConcept> myCode2Concept;
055        protected final Properties myUploadProperties;
056        protected String myLoincCopyrightStatement;
057
058        BaseLoincHandler(Map<String, TermConcept> theCode2Concept, List<ValueSet> theValueSets,
059                        List<ConceptMap> theConceptMaps, Properties theUploadProperties) {
060                this(theCode2Concept, theValueSets, theConceptMaps, theUploadProperties, null);
061        }
062
063        BaseLoincHandler(Map<String, TermConcept> theCode2Concept, List<ValueSet> theValueSets,
064                        List<ConceptMap> theConceptMaps, Properties theUploadProperties, String theCopyrightStatement) {
065                myValueSets = theValueSets;
066                myValueSets.forEach(t -> myIdToValueSet.put(t.getId(), t));
067                myCode2Concept = theCode2Concept;
068                myConceptMaps = theConceptMaps;
069                myConceptMaps.forEach(t -> myIdToConceptMaps.put(t.getId(), t));
070                myUploadProperties = theUploadProperties;
071                myLoincCopyrightStatement = theCopyrightStatement;
072        }
073
074        void addCodeAsIncludeToValueSet(ValueSet theVs, String theCodeSystemUrl, String theCode, String theDisplayName) {
075                ValueSet.ConceptSetComponent include = null;
076                for (ValueSet.ConceptSetComponent next : theVs.getCompose().getInclude()) {
077                        if (next.getSystem().equals(theCodeSystemUrl)) {
078                                include = next;
079                                break;
080                        }
081                }
082                if (include == null) {
083                        include = theVs.getCompose().addInclude();
084                        include.setSystem(theCodeSystemUrl);
085                        if (StringUtils.isNotBlank(theVs.getVersion())) {
086                                include.setVersion(theVs.getVersion());
087                        }
088                }
089
090                boolean found = false;
091                for (ValueSet.ConceptReferenceComponent next : include.getConcept()) {
092                        if (next.getCode().equals(theCode)) {
093                                found = true;
094                        }
095                }
096                if (!found) {
097
098                        String displayName = theDisplayName;
099                        if (isBlank(displayName)) {
100                                TermConcept concept = myCode2Concept.get(theCode);
101                                if (concept != null) {
102                                        displayName = concept.getDisplay();
103                                }
104                        }
105
106                        include
107                                .addConcept()
108                                .setCode(theCode)
109                                .setDisplay(displayName);
110
111                }
112        }
113
114
115        void addConceptMapEntry(ConceptMapping theMapping, String theExternalCopyright) {
116                if (isBlank(theMapping.getSourceCode())) {
117                        return;
118                }
119                if (isBlank(theMapping.getTargetCode())) {
120                        return;
121                }
122
123                ConceptMap conceptMap;
124                if (!myIdToConceptMaps.containsKey(theMapping.getConceptMapId())) {
125                        conceptMap = new ConceptMap();
126                        conceptMap.setId(theMapping.getConceptMapId());
127                        conceptMap.setUrl(theMapping.getConceptMapUri());
128                        conceptMap.setName(theMapping.getConceptMapName());
129                        conceptMap.setVersion(theMapping.getConceptMapVersion());
130                        conceptMap.setPublisher(REGENSTRIEF_INSTITUTE_INC);
131                        conceptMap.addContact()
132                                .setName(REGENSTRIEF_INSTITUTE_INC)
133                                .addTelecom()
134                                .setSystem(ContactPoint.ContactPointSystem.URL)
135                                .setValue(LOINC_WEBSITE_URL);
136
137                        String copyright = theExternalCopyright;
138                        if (!copyright.contains("LOINC")) {
139                                copyright = myLoincCopyrightStatement +
140                                        (myLoincCopyrightStatement.endsWith(".") ? " " : ". ") + copyright;
141                        }
142                        conceptMap.setCopyright(copyright);
143
144                        myIdToConceptMaps.put(theMapping.getConceptMapId(), conceptMap);
145                        myConceptMaps.add(conceptMap);
146                } else {
147                        conceptMap = myIdToConceptMaps.get(theMapping.getConceptMapId());
148                }
149
150                if (isBlank(theMapping.getCopyright())) {
151                        conceptMap.setCopyright(theMapping.getCopyright());
152                }
153
154                ConceptMap.SourceElementComponent source = null;
155                ConceptMap.ConceptMapGroupComponent group = null;
156
157                for (ConceptMap.ConceptMapGroupComponent next : conceptMap.getGroup()) {
158                        if (next.getSource().equals(theMapping.getSourceCodeSystem())) {
159                                if (next.getTarget().equals(theMapping.getTargetCodeSystem())) {
160                                        if (!defaultString(theMapping.getTargetCodeSystemVersion()).equals(defaultString(next.getTargetVersion()))) {
161                                                continue;
162                                        }
163                                        group = next;
164                                        break;
165                                }
166                        }
167                }
168                if (group == null) {
169                        group = conceptMap.addGroup();
170                        group.setSource(theMapping.getSourceCodeSystem());
171                        group.setSourceVersion(theMapping.getSourceCodeSystemVersion());
172                        group.setTarget(theMapping.getTargetCodeSystem());
173                        group.setTargetVersion(defaultIfBlank(theMapping.getTargetCodeSystemVersion(), null));
174                }
175
176                for (ConceptMap.SourceElementComponent next : group.getElement()) {
177                        if (next.getCode().equals(theMapping.getSourceCode())) {
178                                source = next;
179                        }
180                }
181                if (source == null) {
182                        source = group.addElement();
183                        source.setCode(theMapping.getSourceCode());
184                        source.setDisplay(theMapping.getSourceDisplay());
185                }
186
187                boolean found = false;
188                for (ConceptMap.TargetElementComponent next : source.getTarget()) {
189                        if (next.getCode().equals(theMapping.getTargetCode())) {
190                                found = true;
191                        }
192                }
193                if (!found) {
194                        source
195                                .addTarget()
196                                .setCode(theMapping.getTargetCode())
197                                .setDisplay(theMapping.getTargetDisplay())
198                                .setEquivalence(theMapping.getEquivalence());
199                } else {
200                        ourLog.info("Not going to add a mapping from [{}/{}] to [{}/{}] because one already exists", theMapping.getSourceCodeSystem(), theMapping.getSourceCode(), theMapping.getTargetCodeSystem(), theMapping.getTargetCode());
201                }
202        }
203
204        ValueSet getValueSet(String theValueSetId, String theValueSetUri, String theValueSetName, String theVersionPropertyName) {
205
206                String version;
207                String codeSystemVersion = myUploadProperties.getProperty(LOINC_CODESYSTEM_VERSION.getCode());
208                if (isNotBlank(theVersionPropertyName)) {
209                        if (codeSystemVersion != null) {
210                                version = myUploadProperties.getProperty(theVersionPropertyName) + "-" + codeSystemVersion;
211                        } else {
212                                version = myUploadProperties.getProperty(theVersionPropertyName);
213                        }
214                } else {
215                        version = codeSystemVersion;
216                }
217
218                ValueSet vs;
219                if (!myIdToValueSet.containsKey(theValueSetId)) {
220                        vs = new ValueSet();
221                        vs.setUrl(theValueSetUri);
222                        vs.setId(theValueSetId);
223                        vs.setVersion(version);
224                        vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
225                        vs.setPublisher(REGENSTRIEF_INSTITUTE_INC);
226                        vs.addContact()
227                                .setName(REGENSTRIEF_INSTITUTE_INC)
228                                .addTelecom()
229                                .setSystem(ContactPoint.ContactPointSystem.URL)
230                                .setValue(LOINC_WEBSITE_URL);
231                        vs.setCopyright(myLoincCopyrightStatement);
232                        myIdToValueSet.put(theValueSetId, vs);
233                        myValueSets.add(vs);
234                } else {
235                        vs = myIdToValueSet.get(theValueSetId);
236                }
237
238                if (isBlank(vs.getName()) && isNotBlank(theValueSetName)) {
239                        vs.setName(theValueSetName);
240                }
241
242                return vs;
243        }
244
245
246        static class ConceptMapping {
247
248                private String myCopyright;
249                private String myConceptMapId;
250                private String myConceptMapUri;
251                private String myConceptMapVersion;
252                private String myConceptMapName;
253                private String mySourceCodeSystem;
254                private String mySourceCodeSystemVersion;
255                private String mySourceCode;
256                private String mySourceDisplay;
257                private String myTargetCodeSystem;
258                private String myTargetCode;
259                private String myTargetDisplay;
260                private Enumerations.ConceptMapEquivalence myEquivalence;
261                private String myTargetCodeSystemVersion;
262
263                String getConceptMapId() {
264                        return myConceptMapId;
265                }
266
267                ConceptMapping setConceptMapId(String theConceptMapId) {
268                        myConceptMapId = theConceptMapId;
269                        return this;
270                }
271
272                String getConceptMapName() {
273                        return myConceptMapName;
274                }
275
276                ConceptMapping setConceptMapName(String theConceptMapName) {
277                        myConceptMapName = theConceptMapName;
278                        return this;
279                }
280
281                String getConceptMapUri() {
282                        return myConceptMapUri;
283                }
284
285                ConceptMapping setConceptMapUri(String theConceptMapUri) {
286                        myConceptMapUri = theConceptMapUri;
287                        return this;
288                }
289
290                String getConceptMapVersion() {
291                        return myConceptMapVersion;
292                }
293
294                ConceptMapping setConceptMapVersion(String theConceptMapVersion) {
295                        myConceptMapVersion = theConceptMapVersion;
296                        return this;
297                }
298
299                String getCopyright() {
300                        return myCopyright;
301                }
302
303                ConceptMapping setCopyright(String theCopyright) {
304                        myCopyright = theCopyright;
305                        return this;
306                }
307
308                Enumerations.ConceptMapEquivalence getEquivalence() {
309                        return myEquivalence;
310                }
311
312                ConceptMapping setEquivalence(Enumerations.ConceptMapEquivalence theEquivalence) {
313                        myEquivalence = theEquivalence;
314                        return this;
315                }
316
317                String getSourceCode() {
318                        return mySourceCode;
319                }
320
321                ConceptMapping setSourceCode(String theSourceCode) {
322                        mySourceCode = theSourceCode;
323                        return this;
324                }
325
326                String getSourceCodeSystem() {
327                        return mySourceCodeSystem;
328                }
329
330                ConceptMapping setSourceCodeSystem(String theSourceCodeSystem) {
331                        mySourceCodeSystem = theSourceCodeSystem;
332                        return this;
333                }
334
335                String getSourceCodeSystemVersion() {
336                        return mySourceCodeSystemVersion;
337                }
338
339                ConceptMapping setSourceCodeSystemVersion(String theSourceCodeSystemVersion) {
340                        mySourceCodeSystemVersion = theSourceCodeSystemVersion;
341                        return this;
342                }
343
344                String getSourceDisplay() {
345                        return mySourceDisplay;
346                }
347
348                ConceptMapping setSourceDisplay(String theSourceDisplay) {
349                        mySourceDisplay = theSourceDisplay;
350                        return this;
351                }
352
353                String getTargetCode() {
354                        return myTargetCode;
355                }
356
357                ConceptMapping setTargetCode(String theTargetCode) {
358                        myTargetCode = theTargetCode;
359                        return this;
360                }
361
362                String getTargetCodeSystem() {
363                        return myTargetCodeSystem;
364                }
365
366                ConceptMapping setTargetCodeSystem(String theTargetCodeSystem) {
367                        myTargetCodeSystem = theTargetCodeSystem;
368                        return this;
369                }
370
371                String getTargetCodeSystemVersion() {
372                        return myTargetCodeSystemVersion;
373                }
374
375                ConceptMapping setTargetCodeSystemVersion(String theTargetCodeSystemVersion) {
376                        myTargetCodeSystemVersion = theTargetCodeSystemVersion;
377                        return this;
378                }
379
380                String getTargetDisplay() {
381                        return myTargetDisplay;
382                }
383
384                ConceptMapping setTargetDisplay(String theTargetDisplay) {
385                        myTargetDisplay = theTargetDisplay;
386                        return this;
387                }
388
389        }
390}