From 1efb394d447b20157ac3092bf8c14c0858d5d47a Mon Sep 17 00:00:00 2001 From: ao508 <15623749+ao508@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:09:47 -0400 Subject: [PATCH] Allow Cohort sample list updates (#1212) Signed-off-by: Angelica Ochoa <15623749+ao508@users.noreply.github.com> --- .../org/mskcc/smile/model/tempo/Cohort.java | 21 +++++++++- .../smile/service/CohortCompleteService.java | 5 ++- .../impl/CohortCompleteServiceImpl.java | 19 ++++++--- .../smile/service/impl/SampleServiceImpl.java | 3 +- .../impl/TempoMessageHandlingServiceImpl.java | 19 +++++---- .../mskcc/smile/service/TempoServiceTest.java | 40 +++++++++++++++++-- ...lete_CCS_PPPQQQQ_updated_samples_only.json | 32 +++++++++++++++ .../data/tempo/mocked_tempo_data_details.txt | 1 + 8 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ_updated_samples_only.json diff --git a/model/src/main/java/org/mskcc/smile/model/tempo/Cohort.java b/model/src/main/java/org/mskcc/smile/model/tempo/Cohort.java index f9e567e2..55c78263 100644 --- a/model/src/main/java/org/mskcc/smile/model/tempo/Cohort.java +++ b/model/src/main/java/org/mskcc/smile/model/tempo/Cohort.java @@ -1,9 +1,12 @@ package org.mskcc.smile.model.tempo; import java.io.Serializable; +import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.commons.lang.builder.ToStringBuilder; import org.mskcc.smile.model.SmileSample; import org.mskcc.smile.model.tempo.json.CohortCompleteJson; @@ -103,7 +106,7 @@ public void setCohortSamples(List cohortSamples) { /** * Returns latest cohort complete data. - * @return + * @return CohortComplete */ public CohortComplete getLatestCohortComplete() { if (cohortCompleteList != null && !cohortCompleteList.isEmpty()) { @@ -116,6 +119,22 @@ public CohortComplete getLatestCohortComplete() { return null; } + /** + * Returns sample ids as set of strings. + * @return Set + * @throws ParseException + */ + public Set getCohortSamplePrimaryIds() throws ParseException { + if (cohortSamples == null) { + return new HashSet<>(); + } + Set sampleIds = new HashSet<>(); + for (SmileSample sample : cohortSamples) { + sampleIds.add(sample.getPrimarySampleAlias()); + } + return sampleIds; + } + @Override public String toString() { return ToStringBuilder.reflectionToString(this); diff --git a/service/src/main/java/org/mskcc/smile/service/CohortCompleteService.java b/service/src/main/java/org/mskcc/smile/service/CohortCompleteService.java index ce9cc595..b59aefe9 100644 --- a/service/src/main/java/org/mskcc/smile/service/CohortCompleteService.java +++ b/service/src/main/java/org/mskcc/smile/service/CohortCompleteService.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.Set; import org.mskcc.smile.model.tempo.Cohort; -import org.mskcc.smile.model.tempo.CohortComplete; /** * @@ -13,6 +12,8 @@ public interface CohortCompleteService { Cohort saveCohort(Cohort cohort, Set samplePrimaryIds) throws Exception; Cohort getCohortByCohortId(String cohortId) throws Exception; List getCohortsBySamplePrimaryId(String primaryId) throws Exception; - Boolean hasUpdates(Cohort cohort, CohortComplete newCohortComplete) throws Exception; + Boolean hasUpdates(Cohort existingCohort, Cohort cohort) throws Exception; + Boolean hasCohortCompleteUpdates(Cohort existingCohort, Cohort cohort) + throws Exception; Cohort updateCohort(Cohort cohort) throws Exception; } diff --git a/service/src/main/java/org/mskcc/smile/service/impl/CohortCompleteServiceImpl.java b/service/src/main/java/org/mskcc/smile/service/impl/CohortCompleteServiceImpl.java index d2825ce4..e1661e82 100644 --- a/service/src/main/java/org/mskcc/smile/service/impl/CohortCompleteServiceImpl.java +++ b/service/src/main/java/org/mskcc/smile/service/impl/CohortCompleteServiceImpl.java @@ -10,7 +10,6 @@ import org.mskcc.smile.commons.JsonComparator; import org.mskcc.smile.model.SmileSample; import org.mskcc.smile.model.tempo.Cohort; -import org.mskcc.smile.model.tempo.CohortComplete; import org.mskcc.smile.persistence.neo4j.CohortCompleteRepository; import org.mskcc.smile.service.CohortCompleteService; import org.mskcc.smile.service.SmileSampleService; @@ -87,10 +86,20 @@ public List getCohortsBySamplePrimaryId(String primaryId) throws Excepti } @Override - public Boolean hasUpdates(Cohort cohort, CohortComplete cohortComplete) throws Exception { - String existingCohortComplete = mapper.writeValueAsString(cohort.getLatestCohortComplete()); - String currentCohortComplete = mapper.writeValueAsString(cohortComplete); - return !jsonComparator.isConsistentGenericComparison(existingCohortComplete, currentCohortComplete); + public Boolean hasUpdates(Cohort existingCohort, Cohort cohort) throws Exception { + // check cohort complete data for updates first + Boolean hasUpdates = hasCohortCompleteUpdates(existingCohort, cohort); + Set newSamples = cohort.getCohortSamplePrimaryIds(); + newSamples.removeAll(existingCohort.getCohortSamplePrimaryIds()); + return (hasUpdates || !newSamples.isEmpty()); + } + + @Override + public Boolean hasCohortCompleteUpdates(Cohort existingCohort, Cohort cohort) throws Exception { + String existingCohortComplete = mapper.writeValueAsString(existingCohort.getLatestCohortComplete()); + String currentCohortComplete = mapper.writeValueAsString(cohort.getLatestCohortComplete()); + return !jsonComparator.isConsistentGenericComparison(existingCohortComplete, + currentCohortComplete); } @Override diff --git a/service/src/main/java/org/mskcc/smile/service/impl/SampleServiceImpl.java b/service/src/main/java/org/mskcc/smile/service/impl/SampleServiceImpl.java index 4b2d0da9..cd0681b7 100644 --- a/service/src/main/java/org/mskcc/smile/service/impl/SampleServiceImpl.java +++ b/service/src/main/java/org/mskcc/smile/service/impl/SampleServiceImpl.java @@ -419,7 +419,8 @@ public SmileSample getDetailedSmileSample(SmileSample sample) throws Exception { sample.setTempo(tempo); } catch (IncorrectResultSizeDataAccessException e) { if (e.getActualSize() < 0) { - LOG.warn("There is no TEMPO data for sample: " + sample.getPrimarySampleAlias()); + LOG.warn("There is no TEMPO data for sample: " + sample.getPrimarySampleAlias() + + " - TEMPO data will not be set for sample."); } else { StringBuilder b = new StringBuilder(); b.append("Error fetching TEMPO data for sample: ") diff --git a/service/src/main/java/org/mskcc/smile/service/impl/TempoMessageHandlingServiceImpl.java b/service/src/main/java/org/mskcc/smile/service/impl/TempoMessageHandlingServiceImpl.java index 2bbdef77..d4512a9b 100644 --- a/service/src/main/java/org/mskcc/smile/service/impl/TempoMessageHandlingServiceImpl.java +++ b/service/src/main/java/org/mskcc/smile/service/impl/TempoMessageHandlingServiceImpl.java @@ -3,11 +3,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.nats.client.Message; -import java.nio.charset.StandardCharsets; import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -26,7 +22,6 @@ import org.mskcc.smile.model.SmileSample; import org.mskcc.smile.model.tempo.BamComplete; import org.mskcc.smile.model.tempo.Cohort; -import org.mskcc.smile.model.tempo.CohortComplete; import org.mskcc.smile.model.tempo.MafComplete; import org.mskcc.smile.model.tempo.QcComplete; import org.mskcc.smile.model.tempo.Tempo; @@ -273,10 +268,18 @@ public void run() { // compiles them into a set list of strings cohortCompleteService.saveCohort(cohort, ccJson.getTumorNormalPairsAsSet()); } else if (cohortCompleteService.hasUpdates(existingCohort, - cohort.getLatestCohortComplete())) { + cohort)) { LOG.info("Received updates for cohort: " + ccJson.getCohortId()); - existingCohort.addCohortComplete(cohort.getLatestCohortComplete()); - cohortCompleteService.updateCohort(existingCohort); + if (cohortCompleteService.hasCohortCompleteUpdates(existingCohort, cohort)) { + existingCohort.addCohortComplete(cohort.getLatestCohortComplete()); + } + + // new samples refer to samples that aren't yet linked to the given cohort + Set newSamples = ccJson.getTumorNormalPairsAsSet(); + newSamples.removeAll(existingCohort.getCohortSamplePrimaryIds()); + + // persist updates to db + cohortCompleteService.saveCohort(existingCohort, newSamples); } else { LOG.error("Cohort " + ccJson.getCohortId() + " already exists and no new updates were received."); diff --git a/service/src/test/java/org/mskcc/smile/service/TempoServiceTest.java b/service/src/test/java/org/mskcc/smile/service/TempoServiceTest.java index 2d2bc681..4cb74cd8 100644 --- a/service/src/test/java/org/mskcc/smile/service/TempoServiceTest.java +++ b/service/src/test/java/org/mskcc/smile/service/TempoServiceTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; import java.util.Map; +import java.util.Set; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.mskcc.smile.model.SmileRequest; @@ -103,8 +104,16 @@ public void initializeMockDatabase() throws Exception { // mock request id: MOCKREQUEST1_B MockJsonTestData request1Data = mockDataUtils.mockedRequestJsonDataMap .get("mockIncomingRequest1JsonDataWith2T2N"); - SmileRequest request1 = RequestDataFactory.buildNewLimsRequestFromJson(request1Data.getJsonString()); + SmileRequest request1 = + RequestDataFactory.buildNewLimsRequestFromJson(request1Data.getJsonString()); requestService.saveRequest(request1); + + // mock request id: 22022_BZ + MockJsonTestData request2bData = mockDataUtils.mockedRequestJsonDataMap + .get("mockIncomingRequest2bJsonDataMissing1N"); + SmileRequest request2b = + RequestDataFactory.buildNewLimsRequestFromJson(request2bData.getJsonString()); + requestService.saveRequest(request2b); } @Test @@ -213,8 +222,7 @@ public void testUpdateCohortCompleteData() throws Exception { CohortCompleteJson ccJsonUpdate = getCohortEventData("mockCohortCompleteCCSPPPQQQQUpdated"); Cohort updatedCohort = new Cohort(ccJsonUpdate); - CohortComplete updatedCohortComplete = updatedCohort.getLatestCohortComplete(); - Boolean hasUpdates = cohortCompleteService.hasUpdates(cohort, updatedCohortComplete); + Boolean hasUpdates = cohortCompleteService.hasUpdates(cohort, updatedCohort); Assertions.assertThat(hasUpdates).isTrue(); } @@ -251,6 +259,32 @@ public void testNormalSampleTumorSampleTempoImportDefaults() throws Exception { Assertions.assertThat(tempoAfterSave3.getAccessLevel()).isNotBlank(); } + @Test + public void testCohortSampleListUpdate() throws Exception { + CohortCompleteJson ccJson = getCohortEventData("mockCohortCompleteCCSPPPQQQQ"); + cohortCompleteService.saveCohort(new Cohort(ccJson), ccJson.getTumorNormalPairsAsSet()); + Cohort cohort = cohortCompleteService.getCohortByCohortId("CCS_PPPQQQQ"); + Assertions.assertThat(cohort.getCohortSamplePrimaryIds().size()).isEqualTo(4); + + CohortCompleteJson ccJsonUpdate = + getCohortEventData("mockCohortCompleteCCSPPPQQQQUpdatedSamplesOnly"); + Assertions.assertThat(ccJsonUpdate.getTumorNormalPairsAsSet().size()).isEqualTo(6); + + Cohort updatedCohort = new Cohort(ccJsonUpdate); + Boolean hasUpdates = cohortCompleteService.hasUpdates(cohort, updatedCohort); + Assertions.assertThat(hasUpdates).isTrue(); + + // verify there are 2 new samples getting added to the cohort + Set newSamples = ccJsonUpdate.getTumorNormalPairsAsSet(); + newSamples.removeAll(cohort.getCohortSamplePrimaryIds()); + Assertions.assertThat(newSamples.size()).isEqualTo(2); + + // save cohort and verify that it now has 6 samples instead of 4 + cohortCompleteService.saveCohort(cohort, newSamples); + Cohort cohortAfterSave = cohortCompleteService.getCohortByCohortId("CCS_PPPQQQQ"); + Assertions.assertThat(cohortAfterSave.getCohortSamplePrimaryIds().size()).isEqualTo(6); + } + private CohortCompleteJson getCohortEventData(String dataIdentifier) throws JsonProcessingException { MockJsonTestData mockData = mockDataUtils.mockedTempoDataMap.get(dataIdentifier); CohortCompleteJson cohortCompleteData = mapper.readValue(mockData.getJsonString(), diff --git a/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ_updated_samples_only.json b/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ_updated_samples_only.json new file mode 100644 index 00000000..615c36eb --- /dev/null +++ b/service/src/test/resources/data/tempo/cohort_complete_CCS_PPPQQQQ_updated_samples_only.json @@ -0,0 +1,32 @@ +{ + "cohortId": "CCS_PPPQQQQ", + "date": "2022-11-12 21:59", + "type": "investigator", + "endUsers": [ + "enduser1", + "enduser2", + "enduser3" + ], + "pmUsers": [ + "pmuser1", + "pmuser2", + "pmuser3" + ], + "projectTitle": "A title", + "projectSubtitle": "A longer description", + "samples": [ + { + "primaryId": "MOCKREQUEST1_B_1", + "normalPrimaryId": "MOCKREQUEST1_B_2" + }, + { + "primaryId": "MOCKREQUEST1_B_3", + "normalPrimaryId": "MOCKREQUEST1_B_4" + }, + { + "primaryId": "22022_BZ_15", + "normalPrimaryId": "22022_BZ_19" + } + ], + "status": "PASS" +} diff --git a/service/src/test/resources/data/tempo/mocked_tempo_data_details.txt b/service/src/test/resources/data/tempo/mocked_tempo_data_details.txt index 248cc9ca..7400742d 100644 --- a/service/src/test/resources/data/tempo/mocked_tempo_data_details.txt +++ b/service/src/test/resources/data/tempo/mocked_tempo_data_details.txt @@ -16,3 +16,4 @@ mockMafCompleteSampleB3pass tempo/maf_complete_MOCKREQUEST1_B_3_pass.json Mocked mockCohortCompleteCCSPPPQQQQ tempo/cohort_complete_CCS_PPPQQQQ.json Mocked Cohort complete JSON with cohort ID CCS_PPPQQQQ. Has 4 samples associated with it. mockCohortCompleteCCSPPPQQQQ2 tempo/cohort_complete_CCS_PPPQQQQ_2.json Mocked Cohort complete JSON with cohort ID CCS_PPPQQQQ_2. Has 2 samples associated with it. mockCohortCompleteCCSPPPQQQQUpdated tempo/cohort_complete_CCS_PPPQQQQ_updated.json Mocked updated Cohort complete JSON with cohort ID CCS_PPPQQQQ. Has 4 samples associated with it. +mockCohortCompleteCCSPPPQQQQUpdatedSamplesOnly tempo/cohort_complete_CCS_PPPQQQQ_updated_samples_only.json Mocked updated Cohort complete JSON with cohort ID CCS_PPPQQQQ. Has 6 samples associated with it.