refs/refs/ETL-German-FHIR-Core/src/main/java/org/miracum/etl/fhirtoomop/mapper/ConditionMapper.java

lines 921–988 1655 lines · java
1package org.miracum.etl.fhirtoomop.mapper;
3import static org.miracum.etl.fhirtoomop.Constants.CONCEPT_EHR;
4import static org.miracum.etl.fhirtoomop.Constants.CONCEPT_FINDING_SITE;
5import static org.miracum.etl.fhirtoomop.Constants.CONCEPT_SEVERITY;
6import static org.miracum.etl.fhirtoomop.Constants.CONCEPT_STAGE;
7import static org.miracum.etl.fhirtoomop.Constants.FHIR_RESOURCE_CONDITION_ACCEPTABLE_STATUS_LIST;
8import static org.miracum.etl.fhirtoomop.Constants.OMOP_DOMAIN_CONDITION;
9import static org.miracum.etl.fhirtoomop.Constants.OMOP_DOMAIN_MEASUREMENT;
10import static org.miracum.etl.fhirtoomop.Constants.OMOP_DOMAIN_OBSERVATION;
11import static org.miracum.etl.fhirtoomop.Constants.OMOP_DOMAIN_PROCEDURE;
12import static org.miracum.etl.fhirtoomop.Constants.SOURCE_VOCABULARY_ID_DIAGNOSTIC_CONFIDENCE;
13import static org.miracum.etl.fhirtoomop.Constants.SOURCE_VOCABULARY_ID_ICD_LOCALIZATION;
14import static org.miracum.etl.fhirtoomop.Constants.VOCABULARY_ICD10GM;
16import com.google.common.base.Strings;
17import io.micrometer.core.instrument.Counter;
18import java.sql.Timestamp;
19import java.time.LocalDate;
20import java.time.LocalDateTime;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.Collections;
24import java.util.List;
25import lombok.extern.slf4j.Slf4j;
26import org.apache.commons.lang3.tuple.Pair;
27import org.hl7.fhir.r4.model.Coding;
28import org.hl7.fhir.r4.model.Condition;
29import org.hl7.fhir.r4.model.Enumerations.ResourceType;
30import org.hl7.fhir.r4.model.StringType;
31import org.miracum.etl.fhirtoomop.DbMappings;
32import org.miracum.etl.fhirtoomop.config.FhirSystems;
33import org.miracum.etl.fhirtoomop.mapper.helpers.FindOmopConcepts;
34import org.miracum.etl.fhirtoomop.mapper.helpers.MapperMetrics;
35import org.miracum.etl.fhirtoomop.mapper.helpers.ResourceCheckDataAbsentReason;
36import org.miracum.etl.fhirtoomop.mapper.helpers.ResourceFhirReferenceUtils;
37import org.miracum.etl.fhirtoomop.mapper.helpers.ResourceOmopReferenceUtils;
38import org.miracum.etl.fhirtoomop.mapper.helpers.ResourceOnset;
39import org.miracum.etl.fhirtoomop.model.IcdSnomedDomainLookup;
40import org.miracum.etl.fhirtoomop.model.OmopModelWrapper;
41import org.miracum.etl.fhirtoomop.model.OrphaSnomedMapping;
42import org.miracum.etl.fhirtoomop.model.PostProcessMap;
43import org.miracum.etl.fhirtoomop.model.omop.Concept;
44import org.miracum.etl.fhirtoomop.model.omop.ConditionOccurrence;
45import org.miracum.etl.fhirtoomop.model.omop.Measurement;
46import org.miracum.etl.fhirtoomop.model.omop.OmopObservation;
47import org.miracum.etl.fhirtoomop.model.omop.ProcedureOccurrence;
48import org.miracum.etl.fhirtoomop.model.omop.SourceToConceptMap;
49import org.miracum.etl.fhirtoomop.repository.service.ConditionMapperServiceImpl;
50import org.miracum.etl.fhirtoomop.repository.service.OmopConceptServiceImpl;
51import org.springframework.beans.factory.annotation.Autowired;
52import org.springframework.lang.Nullable;
53import org.springframework.stereotype.Component;
55/**
56 * The ConditionMapper class describes the business logic of transforming a FHIR Condition resource
57 * to OMOP CDM.
58 *
59 * @author Elisa Henke
60 * @author Yuan Peng
61 */
62@Slf4j
63@Component
64public class ConditionMapper implements FhirMapper<Condition> {
66 private static final FhirSystems fhirSystems = new FhirSystems();
67 private final DbMappings dbMappings;
68 private final Boolean bulkload;
70 @Autowired OmopConceptServiceImpl omopConceptService;
71 @Autowired ResourceFhirReferenceUtils fhirReferenceUtils;
72 @Autowired ResourceOmopReferenceUtils omopReferenceUtils;
73 @Autowired ConditionMapperServiceImpl conditionService;
74 @Autowired ResourceCheckDataAbsentReason checkDataAbsentReason;
75 @Autowired FindOmopConcepts findOmopConcepts;
77 private static final Counter noStartDateCounter =
78 MapperMetrics.setNoStartDateCounter("stepProcessConditions");
79 private static final Counter noPersonIdCounter =
80 MapperMetrics.setNoPersonIdCounter("stepProcessConditions");
81 private static final Counter invalidCodeCounter =
82 MapperMetrics.setInvalidCodeCounter("stepProcessConditions");
83 private static final Counter noCodeCounter =
84 MapperMetrics.setNoCodeCounter("stepProcessConditions");
85 private static final Counter noFhirReferenceCounter =
86 MapperMetrics.setNoFhirReferenceCounter("stepProcessConditions");
87 private static final Counter deletedFhirReferenceCounter =
88 MapperMetrics.setDeletedFhirRessourceCounter("stepProcessConditions");
90 /**
91 * Constructor for objects of the class ConditionMapper.
92 *
93 * @param referenceUtils utilities for the identification of FHIR resource references
94 * @param bulkload parameter which indicates whether the Job should be run as bulk load or
95 * incremental load
96 * @param dbMappings collections for the intermediate storage of data from OMOP CDM in RAM
97 */
98 @Autowired
99 public ConditionMapper(Boolean bulkload, DbMappings dbMappings) {
101 this.bulkload = bulkload;
102 this.dbMappings = dbMappings;
105 /**
106 * Maps a FHIR Condition resource to several OMOP CDM tables.
108 * @param srcCondition FHIR Condition resource
109 * @param isDeleted a flag, whether the FHIR resource is deleted in the source
110 * @return OmopModelWrapper cache of newly created OMOP CDM records from the FHIR Condition
111 * resource
112 */
113 @Override
114 public OmopModelWrapper map(Condition srcCondition, boolean isDeleted) {
115 var wrapper = new OmopModelWrapper();
117 var conditionLogicId = fhirReferenceUtils.extractId(srcCondition);
118 var conditionSourceIdentifier = fhirReferenceUtils.extractResourceFirstIdentifier(srcCondition);
119 if (Strings.isNullOrEmpty(conditionLogicId)
120 && Strings.isNullOrEmpty(conditionSourceIdentifier)) {
121 log.warn("No [Identifier] or [Id] found. [Condition] resource is invalid. Skip resource");
122 noFhirReferenceCounter.increment();
123 return null;
126 String conditionId = "";
127 if (!Strings.isNullOrEmpty(conditionLogicId)) {
128 conditionId = srcCondition.getId();
131 if (bulkload.equals(Boolean.FALSE)) {
132 deleteExistingConditionEntry(conditionLogicId, conditionSourceIdentifier);
133 if (isDeleted) {
134 deleteExistingPostProcessMapEntry(conditionLogicId, conditionSourceIdentifier);
135 deletedFhirReferenceCounter.increment();
136 log.info("Found a deleted [Condition] resource {}. Deleting from OMOP DB.", conditionId);
137 return null;
139 updateExistingPostProcessMapEntry(conditionLogicId, conditionSourceIdentifier);
142 var verificationStatusValue = getVerificationStatusValue(srcCondition);
143 if (!Strings.isNullOrEmpty(verificationStatusValue)
144 && !FHIR_RESOURCE_CONDITION_ACCEPTABLE_STATUS_LIST.contains(verificationStatusValue)) {
145 log.error(
146 "The [verification status]: {} of {} is not acceptable for writing into OMOP CDM. Skip resource.",
147 verificationStatusValue,
148 conditionId);
149 return null;
152 var diagnoseCodingList = getDiagnoseCoding(srcCondition);
153 if (diagnoseCodingList.isEmpty()) {
154 log.warn("No [code] found for [Condition]: {}. Skip resource.", conditionId);
155 noCodeCounter.increment();
156 return null;
159 var personId = getPersonId(srcCondition, conditionLogicId, conditionId);
160 if (personId == null) {
161 log.warn("No matching [Person] found for [Condition]: {}. Skip resource", conditionId);
162 noPersonIdCounter.increment();
163 return null;
166 var visitOccId = getVisitOccId(srcCondition, conditionId, personId);
168 var diagnoseOnset = getConditionOnset(srcCondition);
169 if (diagnoseOnset.getStartDateTime() == null) {
170 log.warn("No [Date] found for [Condition]: {}. Skip resource", conditionId);
171 noStartDateCounter.increment();
172 return null;
174 var severityCoding = getSeverity(srcCondition);
175 var stageCoding = getStage(srcCondition);
177 if (diagnoseCodingList.size() == 1) {
178 var diagnoseCoding = diagnoseCodingList.get(0);
179 var icdBodyLocalizationConcepts =
180 getBodySiteLocalizationConcepts(
181 diagnoseCoding,
182 srcCondition,
183 diagnoseOnset.getStartDateTime().toLocalDate(),
184 conditionId);
185 var diagnosticConfidence = getDiagnosticConfidence(diagnoseCoding, conditionId);
186 var diagnosticConfidenceConcept = getDiagnosticConfidenceConcept(diagnosticConfidence);
187 setDiagnoseCodesUsingSingleCoding(
188 wrapper,
189 conditionLogicId,
190 conditionSourceIdentifier,
191 personId,
192 visitOccId,
193 diagnoseOnset,
194 diagnoseCoding,
195 icdBodyLocalizationConcepts,
196 severityCoding,
197 stageCoding,
198 diagnosticConfidenceConcept,
199 conditionId);
200 } else {
201 setDiagnoseCodesUsingMultipleCodings(
202 wrapper,
203 conditionLogicId,
204 conditionSourceIdentifier,
205 personId,
206 visitOccId,
207 diagnoseOnset,
208 diagnoseCodingList,
209 severityCoding,
210 stageCoding,
211 srcCondition,
212 conditionId);
215 return wrapper;
218 /**
219 * Extracts the verification status from the FHIR Condition resource.
221 * @param srcMedication FHIR Condition resource
222 * @return verification status from the FHIR Condition resource
223 */
224 private String getVerificationStatusValue(Condition srcCondition) {
225 var verificationStatusCoding = srcCondition.getVerificationStatus();
226 if (verificationStatusCoding == null) {
227 return null;
229 var verificationStatusValue =
230 verificationStatusCoding.getCoding().stream()
231 .filter(status -> fhirSystems.getVerificationStatus().contains(status.getSystem()))
232 .findFirst();
233 if (verificationStatusValue.isPresent()) {
234 return verificationStatusValue.get().getCode();
236 return null;
239 private void setDiagnoseCodesUsingMultipleCodings(
240 OmopModelWrapper wrapper,
241 String conditionLogicId,
242 String conditionSourceIdentifier,
243 Long personId,
244 Long visitOccId,
245 ResourceOnset diagnoseOnset,
246 List<Coding> diagnoseCodings,
247 Coding severityCoding,
248 Coding stageCoding,
249 Condition srcCondition,
250 String conditionId) {
252 Coding uncheckedIcdCoding = null;
253 Coding uncheckedSnomedCoding = null;
254 Coding uncheckedOrphaCoding = null;
255 Coding diagnosisCoding = null;
257 for (var uncheckedCoding : diagnoseCodings) {
258 var system = uncheckedCoding.getSystem();
259 if (fhirSystems.getOrpha().equals(system)) {
260 uncheckedOrphaCoding = uncheckedCoding;
262 if (fhirSystems.getIcd10gm().contains(system)) {
263 uncheckedIcdCoding = uncheckedCoding;
265 if (fhirSystems.getSnomed().equals(system)) {
266 uncheckedSnomedCoding = uncheckedCoding;
269 if (uncheckedIcdCoding == null
270 && uncheckedSnomedCoding == null
271 && uncheckedOrphaCoding == null) {
272 return;
274 var diagnosticConfidence = getDiagnosticConfidence(uncheckedIcdCoding, conditionId);
275 var diagnosticConfidenceConcept = getDiagnosticConfidenceConcept(diagnosticConfidence);
276 var icdBodyLocalizationConcepts =
277 getBodySiteLocalizationConcepts(
278 uncheckedIcdCoding,
279 srcCondition,
280 diagnoseOnset.getStartDateTime().toLocalDate(),
281 conditionId);
282 // Orpha
283 var orphaSnomedMapPairList =
284 getOrphaSnomedMap(
285 uncheckedOrphaCoding, diagnoseOnset.getStartDateTime().toLocalDate(), conditionId);
287 // SNOMED
288 var snomedConcept =
289 findOmopConcepts.getConcepts(
290 uncheckedSnomedCoding,
291 diagnoseOnset.getStartDateTime().toLocalDate(),
292 bulkload,
293 dbMappings,
294 conditionId);
296 // ICD
297 List<Coding> uncheckedIcdCodings =
298 splitDiagnoseCodes(uncheckedIcdCoding, uncheckedIcdCoding.getVersionElement());
299 var icdSnomedMapPairList =
300 getValidIcdCodes(
301 uncheckedIcdCodings,
302 diagnoseOnset.getStartDateTime().toLocalDate(),
303 conditionLogicId,
304 conditionId);
306 if (icdSnomedMapPairList.isEmpty()
307 && snomedConcept == null
308 && orphaSnomedMapPairList.isEmpty()) {
309 return;
310 } else if (!orphaSnomedMapPairList.isEmpty()) {
311 // Orpha
312 diagnosisCoding = uncheckedOrphaCoding;
313 } else if (!icdSnomedMapPairList.isEmpty()) {
314 // ICD
315 diagnosisCoding = uncheckedIcdCoding;
316 } else if (snomedConcept != null) {
317 // SNOMED
318 diagnosisCoding = uncheckedSnomedCoding;
320 setDiagnoseCodesUsingSingleCoding(
321 wrapper,
322 conditionLogicId,
323 conditionSourceIdentifier,
324 personId,
325 visitOccId,
326 diagnoseOnset,
327 diagnosisCoding,
328 icdBodyLocalizationConcepts,
329 severityCoding,
330 stageCoding,
331 diagnosticConfidenceConcept,
332 conditionId);
335 private void setDiagnoseCodesUsingSingleCoding(
336 OmopModelWrapper wrapper,
337 String conditionLogicId,
338 String conditionSourceIdentifier,
339 Long personId,
340 Long visitOccId,
341 ResourceOnset diagnoseOnset,
342 Coding diagnoseCoding,
343 Pair<String, Integer> icdBodyLocalizationConcepts,
344 Coding severityCoding,
345 Coding stageCoding,
346 SourceToConceptMap diagnosticConfidenceConcept,
347 String conditionId) {
349 List<Coding> uncheckedDiagnoseCodes =
350 splitDiagnoseCodes(diagnoseCoding, diagnoseCoding.getVersionElement());
351 List<Pair<String, List<IcdSnomedDomainLookup>>> icdSnomedMapPairList = null;
352 List<Pair<String, List<OrphaSnomedMapping>>> orphaSnomedMapPairList = null;
353 Concept snomedConcept = null;
355 if (fhirSystems.getIcd10gm().contains(diagnoseCoding.getSystem())) {
356 // for ICD codes
358 icdSnomedMapPairList =
359 getValidIcdCodes(
360 uncheckedDiagnoseCodes,
361 diagnoseOnset.getStartDateTime().toLocalDate(),
362 conditionLogicId,
363 conditionId);
365 if (icdSnomedMapPairList.isEmpty()) {
366 return;
368 for (var singlePair : icdSnomedMapPairList) {
369 icdProcessor(
370 singlePair,
371 null,
372 null,
373 wrapper,
374 diagnoseOnset,
375 diagnosticConfidenceConcept,
376 conditionLogicId,
377 conditionSourceIdentifier,
378 personId,
379 visitOccId);
382 if (icdSnomedMapPairList.size() == 2) {
383 var icdPairs =
384 setIcdPairs(icdSnomedMapPairList, conditionLogicId, conditionSourceIdentifier);
385 wrapper.setPostProcessMap(icdPairs);
387 } else if (fhirSystems.getSnomed().equals(diagnoseCoding.getSystem())) {
388 // for Snomed codes
390 snomedConcept =
391 findOmopConcepts.getConcepts(
392 diagnoseCoding,
393 diagnoseOnset.getStartDateTime().toLocalDate(),
394 bulkload,
395 dbMappings,
396 conditionId);
398 if (snomedConcept == null) {
399 return;
402 icdProcessor(
403 null,
404 snomedConcept,
405 null,
406 wrapper,
407 diagnoseOnset,
408 diagnosticConfidenceConcept,
409 conditionLogicId,
410 conditionSourceIdentifier,
411 personId,
412 visitOccId);
413 } else if (fhirSystems.getOrpha().equals(diagnoseCoding.getSystem())) {
414 // for Orpha codes
416 orphaSnomedMapPairList =
417 getOrphaSnomedMap(
418 diagnoseCoding, diagnoseOnset.getStartDateTime().toLocalDate(), conditionId);
420 if (orphaSnomedMapPairList.isEmpty()) {
421 return;
423 for (var orphaSnomedPair : orphaSnomedMapPairList) {
424 icdProcessor(
425 null,
426 null,
427 orphaSnomedPair,
428 wrapper,
429 diagnoseOnset,
430 diagnosticConfidenceConcept,
431 conditionLogicId,
432 conditionSourceIdentifier,
433 personId,
434 visitOccId);
437 } else {
438 return;
441 setBodySiteLocalization(
442 wrapper,
443 icdSnomedMapPairList,
444 snomedConcept,
445 conditionLogicId,
446 conditionSourceIdentifier,
447 personId,
448 visitOccId,
449 diagnoseOnset,
450 icdBodyLocalizationConcepts);
452 setDiagnoseMetaInfo(
453 wrapper,
454 icdSnomedMapPairList,
455 snomedConcept,
456 conditionLogicId,
457 conditionSourceIdentifier,
458 personId,
459 visitOccId,
460 diagnoseOnset,
461 severityCoding,
462 "severity",
463 conditionId);
465 setDiagnoseMetaInfo(
466 wrapper,
467 icdSnomedMapPairList,
468 snomedConcept,
469 conditionLogicId,
470 conditionSourceIdentifier,
471 personId,
472 visitOccId,
473 diagnoseOnset,
474 stageCoding,
475 "stage",
476 conditionId);
479 /**
480 * Create new entry in Observation for bodySite or site localization of a diagnosis, and create
481 * new entry in POST_PROCESS_MAP for referencing later.
483 * @param wrapper the OMOP model wrapper
484 * @param icdSnomedMapList a List of pairs of ICD-Snomed mapping
485 * @param omopConcept extracted Concept from OMOP
486 * @param conditionLogicId logical id of the FHIR Condition resource
487 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
488 * @param personId person_id of the referenced FHIR Patient resource
489 * @param visitOccId the visit_occurrence_id of the referenced FHIR Encounter resource from
490 * VISIT_OCCURRENCE table in OMOP CDM
491 * @param diagnoseOnset start date time and end date time of the FHIR Condition resource
492 * @param icdBodyLocalizationConcept a pair of the body site or site localization name and OMOP
493 * concept_id
494 */
495 private void setBodySiteLocalization(
496 OmopModelWrapper wrapper,
497 @Nullable List<Pair<String, List<IcdSnomedDomainLookup>>> icdSnomedMapList,
498 @Nullable Concept omopConcept,
499 String conditionLogicId,
500 String conditionSourceIdentifier,
501 Long personId,
502 Long visitOccId,
503 ResourceOnset diagnoseOnset,
504 Pair<String, Integer> icdBodyLocalizationConcept) {
505 var siteLocalization =
506 createSiteLocalization(
507 icdBodyLocalizationConcept,
508 conditionLogicId,
509 conditionSourceIdentifier,
510 personId,
511 visitOccId,
512 diagnoseOnset);
513 if (siteLocalization != null) {
514 wrapper.getObservation().add(siteLocalization);
516 var icdSiteLocalization =
517 setBodySiteLocalizationReference(
518 icdSnomedMapList,
519 omopConcept,
520 personId,
521 siteLocalization,
522 conditionLogicId,
523 conditionSourceIdentifier);
524 if (icdSiteLocalization != null) {
525 wrapper.getPostProcessMap().add(icdSiteLocalization);
530 /**
531 * Create new entry in Observation for severity of a diagnosis, and create new entry in
532 * POST_PROCESS_MAP for referencing later.
534 * @param wrapper the OMOP model wrapper
535 * @param icdSnomedMapList a List of pairs of ICD-Snomed mapping
536 * @param omopConcept extracted Concept from OMOP
537 * @param conditionLogicId logical id of the FHIR Condition resource
538 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
539 * @param personId person_id of the referenced FHIR Patient resource
540 * @param visitOccId the visit_occurrence_id of the referenced FHIR Encounter resource from
541 * VISIT_OCCURRENCE table in OMOP CDM
542 * @param diagnoseOnset start date time and end date time of the FHIR Condition resource
543 * @param severityCoding coding of severity information from FHIR Condition resource
544 */
545 private void setDiagnoseMetaInfo(
546 OmopModelWrapper wrapper,
547 @Nullable List<Pair<String, List<IcdSnomedDomainLookup>>> icdSnomedMapList,
548 @Nullable Concept omopConcept,
549 String conditionLogicId,
550 String conditionSourceIdentifier,
551 Long personId,
552 Long visitOccId,
553 ResourceOnset diagnoseOnset,
554 Coding diagnoseMetaInfoCoding,
555 String metaInfoType,
556 String conditionId) {
557 if (diagnoseMetaInfoCoding == null) {
558 return;
560 var diagnoseMetaInfoConcept =
561 findOmopConcepts.getConcepts(
562 diagnoseMetaInfoCoding,
563 diagnoseOnset.getStartDateTime().toLocalDate(),
564 bulkload,
565 dbMappings,
566 conditionId);
567 var diagnoseMetaInfo =
568 createDiagnoseMetaInfo(
569 diagnoseMetaInfoConcept,
570 conditionLogicId,
571 conditionSourceIdentifier,
572 personId,
573 visitOccId,
574 diagnoseOnset,
575 metaInfoType);
576 if (diagnoseMetaInfo == null) {
577 return;
579 wrapper.getObservation().add(diagnoseMetaInfo);
581 var diagnoseMetaInfoReference =
582 setDiagnoseMetaInfoReference(
583 icdSnomedMapList,
584 omopConcept,
585 personId,
586 diagnoseMetaInfo,
587 conditionLogicId,
588 conditionSourceIdentifier,
589 metaInfoType);
590 if (diagnoseMetaInfoReference == null) {
591 return;
593 wrapper.getPostProcessMap().add(diagnoseMetaInfoReference);
596 /**
597 * Extract valid pairs of ICD code and its OMOP concept_id and domain information as a list
599 * @param uncheckedIcds unchecked ICD codes
600 * @param diagnoseDate the start date of diagnose
601 * @param conditionLogicId logical id of the FHIR Condition resource
602 * @return a list of valid pairs of ICD code and its OMOP concept_id and domain information
603 */
604 private List<Pair<String, List<IcdSnomedDomainLookup>>> getValidIcdCodes(
605 List<Coding> uncheckedIcds,
606 LocalDate diagnoseDate,
607 String conditionLogicId,
608 String conditionId) {
609 if (uncheckedIcds.isEmpty()) {
610 return Collections.emptyList();
613 List<Pair<String, List<IcdSnomedDomainLookup>>> validIcdSnomedConceptMaps = new ArrayList<>();
614 for (var uncheckedCode : uncheckedIcds) {
615 String icdCode = uncheckedCode.getCode();
616 if (icdCode == null) {
617 return Collections.emptyList();
620 List<IcdSnomedDomainLookup> icdSnomedMap =
621 findOmopConcepts.getIcdSnomedConcepts(
622 uncheckedCode, diagnoseDate, bulkload, dbMappings, conditionLogicId);
623 if (icdSnomedMap.isEmpty()) {
624 log.warn(
625 "ICD Code [{}] in [Condition] {} is not valid in OMOP.",
626 uncheckedCode.getCode(),
627 conditionId);
628 return Collections.emptyList();
631 validIcdSnomedConceptMaps.add(Pair.of(uncheckedCode.getCode(), icdSnomedMap));
633 return validIcdSnomedConceptMaps;
636 /**
637 * Extract valid pairs of Orpha code and its SNOMED concept_id and domain information as a list
639 * @param orphaCoding
640 * @param diagnoseDate the start date of diagnose
641 * @param conditionId logical id of the FHIR Condition resource
642 * @return a list of valid pairs of Orpha code and its SNOMED concept_id and domain information
643 */
644 private List<Pair<String, List<OrphaSnomedMapping>>> getOrphaSnomedMap(
645 Coding orphaCoding, LocalDate diagnoseDate, String conditionId) {
646 if (orphaCoding.isEmpty()) {
647 return Collections.emptyList();
650 List<Pair<String, List<OrphaSnomedMapping>>> validOrphaSnomedConceptMaps = new ArrayList<>();
651 List<OrphaSnomedMapping> orphaSnomedMap =
652 findOmopConcepts.getOrphaSnomedConcepts(
653 orphaCoding, diagnoseDate, bulkload, dbMappings, conditionId);
654 if (orphaSnomedMap.isEmpty()) {
655 return Collections.emptyList();
658 validOrphaSnomedConceptMaps.add(Pair.of(orphaCoding.getCode(), orphaSnomedMap));
660 return validOrphaSnomedConceptMaps;
663 /**
664 * Extracts information of diagnostic codes from the FHIR Condition resource.
666 * @param srcCondition FHIR Condition resource
667 * @return Coding part of the FHIR Condition resource, which contains diagnostic codes
668 */
669 private List<Coding> getDiagnoseCoding(Condition srcCondition) {
670 var diagnoseCodeableConcept = checkDataAbsentReason.getValue(srcCondition.getCode());
671 if (diagnoseCodeableConcept == null) {
672 return Collections.emptyList();
674 var diagnoseCodings = diagnoseCodeableConcept.getCoding();
675 if (diagnoseCodings.isEmpty()) {
676 return Collections.emptyList();
679 List<Coding> diagnoseCodingList = new ArrayList<>();
681 for (var diagnoseCoding : diagnoseCodings) {
683 if (fhirSystems.getDiagnoseCode().contains(diagnoseCoding.getSystem())) {
684 diagnoseCodingList.add(diagnoseCoding);
687 return diagnoseCodingList;
690 /**
691 * Returns the person_id of the referenced FHIR Patient resource for the processed FHIR Condition
692 * resource.
694 * @param srcCondition FHIR Condition resource
695 * @param conditionLogicId logical id of the FHIR Condition resource
696 * @return person_id of the referenced FHIR Patient resource from person table in OMOP CDM
697 */
698 private Long getPersonId(Condition srcCondition, String conditionLogicId, String conditionId) {
699 var patientReferenceIdentifier = fhirReferenceUtils.getSubjectReferenceIdentifier(srcCondition);
700 var patientReferenceLogicalId = fhirReferenceUtils.getSubjectReferenceLogicalId(srcCondition);
702 return omopReferenceUtils.getPersonId(
703 patientReferenceIdentifier, patientReferenceLogicalId, conditionLogicId, conditionId);
706 /**
707 * Returns the visit_occurrence_id of the referenced FHIR Encounter resource for the processed
708 * FHIR Condition resource.
710 * @param srcCondition FHIR Condition resource
711 * @param conditionId logical id of the FHIR Condition resource
712 * @param personId person_id of the referenced FHIR Patient resource
713 * @return visit_occurrence_id of the referenced FHIR Encounter resource from visit_occurrence
714 * table in OMOP CDM
715 */
716 private Long getVisitOccId(Condition srcCondition, String conditionId, Long personId) {
717 var encounterReferenceIdentifier =
718 fhirReferenceUtils.getEncounterReferenceIdentifier(srcCondition);
719 var encounterReferenceLogicalId =
720 fhirReferenceUtils.getEncounterReferenceLogicalId(srcCondition);
721 var visitOccId =
722 omopReferenceUtils.getVisitOccId(
723 encounterReferenceIdentifier, encounterReferenceLogicalId, personId, conditionId);
724 if (visitOccId == null) {
725 log.debug("No matching [Encounter] found for [Condition]: {}.", conditionId);
728 return visitOccId;
731 /**
732 * Extracts date time information from the FHIR Condition resource.
734 * @param srcCondition FHIR Condition resource
735 * @return start date time and end date time of the FHIR Condition resource
736 */
737 private ResourceOnset getConditionOnset(Condition srcCondition) {
738 var resourceOnset = new ResourceOnset();
740 if (srcCondition.hasOnsetDateTimeType()) {
741 var onsetDateTimeType = srcCondition.getOnsetDateTimeType();
742 if (!onsetDateTimeType.isEmpty()) {
743 resourceOnset.setStartDateTime(
744 new Timestamp(onsetDateTimeType.getValue().getTime()).toLocalDateTime());
745 return resourceOnset;
749 if (srcCondition.hasOnsetPeriod()) {
750 var onsetPeriod = srcCondition.getOnsetPeriod();
751 if (!onsetPeriod.isEmpty()) {
752 if (onsetPeriod.getStart() != null) {
753 resourceOnset.setStartDateTime(
754 new Timestamp(onsetPeriod.getStart().getTime()).toLocalDateTime());
756 if (onsetPeriod.getEnd() != null) {
757 resourceOnset.setEndDateTime(
758 new Timestamp(onsetPeriod.getEnd().getTime()).toLocalDateTime());
760 return resourceOnset;
764 var recordedDateElement = srcCondition.getRecordedDateElement();
765 if (!recordedDateElement.isEmpty()) {
766 var recordedDate = checkDataAbsentReason.getValue(recordedDateElement);
767 if (recordedDate != null) {
768 resourceOnset.setStartDateTime(recordedDate);
769 return resourceOnset;
772 return resourceOnset;
775 /**
776 * Separates primary and secondary ICD codes.
778 * @param icdSourceCodes string which contains both primary and secondary ICD codes
779 * @return list of primary and secondary ICD codes as FHIR Coding-Type
780 */
781 private List<Coding> splitDiagnoseCodes(Coding diagnoseSourceCoding, StringType version) {
782 List<Coding> uncheckedDiagnoseCodes = new ArrayList<>();
783 var vocabularyId = findOmopConcepts.getOmopVocabularyId(diagnoseSourceCoding.getSystem());
784 if (vocabularyId.equals(VOCABULARY_ICD10GM) && diagnoseSourceCoding.getCode() != null) {
785 var sourceCodes = diagnoseSourceCoding.getCode().strip();
787 if (sourceCodes.contains(" ")) {
788 var codeArr = Arrays.asList(sourceCodes.split(" ", 2));
789 for (var code : codeArr) {
790 var element =
791 new Coding()
792 .setCode(code)
793 .setSystem(diagnoseSourceCoding.getSystem())
794 .setVersionElement(version)
795 .setExtension(diagnoseSourceCoding.getExtension());
796 var singleCoding = element.castToCoding(element);
797 uncheckedDiagnoseCodes.add(singleCoding);
800 } else {
801 uncheckedDiagnoseCodes.add(diagnoseSourceCoding);
803 } else {
804 uncheckedDiagnoseCodes.add(diagnoseSourceCoding);
806 return uncheckedDiagnoseCodes;
809 /**
810 * Processes information from FHIR Condition resource and transforms them into records OMOP CDM
811 * tables.
813 * @param singlePair one pair of ICD code and its OMOP concept_id and domain information
814 * @param omopConcept extracted Concept from OMOP
815 * @param orphaSnomedPair one pair of Orpha code and its SNOMED concept_id and domain information
816 * @param wrapper the OMOP model wrapper
817 * @param diagnoseOnset start date time and end date time of the FHIR Condition resource
818 * @param diagnosticConfidenceConcept
819 * @param conditionLogicId logical id of the FHIR Condition resource
820 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
821 * @param personId person_id of the referenced FHIR Patient resource
822 * @param visiOccId visit_occurrence_id of the referenced FHIR Encounter resource
823 */
824 private void icdProcessor(
825 @Nullable Pair<String, List<IcdSnomedDomainLookup>> singlePair,
826 @Nullable Concept omopConcept,
827 @Nullable Pair<String, List<OrphaSnomedMapping>> orphaSnomedPair,
828 OmopModelWrapper wrapper,
829 ResourceOnset diagnoseOnset,
830 SourceToConceptMap diagnosticConfidenceConcept,
831 String conditionLogicId,
832 String conditionSourceIdentifier,
833 Long personId,
834 Long visiOccId) {
836 if (singlePair == null && omopConcept == null && orphaSnomedPair == null) {
837 return;
840 if (orphaSnomedPair != null) {
841 var orphaCode = orphaSnomedPair.getLeft();
842 var orphaSnomedMaps = orphaSnomedPair.getRight();
844 for (var orphaSnomedMap : orphaSnomedMaps) {
845 var domain = orphaSnomedMap.getSnomedDomainId();
846 setDiagnoses(
847 diagnosticConfidenceConcept,
848 wrapper,
849 diagnoseOnset,
850 conditionLogicId,
851 conditionSourceIdentifier,
852 personId,
853 visiOccId,
854 orphaCode,
855 orphaSnomedMap.getSnomedConceptId(),
856 orphaSnomedMap.getOrphaConceptId(),
857 domain);
860 } else if (singlePair != null) {
861 var rawIcdCode = singlePair.getLeft();
862 var icdSnomedMaps = singlePair.getRight();
864 for (var icdCodeCheck : icdSnomedMaps) {
865 var domain = icdCodeCheck.getSnomedDomainId();
866 setDiagnoses(
867 diagnosticConfidenceConcept,
868 wrapper,
869 diagnoseOnset,
870 conditionLogicId,
871 conditionSourceIdentifier,
872 personId,
873 visiOccId,
874 rawIcdCode,
875 icdCodeCheck.getSnomedConceptId(),
876 icdCodeCheck.getIcdGmConceptId(),
877 domain);
879 } else {
880 setDiagnoses(
881 diagnosticConfidenceConcept,
882 wrapper,
883 diagnoseOnset,
884 conditionLogicId,
885 conditionSourceIdentifier,
886 personId,
887 visiOccId,
888 omopConcept.getConceptCode(),
889 omopConcept.getConceptId(),
890 omopConcept.getConceptId(),
891 omopConcept.getDomainId());
895 /**
896 * Write diagnosis information into corrected OMOP tables based on their domains.
898 * @param diagnosticConfidenceConcept OMOP concept for diagnostic confidence
899 * @param wrapper the OMOP model wrapper
900 * @param diagnoseOnset start date time and end date time of the FHIR Condition resource
901 * @param conditionLogicId logical id of the FHIR Condition resource
902 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
903 * @param personId person_id of the referenced FHIR Patient resource
904 * @param visiOccId visit_occurrence_id of the referenced FHIR Encounter resource
905 * @param diagnosisCode diagnosis code
906 * @param diagnoseConceptId the OMOP concept_id of diagnosis Code as concept_id
907 * @param diagnoseSourceConceptId the OMOP concept_id of diagnosis Code as source_concept_id
908 * @param domain the OMOP domain of the diagnosis code
909 */
910 private void setDiagnoses(
911 SourceToConceptMap diagnosticConfidenceConcept,
912 OmopModelWrapper wrapper,
913 ResourceOnset diagnoseOnset,
914 String conditionLogicId,
915 String conditionSourceIdentifier,
916 Long personId,
917 Long visiOccId,
918 String diagnosisCode,
919 Integer diagnoseConceptId,
920 Integer diagnoseSourceConceptId,
921 String domain) {
922 switch (domain) {
923 case OMOP_DOMAIN_CONDITION:
924 var condition =
925 setUpCondition(
926 diagnoseOnset,
927 diagnoseConceptId,
928 diagnoseSourceConceptId,
929 diagnosisCode,
930 diagnosticConfidenceConcept,
931 personId,
932 visiOccId,
933 conditionLogicId,
934 conditionSourceIdentifier);
936 wrapper.getConditionOccurrence().add(condition);
938 break;
939 case OMOP_DOMAIN_OBSERVATION:
940 var observation =
941 setUpObservation(
942 diagnoseOnset.getStartDateTime(),
943 diagnoseConceptId,
944 diagnoseSourceConceptId,
945 diagnosisCode,
946 diagnosticConfidenceConcept,
947 personId,
948 visiOccId,
949 conditionLogicId,
950 conditionSourceIdentifier);
952 wrapper.getObservation().add(observation);
954 break;
955 case OMOP_DOMAIN_PROCEDURE:
956 var procedure =
957 setUpProcedure(
958 diagnoseOnset.getStartDateTime(),
959 diagnoseConceptId,
960 diagnoseSourceConceptId,
961 diagnosisCode,
962 diagnosticConfidenceConcept,
963 personId,
964 visiOccId,
965 conditionLogicId,
966 conditionSourceIdentifier);
968 wrapper.getProcedureOccurrence().add(procedure);
970 break;
971 case OMOP_DOMAIN_MEASUREMENT:
972 var measurement =
973 setUpMeasurement(
974 diagnoseOnset.getStartDateTime(),
975 diagnoseConceptId,
976 diagnoseSourceConceptId,
977 diagnosisCode,
978 diagnosticConfidenceConcept,
979 personId,
980 visiOccId,
981 conditionLogicId,
982 conditionSourceIdentifier);
984 wrapper.getMeasurement().add(measurement);
986 break;
987 default:
988 throw new UnsupportedOperationException(String.format("Unsupported domain %s", domain));
992 /**
993 * Extracts diagnostic confidence information from FHIR Condition resource.
995 * @param icdCoding part of the FHIR Condition resource, which contains ICD codes
996 * @param conditionId logical id of the FHIR Condition resource
997 * @return diagnostic confidence from FHIR Condition resource
998 */
999 private Coding getDiagnosticConfidence(Coding icdCoding, String conditionId) {
1000 if (!icdCoding.hasExtension(fhirSystems.getDiagnosticConfidence())) {
1001 log.debug("No [Diagnostic confidence] found for [Condition] {}.", conditionId);
1002 return null;
1005 var diagnosticConfidenceType =
1006 icdCoding.getExtensionByUrl(fhirSystems.getDiagnosticConfidence()).getValue();
1007 var diagnosticConfidence = diagnosticConfidenceType.castToCoding(diagnosticConfidenceType);
1008 if (!diagnosticConfidence.hasCode() || Strings.isNullOrEmpty(diagnosticConfidence.getCode())) {
1009 log.debug("No [Diagnostic confidence] found for [Condition] {}.", conditionId);
1010 return null;
1012 return diagnosticConfidence;
1015 /**
1016 * Deletes FHIR Condition resources from OMOP CDM tables using fhir_logical_id and fhir_identifier
1018 * @param conditionLogicId logical id of the FHIR Condition resource
1019 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
1020 */
1021 private void deleteExistingConditionEntry(
1022 String conditionLogicId, String conditionSourceIdentifier) {
1023 if (!Strings.isNullOrEmpty(conditionLogicId)) {
1024 conditionService.deleteExistingConditionsByFhirLogicalId(conditionLogicId);
1025 } else {
1026 conditionService.deleteExistingConditionsByFhirIdentifier(conditionSourceIdentifier);
1030 /**
1031 * Deletes rank and use information from post_process_map table using fhir_logical_id and
1032 * fhir_identifier
1034 * @param conditionLogicId logical id of the FHIR Condition resource
1035 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
1036 */
1037 private void deleteExistingPostProcessMapEntry(
1038 String conditionLogicId, String conditionSourceIdentifier) {
1039 if (!Strings.isNullOrEmpty(conditionLogicId)) {
1040 conditionService.deleteExistingPpmEntriesByFhirLogicalId(conditionLogicId);
1041 } else {
1042 conditionService.deleteExistingPpmEntriesByFhirIdentifier(conditionSourceIdentifier);
1046 /**
1047 * Updates flag for rank and use information in post_process_map table using fhir_logical_id and
1048 * fhir_identifier
1050 * @param conditionLogicId logical id of the FHIR Condition resource
1051 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
1052 */
1053 private void updateExistingPostProcessMapEntry(
1054 String conditionLogicId, String conditionSourceIdentifier) {
1055 if (!Strings.isNullOrEmpty(conditionLogicId)) {
1056 conditionService.updateExistingPpmEntriesByFhirLogicalId(conditionLogicId);
1057 } else {
1058 conditionService.updateExistingPpmEntriesByFhirIdentifier(conditionSourceIdentifier);
1062 /**
1063 * Creates a new record of the condition_occurrence table in OMOP CDM for the processed FHIR
1064 * Condition resource. The extracted ICD code of the FHIR Condition resource belongs to the
1065 * Condition domain in OMOP CDM.
1067 * @param diagnoseOnset start date time and end date time of the FHIR Condition resource
1068 * @param diagnoseConceptId the OMOP concept_id of diagnose Code as concept_id
1069 * @param diagnoseSourceConceptId the OMOP concept_id of diagnose Code as source_concept_id
1070 * @param rawIcdCode ICD code with special characters
1071 * @param diagnosticConfidenceConcept the OMOP concept of diagnostic confidence
1072 * @param personId person_id of the referenced FHIR Patient resource
1073 * @param visitOccId visit_occurrence_id of the referenced FHIR Encounter resource
1074 * @param conditionLogicId logical id of the FHIR Condition resource
1075 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
1076 * @return new record of the condition_occurrence table in OMOP CDM for the processed FHIR
1077 * Condition resource
1078 */
1079 private ConditionOccurrence setUpCondition(
1080 ResourceOnset diagnoseOnset,
1081 Integer diagnoseConceptId,
1082 Integer diagnoseSourceConceptId,
1083 String rawIcdCode,
1084 SourceToConceptMap diagnosticConfidenceConcept,
1085 Long personId,
1086 Long visitOccId,
1087 String conditionLogicId,
1088 String conditionSourceIdentifier) {
1089 var startDatetime = diagnoseOnset.getStartDateTime();
1090 var endDatetime = diagnoseOnset.getEndDateTime();
1092 var newConditionOcc =
1093 ConditionOccurrence.builder()
1094 .personId(personId)
1095 .conditionStartDate(startDatetime.toLocalDate())
1096 .conditionStartDatetime(startDatetime)
1097 .conditionEndDatetime(endDatetime)
1098 .conditionEndDate(endDatetime != null ? endDatetime.toLocalDate() : null)
1099 .visitOccurrenceId(visitOccId)
1100 .conditionSourceConceptId(diagnoseSourceConceptId)
1101 .conditionConceptId(diagnoseConceptId)
1102 .conditionTypeConceptId(CONCEPT_EHR)
1103 .conditionSourceValue(rawIcdCode)
1104 .fhirLogicalId(conditionLogicId)
1105 .fhirIdentifier(conditionSourceIdentifier)
1106 .build();
1108 if (diagnosticConfidenceConcept != null
1109 && diagnosticConfidenceConcept.getTargetConceptId() != null) {
1111 newConditionOcc.setConditionStatusSourceValue(diagnosticConfidenceConcept.getSourceCode());
1112 newConditionOcc.setConditionStatusConceptId(diagnosticConfidenceConcept.getTargetConceptId());
1115 return newConditionOcc;
1118 /**
1119 * Find the concept_id of diagnostic confidence.
1121 * @param diagnosticConfidenceCoding Diagnostic confidence of Condition FHIR Resource
1122 * @return a entry of SOURCE_TO_CONCEPT_MAP for diagnostic confidence
1123 */
1124 private SourceToConceptMap getDiagnosticConfidenceConcept(Coding diagnosticConfidenceCoding) {
1125 if (diagnosticConfidenceCoding == null) {
1126 return null;
1129 return findOmopConcepts.getCustomConcepts(
1130 diagnosticConfidenceCoding.getCode(),
1131 SOURCE_VOCABULARY_ID_DIAGNOSTIC_CONFIDENCE,
1132 dbMappings);
1135 /**
1136 * Creates a new record of the observation table in OMOP CDM for the processed FHIR Condition
1137 * resource. The extracted ICD code of the FHIR Condition resource belongs to the Observation
1138 * domain in OMOP CDM.
1140 * @param startDatetime start date time of the FHIR Condition resource
1141 * @param diagnoseConceptId the OMOP concept_id of diagnose Code as concept_id
1142 * @param diagnoseSourceConceptId the OMOP concept_id of diagnose Code as source_concept_id
1143 * @param rawIcdCode ICD code with special characters
1144 * @param diagnosticConfidenceConcept the OMOP concept of diagnostic confidence
1145 * @param personId person_id of the referenced FHIR Patient resource
1146 * @param visitOccId visit_occurrence_id of the referenced FHIR Encounter resource
1147 * @param conditionLogicId logical id of the FHIR Condition resource
1148 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
1149 * @return new record of the observation table in OMOP CDM for the processed FHIR Condition
1150 * resource
1151 */
1152 private OmopObservation setUpObservation(
1153 LocalDateTime startDatetime,
1154 Integer diagnoseConceptId,
1155 Integer diagnoseSourceConceptId,
1156 String rawIcdCode,
1157 SourceToConceptMap diagnosticConfidenceConcept,
1158 Long personId,
1159 Long visitOccId,
1160 String conditionLogicId,
1161 String conditionSourceIdentifier) {
1163 var newObservation =
1164 OmopObservation.builder()
1165 .personId(personId)
1166 .observationDate(startDatetime.toLocalDate())
1167 .observationDatetime(startDatetime)
1168 .visitOccurrenceId(visitOccId)
1169 .observationSourceConceptId(diagnoseSourceConceptId)
1170 .observationConceptId(diagnoseConceptId)
1171 .observationTypeConceptId(CONCEPT_EHR)
1172 .observationSourceValue(rawIcdCode)
1173 .fhirLogicalId(conditionLogicId)
1174 .fhirIdentifier(conditionSourceIdentifier)
1175 .build();
1177 if (diagnosticConfidenceConcept != null) {
1179 newObservation.setQualifierSourceValue(diagnosticConfidenceConcept.getSourceCode());
1181 newObservation.setQualifierConceptId(diagnosticConfidenceConcept.getTargetConceptId());
1184 return newObservation;
1187 /**
1188 * Creates a new record of the procedure_occurrence table in OMOP CDM for the processed FHIR
1189 * Condition resource. The extracted ICD code of the FHIR Condition resource belongs to the
1190 * Procedure domain in OMOP CDM.
1192 * @param startDatetime start date time of the FHIR Condition resource
1193 * @param diagnoseConceptId the OMOP concept_id of diagnose Code as concept_id
1194 * @param diagnoseSourceConceptId the OMOP concept_id of diagnose Code as source_concept_id
1195 * @param rawIcdCode ICD code with special characters
1196 * @param diagnosticConfidenceConcept the OMOP concept of diagnostic confidence
1197 * @param personId person_id of the referenced FHIR Patient resource
1198 * @param visitOccId visit_occurrence_id of the referenced FHIR Encounter resource
1199 * @param conditionLogicId logical id of the FHIR Condition resource
1200 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
1201 * @return new record of the procedure_occurrence table in OMOP CDM for the processed FHIR
1202 * Condition resource
1203 */
1204 private ProcedureOccurrence setUpProcedure(
1205 LocalDateTime startDatetime,
1206 Integer diagnoseConceptId,
1207 Integer diagnoseSourceConceptId,
1208 String rawIcdCode,
1209 SourceToConceptMap diagnosticConfidenceConcept,
1210 Long personId,
1211 Long visitOccId,
1212 String conditionLogicId,
1213 String conditionSourceIdentifier) {
1215 var newProcedureOcc =
1216 ProcedureOccurrence.builder()
1217 .personId(personId)
1218 .procedureDate(startDatetime.toLocalDate())
1219 .procedureDatetime(startDatetime)
1220 .visitOccurrenceId(visitOccId)
1221 .procedureSourceConceptId(diagnoseSourceConceptId)
1222 .procedureConceptId(diagnoseConceptId)
1223 .procedureTypeConceptId(CONCEPT_EHR)
1224 .procedureSourceValue(rawIcdCode)
1225 .fhirLogicalId(conditionLogicId)
1226 .fhirIdentifier(conditionSourceIdentifier)
1227 .build();
1229 if (diagnosticConfidenceConcept != null) {
1230 newProcedureOcc.setModifierSourceValue(diagnosticConfidenceConcept.getSourceCode());
1232 newProcedureOcc.setModifierConceptId(diagnosticConfidenceConcept.getTargetConceptId());
1235 return newProcedureOcc;
1238 /**
1239 * Creates a new record of the measurement table in OMOP CDM for the processed FHIR Condition
1240 * resource. The extracted ICD code of the FHIR Condition resource belongs to the Measurement
1241 * domain in OMOP CDM.
1243 * @param startDatetime start date time of the FHIR Condition resource
1244 * @param diagnoseConceptId the OMOP concept_id of diagnose Code as concept_id
1245 * @param diagnoseSourceConceptId the OMOP concept_id of diagnose Code as source_concept_id
1246 * @param rawIcdCode ICD code with special characters
1247 * @param diagnosticConfidenceConcept the OMOP concept of diagnostic confidence
1248 * @param personId person_id of the referenced FHIR Patient resource
1249 * @param visitOccId visit_occurrence_id of the referenced FHIR Encounter resource
1250 * @param conditionLogicId logical id of the FHIR Condition resource
1251 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
1252 * @return new record of the measurement table in OMOP CDM for the processed FHIR Condition
1253 * resource
1254 */
1255 private Measurement setUpMeasurement(
1256 LocalDateTime startDatetime,
1257 Integer diagnoseConceptId,
1258 Integer diagnoseSourceConceptId,
1259 String rawIcdCode,
1260 SourceToConceptMap diagnosticConfidenceConcept,
1261 Long personId,
1262 Long visitOccId,
1263 String conditionLogicId,
1264 String conditionSourceIdentifier) {
1266 var newMeasurement =
1267 Measurement.builder()
1268 .personId(personId)
1269 .measurementDate(startDatetime.toLocalDate())
1270 .measurementDatetime(startDatetime)
1271 .visitOccurrenceId(visitOccId)
1272 .measurementSourceConceptId(diagnoseSourceConceptId)
1273 .measurementConceptId(diagnoseConceptId)
1274 .measurementTypeConceptId(CONCEPT_EHR)
1275 .measurementSourceValue(rawIcdCode)
1276 .fhirLogicalId(conditionLogicId)
1277 .fhirIdentifier(conditionSourceIdentifier)
1278 .build();
1280 if (diagnosticConfidenceConcept != null) {
1281 newMeasurement.setValueSourceValue(diagnosticConfidenceConcept.getSourceCode());
1283 newMeasurement.setValueAsConceptId(diagnosticConfidenceConcept.getTargetConceptId());
1286 return newMeasurement;
1289 /**
1290 * Creates new records of the post_process_map table in OMOP CDM for ICD pairs.
1292 * @param icdSnomedPairMaps a list of valid pairs of ICD code and its OMOP concept_id and domain
1293 * information
1294 * @param personIdperson_id of the referenced FHIR Patient resource
1295 * @param conditionLogicId logical id of the FHIR Condition resource
1296 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
1297 * @return list of new records of the post_process_map table in OMOP CDM for the processed FHIR
1298 * Condition resource
1299 */
1300 private List<PostProcessMap> setIcdPairs(
1301 List<Pair<String, List<IcdSnomedDomainLookup>>> icdSnomedPairMaps,
1302 String conditionLogicId,
1303 String conditionSourceIdentifier) {
1305 var primary = icdSnomedPairMaps.get(0);
1306 var secondary = icdSnomedPairMaps.get(1);
1308 var primaryIcdCode = primary.getLeft();
1309 var secondaryIcdCode = secondary.getLeft();
1311 var primaryIcdSnomedMap = primary.getRight();
1312 var secondaryIcdSnomedMap = secondary.getRight();
1314 ArrayList<PostProcessMap> icdPairs = new ArrayList<>();
1316 for (var pri : primaryIcdSnomedMap) {
1318 for (var sec : secondaryIcdSnomedMap) {
1319 var ppmIcdPairs =
1320 PostProcessMap.builder()
1321 .type(ResourceType.CONDITION.name())
1322 .dataOne(primaryIcdCode + ":" + pri.getSnomedDomainId())
1323 .dataTwo(secondaryIcdCode + ":" + sec.getSnomedDomainId())
1324 .omopId(0L)
1325 .omopTable("primary_secondary_icd")
1326 .fhirLogicalId(conditionLogicId)
1327 .fhirIdentifier(conditionSourceIdentifier)
1328 .build();
1329 if (!icdPairs.contains(ppmIcdPairs)) {
1330 icdPairs.add(ppmIcdPairs);
1334 return icdPairs;
1337 /**
1338 * * Creates new record of the post_process_map table in OMOP CDM for extracted site localization
1339 * information from the processed FHIR Condition resource.
1341 * @param icdSnomedMapList a list of valid pairs of ICD code and its OMOP concept_id and domain
1342 * information
1343 * @param omopConcept extracted Concept from OMOP
1344 * @param personId person_id of the referenced FHIR Patient resource
1345 * @param siteLocalization site localization information as OMOP observation from the processed
1346 * FHIR Condition resource
1347 * @param conditionLogicId logical id of the FHIR Condition resource
1348 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
1349 * @return new record of the post_process_map table in OMOP CDM for the processed FHIR Condition
1350 * resource
1351 */
1352 private PostProcessMap setBodySiteLocalizationReference(
1353 @Nullable List<Pair<String, List<IcdSnomedDomainLookup>>> icdSnomedMapList,
1354 @Nullable Concept omopConcept,
1355 Long personId,
1356 OmopObservation siteLocalization,
1357 String conditionLogicId,
1358 String conditionSourceIdentifier) {
1360 if ((icdSnomedMapList == null && omopConcept == null) || icdSnomedMapList == null) {
1361 return null;
1364 return PostProcessMap.builder()
1365 .type(ResourceType.CONDITION.name())
1366 .dataOne(siteLocalization.getObservationSourceValue() + ":27")
1367 .dataTwo(Integer.toString(siteLocalization.getObservationConceptId()))
1368 .omopId(personId)
1369 .omopTable("site_localization")
1370 .fhirLogicalId(conditionLogicId)
1371 .fhirIdentifier(conditionSourceIdentifier)
1372 .build();
1375 /**
1376 * Create a new record of the post_process_map table in OMOP CDM for extracted severity or stage
1377 * information from the processed FHIR Condition resource.
1379 * @param icdSnomedMapList a list of valid pairs of ICD code and its OMOP concept_id and domain
1380 * information
1381 * @param omopConcept extracted Concept from OMOP
1382 * @param personId person_id of the referenced FHIR Patient resource
1383 * @param diagnoseMetaInfo severity or stage information as OMOP observation from the processed
1384 * FHIR Condition resource
1385 * @param conditionLogicId logical id of the FHIR Condition resource
1386 * @param conditionSourceIdentifier identifier of the FHIR Condition resource
1387 * @return new record of the post_process_map table in OMOP CDM for the processed FHIR Condition
1388 * resource
1389 */
1390 private PostProcessMap setDiagnoseMetaInfoReference(
1391 @Nullable List<Pair<String, List<IcdSnomedDomainLookup>>> icdSnomedMapList,
1392 @Nullable Concept omopConcept,
1393 Long personId,
1394 OmopObservation diagnoseMetaInfo,
1395 String conditionLogicId,
1396 String conditionSourceIdentifier,
1397 String metaInfoType) {
1399 if ((icdSnomedMapList == null && omopConcept == null) || icdSnomedMapList == null) {
1400 return null;
1403 return PostProcessMap.builder()
1404 .type(ResourceType.CONDITION.name())
1405 .dataOne(diagnoseMetaInfo.getObservationSourceValue() + ":27")
1406 .dataTwo(Integer.toString(diagnoseMetaInfo.getObservationConceptId()))
1407 .omopId(personId)
1408 .omopTable(metaInfoType)
1409 .fhirLogicalId(conditionLogicId)
1410 .fhirIdentifier(conditionSourceIdentifier)
1411 .build();
1414 /**
1415 * Creates a new record of the observation table in OMOP CDM for extracted site localization
1416 * information from the processed FHIR Condition resource.
1418 * @param icdBodyLocalization a pair of the body site or site localization name and OMOP
1419 * concept_id
1420 * @param conditionLogicId logical id of FHIR Condition resource
1421 * @param conditionSourceIdentifier identifier of FHIR Condition resource
1422 * @param personId person_id of the referenced FHIR Patient resource
1423 * @param visitOccId visit_occurrence_id of the referenced FHIR Encounter resource
1424 * @param diagnoseOnset start date time and end date time of the FHIR Condition resource
1425 * @return new record of the observation table in OMOP CDM for extracted site localization
1426 * information from the processed FHIR Condition resource
1427 */
1428 private OmopObservation createSiteLocalization(
1429 Pair<String, Integer> icdBodyLocalization,
1430 String conditionLogicId,
1431 String conditionSourceIdentifier,
1432 Long personId,
1433 Long visitOccId,
1434 ResourceOnset diagnoseOnset) {
1436 if (icdBodyLocalization != null && !Strings.isNullOrEmpty(icdBodyLocalization.getLeft())) {
1438 return OmopObservation.builder()
1439 .personId(personId)
1440 .observationDate(diagnoseOnset.getStartDateTime().toLocalDate())
1441 .observationDatetime(diagnoseOnset.getStartDateTime())
1442 .observationTypeConceptId(CONCEPT_EHR)
1443 .observationConceptId(icdBodyLocalization.getRight())
1444 .valueAsString(icdBodyLocalization.getLeft())
1445 .observationSourceValue(icdBodyLocalization.getLeft())
1446 .observationSourceConceptId(icdBodyLocalization.getRight())
1447 .qualifierConceptId(CONCEPT_FINDING_SITE)
1448 .visitOccurrenceId(visitOccId)
1449 .fhirLogicalId(conditionLogicId)
1450 .fhirIdentifier(conditionSourceIdentifier)
1451 .build();
1453 return null;
1456 /**
1457 * Creates a new record of the observation table in OMOP CDM for extracted severity information
1458 * from the processed FHIR Condition resource.
1460 * @param diagnoseMetaInfoConcept concept of diagnose meta information
1461 * @param conditionLogicId logical id of FHIR Condition resource
1462 * @param conditionSourceIdentifier identifier of FHIR Condition resource
1463 * @param personId person_id of the referenced FHIR Patient resource
1464 * @param visitOccId visit_occurrence_id of the referenced FHIR Encounter resource
1465 * @param diagnoseOnset start date time and end date time of the FHIR Condition resource
1466 * @return new record of the observation table in OMOP CDM for extracted site localization
1467 * information from the processed FHIR Condition resource
1468 */
1469 private OmopObservation createDiagnoseMetaInfo(
1470 Concept diagnoseMetaInfoConcept,
1471 String conditionLogicId,
1472 String conditionSourceIdentifier,
1473 Long personId,
1474 Long visitOccId,
1475 ResourceOnset diagnoseOnset,
1476 String metaInfoType) {
1478 if (diagnoseMetaInfoConcept == null) {
1480 return null;
1482 return OmopObservation.builder()
1483 .personId(personId)
1484 .observationDate(diagnoseOnset.getStartDateTime().toLocalDate())
1485 .observationDatetime(diagnoseOnset.getStartDateTime())
1486 .observationTypeConceptId(CONCEPT_EHR)
1487 .observationConceptId(diagnoseMetaInfoConcept.getConceptId())
1488 .valueAsString(diagnoseMetaInfoConcept.getConceptCode())
1489 .observationSourceValue(diagnoseMetaInfoConcept.getConceptCode())
1490 .observationSourceConceptId(diagnoseMetaInfoConcept.getConceptId())
1491 .qualifierConceptId(metaInfoType.equals("stage") ? CONCEPT_STAGE : CONCEPT_SEVERITY)
1492 .visitOccurrenceId(visitOccId)
1493 .fhirLogicalId(conditionLogicId)
1494 .fhirIdentifier(conditionSourceIdentifier)
1495 .build();
1498 /**
1499 * Extract the site localization name and its OMOP concept_id
1501 * @param icdCoding part of the FHIR Condition resource, which contains ICD codes
1502 * @param srcCondition FHIR Condition resource
1503 * @param diagnoseDate start date time of the FHIR Condition resource
1504 * @return a pair of the site localization name and its OMOP concept_id
1505 */
1506 private Pair<String, Integer> getBodySiteLocalizationConcepts(
1507 Coding icdCoding, Condition srcCondition, LocalDate diagnoseDate, String conditionId) {
1508 var icdSiteLocalization = getSiteLocalization(icdCoding);
1509 var diagnoseBodySite = getBodySite(srcCondition);
1511 if (icdSiteLocalization == null && diagnoseBodySite == null) {
1512 return null;
1513 } else if (icdSiteLocalization != null) {
1514 var icdSiteLocalizationConcept =
1515 findOmopConcepts.getCustomConcepts(
1516 icdSiteLocalization.getCode(), SOURCE_VOCABULARY_ID_ICD_LOCALIZATION, dbMappings);
1517 return Pair.of(
1518 icdSiteLocalization.getCode(), icdSiteLocalizationConcept.getTargetConceptId());
1519 } else {
1520 var diagnoseBodySiteConcept =
1521 findOmopConcepts.getConcepts(
1522 diagnoseBodySite, diagnoseDate, bulkload, dbMappings, conditionId);
1523 if (diagnoseBodySiteConcept != null) {
1524 return Pair.of(diagnoseBodySite.getCode(), diagnoseBodySiteConcept.getConceptId());
1527 return null;
1530 /**
1531 * Extracts site localization information from the processed FHIR Condition resource.
1533 * @param icdCoding part of the FHIR Condition resource, which contains ICD codes
1534 * @return site localization from FHIR Condition resource
1535 */
1536 private Coding getSiteLocalization(Coding icdCoding) {
1537 var siteLocalizationCodingExtension =
1538 icdCoding.getExtensionByUrl(fhirSystems.getSiteLocalizationExtension());
1539 if (siteLocalizationCodingExtension == null) {
1540 return null;
1542 var siteLocalizationCoding =
1543 siteLocalizationCodingExtension
1544 .getValue()
1545 .castToCoding(siteLocalizationCodingExtension.getValue());
1546 var siteLocalizationCode = siteLocalizationCoding.getCode();
1547 if (Strings.isNullOrEmpty(siteLocalizationCode)) {
1548 return null;
1551 return siteLocalizationCoding;
1554 /**
1555 * Extracts body site information from the processed FHIR Condition resource.
1557 * @param srcCondition FHIR Condition resource
1558 * @return body site from FHIR Condition resource
1559 */
1560 private Coding getBodySite(Condition srcCondition) {
1561 var bodySiteCodeable = srcCondition.getBodySite();
1562 if (bodySiteCodeable.isEmpty()) {
1563 return null;
1566 var bodySiteCoding =
1567 bodySiteCodeable.stream().filter(codeable -> !codeable.getCoding().isEmpty()).findFirst();
1568 if (bodySiteCoding.isEmpty()) {
1569 return null;
1572 var bodySite =
1573 bodySiteCoding.get().getCoding().stream()
1574 .filter(coding -> coding.getSystem().equals(fhirSystems.getSnomed()))
1575 .findFirst();
1576 if (!bodySite.isPresent()) {
1577 return null;
1579 var bodySiteCode = bodySite.get().getCode();
1580 if (Strings.isNullOrEmpty(bodySiteCode)) {
1581 return null;
1583 return bodySite.get();
1586 /**
1587 * Extracts stage information from the processed FHIR Condition resource.
1589 * @param srcCondition FHIR Condition resource
1590 * @return stage from FHIR Condition resource
1591 */
1592 private Coding getStage(Condition srcCondition) {
1594 if (!srcCondition.hasStage() || srcCondition.getStage().isEmpty()) {
1595 return null;
1597 var stage = srcCondition.getStage();
1599 var stageComponent =
1600 stage.stream().filter(summary -> !summary.getSummary().isEmpty()).findFirst();
1601 if (stageComponent.isEmpty()
1602 || !stageComponent.get().hasSummary()
1603 || stageComponent.get().getSummary() == null) {
1604 return null;
1607 var stageSummaryCodeable = stageComponent.get().getSummary();
1608 if (stageSummaryCodeable.isEmpty() || !stageSummaryCodeable.hasCoding()) {
1609 return null;
1612 var stageSummary =
1613 stageSummaryCodeable.getCoding().stream()
1614 .filter(coding -> coding.getSystem().equals(fhirSystems.getSnomed()))
1615 .findFirst();
1616 if (!stageSummary.isPresent()) {
1617 return null;
1619 var stageCode = stageSummary.get().getCode();
1620 if (Strings.isNullOrEmpty(stageCode)) {
1621 return null;
1623 return stageSummary.get();
1626 /**
1627 * Extracts severity information from the processed FHIR Condition resource.
1629 * @param srcCondition FHIR Condition resource
1630 * @return severity from FHIR Condition resource
1631 */
1632 private Coding getSeverity(Condition srcCondition) {
1633 if (!srcCondition.hasSeverity()) {
1634 return null;
1636 var severityCodeable = srcCondition.getSeverity();
1637 if (severityCodeable.isEmpty() || !severityCodeable.hasCoding()) {
1638 return null;
1641 var severity =
1642 severityCodeable.getCoding().stream()
1643 .filter(coding -> coding.getSystem().equals(fhirSystems.getSnomed()))
1644 .findFirst();
1645 if (!severity.isPresent()) {
1646 return null;
1648 var severityCode = severity.get().getCode();
1649 if (Strings.isNullOrEmpty(severityCode)) {
1650 return null;
1652 return severity.get();