diff --git a/README.md b/README.md
index 5eb406e..6c00b11 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ Um Mithilfe wird gebeten.
| vorherige Molekular-Diagnostik | ✅ | |
| Histologie-Berichte | ✅ | |
| IHC-Berichte | - | Aktuell nicht vorgesehen |
-| MSI-Befunde | ⌛ | Aktuell in Arbeit, https://github.com/dnpm-dip/mtb-model/issues/10 ist behoben |
+| MSI-Befunde | ⛅ | Best effort: Formular OS.Molekulargenetik erfüllt nicht alle Anforderungen (2) |
| NGS-Berichte | ⛅ | Best effort: Formular OS.Molekulargenetik erfüllt nicht alle Anforderungen (2) |
| MTB-Beschlüsse | ✅ | Stützende molekulare Alteration(en) für einfache Variante und CNV (3) |
| Follow-Up Verlauf | - | Späterer Zeitpunkt |
@@ -67,7 +67,10 @@ Um Mithilfe wird gebeten.
1. Nicht alle möglichen Ausprägungen in `OS.Molekulargenetik` vorhanden.
2. Aktuell nicht alle Angaben effektiv im Formular `OS.Molekulargenetik` wie gefordert angebbar.
- Hinweis: Tumorzellgehalt-Methode problematisch, wenn auch im NGS-Bericht histologisch festgestellt.
+ Hinweise:
+ * Tumorzellgehalt-Methode problematisch, wenn auch im NGS-Bericht histologisch festgestellt.
+ * Angabe zu MSI-Interpretation fehlt in Formular, ist aber Pflichtangabe - Wird gefiltert.
+ * Datenbanktabelle für MSI lautet tatsächlich `dk_molekluargenmsi` [sic!]
3. Implementierung des Mappings von HGNC-Symbol (Gen-Name) zu HGNC-ID über enthaltene Gen-Liste.
## Enthaltene Liste mit Genen
diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/ResultSet.java b/src/main/java/dev/pcvolkmer/onco/datamapper/ResultSet.java
index 329a78b..9df5f56 100644
--- a/src/main/java/dev/pcvolkmer/onco/datamapper/ResultSet.java
+++ b/src/main/java/dev/pcvolkmer/onco/datamapper/ResultSet.java
@@ -140,6 +140,28 @@ public class ResultSet {
throw new IllegalArgumentException("Cannot convert " + raw.getClass() + " to Integer");
}
+ /**
+ * Get column value as Double and cast value if possible
+ *
+ * @param columnName The name of the column
+ * @return The column value as Integer
+ */
+ public Double getDouble(String columnName) {
+ var raw = this.rawData.get(columnName);
+
+ if (raw == null) {
+ return null;
+ } else if (raw instanceof Integer) {
+ return ((Integer) raw).doubleValue();
+ } else if (raw instanceof Long) {
+ return ((Long) raw).doubleValue();
+ } else if (raw instanceof Double) {
+ return ((Double) raw);
+ }
+
+ throw new IllegalArgumentException("Cannot convert " + raw.getClass() + " to Integer");
+ }
+
/**
* Get column value as Date 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 460af8c..44cbf46 100644
--- a/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/DataCatalogueFactory.java
+++ b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/DataCatalogueFactory.java
@@ -95,6 +95,8 @@ public class DataCatalogueFactory {
return MolekulargenetikCatalogue.create(jdbcTemplate);
} else if (c == MolekulargenuntersuchungCatalogue.class) {
return MolekulargenuntersuchungCatalogue.create(jdbcTemplate);
+ } else if (c == MolekulargenMsiCatalogue.class) {
+ return MolekulargenMsiCatalogue.create(jdbcTemplate);
} else if (c == RebiopsieCatalogue.class) {
return RebiopsieCatalogue.create(jdbcTemplate);
} else if (c == ReevaluationCatalogue.class) {
diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenMsiCatalogue.java b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenMsiCatalogue.java
new file mode 100644
index 0000000..44b4e56
--- /dev/null
+++ b/src/main/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenMsiCatalogue.java
@@ -0,0 +1,46 @@
+/*
+ * This file is part of mv64e-onkostar-data
+ *
+ * Copyright (C) 2025 Paul-Christian Volkmer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package dev.pcvolkmer.onco.datamapper.datacatalogues;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+
+/**
+ * Load raw result sets from database table 'dk_molekluargenmsi'
+ *
+ * @author Paul-Christian Volkmer
+ * @since 0.1
+ */
+public class MolekulargenMsiCatalogue extends AbstractSubformDataCatalogue {
+
+ private MolekulargenMsiCatalogue(JdbcTemplate jdbcTemplate) {
+ super(jdbcTemplate);
+ }
+
+ @Override
+ protected String getTableName() {
+ return "dk_molekluargenmsi";
+ }
+
+ public static MolekulargenMsiCatalogue create(JdbcTemplate jdbcTemplate) {
+ return new MolekulargenMsiCatalogue(jdbcTemplate);
+ }
+
+}
diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaMolekulargenetikMsiDataMapper.java b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaMolekulargenetikMsiDataMapper.java
new file mode 100644
index 0000000..3b9857e
--- /dev/null
+++ b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaMolekulargenetikMsiDataMapper.java
@@ -0,0 +1,113 @@
+/*
+ * This file is part of mv64e-onkostar-data
+ *
+ * Copyright (C) 2025 Paul-Christian Volkmer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package dev.pcvolkmer.onco.datamapper.mapper;
+
+import dev.pcvolkmer.mv64e.mtb.Msi;
+import dev.pcvolkmer.mv64e.mtb.MsiMethodCoding;
+import dev.pcvolkmer.mv64e.mtb.MsiMethodCodingCode;
+import dev.pcvolkmer.mv64e.mtb.Reference;
+import dev.pcvolkmer.onco.datamapper.ResultSet;
+import dev.pcvolkmer.onco.datamapper.datacatalogues.MolekulargenMsiCatalogue;
+
+/**
+ * Mapper class to load and map prozedur data from database table 'dk_molekluargenetik'
+ *
+ * @author Paul-Christian Volkmer
+ * @since 0.1
+ */
+public class KpaMolekulargenetikMsiDataMapper extends AbstractSubformDataMapper {
+
+ public KpaMolekulargenetikMsiDataMapper(
+ final MolekulargenMsiCatalogue molekulargenMsiCatalogue
+ ) {
+ super(molekulargenMsiCatalogue);
+ }
+
+ /**
+ * Loads and maps Prozedur related by database id
+ *
+ * @param id The database id of the procedure data set
+ * @return The loaded Procedure
+ */
+ @Override
+ public Msi getById(final int id) {
+ return this.map(catalogue.getById(id));
+ }
+
+ @Override
+ protected Msi map(ResultSet resultSet) {
+ var builder = Msi.builder();
+
+ if (!resultSet.getString("komplexerbiomarker").equals("MSI")) {
+ return null;
+ }
+
+ builder
+ .id(resultSet.getString("id"))
+ .patient(resultSet.getPatientReference())
+ .method(getMethodCode(resultSet))
+ .specimen(Reference.builder().id(resultSet.getString("hauptprozedur_id")).type("Specimen").build())
+ // Aktuell nicht in Onkostar vorhanden!
+ //.interpretation()
+ // In Onkostar nur für "Sequenzierung" bzw "BIOINFORMATIC" als Prozentwert angegeben => "0" als Fallback?
+ .value(getSeqProzentwert(resultSet))
+ ;
+
+
+ return builder.build();
+ }
+
+ private MsiMethodCoding getMethodCode(final ResultSet resultSet) {
+ var builder = MsiMethodCoding.builder()
+ .system("dnpm-dip/mtb/msi/method");
+
+ var analysemethoden = resultSet.getMerkmalList("analysemethoden");
+
+ // Achtung: Immer nur eine Methode wird betrachtet! In Onkostar sind gleichzeitig mehrere Angaben möglich!
+ if (analysemethoden == null) {
+ return null;
+ } else if (analysemethoden.contains("S")) {
+ builder.code(MsiMethodCodingCode.BIOINFORMATIC);
+ builder.display(MsiMethodCodingCode.BIOINFORMATIC.toString());
+ } else if (analysemethoden.contains("P")) {
+ builder.code(MsiMethodCodingCode.PCR);
+ builder.display(MsiMethodCodingCode.PCR.toString());
+ } else if (analysemethoden.contains("I")) {
+ builder.code(MsiMethodCodingCode.IHC);
+ builder.display(MsiMethodCodingCode.IHC.toString());
+ } else {
+ return null;
+ }
+
+ return builder.build();
+ }
+
+ private double getSeqProzentwert(final ResultSet resultSet) {
+ var analysemethoden = resultSet.getMerkmalList("analysemethoden");
+
+ // Achtung: Immer nur eine Methode wird betrachtet! In Onkostar sind gleichzeitig mehrere Angaben möglich!
+ if (analysemethoden != null && analysemethoden.contains("S")) {
+ return resultSet.getDouble("seqprozentwert");
+ }
+
+ return 0;
+ }
+}
diff --git a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaMolekulargenetikDataMapper.java b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaMolekulargenetikNgsDataMapper.java
similarity index 98%
rename from src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaMolekulargenetikDataMapper.java
rename to src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaMolekulargenetikNgsDataMapper.java
index 6774805..82a4632 100644
--- a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaMolekulargenetikDataMapper.java
+++ b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/KpaMolekulargenetikNgsDataMapper.java
@@ -38,12 +38,12 @@ import java.util.stream.Collectors;
* @author Paul-Christian Volkmer
* @since 0.1
*/
-public class KpaMolekulargenetikDataMapper implements DataMapper {
+public class KpaMolekulargenetikNgsDataMapper implements DataMapper {
private final MolekulargenetikCatalogue catalogue;
private final MolekulargenuntersuchungCatalogue untersuchungCatalogue;
- public KpaMolekulargenetikDataMapper(
+ public KpaMolekulargenetikNgsDataMapper(
final MolekulargenetikCatalogue catalogue,
final MolekulargenuntersuchungCatalogue untersuchungCatalogue,
final PropertyCatalogue propertyCatalogue
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 01b9120..7eff4be 100644
--- a/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/MtbDataMapper.java
+++ b/src/main/java/dev/pcvolkmer/onco/datamapper/mapper/MtbDataMapper.java
@@ -124,7 +124,9 @@ public class MtbDataMapper implements DataMapper {
catalogueFactory.catalogue(HistologieCatalogue.class)
);
- var kpaMolekulargenetikDataMapper = new KpaMolekulargenetikDataMapper(molekulargenetikCatalogue, catalogueFactory.catalogue(MolekulargenuntersuchungCatalogue.class), propertyCatalogue);
+ var kpaMolekulargenetikNgsDataMapper = new KpaMolekulargenetikNgsDataMapper(molekulargenetikCatalogue, catalogueFactory.catalogue(MolekulargenuntersuchungCatalogue.class), propertyCatalogue);
+ var kpaMolekulargenetikMsiDataMapper = new KpaMolekulargenetikMsiDataMapper(catalogueFactory.catalogue(MolekulargenMsiCatalogue.class));
+
var kpaVorbefundeDataMapper = new KpaVorbefundeDataMapper(
catalogueFactory.catalogue(VorbefundeCatalogue.class),
@@ -196,7 +198,16 @@ public class MtbDataMapper implements DataMapper {
)
// NGS Berichte
.ngsReports(
- kpaMolekulargenetikDataMapper.getAllByKpaId(kpaId)
+ kpaMolekulargenetikNgsDataMapper.getAllByKpaId(kpaId)
+ )
+ // MSI Befunde
+ .msiFindings(
+ kpaMolekulargenetikNgsDataMapper.getAllByKpaId(kpaId).stream()
+ .map(ngs -> Integer.parseInt(ngs.getId()))
+ .flatMap(ngsId -> kpaMolekulargenetikMsiDataMapper.getByParentId(ngsId).stream())
+ // Filtere alle MSI: Nur mit Angabe Interpretation!
+ .filter(msi -> msi.getInterpretation() != null)
+ .collect(Collectors.toList())
)
;
diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/ResultSetTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/ResultSetTest.java
index 793dcc3..07baaca 100644
--- a/src/test/java/dev/pcvolkmer/onco/datamapper/ResultSetTest.java
+++ b/src/test/java/dev/pcvolkmer/onco/datamapper/ResultSetTest.java
@@ -55,6 +55,13 @@ class ResultSetTest {
assertThat(data.getLong("int")).isEqualTo(42L);
}
+ @Test
+ void shouldReturnDoubleValues() {
+ var data = getTestData();
+
+ assertThat(data.getDouble("int")).isEqualTo(42);
+ }
+
@Test
void shouldReturnDateValues() {
var data = getTestData();
diff --git a/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenMsiCatalogueTest.java b/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenMsiCatalogueTest.java
new file mode 100644
index 0000000..baf420b
--- /dev/null
+++ b/src/test/java/dev/pcvolkmer/onco/datamapper/datacatalogues/MolekulargenMsiCatalogueTest.java
@@ -0,0 +1,126 @@
+/*
+ * This file is part of mv64e-onkostar-data
+ *
+ * Copyright (C) 2025 Paul-Christian Volkmer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+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.ArrayList;
+import java.util.HashMap;
+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.*;
+
+@ExtendWith(MockitoExtension.class)
+class MolekulargenMsiCatalogueTest {
+
+ JdbcTemplate jdbcTemplate;
+ MolekulargenMsiCatalogue catalogue;
+
+ @BeforeEach
+ void setUp(@Mock JdbcTemplate jdbcTemplate) {
+ this.jdbcTemplate = jdbcTemplate;
+ this.catalogue = MolekulargenMsiCatalogue.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 patient.patienten_id, dk_molekluargenmsi.*, prozedur.patient_id, prozedur.hauptprozedur_id FROM dk_molekluargenmsi JOIN prozedur ON (prozedur.id = dk_molekluargenmsi.id) JOIN patient ON (patient.id = prozedur.patient_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 patient.patienten_id, dk_molekluargenmsi.*, prozedur.patient_id, prozedur.hauptprozedur_id FROM dk_molekluargenmsi JOIN prozedur ON (prozedur.id = dk_molekluargenmsi.id) JOIN patient ON (patient.id = prozedur.patient_id) WHERE geloescht = 0 AND hauptprozedur_id = ?");
+ }
+
+ @Test
+ void shouldUseCorrectMerkmalQuery(@Mock Map resultSet) {
+ when(resultSet.get(anyString()))
+ .thenReturn(Map.of("feldname", "name", "feldwert", "wert"));
+
+ doAnswer(invocationOnMock -> List.of(resultSet))
+ .when(jdbcTemplate)
+ .queryForList(anyString(), anyInt());
+
+ this.catalogue.getMerkmaleById(1);
+
+ var captor = ArgumentCaptor.forClass(String.class);
+ verify(this.jdbcTemplate).queryForList(captor.capture(), anyInt());
+
+ assertThat(captor.getValue())
+ .isEqualTo("SELECT feldname, feldwert FROM dk_molekluargenmsi_merkmale WHERE eintrag_id = ?");
+ }
+
+ @Test
+ void shouldUseMerkmalList() {
+ doAnswer(invocationOnMock -> {
+ var sql = invocationOnMock.getArgument(0, String.class);
+ ArrayList