From 21c02ac06851afb46e751015aaeda734c4095041 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jul 2023 09:50:29 +0200 Subject: [PATCH 1/9] =?UTF-8?q?Issue=20#29:=20Servicemethoden=20um=20ECOG?= =?UTF-8?q?=20Status=20abzurufen=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 ++ .../DefaultSystemtherapieService.java | 46 ++++++++++++++- .../systemtherapie/SystemtherapieService.java | 56 +++++++++++++++++++ .../DefaultSystemtherapieServiceTest.java | 40 ++++++++++++- 4 files changed, 143 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 65940ef..c304c61 100644 --- a/pom.xml +++ b/pom.xml @@ -155,6 +155,12 @@ 2.3 test + + log4j + log4j + 1.2.17 + test + diff --git a/src/main/java/DNPM/services/systemtherapie/DefaultSystemtherapieService.java b/src/main/java/DNPM/services/systemtherapie/DefaultSystemtherapieService.java index 7c7e2b6..377e3d9 100644 --- a/src/main/java/DNPM/services/systemtherapie/DefaultSystemtherapieService.java +++ b/src/main/java/DNPM/services/systemtherapie/DefaultSystemtherapieService.java @@ -2,11 +2,12 @@ package DNPM.services.systemtherapie; import DNPM.services.SettingsService; import de.itc.onkostar.api.IOnkostarApi; +import de.itc.onkostar.api.Patient; import de.itc.onkostar.api.Procedure; +import de.itc.onkostar.api.ProcedureEditStateType; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; /** * Standardimplementierung des Systemtherapieservices @@ -15,6 +16,8 @@ import java.util.Map; */ public class DefaultSystemtherapieService implements SystemtherapieService { + private static final String ECOG_FIELD = "ECOGvorTherapie"; + private final IOnkostarApi onkostarApi; private final SettingsService settingsService; @@ -51,6 +54,43 @@ public class DefaultSystemtherapieService implements SystemtherapieService { return new OsSystemischeTherapieToProzedurwerteMapper(); } + /** + * Ermittelt den letzten bekannten ECOG-Status aus allen Systemtherapieformularen des Patienten + * + * @param patient Der zu verwendende Patient + * @return Der ECOG-Status als String oder leeres Optional + */ + @Override + public Optional latestEcogStatus(Patient patient) { + return ecogSatus(patient).stream() + .max(Comparator.comparing(EcogStatusWithDate::getDate)) + .map(EcogStatusWithDate::getStatus); + } + + /** + * Ermittelt jeden bekannten ECOG-Status aus allen Systemtherapieformularen des Patienten + * + * @param patient Der zu verwendende Patient + * @return Eine Liste mit Datum und ECOG-Status als String + */ + @Override + public List ecogSatus(Patient patient) { + return patient.getDiseases().stream() + .flatMap(disease -> onkostarApi.getProceduresForDiseaseByForm(disease.getId(), getFormName()).stream()) + .filter(procedure -> procedure.getEditState() == ProcedureEditStateType.COMPLETED) + .filter(procedure -> null != procedure.getStartDate()) + .sorted(Comparator.comparing(Procedure::getStartDate)) + .map(procedure -> { + try { + return new EcogStatusWithDate(procedure.getStartDate(), procedure.getValue(ECOG_FIELD).getString()); + } catch (IllegalArgumentException e) { + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + private String getFormName() { return settingsService .getSetting("systemtherapieform") diff --git a/src/main/java/DNPM/services/systemtherapie/SystemtherapieService.java b/src/main/java/DNPM/services/systemtherapie/SystemtherapieService.java index 00b7a6d..0102fc6 100644 --- a/src/main/java/DNPM/services/systemtherapie/SystemtherapieService.java +++ b/src/main/java/DNPM/services/systemtherapie/SystemtherapieService.java @@ -1,9 +1,13 @@ package DNPM.services.systemtherapie; +import de.itc.onkostar.api.Patient; import de.itc.onkostar.api.Procedure; +import org.springframework.util.Assert; +import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Optional; /** * Service für Systemtherapieformulare @@ -25,4 +29,56 @@ public interface SystemtherapieService { */ ProzedurToProzedurwerteMapper prozedurToProzedurwerteMapper(Procedure procedure); + /** + * Ermittelt den letzten bekannten ECOG-Status aus allen Systemtherapieformularen des Patienten + * @param patient Der zu verwendende Patient + * @return Der ECOG-Status als String oder leeres Optional + */ + Optional latestEcogStatus(Patient patient); + + /** + * Ermittelt jeden bekannten ECOG-Status aus allen Systemtherapieformularen des Patienten + * @param patient Der zu verwendende Patient + * @return Eine Liste mit Datum und ECOG-Status als String + */ + List ecogSatus(Patient patient); + + /** + * Datenklasse zum Abbilden des ECOG-Status und Datum + */ + class EcogStatusWithDate { + private Date date; + private String status; + + public EcogStatusWithDate(Date date, String status) { + Assert.notNull(date, "Date cannot be null"); + Assert.hasText(status, "Status cannot be empty String"); + Assert.isTrue(isValidEcogCode(status), "Not a valid ADT.LeistungszustandECOG code"); + this.date = date; + this.status = status; + } + + private boolean isValidEcogCode(String status) { + switch (status) { + case "1": + case "2": + case "3": + case "4": + case "5": + case "U": + return true; + default: + return false; + } + } + + public Date getDate() { + return date; + } + + public String getStatus() { + return status; + } + } + } diff --git a/src/test/java/DNPM/services/systemtherapie/DefaultSystemtherapieServiceTest.java b/src/test/java/DNPM/services/systemtherapie/DefaultSystemtherapieServiceTest.java index 40ce315..7852187 100644 --- a/src/test/java/DNPM/services/systemtherapie/DefaultSystemtherapieServiceTest.java +++ b/src/test/java/DNPM/services/systemtherapie/DefaultSystemtherapieServiceTest.java @@ -1,8 +1,7 @@ package DNPM.services.systemtherapie; import DNPM.services.SettingsService; -import de.itc.onkostar.api.IOnkostarApi; -import de.itc.onkostar.api.Procedure; +import de.itc.onkostar.api.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -12,6 +11,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.time.Instant; import java.util.*; import static org.assertj.core.api.Assertions.assertThat; @@ -94,4 +94,40 @@ class DefaultSystemtherapieServiceTest { .isExactlyInstanceOf(ArrayList.class) .hasSize(1); } + + @Test + void testShouldReturnListOfEcogStatusWithDate() { + doAnswer(invocationOnMock -> { + var disease = new Disease(onkostarApi); + disease.setId(1); + return List.of(disease); + }).when(this.onkostarApi).getDiseasesByPatientId(anyInt()); + + doAnswer(invocationOnMock -> { + var procedure1 = new Procedure(onkostarApi); + procedure1.setId(1); + procedure1.setFormName("OS.Systemische Therapie"); + procedure1.setStartDate(Date.from(Instant.parse("2023-07-01T06:00:00Z"))); + procedure1.setEditState(ProcedureEditStateType.COMPLETED); + procedure1.setValue("ECOGvorTherapie", new Item("ECOGvorTherapie", 1)); + + var procedure2 = new Procedure(onkostarApi); + procedure2.setId(2); + procedure2.setFormName("OS.Systemische Therapie"); + procedure2.setStartDate(Date.from(Instant.parse("2023-07-12T06:00:00Z"))); + procedure2.setEditState(ProcedureEditStateType.COMPLETED); + procedure2.setValue("ECOGvorTherapie", new Item("ECOGvorTherapie", 2)); + return List.of(procedure1, procedure2); + }).when(this.onkostarApi).getProceduresForDiseaseByForm(anyInt(), anyString()); + + var patient = new Patient(onkostarApi); + patient.setId(1); + + var actual = service.ecogSatus(patient); + + assertThat(actual) + .isNotNull() + .isExactlyInstanceOf(ArrayList.class) + .hasSize(2); + } } From a6238c14e3c08b4f7ee7045824eae4e47890ccd3 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jul 2023 10:11:11 +0200 Subject: [PATCH 2/9] =?UTF-8?q?Issue=20#29:=20Backend-Service=20f=C3=BCr?= =?UTF-8?q?=20ECOG-Status=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/DNPM/DNPMHelper.java | 19 ++++++++++++++++++- src/test/java/DNPM/DNPMHelperTest.java | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/java/DNPM/DNPMHelper.java b/src/main/java/DNPM/DNPMHelper.java index cc8126a..50b153e 100644 --- a/src/main/java/DNPM/DNPMHelper.java +++ b/src/main/java/DNPM/DNPMHelper.java @@ -45,7 +45,7 @@ public class DNPMHelper implements IProcedureAnalyzer { @Override public String getVersion() { - return "0.3.0"; + return "0.4.0"; } @Override @@ -249,4 +249,21 @@ public class DNPMHelper implements IProcedureAnalyzer { } } + + // TODO Achtung, keine Sicherheitsprüfung, darüber kann für jeden Patienten die Liste mit ECOG-Status abgerufen werden! + public List getEcogStatus(final Map input) { + var pid = AnalyzerUtils.getRequiredId(input, "PatientId"); + if (pid.isEmpty()) { + logger.error("Kein Parameter 'PatientId' angegeben, gebe leere Liste zurück"); + return List.of(); + } + + var patient = onkostarApi.getPatient(pid.get()); + if (null == patient) { + logger.error("Patient nicht gefunden, gebe leere Liste zurück"); + return List.of(); + } + + return systemtherapieService.ecogSatus(patient); + } } \ No newline at end of file diff --git a/src/test/java/DNPM/DNPMHelperTest.java b/src/test/java/DNPM/DNPMHelperTest.java index a90f2fb..cd0b81d 100644 --- a/src/test/java/DNPM/DNPMHelperTest.java +++ b/src/test/java/DNPM/DNPMHelperTest.java @@ -3,6 +3,7 @@ package DNPM; import DNPM.services.systemtherapie.SystemtherapieService; import de.itc.onkostar.api.IOnkostarApi; import de.itc.onkostar.api.Item; +import de.itc.onkostar.api.Patient; import de.itc.onkostar.api.Procedure; import org.hibernate.SQLQuery; import org.hibernate.Session; @@ -245,6 +246,23 @@ class DNPMHelperTest { assertThat(argumentCaptor.getValue()).contains("WHERE patient_id = 2 AND geloescht = 0"); } + @Test + void testShouldReturnEcogStatusList() { + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + var patient = new Patient(onkostarApi); + patient.setId(id); + return patient; + }).when(onkostarApi).getPatient(anyInt()); + + dnpmHelper.getEcogStatus(Map.of("PatientId", 42)); + + var argumentCaptor = ArgumentCaptor.forClass(Patient.class); + verify(systemtherapieService, times(1)).ecogSatus(argumentCaptor.capture()); + assertThat(argumentCaptor.getValue()).isNotNull(); + assertThat(argumentCaptor.getValue().getId()).isEqualTo(42); + } + } } From 862e4813730acd47e123730286c8e5f3c8ed8085 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jul 2023 10:35:21 +0200 Subject: [PATCH 3/9] =?UTF-8?q?Issue=20#29:=20M=C3=B6glicher=20Wert=20"0"?= =?UTF-8?q?=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/DNPM/services/systemtherapie/SystemtherapieService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/DNPM/services/systemtherapie/SystemtherapieService.java b/src/main/java/DNPM/services/systemtherapie/SystemtherapieService.java index 0102fc6..a664c05 100644 --- a/src/main/java/DNPM/services/systemtherapie/SystemtherapieService.java +++ b/src/main/java/DNPM/services/systemtherapie/SystemtherapieService.java @@ -60,6 +60,7 @@ public interface SystemtherapieService { private boolean isValidEcogCode(String status) { switch (status) { + case "0": case "1": case "2": case "3": From 9389acdf641016eaf9d9b217ce7d28866a1e9263 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jul 2023 12:55:07 +0200 Subject: [PATCH 4/9] =?UTF-8?q?Issue=20#29:=20F=C3=BCge=20neuer=20ECOG=20E?= =?UTF-8?q?intr=C3=A4ge=20in=20DNPM=20Klinik/Anamnese=20ein?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Es werden lediglich neue Einträge hinzugefügt, betrachtet wird hierbei das Datum der Feststellung und abgeschlossene Formulare Systemische Therapie. Es werden *keine* Einträge nachträglich verändert. --- .../DNPM/analyzer/SystemtherapieAnalyzer.java | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java diff --git a/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java b/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java new file mode 100644 index 0000000..78f2c84 --- /dev/null +++ b/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java @@ -0,0 +1,142 @@ +package DNPM.analyzer; + +import DNPM.services.systemtherapie.SystemtherapieService; +import de.itc.onkostar.api.Disease; +import de.itc.onkostar.api.IOnkostarApi; +import de.itc.onkostar.api.Item; +import de.itc.onkostar.api.Procedure; +import de.itc.onkostar.api.analysis.AnalyseTriggerEvent; +import de.itc.onkostar.api.analysis.AnalyzerRequirement; +import de.itc.onkostar.api.analysis.IProcedureAnalyzer; +import de.itc.onkostar.api.analysis.OnkostarPluginType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Diese Klasse implementiert ein Plugin, welches Aktionen nach Bearbeitung eines Formulars zur Systemtherapie durchführt. + * + * @since 0.4.0 + */ +@Component +public class SystemtherapieAnalyzer implements IProcedureAnalyzer { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final IOnkostarApi onkostarApi; + + private final SystemtherapieService systemtherapieService; + + public SystemtherapieAnalyzer( + final IOnkostarApi onkostarApi, + final SystemtherapieService systemtherapieService + ) { + this.onkostarApi = onkostarApi; + this.systemtherapieService = systemtherapieService; + } + + @Override + public OnkostarPluginType getType() { + return OnkostarPluginType.ANALYZER; + } + + @Override + public String getVersion() { + return "0.4.0"; + } + + @Override + public String getName() { + return "DNPM Systemtherapie Analyzer"; + } + + @Override + public String getDescription() { + return "Aktualisiert verknüpfte Formulare nach Änderungen im Formularen vom Typ Systemtherapie"; + } + + /** + * @deprecated + */ + @Override + public boolean isRelevantForDeletedProcedure() { + return true; + } + + @Override + public boolean isRelevantForAnalyzer(Procedure procedure, Disease disease) { + return null != procedure && ( + procedure.getFormName().equals("OS.Systemische Therapie") + || procedure.getFormName().equals("OS.Systemische Therapie.VarianteUKW") + ); + } + + @Override + public boolean isSynchronous() { + return false; + } + + @Override + public AnalyzerRequirement getRequirement() { + return AnalyzerRequirement.PROCEDURE; + } + + @Override + public Set getTriggerEvents() { + return Set.of( + AnalyseTriggerEvent.EDIT_SAVE, + AnalyseTriggerEvent.EDIT_LOCK, + AnalyseTriggerEvent.REORG + ); + } + + @Override + public void analyze(Procedure procedure, Disease disease) { + var date = procedure.getStartDate(); + var status = procedure.getValue("ECOGvorTherapie"); + + if (null == date || null == status) { + // Ignore + return; + } + + var ecogFromCompleted = systemtherapieService.ecogSatus(procedure.getPatient()); + + procedure.getPatient().getDiseases().stream() + .flatMap(d -> onkostarApi.getProceduresForDiseaseByForm(d.getId(), "DNPM Klinik/Anamnese").stream()) + .forEach(p -> { + var ufEcog = p.getValue("ECOGVerlauf"); + if (null != ufEcog && ufEcog.getValue() instanceof List) { + var shouldSave = false; + var existingDates = ((List>) ufEcog.getValue()).stream() + .map(v -> v.get("Datum").toString()) + .collect(Collectors.toList()); + for (var ecog : ecogFromCompleted) { + var formattedDate = new SimpleDateFormat("yyyy-MM-dd").format(ecog.getDate()); + if (! existingDates.contains(formattedDate)) { + var newSubProcedure = new Procedure(onkostarApi); + newSubProcedure.setStartDate(ecog.getDate()); + newSubProcedure.setValue("Datum", new Item("Datum", ecog.getDate())); + newSubProcedure.setValue("ECOG", new Item("ECOG", ecog.getStatus())); + p.addSubProcedure("ECOGVerlauf", newSubProcedure); + shouldSave = true; + } + } + if (shouldSave) { + try { + onkostarApi.saveProcedure(p, true); + } catch (Exception e) { + logger.error("Cannot update ECOG for procedure '{}'", p.getId()); + } + } + } + }); + } + +} From 27476863809d4f2a0a704afd3a2a30f3feafdde4 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 12 Jul 2023 14:52:09 +0200 Subject: [PATCH 5/9] =?UTF-8?q?Issue=20#29:=20Tests=20f=C3=BCr=20das=20Ein?= =?UTF-8?q?f=C3=BCgen=20in=20ECOG=20Verlauf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DNPM/analyzer/SystemtherapieAnalyzer.java | 22 ++++- .../analyzer/SystemtherapieAnalyzerTest.java | 95 +++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 src/test/java/DNPM/analyzer/SystemtherapieAnalyzerTest.java diff --git a/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java b/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java index 78f2c84..cf7dea8 100644 --- a/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java +++ b/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java @@ -107,6 +107,10 @@ public class SystemtherapieAnalyzer implements IProcedureAnalyzer { } var ecogFromCompleted = systemtherapieService.ecogSatus(procedure.getPatient()); + if (ecogFromCompleted.isEmpty()) { + // Nothing to do + return; + } procedure.getPatient().getDiseases().stream() .flatMap(d -> onkostarApi.getProceduresForDiseaseByForm(d.getId(), "DNPM Klinik/Anamnese").stream()) @@ -114,8 +118,8 @@ public class SystemtherapieAnalyzer implements IProcedureAnalyzer { var ufEcog = p.getValue("ECOGVerlauf"); if (null != ufEcog && ufEcog.getValue() instanceof List) { var shouldSave = false; - var existingDates = ((List>) ufEcog.getValue()).stream() - .map(v -> v.get("Datum").toString()) + var existingDates = ufEcog.>>getValue().stream() + .map(v -> v.get("Datum")) .collect(Collectors.toList()); for (var ecog : ecogFromCompleted) { var formattedDate = new SimpleDateFormat("yyyy-MM-dd").format(ecog.getDate()); @@ -135,6 +139,20 @@ public class SystemtherapieAnalyzer implements IProcedureAnalyzer { logger.error("Cannot update ECOG for procedure '{}'", p.getId()); } } + } else { + p.setValue("ECOGVerlauf", new Item("ECOGVerlauf", List.of())); + for (var ecog : ecogFromCompleted) { + var newSubProcedure = new Procedure(onkostarApi); + newSubProcedure.setStartDate(ecog.getDate()); + newSubProcedure.setValue("Datum", new Item("Datum", ecog.getDate())); + newSubProcedure.setValue("ECOG", new Item("ECOG", ecog.getStatus())); + p.addSubProcedure("ECOGVerlauf", newSubProcedure); + } + try { + onkostarApi.saveProcedure(p, true); + } catch (Exception e) { + logger.error("Create update ECOG for procedure '{}'", p.getId()); + } } }); } diff --git a/src/test/java/DNPM/analyzer/SystemtherapieAnalyzerTest.java b/src/test/java/DNPM/analyzer/SystemtherapieAnalyzerTest.java new file mode 100644 index 0000000..2450f96 --- /dev/null +++ b/src/test/java/DNPM/analyzer/SystemtherapieAnalyzerTest.java @@ -0,0 +1,95 @@ +package DNPM.analyzer; + +import DNPM.services.systemtherapie.SystemtherapieService; +import de.itc.onkostar.api.*; +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 java.util.Date; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class SystemtherapieAnalyzerTest { + + private IOnkostarApi onkostarApi; + + private SystemtherapieService systemtherapieService; + + private SystemtherapieAnalyzer systemtherapieAnalyzer; + + @BeforeEach + void setUp( + @Mock IOnkostarApi onkostarApi, + @Mock SystemtherapieService systemtherapieService + ) { + this.onkostarApi = onkostarApi; + this.systemtherapieService = systemtherapieService; + this.systemtherapieAnalyzer = new SystemtherapieAnalyzer(onkostarApi, systemtherapieService); + } + + @Test + void shouldInsertNewEcogStatus() throws Exception { + doAnswer(invocationOnMock -> List.of(new SystemtherapieService.EcogStatusWithDate(new Date(), "0"))) + .when(systemtherapieService).ecogSatus(any(Patient.class)); + + var patient = new Patient(onkostarApi); + patient.setId(1); + + var procedure = new Procedure(onkostarApi); + procedure.setId(1000); + procedure.setStartDate(new Date()); + procedure.setEditState(ProcedureEditStateType.COMPLETED); + procedure.setPatientId(1); + procedure.setPatient(patient); + procedure.setValue("ECOGvorTherapie", new Item("ECOGvorTherapie", 1)); + + doAnswer(invocationOnMock -> { + var disease = new Disease(onkostarApi); + disease.setId(42); + return List.of(disease); + }).when(this.onkostarApi).getDiseasesByPatientId(anyInt()); + + doAnswer(invocationOnMock -> List.of(procedure)).when(onkostarApi).getProceduresForDiseaseByForm(anyInt(), anyString()); + + systemtherapieAnalyzer.analyze(procedure, null); + + var idCaptor = ArgumentCaptor.forClass(Integer.class); + var formNameCaptor = ArgumentCaptor.forClass(String.class); + verify(onkostarApi, times(1)).getProceduresForDiseaseByForm(idCaptor.capture(), formNameCaptor.capture()); + assertThat(idCaptor.getValue()).isEqualTo(42); + assertThat(formNameCaptor.getValue()).isEqualTo("DNPM Klinik/Anamnese"); + + verify(onkostarApi, times(1)).saveProcedure(any(Procedure.class), anyBoolean()); + } + + @Test + void shouldNotModifyEcogStatusIfNoCompletedSystemTherapy() throws Exception { + doAnswer(invocationOnMock -> List.of()) + .when(systemtherapieService).ecogSatus(any(Patient.class)); + + var patient = new Patient(onkostarApi); + patient.setId(1); + + var procedure = new Procedure(onkostarApi); + procedure.setId(1000); + procedure.setStartDate(new Date()); + procedure.setEditState(ProcedureEditStateType.COMPLETED); + procedure.setPatientId(1); + procedure.setPatient(patient); + procedure.setValue("ECOGvorTherapie", new Item("ECOGvorTherapie", 1)); + + systemtherapieAnalyzer.analyze(procedure, null); + + verify(onkostarApi, times(0)).getProceduresForDiseaseByForm(anyInt(), anyString()); + verify(onkostarApi, times(0)).saveProcedure(any(Procedure.class), anyBoolean()); + } + +} From cf9a68f6cd1a689145c52728309269e466c0113e Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Mon, 28 Aug 2023 11:51:46 +0200 Subject: [PATCH 6/9] Issue #29: Methoden zum Anlegen und Aktualisieren des Ecog-Verlaufs extrahiert --- .../DNPM/analyzer/SystemtherapieAnalyzer.java | 80 ++++++++++--------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java b/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java index cf7dea8..16fe577 100644 --- a/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java +++ b/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java @@ -73,7 +73,7 @@ public class SystemtherapieAnalyzer implements IProcedureAnalyzer { public boolean isRelevantForAnalyzer(Procedure procedure, Disease disease) { return null != procedure && ( procedure.getFormName().equals("OS.Systemische Therapie") - || procedure.getFormName().equals("OS.Systemische Therapie.VarianteUKW") + || procedure.getFormName().equals("OS.Systemische Therapie.VarianteUKW") ); } @@ -117,44 +117,52 @@ public class SystemtherapieAnalyzer implements IProcedureAnalyzer { .forEach(p -> { var ufEcog = p.getValue("ECOGVerlauf"); if (null != ufEcog && ufEcog.getValue() instanceof List) { - var shouldSave = false; - var existingDates = ufEcog.>>getValue().stream() - .map(v -> v.get("Datum")) - .collect(Collectors.toList()); - for (var ecog : ecogFromCompleted) { - var formattedDate = new SimpleDateFormat("yyyy-MM-dd").format(ecog.getDate()); - if (! existingDates.contains(formattedDate)) { - var newSubProcedure = new Procedure(onkostarApi); - newSubProcedure.setStartDate(ecog.getDate()); - newSubProcedure.setValue("Datum", new Item("Datum", ecog.getDate())); - newSubProcedure.setValue("ECOG", new Item("ECOG", ecog.getStatus())); - p.addSubProcedure("ECOGVerlauf", newSubProcedure); - shouldSave = true; - } - } - if (shouldSave) { - try { - onkostarApi.saveProcedure(p, true); - } catch (Exception e) { - logger.error("Cannot update ECOG for procedure '{}'", p.getId()); - } - } + updateExistingEcogVerlauf(p, ecogFromCompleted, ufEcog); } else { - p.setValue("ECOGVerlauf", new Item("ECOGVerlauf", List.of())); - for (var ecog : ecogFromCompleted) { - var newSubProcedure = new Procedure(onkostarApi); - newSubProcedure.setStartDate(ecog.getDate()); - newSubProcedure.setValue("Datum", new Item("Datum", ecog.getDate())); - newSubProcedure.setValue("ECOG", new Item("ECOG", ecog.getStatus())); - p.addSubProcedure("ECOGVerlauf", newSubProcedure); - } - try { - onkostarApi.saveProcedure(p, true); - } catch (Exception e) { - logger.error("Create update ECOG for procedure '{}'", p.getId()); - } + newEcogverlauf(p, ecogFromCompleted); } }); } + private void updateExistingEcogVerlauf(Procedure p, List ecogFromCompleted, Item ufEcog) { + var shouldSave = false; + var existingDates = ufEcog.>>getValue().stream() + .map(v -> v.get("Datum")) + .collect(Collectors.toList()); + for (var ecog : ecogFromCompleted) { + var formattedDate = new SimpleDateFormat("yyyy-MM-dd").format(ecog.getDate()); + if (!existingDates.contains(formattedDate)) { + var newSubProcedure = new Procedure(onkostarApi); + newSubProcedure.setStartDate(ecog.getDate()); + newSubProcedure.setValue("Datum", new Item("Datum", ecog.getDate())); + newSubProcedure.setValue("ECOG", new Item("ECOG", ecog.getStatus())); + p.addSubProcedure("ECOGVerlauf", newSubProcedure); + shouldSave = true; + } + } + if (shouldSave) { + try { + onkostarApi.saveProcedure(p, true); + } catch (Exception e) { + logger.error("Cannot update ECOG for procedure '{}'", p.getId()); + } + } + } + + private void newEcogverlauf(Procedure p, List ecogFromCompleted) { + p.setValue("ECOGVerlauf", new Item("ECOGVerlauf", List.of())); + for (var ecog : ecogFromCompleted) { + var newSubProcedure = new Procedure(onkostarApi); + newSubProcedure.setStartDate(ecog.getDate()); + newSubProcedure.setValue("Datum", new Item("Datum", ecog.getDate())); + newSubProcedure.setValue("ECOG", new Item("ECOG", ecog.getStatus())); + p.addSubProcedure("ECOGVerlauf", newSubProcedure); + } + try { + onkostarApi.saveProcedure(p, true); + } catch (Exception e) { + logger.error("Create update ECOG for procedure '{}'", p.getId()); + } + } + } From aef9dd7889ddf03cc75f5d682a26380ac6fb75d3 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Mon, 28 Aug 2023 12:17:29 +0200 Subject: [PATCH 7/9] Issue #29: Aktualisiere keinen EcogStatus vor Diagnosedatum --- .../DNPM/analyzer/SystemtherapieAnalyzer.java | 8 ++- .../analyzer/SystemtherapieAnalyzerTest.java | 66 +++++++++++++++---- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java b/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java index 16fe577..e0d3037 100644 --- a/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java +++ b/src/main/java/DNPM/analyzer/SystemtherapieAnalyzer.java @@ -71,7 +71,7 @@ public class SystemtherapieAnalyzer implements IProcedureAnalyzer { @Override public boolean isRelevantForAnalyzer(Procedure procedure, Disease disease) { - return null != procedure && ( + return null != procedure && null != disease && ( procedure.getFormName().equals("OS.Systemische Therapie") || procedure.getFormName().equals("OS.Systemische Therapie.VarianteUKW") ); @@ -106,7 +106,11 @@ public class SystemtherapieAnalyzer implements IProcedureAnalyzer { return; } - var ecogFromCompleted = systemtherapieService.ecogSatus(procedure.getPatient()); + var ecogFromCompleted = systemtherapieService.ecogSatus(procedure.getPatient()) + .stream() + .filter(ecogStatusWithDate -> ecogStatusWithDate.getDate().after(disease.getDiagnosisDate())) + .collect(Collectors.toList()); + if (ecogFromCompleted.isEmpty()) { // Nothing to do return; diff --git a/src/test/java/DNPM/analyzer/SystemtherapieAnalyzerTest.java b/src/test/java/DNPM/analyzer/SystemtherapieAnalyzerTest.java index 2450f96..4028aec 100644 --- a/src/test/java/DNPM/analyzer/SystemtherapieAnalyzerTest.java +++ b/src/test/java/DNPM/analyzer/SystemtherapieAnalyzerTest.java @@ -9,6 +9,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.List; @@ -25,10 +27,21 @@ class SystemtherapieAnalyzerTest { private SystemtherapieAnalyzer systemtherapieAnalyzer; + private Disease dummyDisease(int id, Date diagnosisDate) { + var disease = new Disease(onkostarApi); + disease.setId(id); + disease.setDiagnosisDate(diagnosisDate); + return disease; + } + + private Date daysPassed(int days) { + return Date.from(Instant.now().minus(days, ChronoUnit.DAYS)); + } + @BeforeEach void setUp( - @Mock IOnkostarApi onkostarApi, - @Mock SystemtherapieService systemtherapieService + @Mock IOnkostarApi onkostarApi, + @Mock SystemtherapieService systemtherapieService ) { this.onkostarApi = onkostarApi; this.systemtherapieService = systemtherapieService; @@ -37,7 +50,11 @@ class SystemtherapieAnalyzerTest { @Test void shouldInsertNewEcogStatus() throws Exception { - doAnswer(invocationOnMock -> List.of(new SystemtherapieService.EcogStatusWithDate(new Date(), "0"))) + final var diagnosisDate = daysPassed(7); + final var ecogDate = daysPassed(1); + final var procedureDate = daysPassed(1); + + doAnswer(invocationOnMock -> List.of(new SystemtherapieService.EcogStatusWithDate(ecogDate, "0"))) .when(systemtherapieService).ecogSatus(any(Patient.class)); var patient = new Patient(onkostarApi); @@ -45,21 +62,17 @@ class SystemtherapieAnalyzerTest { var procedure = new Procedure(onkostarApi); procedure.setId(1000); - procedure.setStartDate(new Date()); + procedure.setStartDate(procedureDate); procedure.setEditState(ProcedureEditStateType.COMPLETED); procedure.setPatientId(1); procedure.setPatient(patient); procedure.setValue("ECOGvorTherapie", new Item("ECOGvorTherapie", 1)); - doAnswer(invocationOnMock -> { - var disease = new Disease(onkostarApi); - disease.setId(42); - return List.of(disease); - }).when(this.onkostarApi).getDiseasesByPatientId(anyInt()); + doAnswer(invocationOnMock -> List.of(dummyDisease(42, diagnosisDate))).when(this.onkostarApi).getDiseasesByPatientId(anyInt()); doAnswer(invocationOnMock -> List.of(procedure)).when(onkostarApi).getProceduresForDiseaseByForm(anyInt(), anyString()); - systemtherapieAnalyzer.analyze(procedure, null); + systemtherapieAnalyzer.analyze(procedure, dummyDisease(10, diagnosisDate)); var idCaptor = ArgumentCaptor.forClass(Integer.class); var formNameCaptor = ArgumentCaptor.forClass(String.class); @@ -72,6 +85,9 @@ class SystemtherapieAnalyzerTest { @Test void shouldNotModifyEcogStatusIfNoCompletedSystemTherapy() throws Exception { + final var diagnosisDate = daysPassed(7); + final var procedureDate = daysPassed(1); + doAnswer(invocationOnMock -> List.of()) .when(systemtherapieService).ecogSatus(any(Patient.class)); @@ -80,13 +96,39 @@ class SystemtherapieAnalyzerTest { var procedure = new Procedure(onkostarApi); procedure.setId(1000); - procedure.setStartDate(new Date()); + procedure.setStartDate(procedureDate); procedure.setEditState(ProcedureEditStateType.COMPLETED); procedure.setPatientId(1); procedure.setPatient(patient); procedure.setValue("ECOGvorTherapie", new Item("ECOGvorTherapie", 1)); - systemtherapieAnalyzer.analyze(procedure, null); + systemtherapieAnalyzer.analyze(procedure, dummyDisease(10, diagnosisDate)); + + verify(onkostarApi, times(0)).getProceduresForDiseaseByForm(anyInt(), anyString()); + verify(onkostarApi, times(0)).saveProcedure(any(Procedure.class), anyBoolean()); + } + + @Test + void shouldNotIncludeEcogStatusBeforeDiagnosisDate() throws Exception { + final var diagnosisDate = daysPassed(7); + final var ecogDate = daysPassed(28); + final var procedureDate = daysPassed(1); + + doAnswer(invocationOnMock -> List.of(new SystemtherapieService.EcogStatusWithDate(ecogDate, "0"))) + .when(systemtherapieService).ecogSatus(any(Patient.class)); + + var patient = new Patient(onkostarApi); + patient.setId(1); + + var procedure = new Procedure(onkostarApi); + procedure.setId(1000); + procedure.setStartDate(procedureDate); + procedure.setEditState(ProcedureEditStateType.COMPLETED); + procedure.setPatientId(1); + procedure.setPatient(patient); + procedure.setValue("ECOGvorTherapie", new Item("ECOGvorTherapie", 1)); + + systemtherapieAnalyzer.analyze(procedure, dummyDisease(10, diagnosisDate)); verify(onkostarApi, times(0)).getProceduresForDiseaseByForm(anyInt(), anyString()); verify(onkostarApi, times(0)).saveProcedure(any(Procedure.class), anyBoolean()); From 74a6e7e79a56b016781d5eb07f85f1d40ef5b511 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Mon, 28 Aug 2023 14:17:39 +0200 Subject: [PATCH 8/9] Issue #29: Add example script to insert ECOG Verlauf on form creation --- .../klinik_anamnese-getEcogStatus.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 examples/uf-einzelempfehlung/klinik_anamnese-getEcogStatus.js diff --git a/examples/uf-einzelempfehlung/klinik_anamnese-getEcogStatus.js b/examples/uf-einzelempfehlung/klinik_anamnese-getEcogStatus.js new file mode 100644 index 0000000..02a2fea --- /dev/null +++ b/examples/uf-einzelempfehlung/klinik_anamnese-getEcogStatus.js @@ -0,0 +1,27 @@ +// To be included in Script "Beim Neuanlegen" of form "DNPM Klink/Anamnese" + +executePluginMethod('DNPMHelper', 'getEcogStatus', {PatientId: getPatient().id}, (resp) => { + if (resp.status.code === 1) { + // Hack: Get version id of ECOG status as stored in Database + // by using initial empty entry and its version. + // Since OS always creates an initial empty entry for subforms + // this can be used to get required version id from within a form script. + let version = getFieldValue('ECOGVerlauf')[0].ECOG.version; + + // Abort if no version available. + if (version == null) { + return; + } + + let uf = resp.result.map(item => { + let date = new Date(item.date).toISOString().split('T')[0]; + let ecog = []; + ecog.val = item.status; + ecog.version = version; + return { + Datum: [date, 'exact'], ECOG: ecog + }; + }); + setFieldValue('ECOGVerlauf', uf); + } +}, false); \ No newline at end of file From 35f1aa0d757900e1a5d22d04ab85c9b22882a07b Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Mon, 28 Aug 2023 14:39:43 +0200 Subject: [PATCH 9/9] Issue #29: Abgesicherter Zugriff auf ECOG Verlauf --- src/main/java/DNPM/DNPMHelper.java | 19 ++++++++++++++-- src/test/java/DNPM/DNPMHelperTest.java | 30 ++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/main/java/DNPM/DNPMHelper.java b/src/main/java/DNPM/DNPMHelper.java index 50b153e..838ca9a 100644 --- a/src/main/java/DNPM/DNPMHelper.java +++ b/src/main/java/DNPM/DNPMHelper.java @@ -1,6 +1,9 @@ package DNPM; import DNPM.analyzer.AnalyzerUtils; +import DNPM.security.IllegalSecuredObjectAccessException; +import DNPM.security.PermissionType; +import DNPM.security.PersonPoolBasedPermissionEvaluator; import DNPM.services.systemtherapie.SystemtherapieService; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -17,6 +20,7 @@ import org.hibernate.transform.Transformers; import org.hibernate.type.StandardBasicTypes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.security.core.context.SecurityContextHolder; import java.util.ArrayList; import java.util.HashMap; @@ -31,9 +35,16 @@ public class DNPMHelper implements IProcedureAnalyzer { private final SystemtherapieService systemtherapieService; - public DNPMHelper(final IOnkostarApi onkostarApi, final SystemtherapieService systemtherapieService) { + private final PersonPoolBasedPermissionEvaluator personPoolBasedPermissionEvaluator; + + public DNPMHelper( + final IOnkostarApi onkostarApi, + final SystemtherapieService systemtherapieService, + final PersonPoolBasedPermissionEvaluator permissionEvaluator + ) { this.onkostarApi = onkostarApi; this.systemtherapieService = systemtherapieService; + this.personPoolBasedPermissionEvaluator = permissionEvaluator; } @Override @@ -264,6 +275,10 @@ public class DNPMHelper implements IProcedureAnalyzer { return List.of(); } - return systemtherapieService.ecogSatus(patient); + if (personPoolBasedPermissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), patient, PermissionType.READ)) { + return systemtherapieService.ecogSatus(patient); + } + + throw new IllegalSecuredObjectAccessException("Kein Zugriff auf diesen Patienten"); } } \ No newline at end of file diff --git a/src/test/java/DNPM/DNPMHelperTest.java b/src/test/java/DNPM/DNPMHelperTest.java index cd0b81d..17e8901 100644 --- a/src/test/java/DNPM/DNPMHelperTest.java +++ b/src/test/java/DNPM/DNPMHelperTest.java @@ -1,5 +1,8 @@ package DNPM; +import DNPM.security.IllegalSecuredObjectAccessException; +import DNPM.security.PermissionType; +import DNPM.security.PersonPoolBasedPermissionEvaluator; import DNPM.services.systemtherapie.SystemtherapieService; import de.itc.onkostar.api.IOnkostarApi; import de.itc.onkostar.api.Item; @@ -22,6 +25,7 @@ import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) @@ -31,16 +35,20 @@ class DNPMHelperTest { private SystemtherapieService systemtherapieService; + private PersonPoolBasedPermissionEvaluator personPoolBasedPermissionEvaluator; + private DNPMHelper dnpmHelper; @BeforeEach void setup( @Mock IOnkostarApi onkostarApi, - @Mock SystemtherapieService systemtherapieService + @Mock SystemtherapieService systemtherapieService, + @Mock PersonPoolBasedPermissionEvaluator personPoolBasedPermissionEvaluator ) { this.onkostarApi = onkostarApi; this.systemtherapieService = systemtherapieService; - this.dnpmHelper = new DNPMHelper(onkostarApi, systemtherapieService); + this.personPoolBasedPermissionEvaluator = personPoolBasedPermissionEvaluator; + this.dnpmHelper = new DNPMHelper(onkostarApi, systemtherapieService, personPoolBasedPermissionEvaluator); } @Test @@ -248,6 +256,9 @@ class DNPMHelperTest { @Test void testShouldReturnEcogStatusList() { + when(personPoolBasedPermissionEvaluator.hasPermission(any(), any(Patient.class), any(PermissionType.class))) + .thenReturn(true); + doAnswer(invocationOnMock -> { var id = invocationOnMock.getArgument(0, Integer.class); var patient = new Patient(onkostarApi); @@ -263,6 +274,21 @@ class DNPMHelperTest { assertThat(argumentCaptor.getValue().getId()).isEqualTo(42); } + @Test + void testShouldNotReturnEcogStatusListIfNoPermissionGranted() { + when(personPoolBasedPermissionEvaluator.hasPermission(any(), any(Patient.class), any(PermissionType.class))) + .thenReturn(false); + + doAnswer(invocationOnMock -> { + var id = invocationOnMock.getArgument(0, Integer.class); + var patient = new Patient(onkostarApi); + patient.setId(id); + return patient; + }).when(onkostarApi).getPatient(anyInt()); + + assertThrows(IllegalSecuredObjectAccessException.class, () -> dnpmHelper.getEcogStatus(Map.of("PatientId", 42))); + } + } }