From 1ab33de3f88415f949c4a5fc725a0f3744b8af31 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Sun, 22 Jun 2025 19:11:24 +0200 Subject: [PATCH] feat: add initial support for recommendations --- .../pcvolkmer/onco/datamapper/ResultSet.java | 19 +--- .../datacatalogues/DataCatalogueFactory.java | 2 + .../EinzelempfehlungCatalogue.java | 26 +++++ .../EinzelempfehlungProzedurDataMapper.java | 101 ++++++++++++++++++ .../EinzelempfehlungWirkstoffDataMapper.java | 80 ++++++++++++++ .../datamapper/mapper/KpaEcogDataMapper.java | 2 +- .../mapper/KpaProzedurDataMapper.java | 6 +- .../mapper/KpaTherapielinieDataMapper.java | 6 +- .../onco/datamapper/mapper/MtbDataMapper.java | 6 +- .../mapper/TherapieplanDataMapper.java | 10 ++ .../EinzelempfehlungCatalogueTest.java | 62 +++++++++++ .../mapper/KpaEcogDataMapperTest.java | 2 +- .../mapper/KpaProzedurDataMapperTest.java | 4 +- .../KpaTherapielinieDataMapperTest.java | 4 +- .../mapper/TherapieplanDataMapperTest.java | 6 +- 15 files changed, 306 insertions(+), 30 deletions(-) create mode 100644 src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/EinzelempfehlungCatalogue.java create mode 100644 src/main/java/dev/pcvolkmer/onco/datamapper/mapper/EinzelempfehlungProzedurDataMapper.java create mode 100644 src/main/java/dev/pcvolkmer/onco/datamapper/mapper/EinzelempfehlungWirkstoffDataMapper.java create mode 100644 src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/EinzelempfehlungCatalogueTest.java diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/ResultSet.java b/src/main/java/dev/pcvolkmer/onco/datamapper/ResultSet.java index 9175ede..abd8cfc 100644 --- a/src/main/java/dev/pcvolkmer/onco/datamapper/ResultSet.java +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/ResultSet.java @@ -30,11 +30,11 @@ public class ResultSet { } /** - * Get the procedure id + * Get the id * - * @return The procedure id if any + * @return The procedures id */ - public Integer getProcedureId() { + public Integer getId() { var procedureId = this.getInteger("id"); if (procedureId == null) { throw new DataAccessException("No procedure id found"); @@ -42,19 +42,6 @@ public class ResultSet { return procedureId; } - /** - * Get the disease id - * - * @return The procedure id if any - */ - public Integer getDiseaseId() { - var diseaseId = this.getInteger("id"); - if (diseaseId == null) { - throw new DataAccessException("No disease id found"); - } - return diseaseId; - } - /** * Get column value as String and cast value if possible * 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 d51d4e4..4cc51dd 100644 --- a/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/DataCatalogueFactory.java +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/DataCatalogueFactory.java @@ -69,6 +69,8 @@ public class DataCatalogueFactory { return VorbefundeCatalogue.create(jdbcTemplate); } else if (c == TherapieplanCatalogue.class) { return TherapieplanCatalogue.create(jdbcTemplate); + } else if (c == EinzelempfehlungCatalogue.class) { + return EinzelempfehlungCatalogue.create(jdbcTemplate); } throw new DataCatalogueCreationException(clazz); }); diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/EinzelempfehlungCatalogue.java b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/EinzelempfehlungCatalogue.java new file mode 100644 index 0000000..b141032 --- /dev/null +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/EinzelempfehlungCatalogue.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_einzelempfehlung' + * + * @author Paul-Christian Volkmer + * @since 0.1 + */ +public class EinzelempfehlungCatalogue extends AbstractSubformDataCatalogue { + + private EinzelempfehlungCatalogue(JdbcTemplate jdbcTemplate) { + super(jdbcTemplate); + } + + @Override + protected String getTableName() { + return "dk_dnpm_uf_einzelempfehlung"; + } + + public static EinzelempfehlungCatalogue create(JdbcTemplate jdbcTemplate) { + return new EinzelempfehlungCatalogue(jdbcTemplate); + } + +} diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/EinzelempfehlungProzedurDataMapper.java b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/EinzelempfehlungProzedurDataMapper.java new file mode 100644 index 0000000..375f03a --- /dev/null +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/EinzelempfehlungProzedurDataMapper.java @@ -0,0 +1,101 @@ +package dev.pcvolkmer.onco.datamapper.mapper; + +import dev.pcvolkmer.mv64e.mtb.*; +import dev.pcvolkmer.onco.datamapper.ResultSet; +import dev.pcvolkmer.onco.datamapper.datacatalogues.EinzelempfehlungCatalogue; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static dev.pcvolkmer.onco.datamapper.mapper.MapperUtils.getPatientReference; + +/** + * Mapper class to load and map diagnosis data from database table 'dk_dnpm_einzelempfehlung' + * + * @author Paul-Christian Volkmer + * @since 0.1 + */ +public class EinzelempfehlungProzedurDataMapper extends AbstractSubformDataMapper { + + public EinzelempfehlungProzedurDataMapper(EinzelempfehlungCatalogue einzelempfehlungCatalogue) { + super(einzelempfehlungCatalogue); + } + + @Override + protected ProcedureRecommendation map(ResultSet resultSet) { + return ProcedureRecommendation.builder() + .id(resultSet.getString("id")) + .patient(getPatientReference(resultSet.getString("patient_id"))) + // TODO Fix id? + .reason(Reference.builder().id(resultSet.getString("id")).build()) + .issuedOn(resultSet.getDate("datum")) + .priority( + getRecommendationPriorityCoding( + resultSet.getString("evidenzlevel"), + resultSet.getInteger("evidenzlevel_propcat_version") + ) + ) + .code( + getMtbProcedureRecommendationCategoryCoding( + resultSet.getString("art_der_therapie"), + resultSet.getInteger("art_der_therapie_propcat_version") + ) + ) + .build(); + } + + @Override + public ProcedureRecommendation getById(int id) { + return this.map(this.catalogue.getById(id)); + } + + @Override + public List getByParentId(final int parentId) { + return catalogue.getAllByParentId(parentId) + .stream() + // Filter Prozedurempfehlung (Weitere Empfehlungen) + .filter(it -> it.getString("art_der_therapie") != null && !it.getString("art_der_therapie").isBlank() ) + .map(this::map) + .collect(Collectors.toList()); + } + + private RecommendationPriorityCoding getRecommendationPriorityCoding(String code, int version) { + if (code == null || !Arrays.stream(RecommendationPriorityCodingCode.values()).map(RecommendationPriorityCodingCode::toValue).collect(Collectors.toSet()).contains(code)) { + return null; + } + + var resultBuilder = RecommendationPriorityCoding.builder() + .system("dnpm-dip/recommendation/priority"); + + try { + resultBuilder + .code(RecommendationPriorityCodingCode.forValue(code)) + .display(code); + } catch (IOException e) { + return null; + } + + return resultBuilder.build(); + } + + private MtbProcedureRecommendationCategoryCoding getMtbProcedureRecommendationCategoryCoding(String code, int version) { + if (code == null || !Arrays.stream(MtbProcedureRecommendationCategoryCodingCode.values()).map(MtbProcedureRecommendationCategoryCodingCode::toValue).collect(Collectors.toSet()).contains(code)) { + return null; + } + + var resultBuilder = MtbProcedureRecommendationCategoryCoding.builder() + .system("dnpm-dip/mtb/recommendation/procedure/category"); + + try { + resultBuilder + .code(MtbProcedureRecommendationCategoryCodingCode.forValue(code)) + .display(code); + } catch (IOException e) { + return null; + } + + return resultBuilder.build(); + } +} diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/EinzelempfehlungWirkstoffDataMapper.java b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/EinzelempfehlungWirkstoffDataMapper.java new file mode 100644 index 0000000..c2cb602 --- /dev/null +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/EinzelempfehlungWirkstoffDataMapper.java @@ -0,0 +1,80 @@ +package dev.pcvolkmer.onco.datamapper.mapper; + +import dev.pcvolkmer.mv64e.mtb.MtbMedicationRecommendation; +import dev.pcvolkmer.mv64e.mtb.RecommendationPriorityCoding; +import dev.pcvolkmer.mv64e.mtb.RecommendationPriorityCodingCode; +import dev.pcvolkmer.mv64e.mtb.Reference; +import dev.pcvolkmer.onco.datamapper.ResultSet; +import dev.pcvolkmer.onco.datamapper.datacatalogues.EinzelempfehlungCatalogue; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static dev.pcvolkmer.onco.datamapper.mapper.MapperUtils.getPatientReference; + +/** + * Mapper class to load and map diagnosis data from database table 'dk_dnpm_einzelempfehlung' + * + * @author Paul-Christian Volkmer + * @since 0.1 + */ +public class EinzelempfehlungWirkstoffDataMapper extends AbstractSubformDataMapper { + + public EinzelempfehlungWirkstoffDataMapper(EinzelempfehlungCatalogue einzelempfehlungCatalogue) { + super(einzelempfehlungCatalogue); + } + + @Override + protected MtbMedicationRecommendation map(ResultSet resultSet) { + return MtbMedicationRecommendation.builder() + .id(resultSet.getString("id")) + .patient(getPatientReference(resultSet.getString("patient_id"))) + // TODO Fix id? + .reason(Reference.builder().id(resultSet.getString("id")).build()) + .issuedOn(resultSet.getDate("datum")) + .priority( + getRecommendationPriorityCoding( + resultSet.getString("evidenzlevel"), + resultSet.getInteger("evidenzlevel_propcat_version") + ) + ) + .build(); + } + + @Override + public MtbMedicationRecommendation getById(int id) { + return this.map(this.catalogue.getById(id)); + } + + @Override + public List getByParentId(final int parentId) { + return catalogue.getAllByParentId(parentId) + .stream() + // Filter Wirkstoffempfehlung (Systemische Therapie) + .filter(it -> it.getString("art_der_therapie") == null || it.getString("art_der_therapie").isBlank() ) + .map(this::map) + .collect(Collectors.toList()); + } + + private RecommendationPriorityCoding getRecommendationPriorityCoding(String code, int version) { + if (code == null || !Arrays.stream(RecommendationPriorityCodingCode.values()).map(RecommendationPriorityCodingCode::toValue).collect(Collectors.toSet()).contains(code)) { + return null; + } + + var resultBuilder = RecommendationPriorityCoding.builder() + .system("dnpm-dip/recommendation/priority"); + + try { + resultBuilder + .code(RecommendationPriorityCodingCode.forValue(code)) + .display(code); + } catch (IOException e) { + return null; + } + + return resultBuilder.build(); + } + +} diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaEcogDataMapper.java b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaEcogDataMapper.java index c53aed2..283321a 100644 --- a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaEcogDataMapper.java +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaEcogDataMapper.java @@ -51,7 +51,7 @@ public class KpaEcogDataMapper extends AbstractSubformDataMapper { ); var therapieplanCatalogue = catalogueFactory.catalogue(TherapieplanCatalogue.class); - var therapieplanDataMapper = new TherapieplanDataMapper(therapieplanCatalogue, propertyCatalogue); + var therapieplanDataMapper = new TherapieplanDataMapper( + therapieplanCatalogue, + catalogueFactory.catalogue(EinzelempfehlungCatalogue.class), + propertyCatalogue + ); var resultBuilder = Mtb.builder(); diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/TherapieplanDataMapper.java b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/TherapieplanDataMapper.java index 9c35df6..1ef55d0 100644 --- a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/TherapieplanDataMapper.java +++ b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/TherapieplanDataMapper.java @@ -2,6 +2,7 @@ package dev.pcvolkmer.onco.datamapper.mapper; import dev.pcvolkmer.mv64e.mtb.MtbCarePlan; import dev.pcvolkmer.onco.datamapper.PropertyCatalogue; +import dev.pcvolkmer.onco.datamapper.datacatalogues.EinzelempfehlungCatalogue; import dev.pcvolkmer.onco.datamapper.datacatalogues.TherapieplanCatalogue; import static dev.pcvolkmer.onco.datamapper.mapper.MapperUtils.getPatientReference; @@ -17,12 +18,19 @@ public class TherapieplanDataMapper implements DataMapper { private final TherapieplanCatalogue therapieplanCatalogue; private final PropertyCatalogue propertyCatalogue; + private final EinzelempfehlungProzedurDataMapper einzelempfehlungProzedurDataMapper; + private final EinzelempfehlungWirkstoffDataMapper einzelempfehlungWirkstoffDataMapper; + public TherapieplanDataMapper( final TherapieplanCatalogue therapieplanCatalogue, + final EinzelempfehlungCatalogue einzelempfehlungCatalogue, final PropertyCatalogue propertyCatalogue ) { this.therapieplanCatalogue = therapieplanCatalogue; this.propertyCatalogue = propertyCatalogue; + + this.einzelempfehlungProzedurDataMapper = new EinzelempfehlungProzedurDataMapper(einzelempfehlungCatalogue); + this.einzelempfehlungWirkstoffDataMapper = new EinzelempfehlungWirkstoffDataMapper(einzelempfehlungCatalogue); } /** @@ -40,6 +48,8 @@ public class TherapieplanDataMapper implements DataMapper { .id(therapieplanData.getString("id")) .patient(getPatientReference(therapieplanData.getString("patient_id"))) .issuedOn(therapieplanData.getDate("datum")) + .medicationRecommendations(einzelempfehlungWirkstoffDataMapper.getByParentId(id)) + .procedureRecommendations(einzelempfehlungProzedurDataMapper.getByParentId(id)) ; return builder.build(); } diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/EinzelempfehlungCatalogueTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/EinzelempfehlungCatalogueTest.java new file mode 100644 index 0000000..abab5b3 --- /dev/null +++ b/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/EinzelempfehlungCatalogueTest.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 EinzelempfehlungCatalogueTest { + + JdbcTemplate jdbcTemplate; + EinzelempfehlungCatalogue catalogue; + + @BeforeEach + void setUp(@Mock JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + this.catalogue = EinzelempfehlungCatalogue.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_einzelempfehlung JOIN prozedur ON (prozedur.id = dk_dnpm_uf_einzelempfehlung.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_einzelempfehlung JOIN prozedur ON (prozedur.id = dk_dnpm_uf_einzelempfehlung.id) WHERE geloescht = 0 AND hauptprozedur_id = ?"); + } + +} diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaEcogDataMapperTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaEcogDataMapperTest.java index d2869b9..2d98621 100644 --- a/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaEcogDataMapperTest.java +++ b/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaEcogDataMapperTest.java @@ -55,7 +55,7 @@ class KpaEcogDataMapperTest { return testData.get(columnName); }).when(resultSet).getDate(anyString()); - when(resultSet.getProcedureId()).thenReturn(1); + when(resultSet.getId()).thenReturn(1); doAnswer(invocationOnMock -> List.of(resultSet)) .when(catalogue) diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaProzedurDataMapperTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaProzedurDataMapperTest.java index 5c15101..d678a11 100644 --- a/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaProzedurDataMapperTest.java +++ b/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaProzedurDataMapperTest.java @@ -69,8 +69,8 @@ class KpaProzedurDataMapperTest { return testData.get(columnName); }).when(resultSet).getDate(anyString()); - when(resultSet.getDiseaseId()).thenReturn(1); - when(resultSet.getProcedureId()).thenReturn(1); + when(resultSet.getInteger(anyString())).thenReturn(1); + when(resultSet.getId()).thenReturn(1); doAnswer(invocationOnMock -> List.of(resultSet)) .when(catalogue) diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaTherapielinieDataMapperTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaTherapielinieDataMapperTest.java index e616812..17465f1 100644 --- a/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaTherapielinieDataMapperTest.java +++ b/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/KpaTherapielinieDataMapperTest.java @@ -68,8 +68,8 @@ class KpaTherapielinieDataMapperTest { return testData.get(columnName); }).when(resultSet).getDate(anyString()); - when(resultSet.getDiseaseId()).thenReturn(1); - when(resultSet.getProcedureId()).thenReturn(1); + when(resultSet.getInteger(anyString())).thenReturn(1); + when(resultSet.getId()).thenReturn(1); doAnswer(invocationOnMock -> List.of(resultSet)) .when(catalogue) diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/TherapieplanDataMapperTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/TherapieplanDataMapperTest.java index e7f1ed0..7e62c37 100644 --- a/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/TherapieplanDataMapperTest.java +++ b/src/test/java/dev/pcvolkmer/onco/datamapper/mapper/TherapieplanDataMapperTest.java @@ -4,6 +4,7 @@ import dev.pcvolkmer.mv64e.mtb.MtbCarePlan; import dev.pcvolkmer.mv64e.mtb.Reference; import dev.pcvolkmer.onco.datamapper.PropertyCatalogue; import dev.pcvolkmer.onco.datamapper.ResultSet; +import dev.pcvolkmer.onco.datamapper.datacatalogues.EinzelempfehlungCatalogue; import dev.pcvolkmer.onco.datamapper.datacatalogues.TherapieplanCatalogue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,6 +23,7 @@ import static org.mockito.Mockito.doAnswer; class TherapieplanDataMapperTest { TherapieplanCatalogue therapieplanCatalogue; + EinzelempfehlungCatalogue einzelempfehlungCatalogue; PropertyCatalogue propertyCatalogue; TherapieplanDataMapper dataMapper; @@ -29,11 +31,13 @@ class TherapieplanDataMapperTest { @BeforeEach void setUp( @Mock TherapieplanCatalogue therapieplanCatalogue, + @Mock EinzelempfehlungCatalogue einzelempfehlungCatalogue, @Mock PropertyCatalogue propertyCatalogue ) { this.therapieplanCatalogue = therapieplanCatalogue; + this.einzelempfehlungCatalogue = einzelempfehlungCatalogue; this.propertyCatalogue = propertyCatalogue; - this.dataMapper = new TherapieplanDataMapper(therapieplanCatalogue, propertyCatalogue); + this.dataMapper = new TherapieplanDataMapper(therapieplanCatalogue, einzelempfehlungCatalogue, propertyCatalogue); } @Test