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

lines 653–675 700 lines · java
1package org.miracum.etl.fhirtoomop.mapper;
3import static org.miracum.etl.fhirtoomop.Constants.CONCEPT_EHR_RECORD_STATUS_DECEASED;
4import static org.miracum.etl.fhirtoomop.Constants.CONCEPT_GENDER_UNKNOWN;
5import static org.miracum.etl.fhirtoomop.Constants.CONCEPT_HISPANIC_OR_LATINO;
6import static org.miracum.etl.fhirtoomop.Constants.CONCEPT_NO_MATCHING_CONCEPT;
7import static org.miracum.etl.fhirtoomop.Constants.CONCEPT_UNKNOWN_RACIAL_GROUP;
8import static org.miracum.etl.fhirtoomop.Constants.ETHNICITY_SOURCE_HISPANIC_OR_LATINO;
9import static org.miracum.etl.fhirtoomop.Constants.ETHNICITY_SOURCE_MIXED;
10import static org.miracum.etl.fhirtoomop.Constants.MAX_LOCATION_CITY_LENGTH;
11import static org.miracum.etl.fhirtoomop.Constants.MAX_LOCATION_COUNTRY_LENGTH;
12import static org.miracum.etl.fhirtoomop.Constants.MAX_LOCATION_STATE_LENGTH;
13import static org.miracum.etl.fhirtoomop.Constants.MAX_LOCATION_ZIP_LENGTH;
14import static org.miracum.etl.fhirtoomop.Constants.MAX_SOURCE_VALUE_LENGTH;
15import static org.miracum.etl.fhirtoomop.Constants.SOURCE_VOCABULARY_ID_GENDER;
17import ca.uhn.fhir.fhirpath.IFhirPath;
18import com.google.common.base.Strings;
19import io.micrometer.core.instrument.Counter;
20import java.sql.Timestamp;
21import java.time.LocalDateTime;
22import java.time.ZoneId;
23import java.time.format.DateTimeFormatter;
24import java.util.Collections;
25import java.util.Map;
26import java.util.stream.Collectors;
27import lombok.extern.slf4j.Slf4j;
28import org.apache.commons.lang3.StringUtils;
29import org.hl7.fhir.r4.model.Address;
30import org.hl7.fhir.r4.model.Age;
31import org.hl7.fhir.r4.model.Coding;
32import org.hl7.fhir.r4.model.DateTimeType;
33import org.hl7.fhir.r4.model.DateType;
34import org.hl7.fhir.r4.model.Enumerations.ResourceType;
35import org.hl7.fhir.r4.model.Extension;
36import org.hl7.fhir.r4.model.Patient;
37import org.miracum.etl.fhirtoomop.DbMappings;
38import org.miracum.etl.fhirtoomop.config.FhirSystems;
39import org.miracum.etl.fhirtoomop.mapper.helpers.FindOmopConcepts;
40import org.miracum.etl.fhirtoomop.mapper.helpers.MapperMetrics;
41import org.miracum.etl.fhirtoomop.mapper.helpers.ResourceCheckDataAbsentReason;
42import org.miracum.etl.fhirtoomop.mapper.helpers.ResourceFhirReferenceUtils;
43import org.miracum.etl.fhirtoomop.mapper.helpers.ResourceOmopReferenceUtils;
44import org.miracum.etl.fhirtoomop.model.OmopModelWrapper;
45import org.miracum.etl.fhirtoomop.model.PostProcessMap;
46import org.miracum.etl.fhirtoomop.model.omop.Person;
47import org.miracum.etl.fhirtoomop.repository.service.PatientMapperServiceImpl;
48import org.springframework.beans.factory.annotation.Autowired;
49import org.springframework.stereotype.Component;
51/**
52 * The PatientMapper class describes the business logic of transforming a FHIR Patient resource to
53 * OMOP CDM.
54 *
55 * @author Elisa Henke
56 * @author Yuan Peng
57 */
58@Slf4j
59@Component
60public class PatientMapper implements FhirMapper<Patient> {
61 private static final FhirSystems fhirSystems = new FhirSystems();
62 private final IFhirPath fhirPath;
63 private final Boolean bulkload;
64 private final DbMappings dbMappings;
66 @Autowired ResourceOmopReferenceUtils omopReferenceUtils;
67 @Autowired ResourceFhirReferenceUtils fhirReferenceUtils;
68 @Autowired PatientMapperServiceImpl patientService;
69 @Autowired ResourceCheckDataAbsentReason checkDataAbsentReason;
70 @Autowired FindOmopConcepts findOmopConcepts;
72 private static final Counter noFhirReferenceCounter =
73 MapperMetrics.setNoFhirReferenceCounter("stepProcessPatients");
74 private static final Counter deletedFhirReferenceCounter =
75 MapperMetrics.setDeletedFhirRessourceCounter("stepProcessPatients");
77 /**
78 * Constructor for objects of the class PatientMapper.
79 *
80 * @param fhirPath FhirPath engine to evaluate path expressions over FHIR resources
81 * @param bulkload parameter which indicates whether the Job should be run as bulk load or
82 * incremental load
83 * @param dbMappings collections for the intermediate storage of data from OMOP CDM in RAM
84 */
85 @Autowired
86 public PatientMapper(IFhirPath fhirPath, Boolean bulkload, DbMappings dbMappings) {
88 this.fhirPath = fhirPath;
89 this.bulkload = bulkload;
90 this.dbMappings = dbMappings;
91 }
93 /**
94 * Maps a FHIR Patient resource to several OMOP CDM tables.
95 *
96 * @param srcPatient FHIR Patient resource
97 * @param isDeleted a flag, whether the FHIR resource is deleted in the source
98 * @return OmopModelWrapper cache of newly created OMOP CDM records from the FHIR Patient resource
99 */
100 @Override
101 public OmopModelWrapper map(Patient srcPatient, boolean isDeleted) {
103 var wrapper = new OmopModelWrapper();
105 var patientSourceIdentifier = fhirReferenceUtils.extractIdentifier(srcPatient, "MR");
106 var patientLogicId = fhirReferenceUtils.extractId(srcPatient);
107 if (Strings.isNullOrEmpty(patientLogicId) && Strings.isNullOrEmpty(patientSourceIdentifier)) {
108 log.warn("No [Identifier] or [Id] found. [Patient] resource is invalid. Skip resource");
109 noFhirReferenceCounter.increment();
110 return null;
111 }
113 String patientId = "";
114 if (!Strings.isNullOrEmpty(patientLogicId)) {
115 patientId = srcPatient.getId();
116 }
118 var ageExtensionMap = extractAgeExtension(srcPatient);
119 var ageAtDiagnosis = setAgeAtDiagnosis(patientLogicId, patientSourceIdentifier);
120 var realBirthDate = extractBirthDate(srcPatient);
121 var calculatedBirthDate =
122 extractCalculatedBirthDate(ageExtensionMap, ageAtDiagnosis, patientId);
124 if (realBirthDate == null && calculatedBirthDate == null) {
125 log.info("No [Birthdate] found for [Patient]: {}. Skip Resource.", patientId);
126 if (bulkload.equals(Boolean.FALSE)) {
127 deleteExistingPatients(patientLogicId, patientSourceIdentifier);
128 }
129 return null;
130 }
132 if (bulkload.equals(Boolean.FALSE) && isDeleted) {
133 log.info("Found a deleted [Patient] resource {}. Deleting from OMOP DB.", patientId);
134 deleteExistingPatients(patientLogicId, patientSourceIdentifier);
135 deletedFhirReferenceCounter.increment();
136 return null;
137 }
139 if (bulkload.equals(Boolean.FALSE)) {
140 deleteExistingDeath(patientLogicId, patientSourceIdentifier);
141 deleteExistingCalculatedBirthYear(patientLogicId, patientSourceIdentifier);
142 }
144 var newPerson = createNewPerson(srcPatient, patientLogicId, patientSourceIdentifier, patientId);
145 setBirthDate(realBirthDate, calculatedBirthDate, newPerson);
147 var ethnicGroupCoding = extractEthnicGroup(srcPatient);
148 setRaceConcept(ethnicGroupCoding, newPerson, patientLogicId);
149 setEthnicityConcept(ethnicGroupCoding, newPerson);
151 var death = setDeath(srcPatient, patientLogicId, patientSourceIdentifier);
152 if (death != null) {
153 wrapper.getPostProcessMap().add(death);
154 }
156 var location = setLocation(srcPatient, patientLogicId, patientSourceIdentifier);
157 if (location != null) {
158 wrapper.getPostProcessMap().add(location);
159 }
161 wrapper.setPerson(newPerson);
163 if (ageAtDiagnosis.getDataOne() != null) {
164 wrapper.getPostProcessMap().add(ageAtDiagnosis);
165 }
166 return wrapper;
167 }
169 private PostProcessMap setAgeAtDiagnosis(String patientLogicId, String patientSourceIdentifier) {
170 return PostProcessMap.builder()
171 .type(ResourceType.PATIENT.name())
172 .omopTable("age_at_diagnosis")
173 .omopId(Long.valueOf(4307859))
174 .fhirIdentifier(patientSourceIdentifier)
175 .fhirLogicalId(patientLogicId)
176 .build();
177 }
179 /**
180 * Delete FHIR Patient resources from OMOP CDM tables using fhir_logical_id and fhir_identifier
181 *
182 * @param patientLogicId logical id of the FHIR Patient resource
183 * @param patientSourceIdentifier identifier of the FHIR Patient resource
184 */
185 private void deleteExistingPatients(String patientLogicId, String patientSourceIdentifier) {
186 if (!Strings.isNullOrEmpty(patientLogicId)) {
187 patientService.deletePersonByFhirLogicalId(patientLogicId);
188 } else {
189 patientService.deletePersonByFhirIdentifier(patientSourceIdentifier);
190 }
191 }
193 /**
194 * Creates a new record of the person table in OMOP CDM for the processed FHIR Patient resource.
195 *
196 * @param srcPatient FHIR Patient resource
197 * @param patientLogicId logical id of the FHIR Patient resource
198 * @param patientSourceIdentifier identifier of the FHIR Patient resource
199 * @return new record of the person table in OMOP CDM for the processed FHIR Patient resource
200 */
201 private Person createNewPerson(
202 Patient srcPatient, String patientLogicId, String patientSourceIdentifier, String patientId) {
203 var personSourceValue = cutString(patientSourceIdentifier, MAX_SOURCE_VALUE_LENGTH);
205 var person =
206 Person.builder()
207 .personSourceValue(personSourceValue == null ? null : personSourceValue.substring(4))
208 .fhirLogicalId(patientLogicId)
209 .fhirIdentifier(patientSourceIdentifier)
210 .build();
211 var gender = getGender(srcPatient);
212 person.setGenderConceptId(getGenderConceptId(gender));
213 person.setGenderSourceValue(gender);
215 if (bulkload.equals(Boolean.FALSE)) {
216 var existingPersonId =
217 omopReferenceUtils.getExistingPersonId(patientSourceIdentifier, patientLogicId);
218 if (existingPersonId != null) {
219 log.debug("[Patient] {} exists already in person. Update existing person", patientId);
221 person.setPersonId(existingPersonId);
222 }
223 }
224 return person;
225 }
227 /**
228 * Set the information for ethnicity in person record.
229 *
230 * @param ethnicGroupCoding coding of ethnic group
231 * @param person new record of the person table in OMOP CDM
232 */
233 private void setEthnicityConcept(Coding ethnicGroupCoding, Person person) {
234 if (ethnicGroupCoding == null || Strings.isNullOrEmpty(ethnicGroupCoding.getCode())) {
235 person.setEthnicityConceptId(CONCEPT_NO_MATCHING_CONCEPT);
236 return;
237 }
239 var ethnicGroupCode = ethnicGroupCoding.getCode();
240 if (ethnicGroupCode.equals(ETHNICITY_SOURCE_HISPANIC_OR_LATINO)) {
241 person.setEthnicityConceptId(CONCEPT_HISPANIC_OR_LATINO);
242 person.setEthnicitySourceConceptId(CONCEPT_HISPANIC_OR_LATINO);
243 person.setEthnicitySourceValue(ETHNICITY_SOURCE_HISPANIC_OR_LATINO);
245 } else if (ethnicGroupCode.equals(ETHNICITY_SOURCE_MIXED)) {
246 person.setEthnicityConceptId(CONCEPT_NO_MATCHING_CONCEPT);
247 person.setEthnicitySourceValue(ethnicGroupCode);
248 } else {
249 person.setEthnicityConceptId(CONCEPT_NO_MATCHING_CONCEPT);
250 }
251 }
253 /**
254 * Set the information for race in person record.
255 *
256 * @param ethnicGroupCoding coding of ethnic group
257 * @param person new record of the person table in OMOP CDM
258 */
259 private void setRaceConcept(Coding ethnicGroupCoding, Person person, String patientLogicId) {
261 if (ethnicGroupCoding == null || Strings.isNullOrEmpty(ethnicGroupCoding.getCode())) {
262 person.setRaceConceptId(CONCEPT_UNKNOWN_RACIAL_GROUP);
263 return;
264 }
265 var ethnicGroupCode = ethnicGroupCoding.getCode();
266 if (ethnicGroupCode.equals(ETHNICITY_SOURCE_HISPANIC_OR_LATINO)) {
267 person.setRaceConceptId(CONCEPT_UNKNOWN_RACIAL_GROUP);
268 } else if (ethnicGroupCode.equals(ETHNICITY_SOURCE_MIXED)) {
269 person.setRaceConceptId(CONCEPT_NO_MATCHING_CONCEPT);
270 person.setRaceSourceValue(ethnicGroupCode);
271 } else {
272 var ethnicConcept =
273 findOmopConcepts.getSnomedRaceConcepts(
274 ethnicGroupCoding, bulkload, dbMappings, patientLogicId);
275 if (ethnicConcept != null) {
276 person.setRaceConceptId(ethnicConcept.getStandardRaceConceptId());
277 person.setRaceSourceConceptId(ethnicConcept.getSnomedConceptId());
278 person.setRaceSourceValue(ethnicGroupCode);
279 }
280 }
281 }
283 /**
284 * Creates a new record of the post_process_map table in OMOP CDM for extracted location
285 * informations from the processed FHIR Patient resource.
286 *
287 * @param srcPatient FHIR Patient resource
288 * @param patientLogicId logical id of the FHIR Patient resource
289 * @param patientSourceIdentifier identifier of the FHIR Patient resource
290 * @return new record of the post_process_map table in OMOP CDM for extracted location
291 * informations from the processed FHIR Patient resource
292 */
293 private PostProcessMap setLocation(
294 Patient srcPatient, String patientLogicId, String patientSourceIdentifier) {
295 if (!srcPatient.hasAddress() || srcPatient.getAddress().isEmpty()) {
296 return null;
297 }
299 var address = srcPatient.getAddressFirstRep();
300 if (address.getExtensionByUrl(fhirSystems.getDataAbsentReason()) != null) {
301 return null;
302 }
303 StringBuilder dataOne = new StringBuilder();
304 StringBuilder dataTwo = new StringBuilder();
306 addZip(address, dataOne);
307 dataOne.append(";");
308 addCity(address, dataOne);
309 dataOne.append(";");
310 addCountry(address, dataOne);
312 addLines(address, dataTwo);
313 dataTwo.append(";");
314 addState(address, dataTwo);
316 var dataOneStr = dataOne.toString();
317 var dataTwoStr = dataTwo.toString();
318 // if (Strings.isNullOrEmpty(dataOneStr) && Strings.isNullOrEmpty(dataTwoStr)) {
319 // return null;
320 // }
322 if (dataOneStr.equals(";;") && dataTwoStr.equals(";")) {
323 return null;
324 }
326 return PostProcessMap.builder()
327 .dataOne(dataOne.toString())
328 .dataTwo(dataTwo.toString())
329 .omopTable(OmopModelWrapper.Tablename.LOCATION.name())
330 .type(ResourceType.PATIENT.name())
331 .fhirLogicalId(patientLogicId)
332 .fhirIdentifier(patientSourceIdentifier)
333 .build();
334 }
336 /**
337 * Append the state information (if exists) to column dataTwo from POST_PROCESS_MAP table.
338 *
339 * @param address Address component from Patient FHIR resource
340 * @param dataTwo StringBuilder for the dataTwo column from POST_PROCESS_MAP table
341 */
342 private void addState(Address address, StringBuilder dataTwo) {
343 var stateElement = address.getStateElement();
344 if (stateElement.isEmpty()) {
345 return;
346 }
347 var state = checkDataAbsentReason.getValue(stateElement);
349 if (!Strings.isNullOrEmpty(state)) {
350 dataTwo.append(cutString(state, MAX_LOCATION_STATE_LENGTH));
351 }
352 }
354 /**
355 * Append the street information (if exists) to column dataTwo from POST_PROCESS_MAP table.
356 *
357 * @param address Address component from Patient FHIR resource
358 * @param dataTwo StringBuilder for the dataTwo column from POST_PROCESS_MAP table
359 */
360 private void addLines(Address address, StringBuilder dataTwo) {
361 var lines = address.getLine();
362 if (lines.isEmpty()) {
363 return;
364 }
366 for (var line : lines) {
367 if (line.isEmpty()) {
368 continue;
369 }
370 var lineStr = checkDataAbsentReason.getValue(line);
371 if (!Strings.isNullOrEmpty(lineStr)) {
372 dataTwo.append(line);
373 dataTwo.append(" ");
374 }
375 }
376 // if (!dataTwo.isEmpty()) {
377 // dataTwo.append(";");
378 // }
379 }
381 /**
382 * Append the country information (if exists) to column dataTwo from POST_PROCESS_MAP table.
383 *
384 * @param address Address component from Patient FHIR resource
385 * @param dataOne StringBuilder for the dataOne column from POST_PROCESS_MAP table
386 */
387 private void addCountry(Address address, StringBuilder dataOne) {
388 var countryElement = address.getCountryElement();
389 if (countryElement.isEmpty()) {
390 return;
391 }
392 var country = checkDataAbsentReason.getValue(countryElement);
393 if (!Strings.isNullOrEmpty(country)) {
394 dataOne.append(cutString(country.replaceAll("\\s+", ""), MAX_LOCATION_COUNTRY_LENGTH));
395 }
396 }
398 /**
399 * Append the city information (if exists) to column dataTwo from POST_PROCESS_MAP table.
400 *
401 * @param address Address component from Patient FHIR resource
402 * @param dataOne StringBuilder for the dataOne column from POST_PROCESS_MAP table
403 */
404 private void addCity(Address address, StringBuilder dataOne) {
405 var cityElement = address.getCityElement();
406 if (cityElement.isEmpty()) {
407 return;
408 }
409 var city = checkDataAbsentReason.getValue(cityElement);
410 if (!Strings.isNullOrEmpty(city)) {
411 dataOne.append(cutString(city, MAX_LOCATION_CITY_LENGTH));
412 }
413 }
415 /**
416 * Append the zip code information (if exists) to column dataTwo from POST_PROCESS_MAP table.
417 *
418 * @param address Address component from Patient FHIR resource
419 * @param dataOne StringBuilder for the dataOne column from POST_PROCESS_MAP table
420 */
421 private void addZip(Address address, StringBuilder dataOne) {
422 var zipElement = address.getPostalCodeElement();
423 if (zipElement.isEmpty()) {
424 return;
425 }
426 var zip = checkDataAbsentReason.getValue(zipElement);
427 if (!Strings.isNullOrEmpty(zip)) {
428 dataOne.append(cutString(zip, MAX_LOCATION_ZIP_LENGTH));
429 }
430 }
432 /**
433 * Shortens a string value to a specified maximum length.
434 *
435 * @param stringToBeCut string value to be shortened
436 * @param maxLength maximum length of the string value
437 * @return shortened string value
438 */
439 private String cutString(String stringToBeCut, int maxLength) {
440 if (!Strings.isNullOrEmpty(stringToBeCut) && stringToBeCut.length() > maxLength) {
441 log.debug(
442 "The String: {} is longer than allowed. Cut it to a length of {}.",
443 stringToBeCut,
444 maxLength);
445 return StringUtils.left(stringToBeCut, maxLength);
446 }
447 return stringToBeCut;
448 }
450 /**
451 * Sets the extracted birth date information from FHIR Patient resource to the new person record.
452 *
453 * @param birthDate the birth date in dateType from FHIR Patient resource
454 * @param person record of the person table in OMOP CDM for the processed FHIR Patient resource
455 */
456 private void setBirthDate(
457 LocalDateTime realBirthDate, LocalDateTime calculatedBirthDate, Person person) {
458 if (realBirthDate != null) {
459 person.setYearOfBirth(realBirthDate.getYear());
460 person.setMonthOfBirth(realBirthDate.getMonthValue());
461 person.setDayOfBirth(realBirthDate.getDayOfMonth());
462 } else {
463 person.setYearOfBirth(calculatedBirthDate.getYear());
464 }
465 }
467 /**
468 * Extracts birth date information from FHIR Patient resource.
469 *
470 * @param srcPatient FHIR Patient resource
471 * @param patientLogicId logical id of the FHIR Patient resource
472 * @return birth date or the calculated birth date from FHIR Patient resource
473 */
474 private LocalDateTime extractBirthDate(Patient srcPatient) {
475 var birthDate = fhirPath.evaluateFirst(srcPatient, "Patient.birthDate", DateType.class);
476 if (birthDate.isPresent()) {
477 var birthDateElement = birthDate.get();
478 if (!birthDateElement.hasValue() || birthDateElement.getValue() == null) {
479 return null;
480 }
481 return LocalDateTime.ofInstant(
482 birthDateElement.getValue().toInstant(), ZoneId.of("Europe/Berlin"));
483 } else {
484 return null;
485 }
486 }
488 /**
489 * Calculate birth date from FHIR Patient resource.
490 *
491 * @param srcPatient FHIR Patient resource
492 * @return calculated birth date from FHIR Patient resource
493 */
494 private LocalDateTime extractCalculatedBirthDate(
495 Map<String, Extension> ageExtensionMap, PostProcessMap ageAtDiagnosis, String patientId) {
496 if (ageExtensionMap.isEmpty()) {
497 return null;
498 }
500 Age ageValue = extractAgeValue(ageExtensionMap);
501 var documentationDateTime = extractDocumentationDateTime(ageExtensionMap);
502 if (ageValue == null || documentationDateTime == null || ageValue.getCode() == null) {
503 return null;
504 }
506 var ageUnit = ageValue.getCode();
507 var age = ageValue.getValue().intValue();
509 ageAtDiagnosis.setDataOne(documentationDateTime.toString());
510 ageAtDiagnosis.setDataTwo(age + ":" + ageValue.getUnit() + ":" + ageValue.getCode());
512 switch (ageUnit) {
513 case "a":
514 return documentationDateTime.minusYears(age);
515 case "mo":
516 return documentationDateTime.minusMonths(age);
518 case "d":
519 return documentationDateTime.minusDays(age);
520 default:
521 log.warn("Unable to calculate [Birthdate] for [Patient]: {}.", patientId);
522 return null;
523 }
524 }
526 /**
527 * @param srcPatient
528 * @return
529 */
530 private Map<String, Extension> extractAgeExtension(Patient srcPatient) {
531 var ageExtension = srcPatient.getExtensionByUrl(fhirSystems.getAgeExtension());
532 if (ageExtension == null) {
533 return Collections.emptyMap();
534 }
536 var subExtensions = ageExtension.getExtension();
538 return subExtensions.stream().collect(Collectors.toMap(Extension::getUrl, v -> v));
539 }
541 /**
542 * @param ageExtensionMap
543 * @return
544 */
545 private Age extractAgeValue(Map<String, Extension> ageExtensionMap) {
546 if (ageExtensionMap.isEmpty()) {
547 return null;
548 }
549 var ageExtensionAge = ageExtensionMap.get("age");
550 if (ageExtensionAge == null) {
552 return null;
553 }
554 Age ageValue = (Age) ageExtensionAge.getValue();
555 if (ageValue == null) {
556 return null;
557 }
558 return ageValue;
559 }
561 /**
562 * Extract the date of documentation from a FHIR Patient resource
563 *
564 * @param ageExtensionMap the extensions from ageExtention element in a FHIR Patient resource as a
565 * Map.
566 * @return the date of documentation
567 */
568 private LocalDateTime extractDocumentationDateTime(Map<String, Extension> ageExtensionMap) {
569 if (ageExtensionMap.isEmpty()) {
570 return null;
571 }
572 var documentationDateTimeExtension = ageExtensionMap.get("dateTimeOfDocumentation");
573 if (documentationDateTimeExtension == null) {
574 return null;
575 }
576 var extensionValue = (DateTimeType) documentationDateTimeExtension.getValue();
577 if (extensionValue == null) {
578 return null;
579 }
580 return extensionValue
581 .getValue()
582 .toInstant()
583 .atZone(ZoneId.of("Europe/Berlin"))
584 .toLocalDateTime();
585 }
587 /**
588 * Extract coding of ethnic group from a FHIR Patient resource
589 *
590 * @param srcPatient FHIR Patient resource
591 * @return coding of ethnic group
592 */
593 private Coding extractEthnicGroup(Patient srcPatient) {
594 var ethnicGroupExtension = srcPatient.getExtensionByUrl(fhirSystems.getEthnicGroupExtension());
595 if (ethnicGroupExtension == null) {
596 return null;
597 }
598 return ethnicGroupExtension.getValue().castToCoding(ethnicGroupExtension.getValue());
599 }
601 /**
602 * Extracts gender information from FHIR Patient resource.
603 *
604 * @param srcPatient FHIR Patient resource
605 * @return the gender from FHIR Patient resource
606 */
607 private String getGender(Patient srcPatient) {
608 var genderElement = srcPatient.getGenderElement();
609 if (genderElement.isEmpty()) {
610 return null;
611 }
612 var gender = checkDataAbsentReason.getValue(genderElement);
613 if (Strings.isNullOrEmpty(gender)) {
614 return null;
615 }
617 if (gender.equals("other")
618 && genderElement.hasExtension(fhirSystems.getGenderAmtlichDeExtension())) {
619 var administrativeGenderType =
620 genderElement.getExtensionByUrl(fhirSystems.getGenderAmtlichDeExtension()).getValue();
621 var administrativeGender = administrativeGenderType.castToCoding(administrativeGenderType);
622 if (administrativeGender.hasCode()) {
623 return administrativeGender.getCode();
624 }
625 }
626 return gender;
627 }
628 /**
629 * Mapping gender information from FHIR Patient resource to OMOP Concept.
630 *
631 * @param gender gender as String from FHIR Patient resource
632 * @return the gender_concept_id of the gender from FHIR Patient resource
633 */
634 private Integer getGenderConceptId(String gender) {
635 if (StringUtils.isBlank(gender)) {
636 return CONCEPT_GENDER_UNKNOWN;
637 }
638 var sourceToConcepMap =
639 findOmopConcepts.getCustomConcepts(gender, SOURCE_VOCABULARY_ID_GENDER, dbMappings);
640 return sourceToConcepMap.getTargetConceptId();
641 }
643 /**
644 * Extracts death information from the processed FHIR Patient resource and creates a new record of
645 * the post_process_map table in OMOP CDM.
646 *
647 * @param srcPatient FHIR Patient resource
648 * @param patientLogicId logical id of the FHIR Patient resource
649 * @param patientSourceIdentifier identifier of the FHIR Patient resource
650 * @return new record of the post_process_map table in OMOP CDM for death data from the processed
651 * FHIR Patient resource
652 */
653 private PostProcessMap setDeath(
654 Patient srcPatient, String patientLogicId, String patientSourceIdentifier) {
656 if (!srcPatient.hasDeceasedDateTimeType()
657 || srcPatient.getDeceasedDateTimeType() == null
658 || !srcPatient.getDeceasedDateTimeType().hasValue()
659 && srcPatient.getDeceasedDateTimeType().getValue() == null) {
660 return null;
661 }
663 var deathDateTime =
664 new Timestamp(srcPatient.getDeceasedDateTimeType().getValue().getTime()).toLocalDateTime();
666 return PostProcessMap.builder()
667 .dataOne(deathDateTime.toLocalDate().toString())
668 .dataTwo(deathDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
669 .type(ResourceType.PATIENT.name())
670 .omopId(Long.valueOf(CONCEPT_EHR_RECORD_STATUS_DECEASED))
671 .omopTable(OmopModelWrapper.Tablename.DEATH.getTableName())
672 .fhirLogicalId(patientLogicId)
673 .fhirIdentifier(patientSourceIdentifier)
674 .build();
675 }
677 /**
678 * Delete FHIR Patient resources from OMOP CDM tables using fhir_logical_id and fhir_identifier
679 *
680 * @param patientLogicId logical id of the FHIR Patient resource
681 * @param patientSourceIdentifier identifier of the FHIR Patient resource
682 */
683 private void deleteExistingDeath(String patientLogicId, String patientSourceIdentifier) {
684 if (!Strings.isNullOrEmpty(patientLogicId)) {
685 patientService.deleteExistingDeathByFhirLogicalId(patientLogicId);
686 } else {
687 patientService.deleteExistingDeathByFhirIdentifier(patientSourceIdentifier);
688 }
689 }
691 private void deleteExistingCalculatedBirthYear(
692 String patientLogicId, String patientSourceIdentifier) {
693 if (!Strings.isNullOrEmpty(patientLogicId)) {
694 patientService.deleteExistingCalculatedBirthYearByFhirLogicalId(patientLogicId);
695 } else {
696 patientService.deleteExistingCalculatedBirthYearByFhirIdentifier(patientSourceIdentifier);
697 }
698 }