From f0ce22da2f50e1a6587d8503900293d86895a261 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Thu, 19 Jun 2025 15:08:11 +0200 Subject: [PATCH] feat: add patient mapper --- .../onco/datamapper/PatientDataMapper.java | 100 ++++++++++++++++ .../datacatalogues/PatientCatalogue.java | 46 ++++++++ .../datamapper/PatientDataMapperTest.java | 109 ++++++++++++++++++ 3 files changed, 255 insertions(+) create mode 100644 src/main/java/dev/pcvolkmer/onco/datamapper/PatientDataMapper.java create mode 100644 src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/PatientCatalogue.java create mode 100644 src/test/java/dev/pcvolkmer/onco/datamapper/PatientDataMapperTest.java diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/PatientDataMapper.java b/src/main/java/dev/pcvolkmer/onco/datamapper/PatientDataMapper.java new file mode 100644 index 0000000..95c691c --- /dev/null +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/PatientDataMapper.java @@ -0,0 +1,100 @@ +package dev.pcvolkmer.onco.datamapper; + +import dev.pcvolkmer.mv64e.mtb.Address; +import dev.pcvolkmer.mv64e.mtb.GenderCoding; +import dev.pcvolkmer.mv64e.mtb.GenderCodingCode; +import dev.pcvolkmer.mv64e.mtb.Patient; +import dev.pcvolkmer.onco.datamapper.datacatalogues.PatientCatalogue; +import dev.pcvolkmer.onco.datamapper.exceptions.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Date; + +/** + * Mapper class to load and map diagnosis data from database table 'dk_dnpm_kpa' + * + * @author Paul-Christian Volkmer + * @since 0.1 + */ +public class PatientDataMapper implements DataMapper { + + private final JdbcTemplate jdbcTemplate; + + private PatientDataMapper(final JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + /** + * Create instance of the mapper class + * + * @param jdbcTemplate The Spring JdbcTemplate to be used + * @return The initialized mapper + */ + public static PatientDataMapper create(final JdbcTemplate jdbcTemplate) { + return new PatientDataMapper(jdbcTemplate); + } + + /** + * Loads and maps a patient using the patient database id + * + * @param id The database id of the procedure data set + * @return The loaded MtbDiagnosis file + */ + @Override + public Patient getById(int id) { + var patientCatalogue = PatientCatalogue.create(this.jdbcTemplate); + var patientData = patientCatalogue.getById(id); + + var builder = Patient.builder(); + try { + builder + .id(patientData.getString("id")) + .gender(getGenderCoding(patientData)) + .birthDate(mapDate(patientData.getDate("geburtsdatum"))) + .dateOfDeath(mapDate(patientData.getDate("sterbedatum"))) + .address(Address.builder().municipalityCode(getMunicipalityCode(patientData)).build()) + ; + + } catch (SQLException e) { + throw new DataAccessException(e.getMessage()); + } + return builder.build(); + } + + private GenderCoding getGenderCoding(ResultSet data) throws SQLException { + var genderCodingBuilder = GenderCoding.builder(); + String geschlecht = data.getString("geschlecht"); + switch (geschlecht) { + case "M": + genderCodingBuilder.code(GenderCodingCode.MALE); + break; + case "F": + genderCodingBuilder.code(GenderCodingCode.FEMALE); + break; + case "X": + genderCodingBuilder.code(GenderCodingCode.OTHER); + break; + default: + genderCodingBuilder.code(GenderCodingCode.UNKNOWN); + } + return genderCodingBuilder.build(); + } + + private String getMunicipalityCode(ResultSet data) throws SQLException { + var gkz = data.getString("GKZ"); + if (gkz == null || gkz.trim().length() != 8) { + throw new DataAccessException("Municipality code not found"); + } + return gkz.substring(0, 5); + } + + private Date mapDate(java.sql.Date date) { + if (date == null) { + return null; + } + return new Date(date.getTime()); + } + +} diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/PatientCatalogue.java b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/PatientCatalogue.java new file mode 100644 index 0000000..8483cde --- /dev/null +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/PatientCatalogue.java @@ -0,0 +1,46 @@ +package dev.pcvolkmer.onco.datamapper.datacatalogues; + +import dev.pcvolkmer.onco.datamapper.exceptions.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.sql.ResultSet; + +/** + * Load raw result sets from database table 'patient' + * + * @author Paul-Christian Volkmer + * @since 0.1 + */ +public class PatientCatalogue { + + private final JdbcTemplate jdbcTemplate; + + private PatientCatalogue(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public static PatientCatalogue create(JdbcTemplate jdbcTemplate) { + return new PatientCatalogue(jdbcTemplate); + } + + /** + * Get patient result set by procedure id + * @param id The procedure id + * @return The procedure id + */ + public ResultSet getById(int id) { + var result = this.jdbcTemplate.query( + "SELECT * FROM patient WHERE id = ?", + (resultSet, i) -> resultSet, + id); + + if (result.isEmpty()) { + throw new DataAccessException("No patient record found for id: " + id); + } else if (result.size() > 1) { + throw new DataAccessException("Multiple patient records found for id: " + id); + } + + return result.get(0); + } + +} diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/PatientDataMapperTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/PatientDataMapperTest.java new file mode 100644 index 0000000..f86e2fd --- /dev/null +++ b/src/test/java/dev/pcvolkmer/onco/datamapper/PatientDataMapperTest.java @@ -0,0 +1,109 @@ +package dev.pcvolkmer.onco.datamapper; + +import dev.pcvolkmer.mv64e.mtb.Address; +import dev.pcvolkmer.mv64e.mtb.GenderCodingCode; +import dev.pcvolkmer.mv64e.mtb.Patient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +import javax.sql.DataSource; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.doAnswer; + +@ExtendWith(MockitoExtension.class) +class PatientDataMapperTest { + + JdbcTemplate jdbcTemplate; + + PatientDataMapper dataMapper; + + @BeforeEach + void setUp(@Mock JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.dataMapper = PatientDataMapper.create(jdbcTemplate); + } + + @Test + void shouldCreateDataMapper(@Mock DataSource dataSource) { + assertThat(MtbDataMapper.create(dataSource)).isNotNull(); + } + + @Test + void shouldCreatePatientAlive(@Mock ResultSet resultSet) throws SQLException { + var testData = Map.of( + "id", "1", + "geschlecht", "M", + "geburtsdatum", new java.sql.Date(Date.from(Instant.parse("2000-01-01T12:00:00Z")).getTime()), + "sterbedatum", new java.sql.Date(Date.from(Instant.parse("2024-06-19T12:00:00Z")).getTime()), + "GKZ", "06634022" + ); + + doAnswer(invocationOnMock -> { + var columnName = invocationOnMock.getArgument(0, String.class); + return testData.get(columnName); + }).when(resultSet).getString(anyString()); + + doAnswer(invocationOnMock -> { + var columnName = invocationOnMock.getArgument(0, String.class); + return testData.get(columnName); + }).when(resultSet).getDate(anyString()); + + doAnswer(invocationOnMock -> List.of(resultSet)) + .when(jdbcTemplate) + .query(anyString(), any(RowMapper.class), anyInt()); + + var actual = this.dataMapper.getById(1); + assertThat(actual).isInstanceOf(Patient.class); + assertThat(actual.getId()).isEqualTo("1"); + assertThat(actual.getGender().getCode()).isEqualTo(GenderCodingCode.MALE); + assertThat(actual.getBirthDate()).isEqualTo(Date.from(Instant.parse("2000-01-01T12:00:00Z"))); + assertThat(actual.getDateOfDeath()).isEqualTo(Date.from(Instant.parse("2024-06-19T12:00:00Z"))); + assertThat(actual.getAddress()).isEqualTo(Address.builder().municipalityCode("06634").build()); + } + + @Test + void shouldCreatePatientDead(@Mock ResultSet resultSet) throws SQLException { + var testData = Map.of( + "id", "1", + "geschlecht", "M", + "geburtsdatum", new java.sql.Date(Date.from(Instant.parse("2000-01-01T12:00:00Z")).getTime()), + "GKZ", "06634022" + ); + + doAnswer(invocationOnMock -> { + var columnName = invocationOnMock.getArgument(0, String.class); + return testData.get(columnName); + }).when(resultSet).getString(anyString()); + + doAnswer(invocationOnMock -> { + var columnName = invocationOnMock.getArgument(0, String.class); + return testData.get(columnName); + }).when(resultSet).getDate(anyString()); + + doAnswer(invocationOnMock -> List.of(resultSet)) + .when(jdbcTemplate) + .query(anyString(), any(RowMapper.class), anyInt()); + + var actual = this.dataMapper.getById(1); + assertThat(actual).isInstanceOf(Patient.class); + assertThat(actual.getId()).isEqualTo("1"); + assertThat(actual.getGender().getCode()).isEqualTo(GenderCodingCode.MALE); + assertThat(actual.getBirthDate()).isEqualTo(Date.from(Instant.parse("2000-01-01T12:00:00Z"))); + assertThat(actual.getDateOfDeath()).isNull(); + assertThat(actual.getAddress()).isEqualTo(Address.builder().municipalityCode("06634").build()); + } + +}