getRequestSamplesWithUpdates(SmileRequest request) thro
if (existingSample == null) {
continue;
}
- // compare sample metadata from current request and the saved request
- String latestMetadata = mapper.writeValueAsString(existingSample.getLatestSampleMetadata());
- String currentMetadata = mapper.writeValueAsString(sample.getLatestSampleMetadata());
-
- try {
- jsonComparator.isConsistent(latestMetadata, currentMetadata);
- } catch (AssertionError e) {
- LOG.warn("Found discrepancies between JSONs:\n" + e.getLocalizedMessage());
+ Boolean sampleHasUpdates =
+ sampleService.sampleHasMetadataUpdates(existingSample.getLatestSampleMetadata(),
+ sample.getLatestSampleMetadata(), Boolean.TRUE);
+ if (sampleHasUpdates) {
existingSample.updateSampleMetadata(sample.getLatestSampleMetadata());
updatedSamples.add(existingSample);
}
diff --git a/service/src/main/java/org/mskcc/smile/service/impl/ResearchMessageHandlingServiceImpl.java b/service/src/main/java/org/mskcc/smile/service/impl/ResearchMessageHandlingServiceImpl.java
index 0ca73f21..efbecf93 100644
--- a/service/src/main/java/org/mskcc/smile/service/impl/ResearchMessageHandlingServiceImpl.java
+++ b/service/src/main/java/org/mskcc/smile/service/impl/ResearchMessageHandlingServiceImpl.java
@@ -53,6 +53,9 @@ public class ResearchMessageHandlingServiceImpl implements ResearchMessageHandli
@Value("${smile.cmo_sample_update_topic}")
private String CMO_SAMPLE_UPDATE_TOPIC;
+ @Value("${request_reply.cmo_label_generator_topic}")
+ private String CMO_LABEL_GENERATOR_REQREPLY_TOPIC;
+
@Value("${num.new_request_handler_threads}")
private int NUM_NEW_REQUEST_HANDLERS;
@@ -123,10 +126,10 @@ public void run() {
requestService.saveRequest(request);
} else {
// request-service and sample-service methods will check for updates and persist
- // them if applicable
+ // them if applicable (including patient swapping)
requestService.updateRequestMetadata(request.getLatestRequestMetadata());
for (SmileSample sample : request.getSmileSampleList()) {
- sampleService.updateSampleMetadata(sample.getLatestSampleMetadata());
+ sampleService.saveSmileSample(sample);
}
}
// publish updated/saved request to consistency checker or promoted request topic
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 929956f6..6c8e0a06 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
@@ -47,7 +47,7 @@ public class SampleServiceImpl implements SmileSampleService {
@Transactional(rollbackFor = {Exception.class})
public SmileSample saveSmileSample(SmileSample
sample) throws Exception {
- fetchAndLoadSampleDetails(sample);
+ fetchAndLoadPatientDetails(sample);
SmileSample existingSample =
sampleRepository.findSampleByPrimaryId(sample.getPrimarySampleAlias());
if (existingSample == null) {
@@ -55,37 +55,129 @@ public SmileSample saveSmileSample(SmileSample
sample.setSmileSampleId(newSampleId);
return sample;
} else {
- existingSample.updateSampleMetadata(sample.getLatestSampleMetadata());
+ // populate existing sample details and check if there are actual updates to persist
+ getDetailedSmileSample(existingSample);
+ SampleMetadata existingMetadata = existingSample.getLatestSampleMetadata();
+ SampleMetadata sampleMetadata = sample.getLatestSampleMetadata();
+ if (sampleHasMetadataUpdates(existingMetadata, sampleMetadata,
+ sample.getSampleCategory().equals("research"))) {
+ LOG.info("Found updates to persist for sample: " + existingSample.getPrimarySampleAlias());
+ existingSample.updateSampleMetadata(sample.getLatestSampleMetadata());
+
+ // determine where a patient swap is required also
+ if (!sample.getPatient().getSmilePatientId().equals(
+ existingSample.getPatient().getSmilePatientId())) {
+ LOG.info("Updating sample-to-patient relationship and removing connection to patient: "
+ + existingSample.getPatient().getSmilePatientId());
+ sampleRepository.removeSamplePatientRelationship(existingSample.getSmileSampleId(),
+ existingSample.getPatient().getSmilePatientId());
+ existingSample.setPatient(sample.getPatient());
+ }
+ existingSample.setPatient(sample.getPatient());
+ }
sampleRepository.save(existingSample);
return existingSample;
}
}
+ /**
+ * Fetching and loading patient details explained.
+ *
+ * Scenario #1: new sample, new patient
+ * --> new sample and patient are persisted to the database
+ *
+ *
Scenario #2: existing sample with updates and patient swap
+ * a) patient by cmo id in the incoming metadata updates does not already exist and does not match
+ * the patient linked to the existing sample:
+ * --> patient by the new id is persisted to the database, sample-to-patient relationship
+ * is updated to match the newly persisted patient and the former sample-to-patient relationship
+ * is removed
+ * b) patient by cmo id in the incoming metadata updates already exists but does not match
+ * the patient linked to the existing sample:
+ * --> sample-to-patient relationship is updated to match the patient referenced in the incoming
+ * sample updates and the former sample-to-patient relationship is removed
+ *
+ *
Scenario #3: special case where new sample is added to database but there's a mismatch
+ * between the patient that the canonical sample is pointing to and the patient referenced in the
+ * latest sample metadata
+ * a) cmo patient ids do not match
+ * --> construct and persist a new patient node with the cmo id from the latest metadata
+ * b) cmo patient ids match
+ * --> persist patient from sample.getPatient() to database
+ *
+ *
Scenario #4: new sample where sample.getPatient() is null and cmo patient id in latest
+ * metadata is not null and exists in the database
+ * --> throws exception, this is a case that should never happen and would result from malformed data
+ * @param sample
+ * @return SmileSample
+ * @throws Exception
+ */
@Override
- public SmileSample fetchAndLoadSampleDetails(SmileSample sample) throws Exception {
+ public SmileSample fetchAndLoadPatientDetails(SmileSample sample) throws Exception {
SampleMetadata sampleMetadata = sample.getLatestSampleMetadata();
SmilePatient patient = sample.getPatient();
- // find or save new patient for sample
- SmilePatient existingPatient = patientService.getPatientByCmoPatientId(
- sampleMetadata.getCmoPatientId());
- if (existingPatient == null) {
+ // handle the scenario where a patient node does not already exist in the database
+ // to prevent any null pointer exceptions (a situation that had arose in some test dmp sample cases)
+ if (patientService.getPatientByCmoPatientId(
+ sample.getPatient().getCmoPatientId().getValue()) == null) {
patientService.savePatientMetadata(patient);
sample.setPatient(patient);
- } else {
+ }
+
+ // get patient by cmo id from latest sample metadata
+ SmilePatient patientByLatestCmoId = patientService.getPatientByCmoPatientId(
+ sampleMetadata.getCmoPatientId());
+
+ // again this is something that should never happen and would arise from some error
+ // in the data construction/parsing
+ if (patient == null) {
+ throw new IllegalStateException("Patient object assigned to the sample is null "
+ + "- confirm whether data construction and parsing is being handled correctly");
+ }
+
+ // scenario that requires a patient swap and updating the sample-to-patient relationship
+ // in the database and removing the former sample-to-patient relationship
+ if (patientByLatestCmoId == null) {
+ SmilePatient newPatient = new SmilePatient(sampleMetadata.getCmoPatientId(), "cmoId");
+ patientService.savePatientMetadata(newPatient);
+ sample.setPatient(newPatient);
+ // remove sample-to-patient relationship from former patient node
+ sampleRepository.removeSamplePatientRelationship(sample.getSmileSampleId(),
+ patient.getSmilePatientId());
+ return sample;
+ }
+
+ // scenario where we are checking for an update to the existing patient, which is the same
+ // that already existing and is linked to the sample in the database but may contain updates
+ // (i.e., a new patient alias)
+ if (patient.getCmoPatientId().getValue().equals(sampleMetadata.getCmoPatientId())) {
// go through the new patient aliases and indicator for whether a
// new patient alias was added to the existing patient
Boolean patientUpdated = Boolean.FALSE;
for (PatientAlias pa : patient.getPatientAliases()) {
- if (!existingPatient.hasPatientAlias(pa)) {
- existingPatient.addPatientAlias(pa);
+ if (!patientByLatestCmoId.hasPatientAlias(pa)) {
+ patientByLatestCmoId.addPatientAlias(pa);
patientUpdated = Boolean.TRUE;
}
}
if (patientUpdated) {
- patientService.savePatientMetadata(existingPatient);
+ sample.setPatient(patientService.savePatientMetadata(patientByLatestCmoId));
+ } else {
+ sample.setPatient(patientByLatestCmoId);
}
- sample.setPatient(existingPatient);
+ return sample;
+ }
+
+ // scenario where the patient that the sample-to-patient relationship points to in the database
+ // does not match the cmo patient id referenced in the latest sample metadata updates
+ // and the former sample-to-patient relationship needs to be removed
+ if (!patient.getCmoPatientId().getValue().equals(sampleMetadata.getCmoPatientId())) {
+ sample.setPatient(patientByLatestCmoId);
+ sampleRepository.removeSamplePatientRelationship(sample.getSmileSampleId(),
+ patient.getSmilePatientId());
+ return sample;
+
}
return sample;
}
@@ -105,11 +197,13 @@ public Boolean updateSampleMetadata(SampleMetadata sampleMetadata) throws Except
}
// save updates to sample if applicable
SampleMetadata existingMetadata = existingSample.getLatestSampleMetadata();
- if (sampleHasMetadataUpdates(existingMetadata, sampleMetadata)
+
+ Boolean isResearchSample = existingSample.getSampleCategory().equals("research");
+ if (sampleHasMetadataUpdates(existingMetadata, sampleMetadata, isResearchSample)
|| (!sampleHasMetadataUpdates(
- existingMetadata, sampleMetadata))
+ existingMetadata, sampleMetadata, isResearchSample)
&& !existingMetadata.getCmoSampleName()
- .equals(sampleMetadata.getCmoSampleName())) {
+ .equals(sampleMetadata.getCmoSampleName()))) {
LOG.info("Persisting updates for sample: " + sampleMetadata.getPrimaryId());
existingSample.updateSampleMetadata(sampleMetadata);
saveSmileSample(existingSample);
@@ -173,12 +267,22 @@ public List getResearchSampleMetadataHistoryByIgoId(String igoId
@Override
public Boolean sampleHasMetadataUpdates(SampleMetadata existingSampleMetadata,
- SampleMetadata sampleMetadata) throws Exception {
+ SampleMetadata sampleMetadata, Boolean isResearchSample) throws Exception {
String existingMetadata = mapper.writeValueAsString(existingSampleMetadata);
String currentMetadata = mapper.writeValueAsString(sampleMetadata);
- try {
- jsonComparator.isConsistent(currentMetadata, existingMetadata);
- } catch (AssertionError e) {
+ Boolean isConsistent = jsonComparator.isConsistent(currentMetadata, existingMetadata);
+ // if not consistent then return true since changes were detected
+ if (!isConsistent) {
+ return Boolean.TRUE;
+ }
+ // if there is a change to the cmo sample label..
+ if (isResearchSample && !existingSampleMetadata.getCmoSampleName()
+ .equals(sampleMetadata.getCmoSampleName())) {
+ return Boolean.TRUE;
+ }
+ // if there needs to be a patient swap..
+ if (!existingSampleMetadata.getCmoPatientId()
+ .equals(sampleMetadata.getCmoPatientId())) {
return Boolean.TRUE;
}
return Boolean.FALSE;
diff --git a/service/src/main/java/org/mskcc/smile/service/util/SampleDataFactory.java b/service/src/main/java/org/mskcc/smile/service/util/SampleDataFactory.java
index 8ec64ffa..350f0e65 100644
--- a/service/src/main/java/org/mskcc/smile/service/util/SampleDataFactory.java
+++ b/service/src/main/java/org/mskcc/smile/service/util/SampleDataFactory.java
@@ -47,7 +47,9 @@ private static Map initDmpClinicalMetastasisValuesMap() {
public static SmileSample buildNewResearchSampleFromMetadata(String requestId,
SampleMetadata sampleMetadata) {
sampleMetadata.setIgoRequestId(requestId);
- sampleMetadata.setImportDate(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE));
+ if (sampleMetadata.getImportDate() == null) {
+ sampleMetadata.setImportDate(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE));
+ }
SmileSample sample = new SmileSample();
sample.addSampleMetadata(sampleMetadata);
diff --git a/service/src/test/java/org/mskcc/smile/service/SampleServiceTest.java b/service/src/test/java/org/mskcc/smile/service/SampleServiceTest.java
index 587da6d3..9187ab43 100644
--- a/service/src/test/java/org/mskcc/smile/service/SampleServiceTest.java
+++ b/service/src/test/java/org/mskcc/smile/service/SampleServiceTest.java
@@ -214,7 +214,7 @@ public void testSampleHasMetadataUpdates() throws Exception {
SmileSample updatedSample = updatedRequest.getSmileSampleList().get(0);
Boolean hasUpdates = sampleService.sampleHasMetadataUpdates(sample.getLatestSampleMetadata(),
- updatedSample.getLatestSampleMetadata());
+ updatedSample.getLatestSampleMetadata(), Boolean.TRUE);
Assertions.assertThat(hasUpdates).isEqualTo(Boolean.TRUE);
}
@@ -232,9 +232,11 @@ public void testSampleHistoryAfterUpdate() throws Exception {
.get("mockIncomingRequest1UpdatedJsonDataWith2T2N");
SmileRequest updatedRequest = RequestDataFactory.buildNewLimsRequestFromJson(
updatedRequestData.getJsonString());
- SmileSample updatedSample = updatedRequest.getSmileSampleList().get(1);
- sampleService.saveSmileSample(updatedSample);
-
+ for (SmileSample updatedSample : updatedRequest.getSmileSampleList()) {
+ if (updatedSample.getLatestSampleMetadata().getPrimaryId().equals(igoId)) {
+ sampleService.saveSmileSample(updatedSample);
+ }
+ }
List sampleMetadataHistory = sampleService
.getResearchSampleMetadataHistoryByIgoId(igoId);
Assertions.assertThat(sampleMetadataHistory.size()).isEqualTo(2);
@@ -297,29 +299,46 @@ public void testFindSamplesByDate() throws Exception {
String requestId = "MOCKREQUEST1_B";
String igoId = "MOCKREQUEST1_B_2";
+ // fetch sample from db and insert an older version of its metadata
SmileSample sample = sampleService.getResearchSampleByRequestAndIgoId(requestId, igoId);
- sample.getLatestSampleMetadata().setCmoSampleName("C-LATESTLABEL-T02-d002");
-
SampleMetadata updatedMetadata = new SampleMetadata();
updatedMetadata.setImportDate("2000-06-10");
- updatedMetadata.setPrimaryId(igoId);
+ updatedMetadata.setPrimaryId(sample.getPrimarySampleAlias());
+ updatedMetadata.setBaitSet("DIFFERENTBAITSET");
updatedMetadata.setCmoSampleName("C-OLDSAMPLELABEL-T11");
- sample.updateSampleMetadata(updatedMetadata);
- sampleService.saveSmileSample(sample);
+ sample.addSampleMetadata(updatedMetadata);
- // confirm that new sample metadata was persisted
- List sampleMetadataHistory = sampleService
+ // assert that the metadata history size is equal to 1 before any updates are made
+ List sampleMetadataHistoryBeforeUpdate = sampleService
.getResearchSampleMetadataHistoryByIgoId(igoId);
- Assertions.assertThat(sampleMetadataHistory.size()).isEqualTo(3);
+ Assertions.assertThat(sampleMetadataHistoryBeforeUpdate.size()).isEqualTo(1);
+ // persist updates for sample and confirm that the metadata history size increased
+ sampleService.updateSampleMetadata(updatedMetadata);
+ List sampleMetadataHistoryAfterUpdate = sampleService
+ .getResearchSampleMetadataHistoryByIgoId(igoId);
+ Assertions.assertThat(sampleMetadataHistoryAfterUpdate.size()).isEqualTo(2);
+
+ // confirm that new sample metadata was persisted and that there is an older sample
+ // metadata with the date '2000-06-10' that we basically inserted into the
+ // history for this sample
+ Boolean hasMockOldMetadata = Boolean.FALSE;
+ for (SampleMetadata sm : sampleMetadataHistoryAfterUpdate) {
+ if (sm.getImportDate().equals("2000-06-10")) {
+ hasMockOldMetadata = Boolean.TRUE;
+ break;
+ }
+ }
+ Assertions.assertThat(hasMockOldMetadata).isTrue();
// confirms that both methods return the same latest metadata and
// same cmo sample label corresponding to it
+ // the most up-to-date cmo label is C-MP789JR-N001-d based on mocked test data
SmileSample updatedSample = sampleService.getResearchSampleByRequestAndIgoId(requestId, igoId);
Assertions.assertThat(updatedSample.getLatestSampleMetadata().getCmoSampleName())
- .isEqualTo("C-LATESTLABEL-T02-d002");
+ .isEqualTo("C-MP789JR-N001-d");
SampleMetadata latestMetadata =
sampleRepository.findLatestSampleMetadataBySmileId(updatedSample.getSmileSampleId());
- Assertions.assertThat(latestMetadata.getCmoSampleName()).isEqualTo("C-LATESTLABEL-T02-d002");
+ Assertions.assertThat(latestMetadata.getCmoSampleName()).isEqualTo("C-MP789JR-N001-d");
}
/**
@@ -356,17 +375,89 @@ public void testFindSampleByInvalidInputId() throws Exception {
*/
@Test
public void testUpdateSampleMetadata() throws Exception {
+ MockJsonTestData updatedRequestData = mockDataUtils.mockedRequestJsonDataMap
+ .get("mockIncomingRequest1UpdatedJsonDataWith2T2N");
+ SmileRequest updatedRequest = RequestDataFactory.buildNewLimsRequestFromJson(
+ updatedRequestData.getJsonString());
+ // get the updated sample data from the mocked updated request
String igoId = "MOCKREQUEST1_B_2";
+ SmileSample updatedSample = null;
+ for (SmileSample s : updatedRequest.getSmileSampleList()) {
+ if (s.getLatestSampleMetadata().getPrimaryId().equals(igoId)) {
+ updatedSample = s;
+ break;
+ }
+ }
+ Assertions.assertThat(updatedSample).isNotNull();
+ SampleMetadata updatedMetadata = updatedSample.getLatestSampleMetadata();
+ updatedMetadata.setImportDate("2000-10-15");
+ updatedMetadata.setBaitSet("NEW BAIT SET");
+ updatedMetadata.setGenePanel("NEW GENE PANEL");
+ updatedSample.addSampleMetadata(updatedMetadata);
+ sampleService.saveSmileSample(updatedSample);
+ // confirm that the sample metadata history size increases
+ List sampleMetadataHistory = sampleService
+ .getResearchSampleMetadataHistoryByIgoId(igoId);
+ Assertions.assertThat(sampleMetadataHistory.size()).isEqualTo(2);
+ }
+
+
+ /**
+ * Tests if sampleMetadata with updates that includes a patient swap is being persisted correctly
+ * @throws Exception
+ */
+ @Test
+ public void testUpdateSampleMetadataWithPatientSwap() throws Exception {
MockJsonTestData updatedRequestData = mockDataUtils.mockedRequestJsonDataMap
.get("mockIncomingRequest1UpdatedJsonDataWith2T2N");
SmileRequest updatedRequest = RequestDataFactory.buildNewLimsRequestFromJson(
updatedRequestData.getJsonString());
- SmileSample updatedSample = updatedRequest.getSmileSampleList().get(1);
- sampleService.updateSampleMetadata(updatedSample.getLatestSampleMetadata());
+ // get the updated sample data from the mocked updated request
+ String igoId = "MOCKREQUEST1_B_2";
+ SmileSample updatedSample = null;
+ for (SmileSample s : updatedRequest.getSmileSampleList()) {
+ if (s.getLatestSampleMetadata().getPrimaryId().equals(igoId)) {
+ updatedSample = s;
+ break;
+ }
+ }
+ Assertions.assertThat(updatedSample).isNotNull();
+ SampleMetadata updatedMetadata = updatedSample.getLatestSampleMetadata();
+
+ // do a quick string replacement for the current cmo sample label and persist update
+ String currentCmoPtId = updatedMetadata.getCmoPatientId();
+ String swappedCmoPtId = "C-123456H";
+
+ // first confirm that there arent any samples by the swapped cmo pt id
+ List samplesBeforeUpdateForCurrentPt =
+ sampleService.getSamplesByCmoPatientId(currentCmoPtId);
+ Assertions.assertThat(samplesBeforeUpdateForCurrentPt.size()).isEqualTo(4);
+ List samplesBeforeUpdate =
+ sampleService.getSamplesByCmoPatientId(swappedCmoPtId);
+ Assertions.assertThat(samplesBeforeUpdate).isEmpty();
+
+ // perform update on the metadata and save to db
+ String updatedLabel = updatedMetadata.getCmoSampleName().replace(currentCmoPtId, swappedCmoPtId);
+ updatedMetadata.setCmoPatientId(swappedCmoPtId);
+ updatedMetadata.setCmoSampleName(updatedLabel);
+ updatedSample.updateSampleMetadata(updatedMetadata);
+ sampleService.saveSmileSample(updatedSample);
+ // confirm that the sample metadata history size increases
List sampleMetadataHistory = sampleService
.getResearchSampleMetadataHistoryByIgoId(igoId);
Assertions.assertThat(sampleMetadataHistory.size()).isEqualTo(2);
+
+ // confirm that the patient linked to the sample after the update matches the swapped id
+ // first confirm that there arent any samples by the swapped cmo pt id
+ List samplesAfterUpdate =
+ sampleService.getSamplesByCmoPatientId(swappedCmoPtId);
+ Assertions.assertThat(samplesAfterUpdate.size()).isEqualTo(1);
+ List samplesStillLinkedToOldPt =
+ sampleService.getSamplesByCmoPatientId(currentCmoPtId);
+ Assertions.assertThat(samplesStillLinkedToOldPt.size())
+ .isEqualTo(samplesBeforeUpdateForCurrentPt.size() - 1);
+
}
}