From 60cbb0ff8ac4160c215329bccd3c63561e09c326 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Mon, 30 Jun 2025 00:46:07 +0200 Subject: [PATCH] feat: add Tumor-Proben --- README.md | 42 +- .../datacatalogues/DataCatalogueFactory.java | 6 + .../MolekulargenetikCatalogue.java | 26 ++ .../datacatalogues/RebiopsieCatalogue.java | 26 ++ .../datacatalogues/ReevaluationCatalogue.java | 26 ++ .../MolekulargenetikToSpecimenDataMapper.java | 215 +++++++++ .../onco/datamapper/mapper/MtbDataMapper.java | 25 +- .../MolekulargenetikCatalogueTest.java | 62 +++ .../RebiopsieCatalogueTest.java | 62 +++ .../ReevaluationCatalogueTest.java | 62 +++ ...ekulargenetikToSpecimenDataMapperTest.java | 435 ++++++++++++++++++ 11 files changed, 964 insertions(+), 23 deletions(-) create mode 100644 src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenetikCatalogue.java create mode 100644 src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/RebiopsieCatalogue.java create mode 100644 src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/ReevaluationCatalogue.java create mode 100644 src/main/java/dev/pcvolkmer/onco/datamapper/mapper/MolekulargenetikToSpecimenDataMapper.java create mode 100644 src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenetikCatalogueTest.java create mode 100644 src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/RebiopsieCatalogueTest.java create mode 100644 src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/ReevaluationCatalogueTest.java create mode 100644 src/test/java/dev/pcvolkmer/onco/datamapper/mapper/MolekulargenetikToSpecimenDataMapperTest.java diff --git a/README.md b/README.md index 7c8a66d..a587efb 100644 --- a/README.md +++ b/README.md @@ -36,25 +36,25 @@ var jsonResult = Converter.toJsonString( ## Status -| DNPM-Datenmodell 2.1 - Bereich | Status | Anmerkung | -|----------------------------------|--------|----------------------------------------------------------------| -| Patient | ✅ | Verwendet Datenbank-ID, keine managing Site | -| Episoden | ✅ | | -| Diagnosen | ✅ | Entsprechend Formularaufbau nur Diagnose der aktuellen Episode | -| Verwandten-Diagnosen | ✅ | | -| Systemische Leitlinien-Therapien | ✅ | Siehe auch: https://github.com/dnpm-dip/mtb-model/issues/9 | -| Leitlinien-Prozeduren | ✅ | Siehe auch: https://github.com/dnpm-dip/mtb-model/issues/9 | -| ECOG-Verlauf | ✅ | | -| Tumor-Proben | ⌛ | Aktuell in Arbeit | -| vorherige Molekular-Diagnostik | ⌛ | Aktuell in Arbeit | -| Histologie-Berichte | ⌛ | Aktuell in Arbeit | -| IHC-Berichte | | | -| MSI-Befunde | | | -| NGS-Berichte | ⌛ | Aktuell in Arbeit | -| MTB-Beschlüsse | ⌛ | Aktuell in Arbeit | -| Follow-Up Verlauf | | | -| Antrag Kostenübernahme | | | -| Antwort Kostenübernahme | | | -| Therapien | | | -| Response Befunde | | | +| DNPM-Datenmodell 2.1 - Bereich | Status | Anmerkung | +|----------------------------------|--------|----------------------------------------------------------------------------| +| Patient | ✅ | Verwendet Datenbank-ID, keine managing Site | +| Episoden | ✅ | | +| Diagnosen | ✅ | Entsprechend Formularaufbau nur Diagnose der aktuellen Episode | +| Verwandten-Diagnosen | ✅ | | +| Systemische Leitlinien-Therapien | ✅ | Siehe auch: https://github.com/dnpm-dip/mtb-model/issues/9 | +| Leitlinien-Prozeduren | ✅ | Siehe auch: https://github.com/dnpm-dip/mtb-model/issues/9 | +| ECOG-Verlauf | ✅ | | +| Tumor-Proben | ⛅ | Best effort: Formular OS.Molekulargenetik erfüllt nicht alle Anforderungen | +| vorherige Molekular-Diagnostik | ⌛ | Aktuell in Arbeit | +| Histologie-Berichte | ⌛ | Aktuell in Arbeit | +| IHC-Berichte | | | +| MSI-Befunde | | | +| NGS-Berichte | ⌛ | Aktuell in Arbeit | +| MTB-Beschlüsse | ⌛ | Aktuell in Arbeit | +| Follow-Up Verlauf | | | +| Antrag Kostenübernahme | | | +| Antwort Kostenübernahme | | | +| Therapien | | | +| Response Befunde | | | diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/DataCatalogueFactory.java b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/DataCatalogueFactory.java index 4cc51dd..beb8927 100644 --- a/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/DataCatalogueFactory.java +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/DataCatalogueFactory.java @@ -71,6 +71,12 @@ public class DataCatalogueFactory { return TherapieplanCatalogue.create(jdbcTemplate); } else if (c == EinzelempfehlungCatalogue.class) { return EinzelempfehlungCatalogue.create(jdbcTemplate); + } else if (c == MolekulargenetikCatalogue.class) { + return MolekulargenetikCatalogue.create(jdbcTemplate); + } else if (c == RebiopsieCatalogue.class) { + return RebiopsieCatalogue.create(jdbcTemplate); + } else if (c == ReevaluationCatalogue.class) { + return ReevaluationCatalogue.create(jdbcTemplate); } throw new DataCatalogueCreationException(clazz); }); diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenetikCatalogue.java b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenetikCatalogue.java new file mode 100644 index 0000000..ad5d260 --- /dev/null +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenetikCatalogue.java @@ -0,0 +1,26 @@ +package dev.pcvolkmer.onco.datamapper.datacatalogues; + +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * Load raw result sets from database table 'dk_molekulargenetik' + * + * @author Paul-Christian Volkmer + * @since 0.1 + */ +public class MolekulargenetikCatalogue extends AbstractSubformDataCatalogue { + + private MolekulargenetikCatalogue(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + protected String getTableName() { + return "dk_molekulargenetik"; + } + + public static MolekulargenetikCatalogue create(JdbcTemplate jdbcTemplate) { + return new MolekulargenetikCatalogue(jdbcTemplate); + } + +} diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/RebiopsieCatalogue.java b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/RebiopsieCatalogue.java new file mode 100644 index 0000000..ec451f7 --- /dev/null +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/RebiopsieCatalogue.java @@ -0,0 +1,26 @@ +package dev.pcvolkmer.onco.datamapper.datacatalogues; + +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * Load raw result sets from database table 'dk_dnpm_uf_rebiopsie' + * + * @author Paul-Christian Volkmer + * @since 0.1 + */ +public class RebiopsieCatalogue extends AbstractSubformDataCatalogue { + + private RebiopsieCatalogue(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + protected String getTableName() { + return "dk_dnpm_uf_rebiopsie"; + } + + public static RebiopsieCatalogue create(JdbcTemplate jdbcTemplate) { + return new RebiopsieCatalogue(jdbcTemplate); + } + +} diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/ReevaluationCatalogue.java b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/ReevaluationCatalogue.java new file mode 100644 index 0000000..4119aff --- /dev/null +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/ReevaluationCatalogue.java @@ -0,0 +1,26 @@ +package dev.pcvolkmer.onco.datamapper.datacatalogues; + +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * Load raw result sets from database table 'dk_dnpm_uf_reevaluation' + * + * @author Paul-Christian Volkmer + * @since 0.1 + */ +public class ReevaluationCatalogue extends AbstractSubformDataCatalogue { + + private ReevaluationCatalogue(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + protected String getTableName() { + return "dk_dnpm_uf_reevaluation"; + } + + public static ReevaluationCatalogue create(JdbcTemplate jdbcTemplate) { + return new ReevaluationCatalogue(jdbcTemplate); + } + +} diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/MolekulargenetikToSpecimenDataMapper.java b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/MolekulargenetikToSpecimenDataMapper.java new file mode 100644 index 0000000..0dbc0ac --- /dev/null +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/MolekulargenetikToSpecimenDataMapper.java @@ -0,0 +1,215 @@ +package dev.pcvolkmer.onco.datamapper.mapper; + +import dev.pcvolkmer.mv64e.mtb.*; +import dev.pcvolkmer.onco.datamapper.PropertyCatalogue; +import dev.pcvolkmer.onco.datamapper.ResultSet; +import dev.pcvolkmer.onco.datamapper.datacatalogues.*; + +import java.util.List; +import java.util.stream.Collectors; + +import static dev.pcvolkmer.onco.datamapper.mapper.MapperUtils.getPatientReference; + +/** + * Mapper class to load and map patient data from database table 'dk_molekulargenetik' + * + * @author Paul-Christian Volkmer + * @since 0.1 + */ +public class MolekulargenetikToSpecimenDataMapper implements DataMapper { + + private final MolekulargenetikCatalogue molekulargenetikCatalogue; + private final TherapieplanCatalogue therapieplanCatalogue; + private final RebiopsieCatalogue rebiopsieCatalogue; + private final ReevaluationCatalogue reevaluationCatalogue; + private final EinzelempfehlungCatalogue einzelempfehlungCatalogue; + private final PropertyCatalogue propertyCatalogue; + + public MolekulargenetikToSpecimenDataMapper( + final MolekulargenetikCatalogue molekulargenetikCatalogue, + final TherapieplanCatalogue therapieplanCatalogue, + final RebiopsieCatalogue rebiopsieCatalogue, + final ReevaluationCatalogue reevaluationCatalogue, + final EinzelempfehlungCatalogue einzelempfehlungCatalogue, + final PropertyCatalogue propertyCatalogue + ) { + this.molekulargenetikCatalogue = molekulargenetikCatalogue; + this.therapieplanCatalogue = therapieplanCatalogue; + this.rebiopsieCatalogue = rebiopsieCatalogue; + this.reevaluationCatalogue = reevaluationCatalogue; + this.einzelempfehlungCatalogue = einzelempfehlungCatalogue; + this.propertyCatalogue = propertyCatalogue; + } + + /** + * Loads and maps a specimen using the database id + * The result does not include a diagnosis reference! + * + * @param id The database id of the procedure data set + * @return The loaded Patient data + */ + @Override + public TumorSpecimen getById(int id) { + var data = molekulargenetikCatalogue.getById(id); + + var builder = TumorSpecimen.builder(); + builder + .id(data.getString("id")) + .patient(getPatientReference(data.getString("patient_id"))) + .type(getTumorSpecimenCoding(data.getString("materialfixierung"))) + .collection(getCollection(data)) + // TODO add diagnosis later + ; + + + return builder.build(); + } + + /** + * Loads and maps specimens by using the referencing KPA database id + * + * @param kpaId The database id of the referencing KPA procedure data set + * @param diagnoseReferenz The reference object to the diagnosis + * @return The loaded Patient data + */ + public List getAllByKpaId(int kpaId, Reference diagnoseReferenz) { + var therapieplanIds = therapieplanCatalogue.getByKpaId(kpaId); + + var osMolGen = therapieplanIds.stream() + .map(einzelempfehlungCatalogue::getAllByParentId) + .flatMap(einzelempfehlungen -> + einzelempfehlungen + .stream() + .map(einzelempfehlung -> einzelempfehlung.getInteger("ref_molekulargenetik")) + ) + .collect(Collectors.toSet()); + + // Addition: Rebiopsie + osMolGen.addAll( + therapieplanIds.stream() + .map(rebiopsieCatalogue::getAllByParentId) + .flatMap(einzelempfehlungen -> + einzelempfehlungen + .stream() + .map(einzelempfehlung -> einzelempfehlung.getInteger("ref_molekulargenetik")) + ) + .collect(Collectors.toSet()) + ); + + // Addition: Reevaluation + osMolGen.addAll( + therapieplanIds.stream() + .map(reevaluationCatalogue::getAllByParentId) + .flatMap(einzelempfehlungen -> + einzelempfehlungen + .stream() + .map(einzelempfehlung -> einzelempfehlung.getInteger("ref_molekulargenetik")) + ) + .collect(Collectors.toSet()) + ); + + return osMolGen.stream() + .map(this::getById) + .peek(it -> it.setDiagnosis(diagnoseReferenz)) + .collect(Collectors.toList()); + } + + // TODO: Kein genaues Mapping mit Formular OS.Molekulargenetik möglich - best effort + private TumorSpecimenCoding getTumorSpecimenCoding(String value) { + if (value == null) { + return null; + } + + var resultBuilder = TumorSpecimenCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/type"); + + switch (value) { + case "2": + resultBuilder + .code(TumorSpecimenCodingCode.CRYO_FROZEN) + .display("Cryo-frozen"); + break; + case "3": + resultBuilder + .code(TumorSpecimenCodingCode.FFPE) + .display("FFPE"); + break; + default: + resultBuilder + .code(TumorSpecimenCodingCode.UNKNOWN) + .display("Unbekannt"); + break; + } + + return resultBuilder.build(); + } + + private Collection getCollection(ResultSet data) { + if (data == null || data.getString("entnahmemethode") == null || data.getString("probenmaterial") == null) { + return null; + } + + var methodBuilder = TumorSpecimenCollectionMethodCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/method"); + + switch (data.getString("entnahmemethode")) { + case "B": + methodBuilder + .code(TumorSpecimenCollectionMethodCodingCode.BIOPSY) + .display("Biopsie"); + break; + case "R": + methodBuilder + .code(TumorSpecimenCollectionMethodCodingCode.RESECTION) + .display("Resektat"); + break; + case "LB": + methodBuilder + .code(TumorSpecimenCollectionMethodCodingCode.LIQUID_BIOPSY) + .display("Liquid Biopsy"); + break; + case "Z": + methodBuilder + .code(TumorSpecimenCollectionMethodCodingCode.CYTOLOGY) + .display("Zytologie"); + break; + case "U": + default: + methodBuilder + .code(TumorSpecimenCollectionMethodCodingCode.UNKNOWN) + .display("Unbekannt"); + break; + } + + // TODO: Kein genaues Mapping mit Formular OS.Molekulargenetik möglich - best effort + var localizationBuilder = TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization"); + + switch (data.getString("probenmaterial")) { + case "T": + localizationBuilder + .code(TumorSpecimenCollectionLocalizationCodingCode.PRIMARY_TUMOR) + .display("Primärtumor"); + break; + case "LK": + case "M": + case "ITM": + case "SM": + localizationBuilder + .code(TumorSpecimenCollectionLocalizationCodingCode.METASTASIS) + .display("Metastase"); + break; + default: + localizationBuilder + .code(TumorSpecimenCollectionLocalizationCodingCode.UNKNOWN) + .display("Unbekannt"); + break; + } + + return Collection.builder() + .method(methodBuilder.build()) + .localization(localizationBuilder.build()) + .build(); + } + +} diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/MtbDataMapper.java b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/MtbDataMapper.java index e2d102d..ad47f7f 100644 --- a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/MtbDataMapper.java +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/MtbDataMapper.java @@ -1,6 +1,7 @@ package dev.pcvolkmer.onco.datamapper.mapper; import dev.pcvolkmer.mv64e.mtb.Mtb; +import dev.pcvolkmer.mv64e.mtb.Reference; import dev.pcvolkmer.onco.datamapper.PropertyCatalogue; import dev.pcvolkmer.onco.datamapper.datacatalogues.*; import dev.pcvolkmer.onco.datamapper.exceptions.DataAccessException; @@ -80,15 +81,26 @@ public class MtbDataMapper implements DataMapper { catalogueFactory.catalogue(EcogCatalogue.class) ); + var einzelempfehlungCatalogue = catalogueFactory.catalogue(EinzelempfehlungCatalogue.class); var therapieplanCatalogue = catalogueFactory.catalogue(TherapieplanCatalogue.class); var therapieplanDataMapper = new TherapieplanDataMapper( therapieplanCatalogue, - catalogueFactory.catalogue(EinzelempfehlungCatalogue.class), + einzelempfehlungCatalogue, propertyCatalogue ); var verwandteDataMapper = new KpaVerwandteDataMapper(catalogueFactory.catalogue(VerwandteCatalogue.class)); + var molekulargenetikCatalogue = catalogueFactory.catalogue(MolekulargenetikCatalogue.class); + var molekulargenetikToSpecimenDataMapper = new MolekulargenetikToSpecimenDataMapper( + molekulargenetikCatalogue, + therapieplanCatalogue, + catalogueFactory.catalogue(RebiopsieCatalogue.class), + catalogueFactory.catalogue(ReevaluationCatalogue.class), + einzelempfehlungCatalogue, + propertyCatalogue + ); + var resultBuilder = Mtb.builder(); try { @@ -96,11 +108,13 @@ public class MtbDataMapper implements DataMapper { var patient = patientDataMapper.getById(Integer.parseInt(kpaPatient.getId())); kpaPatient.setAddress(patient.getAddress()); + var diagnosis = diagnosisDataMapper.getById(kpaId); + resultBuilder .patient(kpaPatient) .episodesOfCare(List.of(mtbEpisodeDataMapper.getById(kpaId))) // DNPM Klinik/Anamnese - .diagnoses(List.of(diagnosisDataMapper.getById(kpaId))) + .diagnoses(List.of(diagnosis)) .guidelineProcedures(prozedurMapper.getByParentId(kpaId)) .guidelineTherapies(therapielinieMapper.getByParentId(kpaId)) .performanceStatus(ecogMapper.getByParentId(kpaId)) @@ -112,6 +126,13 @@ public class MtbDataMapper implements DataMapper { .map(therapieplanDataMapper::getById) .collect(Collectors.toList()) ) + // Tumorproben + .specimens( + molekulargenetikToSpecimenDataMapper.getAllByKpaId( + kpaId, + Reference.builder().id(diagnosis.getId()).type("MTBDiagnosis").build() + ) + ) ; } catch (DataAccessException e) { logger.error("Error while getting Mtb.", e); diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenetikCatalogueTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenetikCatalogueTest.java new file mode 100644 index 0000000..001a87c --- /dev/null +++ b/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenetikCatalogueTest.java @@ -0,0 +1,62 @@ +package dev.pcvolkmer.onco.datamapper.datacatalogues; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class MolekulargenetikCatalogueTest { + + JdbcTemplate jdbcTemplate; + MolekulargenetikCatalogue catalogue; + + @BeforeEach + void setUp(@Mock JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.catalogue = MolekulargenetikCatalogue.create(jdbcTemplate); + } + + @Test + void shouldUseCorrectQuery(@Mock Map resultSet) { + doAnswer(invocationOnMock -> List.of(resultSet)) + .when(jdbcTemplate) + .queryForList(anyString(), anyInt()); + + this.catalogue.getById(1); + + var captor = ArgumentCaptor.forClass(String.class); + verify(this.jdbcTemplate).queryForList(captor.capture(), anyInt()); + + assertThat(captor.getValue()) + .isEqualTo("SELECT * FROM dk_molekulargenetik JOIN prozedur ON (prozedur.id = dk_molekulargenetik.id) WHERE geloescht = 0 AND prozedur.id = ?"); + } + + @Test + void shouldUseCorrectSubformQuery(@Mock Map resultSet) { + doAnswer(invocationOnMock -> List.of(resultSet)) + .when(jdbcTemplate) + .queryForList(anyString(), anyInt()); + + this.catalogue.getAllByParentId(1); + + var captor = ArgumentCaptor.forClass(String.class); + verify(this.jdbcTemplate).queryForList(captor.capture(), anyInt()); + + assertThat(captor.getValue()) + .isEqualTo("SELECT * FROM dk_molekulargenetik JOIN prozedur ON (prozedur.id = dk_molekulargenetik.id) WHERE geloescht = 0 AND hauptprozedur_id = ?"); + } + +} diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/RebiopsieCatalogueTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/RebiopsieCatalogueTest.java new file mode 100644 index 0000000..b9522e9 --- /dev/null +++ b/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/RebiopsieCatalogueTest.java @@ -0,0 +1,62 @@ +package dev.pcvolkmer.onco.datamapper.datacatalogues; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class RebiopsieCatalogueTest { + + JdbcTemplate jdbcTemplate; + RebiopsieCatalogue catalogue; + + @BeforeEach + void setUp(@Mock JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.catalogue = RebiopsieCatalogue.create(jdbcTemplate); + } + + @Test + void shouldUseCorrectQuery(@Mock Map resultSet) { + doAnswer(invocationOnMock -> List.of(resultSet)) + .when(jdbcTemplate) + .queryForList(anyString(), anyInt()); + + this.catalogue.getById(1); + + var captor = ArgumentCaptor.forClass(String.class); + verify(this.jdbcTemplate).queryForList(captor.capture(), anyInt()); + + assertThat(captor.getValue()) + .isEqualTo("SELECT * FROM dk_dnpm_uf_rebiopsie JOIN prozedur ON (prozedur.id = dk_dnpm_uf_rebiopsie.id) WHERE geloescht = 0 AND prozedur.id = ?"); + } + + @Test + void shouldUseCorrectSubformQuery(@Mock Map resultSet) { + doAnswer(invocationOnMock -> List.of(resultSet)) + .when(jdbcTemplate) + .queryForList(anyString(), anyInt()); + + this.catalogue.getAllByParentId(1); + + var captor = ArgumentCaptor.forClass(String.class); + verify(this.jdbcTemplate).queryForList(captor.capture(), anyInt()); + + assertThat(captor.getValue()) + .isEqualTo("SELECT * FROM dk_dnpm_uf_rebiopsie JOIN prozedur ON (prozedur.id = dk_dnpm_uf_rebiopsie.id) WHERE geloescht = 0 AND hauptprozedur_id = ?"); + } + +} diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/ReevaluationCatalogueTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/ReevaluationCatalogueTest.java new file mode 100644 index 0000000..b974202 --- /dev/null +++ b/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/ReevaluationCatalogueTest.java @@ -0,0 +1,62 @@ +package dev.pcvolkmer.onco.datamapper.datacatalogues; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class ReevaluationCatalogueTest { + + JdbcTemplate jdbcTemplate; + ReevaluationCatalogue catalogue; + + @BeforeEach + void setUp(@Mock JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.catalogue = ReevaluationCatalogue.create(jdbcTemplate); + } + + @Test + void shouldUseCorrectQuery(@Mock Map resultSet) { + doAnswer(invocationOnMock -> List.of(resultSet)) + .when(jdbcTemplate) + .queryForList(anyString(), anyInt()); + + this.catalogue.getById(1); + + var captor = ArgumentCaptor.forClass(String.class); + verify(this.jdbcTemplate).queryForList(captor.capture(), anyInt()); + + assertThat(captor.getValue()) + .isEqualTo("SELECT * FROM dk_dnpm_uf_reevaluation JOIN prozedur ON (prozedur.id = dk_dnpm_uf_reevaluation.id) WHERE geloescht = 0 AND prozedur.id = ?"); + } + + @Test + void shouldUseCorrectSubformQuery(@Mock Map resultSet) { + doAnswer(invocationOnMock -> List.of(resultSet)) + .when(jdbcTemplate) + .queryForList(anyString(), anyInt()); + + this.catalogue.getAllByParentId(1); + + var captor = ArgumentCaptor.forClass(String.class); + verify(this.jdbcTemplate).queryForList(captor.capture(), anyInt()); + + assertThat(captor.getValue()) + .isEqualTo("SELECT * FROM dk_dnpm_uf_reevaluation JOIN prozedur ON (prozedur.id = dk_dnpm_uf_reevaluation.id) WHERE geloescht = 0 AND hauptprozedur_id = ?"); + } + +} diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/MolekulargenetikToSpecimenDataMapperTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/MolekulargenetikToSpecimenDataMapperTest.java new file mode 100644 index 0000000..4a24c17 --- /dev/null +++ b/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/MolekulargenetikToSpecimenDataMapperTest.java @@ -0,0 +1,435 @@ +package dev.pcvolkmer.onco.datamapper.mapper; + +import dev.pcvolkmer.mv64e.mtb.*; +import dev.pcvolkmer.onco.datamapper.PropertyCatalogue; +import dev.pcvolkmer.onco.datamapper.ResultSet; +import dev.pcvolkmer.onco.datamapper.datacatalogues.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class MolekulargenetikToSpecimenDataMapperTest { + + MolekulargenetikCatalogue molekulargenetikCatalogue; + TherapieplanCatalogue therapieplanCatalogue; + RebiopsieCatalogue rebiopsieCatalogue; + ReevaluationCatalogue reevaluationCatalogue; + EinzelempfehlungCatalogue einzelempfehlungCatalogue; + PropertyCatalogue propertyCatalogue; + + MolekulargenetikToSpecimenDataMapper mapper; + + @BeforeEach + void setUp( + @Mock MolekulargenetikCatalogue molekulargenetikCatalogue, + @Mock TherapieplanCatalogue therapieplanCatalogue, + @Mock RebiopsieCatalogue rebiopsieCatalogue, + @Mock ReevaluationCatalogue reevaluationCatalogue, + @Mock EinzelempfehlungCatalogue einzelempfehlungCatalogue, + @Mock PropertyCatalogue propertyCatalogue + ) { + this.molekulargenetikCatalogue = molekulargenetikCatalogue; + this.therapieplanCatalogue = therapieplanCatalogue; + this.rebiopsieCatalogue = rebiopsieCatalogue; + this.reevaluationCatalogue = reevaluationCatalogue; + this.einzelempfehlungCatalogue = einzelempfehlungCatalogue; + this.propertyCatalogue = propertyCatalogue; + + this.mapper = new MolekulargenetikToSpecimenDataMapper( + molekulargenetikCatalogue, + therapieplanCatalogue, + rebiopsieCatalogue, + reevaluationCatalogue, + einzelempfehlungCatalogue, + propertyCatalogue + ); + } + + @Test + void shouldFetchAllRelatedSpecimens() { + + // Mock Einzelempfehlungen ID + when(therapieplanCatalogue.getByKpaId(anyInt())) + .thenReturn(List.of(1, 2)); + + // Mock Rebiopsien - two referencing the same OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return List.of(ResultSet.from(Map.of("id", id, "ref_molekulargenetik", 40))); + }).when(rebiopsieCatalogue).getAllByParentId(anyInt()); + + // Mock Reevaluationen - two referencing the same OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return List.of(ResultSet.from(Map.of("id", id, "ref_molekulargenetik", 41))); + }).when(reevaluationCatalogue).getAllByParentId(anyInt()); + + // Mock Einzelempfehlungen - two referencing the same OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return List.of(ResultSet.from(Map.of("id", id, "ref_molekulargenetik", 42))); + }).when(einzelempfehlungCatalogue).getAllByParentId(anyInt()); + + // Mock OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return ResultSet.from( + Map.of( + "id", id, + "patient_id", 4711, + "entnahmemethode", "B", + "probenmaterial", "T" + ) + ); + }).when(molekulargenetikCatalogue).getById(anyInt()); + + var actual = this.mapper.getAllByKpaId(1, Reference.builder().build()); + + assertThat(actual).hasSize(3); + + assertThat(actual.get(0).getId()) + .isEqualTo("40"); + assertThat(actual.get(1).getId()) + .isEqualTo("41"); + assertThat(actual.get(2).getId()) + .isEqualTo("42"); + + assertThat(actual.get(0).getPatient()) + .isEqualTo(Reference.builder().id("4711").type("Patient").build()); + } + + @Test + void shouldNotFetchRelatedSpecimensTwice() { + + // Mock Einzelempfehlungen ID + when(therapieplanCatalogue.getByKpaId(anyInt())) + .thenReturn(List.of(1, 2)); + + // Mock Rebiopsien - two referencing the same OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return List.of(ResultSet.from(Map.of("id", id, "ref_molekulargenetik", 40))); + }).when(rebiopsieCatalogue).getAllByParentId(anyInt()); + + // Mock Reevaluationen - two referencing the same OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return List.of(ResultSet.from(Map.of("id", id, "ref_molekulargenetik", 40))); + }).when(reevaluationCatalogue).getAllByParentId(anyInt()); + + // Mock Einzelempfehlungen - two referencing the same OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return List.of(ResultSet.from(Map.of("id", id, "ref_molekulargenetik", 42))); + }).when(einzelempfehlungCatalogue).getAllByParentId(anyInt()); + + // Mock OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return ResultSet.from( + Map.of( + "id", id, + "patient_id", 4711, + "entnahmemethode", "B", + "probenmaterial", "T" + ) + ); + }).when(molekulargenetikCatalogue).getById(anyInt()); + + var actual = this.mapper.getAllByKpaId(1, Reference.builder().build()); + + assertThat(actual).hasSize(2); + + assertThat(actual.get(0).getId()) + .isEqualTo("40"); + assertThat(actual.get(1).getId()) + .isEqualTo("42"); + } + + @ParameterizedTest + @MethodSource("specimenTypeTestData") + void shouldReturnExpectedSpecimenType(String value, TumorSpecimenCoding coding) { + + // Mock Einzelempfehlungen ID + when(therapieplanCatalogue.getByKpaId(anyInt())) + .thenReturn(List.of(1, 2)); + + // Mock Einzelempfehlungen - two referencing the same OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return List.of(ResultSet.from(Map.of("id", id, "ref_molekulargenetik", 42))); + }).when(einzelempfehlungCatalogue).getAllByParentId(anyInt()); + + // Mock OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return ResultSet.from( + Map.of( + "id", id, + "patient_id", 4711, + "materialfixierung", value, + "entnahmemethode", "B", + "probenmaterial", "T" + ) + ); + }).when(molekulargenetikCatalogue).getById(anyInt()); + + var actual = this.mapper.getAllByKpaId(1, Reference.builder().build()); + + assertThat(actual).hasSize(1); + assertThat(actual.get(0).getType()).isEqualTo(coding); + } + + // Returns all available Onkostar values and - best effort - expected mapping + // See property catalogue OS.Material and https://ibmi-ut.atlassian.net/wiki/spaces/DAM/pages/698777783/ line 80 + static Stream specimenTypeTestData() { + return Stream.of( + Arguments.of( + "0", + TumorSpecimenCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/type").code(TumorSpecimenCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "1", + TumorSpecimenCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/type").code(TumorSpecimenCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "2", + TumorSpecimenCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/type").code(TumorSpecimenCodingCode.CRYO_FROZEN).display("Cryo-frozen") + .build() + ), + Arguments.of( + "3", + TumorSpecimenCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/type").code(TumorSpecimenCodingCode.FFPE).display("FFPE") + .build() + ), + Arguments.of( + "4", + TumorSpecimenCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/type").code(TumorSpecimenCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "9", + TumorSpecimenCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/type").code(TumorSpecimenCodingCode.UNKNOWN).display("Unbekannt") + .build() + ) + ); + } + + @ParameterizedTest + @MethodSource("specimenMethodTestData") + void shouldReturnExpectedSpecimenMethod(String value, TumorSpecimenCollectionMethodCoding coding) { + + // Mock Einzelempfehlungen ID + when(therapieplanCatalogue.getByKpaId(anyInt())) + .thenReturn(List.of(1, 2)); + + // Mock Einzelempfehlungen - two referencing the same OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return List.of(ResultSet.from(Map.of("id", id, "ref_molekulargenetik", 42))); + }).when(einzelempfehlungCatalogue).getAllByParentId(anyInt()); + + // Mock OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return ResultSet.from( + Map.of( + "id", id, + "patient_id", 4711, + "entnahmemethode", value, + "probenmaterial", "T" + ) + ); + }).when(molekulargenetikCatalogue).getById(anyInt()); + + var actual = this.mapper.getAllByKpaId(1, Reference.builder().build()); + + assertThat(actual).hasSize(1); + assertThat(actual.get(0).getCollection().getMethod()).isEqualTo(coding); + } + + // Returns all available Onkostar values and - best effort - expected mapping + // See property catalogue OS.MolDiagEntnahmemethode and https://ibmi-ut.atlassian.net/wiki/spaces/DAM/pages/698777783/ line 84 + static Stream specimenMethodTestData() { + return Stream.of( + Arguments.of( + "B", + TumorSpecimenCollectionMethodCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/method").code(TumorSpecimenCollectionMethodCodingCode.BIOPSY).display("Biopsie") + .build() + ), + Arguments.of( + "R", + TumorSpecimenCollectionMethodCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/method").code(TumorSpecimenCollectionMethodCodingCode.RESECTION).display("Resektat") + .build() + ), + Arguments.of( + "LB", + TumorSpecimenCollectionMethodCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/method").code(TumorSpecimenCollectionMethodCodingCode.LIQUID_BIOPSY).display("Liquid Biopsy") + .build() + ), + Arguments.of( + "Z", + TumorSpecimenCollectionMethodCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/method").code(TumorSpecimenCollectionMethodCodingCode.CYTOLOGY).display("Zytologie") + .build() + ), + Arguments.of( + "U", + TumorSpecimenCollectionMethodCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/method").code(TumorSpecimenCollectionMethodCodingCode.UNKNOWN).display("Unbekannt") + .build() + ) + ); + } + + @ParameterizedTest + @MethodSource("specimenLocalizationTestData") + void shouldReturnExpectedSpecimenLocalization(String value, TumorSpecimenCollectionLocalizationCoding coding) { + + // Mock Einzelempfehlungen ID + when(therapieplanCatalogue.getByKpaId(anyInt())) + .thenReturn(List.of(1, 2)); + + // Mock Einzelempfehlungen - two referencing the same OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return List.of(ResultSet.from(Map.of("id", id, "ref_molekulargenetik", 42))); + }).when(einzelempfehlungCatalogue).getAllByParentId(anyInt()); + + // Mock OS.Molekulargenetik + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + return ResultSet.from( + Map.of( + "id", id, + "patient_id", 4711, + "entnahmemethode", "B", + "probenmaterial", value + ) + ); + }).when(molekulargenetikCatalogue).getById(anyInt()); + + var actual = this.mapper.getAllByKpaId(1, Reference.builder().build()); + + assertThat(actual).hasSize(1); + assertThat(actual.get(0).getCollection().getLocalization()).isEqualTo(coding); + } + + // Returns all available Onkostar values and - best effort - expected mapping + // See property catalogue OS.Probenmaterial and https://ibmi-ut.atlassian.net/wiki/spaces/DAM/pages/698777783/ line 82 + static Stream specimenLocalizationTestData() { + return Stream.of( + Arguments.of( + "T", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.PRIMARY_TUMOR).display("Primärtumor") + .build() + ), + Arguments.of( + "R", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "LK", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.METASTASIS).display("Metastase") + .build() + ), + Arguments.of( + "M", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.METASTASIS).display("Metastase") + .build() + ), + Arguments.of( + "ITM", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.METASTASIS).display("Metastase") + .build() + ), + Arguments.of( + "SM", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.METASTASIS).display("Metastase") + .build() + ), + Arguments.of( + "KM", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "NG", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "AS", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "PLERG", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "B", + TumorSpecimenCollectionLocalizationCoding.builder(). + system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "L", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "U", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.UNKNOWN).display("Unbekannt") + .build() + ), + Arguments.of( + "S", + TumorSpecimenCollectionLocalizationCoding.builder() + .system("dnpm-dip/mtb/tumor-specimen/collection/localization").code(TumorSpecimenCollectionLocalizationCodingCode.UNKNOWN).display("Unbekannt") + .build() + ) + ); + } + +}