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