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