Skip to content

Commit

Permalink
Support patient ID swap to existing patient
Browse files Browse the repository at this point in the history
Message handler now supports ability to swap to an existing patient node when
handling CMO patient ID swaps

Other updates:
- updates to mocked data to bring it in sync with latest mock data in `cmo-metadb-common`

Co-authored-by: Angelica Ochoa <[email protected]>
Co-authored-by: Divya Madala <[email protected]>

Signed-off-by: Angelica Ochoa <[email protected]>
  • Loading branch information
ao508 committed Feb 18, 2022
1 parent 6295d3f commit d39e783
Show file tree
Hide file tree
Showing 19 changed files with 742 additions and 55 deletions.
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Please follow these checks if any changes were made to any classes in the web, s
Updates were made to the mocked incoming request data and/or mocked published request data:
- [ ] [cmo-metadb test data](https://github.com/mskcc/cmo-metadb/tree/master/service/src/test/resources/data)
- [ ] [cmo-metadb-common test data](https://github.com/mskcc/cmo-metadb-common/tree/master/src/test/resources/data)
- [ ] [cmo-metadb-label-generator test data](https://github.com/mskcc/cmo-metadb-label-generator/tree/master/src/test/resources/data)

**Code checks:**
- [ ] Endpoints were tested to ensure their integrity.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
@NodeEntity
public class SampleMetadata implements Serializable, Comparable<SampleMetadata> {
public class SampleMetadata implements Serializable, Comparable<SampleMetadata>, Cloneable {
@Id @GeneratedValue
@JsonIgnore
private Long id;
Expand Down Expand Up @@ -382,4 +382,9 @@ public int compareTo(SampleMetadata sampleMetadata) {
public String toString() {
return ToStringBuilder.reflectionToString(this);
}

@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); // return shallow copy
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@ MetadbPatient findPatientByCmoPatientId(
+ "RETURN p.metaDbPatientId")
UUID findPatientIdBySample(@Param("metaDbSampleId") UUID metaDbSampleId);

@Query("MATCH (p: Patient)<-[:IS_ALIAS]-(pa: PatientAlias {value: $oldCmoPatientId, namespace: 'cmoId'}) "
+ "SET pa.value = $newCmoPatientId "
@Query("MATCH (p: Patient)<-[:IS_ALIAS]-(pa: PatientAlias {value: $oldCmoId, namespace: 'cmoId'}) "
+ "SET pa.value = $newCmoId "
+ "RETURN p")
MetadbPatient updateCmoPatientIdInPatientNode(@Param("oldCmoPatientId") String oldCmoPatientId,
@Param("newCmoPatientId") String newCmoPatientId);
MetadbPatient updateCmoPatientIdInPatientNode(@Param("oldCmoId") String oldCmoId,
@Param("newCmoId") String newCmoId);

@Query("MATCH (s: Sample)<-[:IS_ALIAS]-(sa: SampleAlias {value: $value, namespace: $namespace}) "
+ "MATCH (s)<-[:HAS_SAMPLE]-(p: Patient) "
+ "RETURN p")
MetadbPatient findPatientByNamespaceValue(
@Param("namespace") String namespace, @Param("value") String value);

@Query("MATCH (p: Patient {metaDbPatientId: $patient.metaDbPatientId})"
+ "<-[:IS_ALIAS]-(pa: PatientAlias) DETACH DELETE p, pa")
void deletePatientAndAliases(@Param("patient") MetadbPatient patient);
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,9 @@ List<MetadbSample> findAllSamplesByCategoryAndCmoPatientId(
List<SampleMetadata> findSampleMetadataHistoryByNamespaceValue(
@Param("namespace") String namespace, @Param("value") String value);

@Query("MATCH (s: Sample {metaDbSampleId: $metaDbSampleId}) "
+ "MATCH (p: Patient {metaDbPatientId: $metaDbPatientId}) "
+ "CREATE (s)<-[:HAS_SAMPLE]-(p)")
void updateSamplePatientRelationship(@Param("metaDbSampleId") UUID metaDbSampleId,
@Param("metaDbPatientId") UUID metaDbPatientId);
}
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<cmo_metadb_messaging_java.version>1.2.1.RELEASE</cmo_metadb_messaging_java.version>
<!-- metadb common centralized config properties -->
<cmo_metadb_common.group>com.github.mskcc</cmo_metadb_common.group>
<cmo_metadb_common.version>1.2.1.RELEASE</cmo_metadb_common.version>
<cmo_metadb_common.version>1.2.2.RELEASE</cmo_metadb_common.version>
<!-- metadb expected schema version -->
<metadb.schema_version>v2.0</metadb.schema_version>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ public interface MetadbPatientService {
MetadbPatient getPatientByCmoPatientId(String cmoPatientId);
UUID getPatientIdBySample(UUID metadbSampleId);
MetadbPatient updateCmoPatientId(String oldCmoPatientId, String newCmoPatientId);
void deletePatient(MetadbPatient patient);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ List<PublishedMetadbSample> getPublishedMetadbSamplesByCmoPatientId(String cmoPa
MetadbSample getClinicalSampleByDmpId(String dmpId) throws Exception;
List<MetadbSample> getSamplesByCategoryAndCmoPatientId(String cmoPatientId,
String sampleCategory) throws Exception;
void updateSamplePatientRelationship(UUID metaDbSampleId, UUID metaDbPatientId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ public class MessageHandlingServiceImpl implements MessageHandlingService {
@Value("${metadb.correct_cmoptid_topic}")
private String CORRECT_CMOPTID_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;

Expand Down Expand Up @@ -117,31 +120,66 @@ public void run() {
Map<String, String> idCorrectionMap =
correctCmoPatientIdQueue.poll(100, TimeUnit.MILLISECONDS);
if (idCorrectionMap != null) {
String oldCmoPatientId = idCorrectionMap.get("oldId");
String newCmoPatientId = idCorrectionMap.get("newId");

// get samples by old cmo patient id before updating the
// cmo patient id for the given patient alias/patient node
List<MetadbSample> samples =
sampleService.getSamplesByCmoPatientId(oldCmoPatientId);
MetadbPatient updatedPatient = patientService.updateCmoPatientId(
oldCmoPatientId, newCmoPatientId);

for (MetadbSample sample: samples) {
SampleMetadata latestMetadata = sample.getLatestSampleMetadata();
latestMetadata.setCmoPatientId(newCmoPatientId);
String oldCmoPtId = idCorrectionMap.get("oldId");
String newCmoPtId = idCorrectionMap.get("newId");

List<MetadbSample> samplesByOldCmoPatient =
sampleService.getSamplesByCmoPatientId(oldCmoPtId);
List<MetadbSample> samplesByNewCmoPatient =
sampleService.getSamplesByCmoPatientId(newCmoPtId);
MetadbPatient patientByNewId = patientService.getPatientByCmoPatientId(newCmoPtId);

// update the cmo patient id for each sample linked to the "old" patient
// and the metadata as well
for (MetadbSample sample : samplesByOldCmoPatient) {
SampleMetadata updatedMetadata = sample.getLatestSampleMetadata();
updatedMetadata.setCmoPatientId(newCmoPtId);

// research samples need a new label as well
if (sample.getSampleCategory().equals("research")) {
LOG.info("Updating patient ID prefix embedded in CMO sample label "
+ "for research sample: " + latestMetadata.getPrimaryId());
String newCmoSampleLabel = latestMetadata.getCmoSampleName()
.replaceAll(oldCmoPatientId, newCmoPatientId);
latestMetadata.setCmoSampleName(newCmoSampleLabel);
LOG.info("Requesting new CMO sample label for sample: "
+ updatedMetadata.getPrimaryId());
Message reply = messagingGateway.request(CMO_LABEL_GENERATOR_REQREPLY_TOPIC,
mapper.writeValueAsString(updatedMetadata));
String newCmoSampleLabel = new String(reply.getData(),
StandardCharsets.UTF_8);
updatedMetadata.setCmoSampleName(newCmoSampleLabel);
}
// now update sample with the target patient we want to swap to
sample.updateSampleMetadata(updatedMetadata);

// update the sample-to-patient relationship if swapping to a different
// patient node. if still using the same node the samples are already linked
// to then there's no need to override the patient currently set for the sample
if (patientByNewId != null) {
sample.setPatient(patientByNewId);
sampleService.updateSamplePatientRelationship(sample.getMetaDbSampleId(),
patientByNewId.getMetaDbPatientId());
}
sample.setPatient(updatedPatient);
sample.updateSampleMetadata(latestMetadata);
LOG.info("Persisting update for sample to database");
sampleService.saveMetadbSample(sample);
}

// delete old patient node if we are swapping to an existing patient node
// otherwise simply update the existing patient node with the new cmo id
if (patientByNewId != null) {
LOG.info("Deleting Patient node (and its relationships) for old ID: "
+ oldCmoPtId);
MetadbPatient patientByOldId =
patientService.getPatientByCmoPatientId(oldCmoPtId);
patientService.deletePatient(patientByOldId);
} else {
patientService.updateCmoPatientId(oldCmoPtId, newCmoPtId);
}

// sanity check the counts before and after the swaps
Integer expectedCount = samplesByOldCmoPatient.size()
+ samplesByNewCmoPatient.size();
List<MetadbSample> samplesAfterSwap =
sampleService.getSamplesByCmoPatientId(newCmoPtId);
if (expectedCount != samplesAfterSwap.size()) {
LOG.error("Expected sample count after patient ID swap does not match actual"
+ " count: " + expectedCount + " != " + samplesAfterSwap.size());
}
}
if (interrupted && correctCmoPatientIdQueue.isEmpty()) {
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.mskcc.cmo.metadb.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.mskcc.cmo.metadb.model.MetadbPatient;
Expand All @@ -9,6 +8,7 @@
import org.mskcc.cmo.metadb.service.MetadbPatientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class PatientServiceImpl implements MetadbPatientService {
Expand All @@ -17,6 +17,7 @@ public class PatientServiceImpl implements MetadbPatientService {
private MetadbPatientRepository patientRepository;

@Override
@Transactional(rollbackFor = {Exception.class})
public MetadbPatient savePatientMetadata(MetadbPatient patient) {
MetadbPatient result = patientRepository.save(patient);
patient.setMetaDbPatientId(result.getMetaDbPatientId());
Expand All @@ -39,11 +40,17 @@ public UUID getPatientIdBySample(UUID metadbSampleId) {
}

@Override
@Transactional(rollbackFor = {Exception.class})
public MetadbPatient updateCmoPatientId(String oldCmoPatientId, String newCmoPatientId) {
if (getPatientByCmoPatientId(oldCmoPatientId) == null) {
return null;
}
return patientRepository.updateCmoPatientIdInPatientNode(oldCmoPatientId, newCmoPatientId);
}

@Override
@Transactional(rollbackFor = {Exception.class})
public void deletePatient(MetadbPatient patient) {
patientRepository.deletePatientAndAliases(patient);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.mskcc.cmo.common.MetadbJsonComparator;
import org.mskcc.cmo.metadb.model.MetadbPatient;
Expand Down Expand Up @@ -51,7 +49,7 @@ public MetadbSample saveMetadbSample(MetadbSample
sample.setMetaDbSampleId(newSampleId);
return sample;
} else {
existingSample.addSampleMetadata(sample.getLatestSampleMetadata());
existingSample.updateSampleMetadata(sample.getLatestSampleMetadata());
sampleRepository.save(existingSample);
return existingSample;
}
Expand Down Expand Up @@ -203,4 +201,10 @@ public List<MetadbSample> getSamplesByCategoryAndCmoPatientId(String cmoPatientI
}
return samples;
}

@Override
@Transactional(rollbackFor = {Exception.class})
public void updateSamplePatientRelationship(UUID metaDbSampleId, UUID metaDbPatientId) {
sampleRepository.updateSamplePatientRelationship(metaDbSampleId, metaDbPatientId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.mskcc.cmo.metadb.service;

import java.util.List;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mskcc.cmo.metadb.model.MetadbPatient;
import org.mskcc.cmo.metadb.model.MetadbRequest;
import org.mskcc.cmo.metadb.model.MetadbSample;
import org.mskcc.cmo.metadb.model.SampleMetadata;
import org.mskcc.cmo.metadb.persistence.neo4j.MetadbPatientRepository;
import org.mskcc.cmo.metadb.persistence.neo4j.MetadbRequestRepository;
import org.mskcc.cmo.metadb.persistence.neo4j.MetadbSampleRepository;
import org.mskcc.cmo.metadb.service.util.RequestDataFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

/**
*
* @author ochoaa
*/
@Testcontainers
@DataNeo4jTest
@Import(MockDataUtils.class)
public class CorrectCmoPatientIdHandlerTest {
@Autowired
private MockDataUtils mockDataUtils;

@Autowired
private MetadbRequestService requestService;

@Autowired
private MetadbSampleService sampleService;

@Autowired
private MetadbPatientService patientService;

@Container
private static final Neo4jContainer databaseServer = new Neo4jContainer<>()
.withEnv("NEO4J_dbms_security_procedures_unrestricted", "apoc.*,algo.*");

@TestConfiguration
static class Config {
@Bean
public org.neo4j.ogm.config.Configuration configuration() {
return new org.neo4j.ogm.config.Configuration.Builder()
.uri(databaseServer.getBoltUrl())
.credentials("neo4j", databaseServer.getAdminPassword())
.build();
}
}

private final MetadbRequestRepository requestRepository;
private final MetadbSampleRepository sampleRepository;
private final MetadbPatientRepository patientRepository;

/**
* Persists the Mock Request data to the test database.
* @throws Exception
*/
@Autowired
public CorrectCmoPatientIdHandlerTest(MetadbRequestRepository requestRepository,
MetadbSampleRepository sampleRepository, MetadbPatientRepository patientRepository) {
this.requestRepository = requestRepository;
this.sampleRepository = sampleRepository;
this.patientRepository = patientRepository;
}


/**
* Persists the Mock Request data to the test database.
* @throws Exception
*/
@Autowired
public void initializeMockDatabase() throws Exception {
// mock request id: MOCKREQUEST1_B
MockJsonTestData request1Data = mockDataUtils.mockedRequestJsonDataMap
.get("mockIncomingRequest1JsonDataWith2T2N");
MetadbRequest request1 = RequestDataFactory.buildNewLimsRequestFromJson(request1Data.getJsonString());
requestService.saveRequest(request1);
// mock request id: 145145_IM
MockJsonTestData request5Data = mockDataUtils.mockedRequestJsonDataMap
.get("mockIncomingRequest5JsonPtMultiSamples");
MetadbRequest request5 = RequestDataFactory.buildNewLimsRequestFromJson(request5Data.getJsonString());
requestService.saveRequest(request5);
}


/**
* Tests sample fetch before patient swap and after the patient id swap in the
* event that the patient already exists by the new id.
*/
@Test
public void testPatientIdSwapWithExistingPatient() throws Exception {
String oldCmoPatientId = "C-MP789JR";
String newCmoPatientId = "C-1MP6YY";


List<MetadbSample> samplesByNewCmoPatient = sampleService.getSamplesByCmoPatientId(newCmoPatientId);
System.out.println("Samples for new cmo patient id: " + samplesByNewCmoPatient.size());

String request1 = "MOCKREQUEST1_B";
String sampleId1 = "MOCKREQUEST1_B_1";
MetadbSample sample1 = sampleService.getResearchSampleByRequestAndIgoId(request1, sampleId1);

MetadbPatient newPatient = patientService.getPatientByCmoPatientId(newCmoPatientId);
SampleMetadata newSample1Metadata = sample1.getLatestSampleMetadata();
newSample1Metadata.setCmoPatientId(newCmoPatientId);
sample1.updateSampleMetadata(newSample1Metadata);
sample1.setPatient(newPatient);
sampleService.saveMetadbSample(sample1);
sampleService.updateSamplePatientRelationship(sample1.getMetaDbSampleId(),
newPatient.getMetaDbPatientId());

Integer expectedSampleCount = samplesByNewCmoPatient.size() + 1;
List<MetadbSample> samplesByNewCmoPatientAfterSwap =
sampleService.getSamplesByCmoPatientId(newCmoPatientId);
Assertions.assertThat(samplesByNewCmoPatientAfterSwap.size())
.isEqualTo(expectedSampleCount);
}
}
Loading

0 comments on commit d39e783

Please sign in to comment.