Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ALS-8098] Produce a list of all patients involved in query #131

Merged
merged 1 commit into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ public String toString() {
break;
case DATAFRAME:
case SECRET_ADMIN_DATAFRAME:
case PATIENTS:
Luke-Sikina marked this conversation as resolved.
Show resolved Hide resolved
writePartFormat("Data Export Fields", fields, builder, true);
break;
case DATAFRAME_TIMESERIES:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,10 @@ public enum ResultType {
* Exports data as PFB, using avro
* <a href="https://uc-cdis.github.io/pypfb/">https://uc-cdis.github.io/pypfb/</a>
*/
DATAFRAME_PFB
DATAFRAME_PFB,

/**
* Patients associated with this query
*/
PATIENTS
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package edu.harvard.hms.dbmi.avillach.hpds.processing.patient;

import edu.harvard.hms.dbmi.avillach.hpds.data.query.Query;
import edu.harvard.hms.dbmi.avillach.hpds.processing.AbstractProcessor;
import edu.harvard.hms.dbmi.avillach.hpds.processing.AsyncResult;
import edu.harvard.hms.dbmi.avillach.hpds.processing.HpdsProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class PatientProcessor implements HpdsProcessor {

private static final Logger LOG = LoggerFactory.getLogger(PatientProcessor.class);
private final AbstractProcessor abstractProcessor;

@Autowired
public PatientProcessor(AbstractProcessor abstractProcessor) {
this.abstractProcessor = abstractProcessor;
}

@Override
public String[] getHeaderRow(Query query) {
return new String[]{"PATIENT_NUM"};
}

@Override
public void runQuery(Query query, AsyncResult asyncResult) {
LOG.info("Pulling results for query {}", query.getId());
// floating all this in memory is a bit gross, but the whole list of
// patient IDs was already there, so I don't feel too bad
List<String[]> allPatients = abstractProcessor.getPatientSubsetForQuery(query).stream()
.map(patient -> new String[]{patient.toString()})
.toList();
LOG.info("Writing results for query {}", query.getId());
asyncResult.appendResults(allPatients);
LOG.info("Completed query {}", query.getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package edu.harvard.hms.dbmi.avillach.hpds.processing.patient;

import edu.harvard.hms.dbmi.avillach.hpds.data.query.Query;
import edu.harvard.hms.dbmi.avillach.hpds.data.query.ResultType;
import edu.harvard.hms.dbmi.avillach.hpds.processing.AbstractProcessor;
import edu.harvard.hms.dbmi.avillach.hpds.processing.AsyncResult;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import java.util.List;
import java.util.TreeSet;

@EnableAutoConfiguration
@SpringBootTest(classes = PatientProcessor.class)
class PatientProcessorTest {

@MockBean
AbstractProcessor abstractProcessor;

@Autowired
PatientProcessor subject;

@Test
void shouldProcessPatientQuery() {
Query q = new Query();
q.setId("frank");
q.setPicSureId("frank");
q.setExpectedResultType(ResultType.PATIENTS);
AsyncResult writeToThis = Mockito.mock(AsyncResult.class);
Mockito.when(abstractProcessor.getPatientSubsetForQuery(q))
.thenReturn(new TreeSet<>(List.of(1, 2, 42)));

subject.runQuery(q, writeToThis);

Mockito.verify(writeToThis, Mockito.times(1))
.appendResults(Mockito.argThat(strings ->
strings.size() == 3 &&
strings.get(0)[0].equals("1") &&
strings.get(1)[0].equals("2") &&
strings.get(2)[0].equals("42"))
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import edu.harvard.hms.dbmi.avillach.hpds.service.filesharing.TestDataService;
import edu.harvard.hms.dbmi.avillach.hpds.service.util.Paginator;
import edu.harvard.hms.dbmi.avillach.hpds.service.util.QueryDecorator;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -32,14 +31,12 @@

import edu.harvard.dbmi.avillach.domain.*;
import edu.harvard.dbmi.avillach.util.UUIDv5;
import edu.harvard.dbmi.avillach.service.IResourceRS;
import edu.harvard.hms.dbmi.avillach.hpds.crypto.Crypto;
import edu.harvard.hms.dbmi.avillach.hpds.data.phenotype.ColumnMeta;
import edu.harvard.hms.dbmi.avillach.hpds.data.query.Query;
import edu.harvard.hms.dbmi.avillach.hpds.processing.*;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

@RequestMapping(value = "PIC-SURE", produces = "application/json")
Expand Down Expand Up @@ -308,7 +305,10 @@ public ResponseEntity writeQueryResult(
success = fileSystemService.createPhenotypicData(query);
} else if ("genomic".equals(datatype)) {
success = fileSystemService.createGenomicData(query);
}
} else if ("patients".equals(datatype)) {
success = ResultType.PATIENTS.equals(query.getExpectedResultType()) &&
fileSystemService.createPatientList(query);
}
return success ? ResponseEntity.ok().build() : ResponseEntity.internalServerError().build();
}

Expand Down Expand Up @@ -391,6 +391,7 @@ private ResponseEntity _querySync(QueryRequest resultRequest) throws IOException
case DATAFRAME:
case SECRET_ADMIN_DATAFRAME:
case DATAFRAME_TIMESERIES:
case PATIENTS:
QueryStatus status = query(resultRequest).getBody();
while (status.getResourceStatus().equalsIgnoreCase("RUNNING")
|| status.getResourceStatus().equalsIgnoreCase("PENDING")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;

import edu.harvard.hms.dbmi.avillach.hpds.processing.patient.PatientProcessor;
import edu.harvard.hms.dbmi.avillach.hpds.processing.timeseries.TimeseriesProcessor;
import edu.harvard.hms.dbmi.avillach.hpds.data.query.ResultType;
import edu.harvard.hms.dbmi.avillach.hpds.processing.dictionary.DictionaryService;
Expand Down Expand Up @@ -50,6 +51,7 @@ public class QueryService {
private final TimeseriesProcessor timeseriesProcessor;
private final CountProcessor countProcessor;
private final MultiValueQueryProcessor multiValueQueryProcessor;
private final PatientProcessor patientProcessor;

private final DictionaryService dictionaryService;
private final QueryDecorator queryDecorator;
Expand All @@ -59,15 +61,15 @@ public class QueryService {

@Autowired
public QueryService (AbstractProcessor abstractProcessor,
QueryProcessor queryProcessor,
TimeseriesProcessor timeseriesProcessor,
CountProcessor countProcessor,
MultiValueQueryProcessor multiValueQueryProcessor,
@Autowired(required = false) DictionaryService dictionaryService,
QueryProcessor queryProcessor,
TimeseriesProcessor timeseriesProcessor,
CountProcessor countProcessor,
MultiValueQueryProcessor multiValueQueryProcessor,
@Autowired(required = false) DictionaryService dictionaryService,
QueryDecorator queryDecorator,
@Value("${SMALL_JOB_LIMIT}") Integer smallJobLimit,
@Value("${SMALL_TASK_THREADS}") Integer smallTaskThreads,
@Value("${LARGE_TASK_THREADS}") Integer largeTaskThreads) {
@Value("${SMALL_JOB_LIMIT}") Integer smallJobLimit,
@Value("${SMALL_TASK_THREADS}") Integer smallTaskThreads,
@Value("${LARGE_TASK_THREADS}") Integer largeTaskThreads, PatientProcessor patientProcessor) {
this.abstractProcessor = abstractProcessor;
this.queryProcessor = queryProcessor;
this.timeseriesProcessor = timeseriesProcessor;
Expand All @@ -79,9 +81,10 @@ public QueryService (AbstractProcessor abstractProcessor,
SMALL_JOB_LIMIT = smallJobLimit;
SMALL_TASK_THREADS = smallTaskThreads;
LARGE_TASK_THREADS = largeTaskThreads;
this.patientProcessor = patientProcessor;


/* These have to be of type Runnable(nothing more specific) in order
/* These have to be of type Runnable(nothing more specific) in order
* to be compatible with ThreadPoolExecutor constructor prototype
*/
largeTaskExecutionQueue = new PriorityBlockingQueue<Runnable>(1000);
Expand Down Expand Up @@ -124,6 +127,8 @@ private AsyncResult initializeResult(Query query) throws IOException {

HpdsProcessor p;
switch(query.getExpectedResultType()) {
case PATIENTS:
p = patientProcessor;
case SECRET_ADMIN_DATAFRAME:
p = queryProcessor;
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package edu.harvard.hms.dbmi.avillach.hpds.service.filesharing;

import edu.harvard.hms.dbmi.avillach.hpds.data.query.Query;
import edu.harvard.hms.dbmi.avillach.hpds.data.query.ResultType;
import edu.harvard.hms.dbmi.avillach.hpds.processing.AsyncResult;
import edu.harvard.hms.dbmi.avillach.hpds.processing.VariantListProcessor;
import edu.harvard.hms.dbmi.avillach.hpds.service.QueryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Optional;

/**
* Used for sharing data. Given a query, this service will write
Expand All @@ -23,21 +20,25 @@ public class FileSharingService {

private static final Logger LOG = LoggerFactory.getLogger(FileSharingService.class);

@Autowired
private QueryService queryService;

@Autowired
private FileSystemService fileWriter;

@Autowired
private VariantListProcessor variantListProcessor;
private final QueryService queryService;
private final FileSystemService fileWriter;
private final VariantListProcessor variantListProcessor;

public FileSharingService(
QueryService queryService, FileSystemService fileWriter,
VariantListProcessor variantListProcessor
) {
this.queryService = queryService;
this.fileWriter = fileWriter;
this.variantListProcessor = variantListProcessor;
}

public boolean createPhenotypicData(Query query) {
AsyncResult result = queryService.getResultFor(query.getId());
if (result == null || result.getStatus() != AsyncResult.Status.SUCCESS) {
return false;
}
return fileWriter.writeResultToFile("phenotypic_data.csv", result, query.getPicSureId());
return createAndWriteData(query, "phenotypic_data.csv");
}

public boolean createPatientList(Query query) {
return createAndWriteData(query, "patients.txt");
}

public boolean createGenomicData(Query query) {
Expand All @@ -49,4 +50,12 @@ public boolean createGenomicData(Query query) {
return false;
}
}

private boolean createAndWriteData(Query query, String fileName) {
AsyncResult result = queryService.getResultFor(query.getId());
if (result == null || result.getStatus() != AsyncResult.Status.SUCCESS) {
return false;
}
return fileWriter.writeResultToFile(fileName, result, query.getPicSureId());
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,49 @@
package edu.harvard.hms.dbmi.avillach.hpds.service.filesharing;

import edu.harvard.hms.dbmi.avillach.hpds.data.query.Query;
import edu.harvard.hms.dbmi.avillach.hpds.data.query.ResultType;
import edu.harvard.hms.dbmi.avillach.hpds.processing.AsyncResult;
import edu.harvard.hms.dbmi.avillach.hpds.processing.VariantListProcessor;
import edu.harvard.hms.dbmi.avillach.hpds.processing.io.ResultWriter;
import edu.harvard.hms.dbmi.avillach.hpds.processing.patient.PatientProcessor;
import edu.harvard.hms.dbmi.avillach.hpds.service.QueryService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import java.io.IOException;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

@ExtendWith(MockitoExtension.class)
@EnableAutoConfiguration
@SpringBootTest(classes = FileSharingService.class)
public class FileSharingServiceTest {

@Mock
@MockBean
QueryService queryService;

@Mock
@MockBean
FileSystemService fileWriter;

@Mock
@MockBean
VariantListProcessor variantListProcessor;

@Mock
@MockBean
PatientProcessor patientProcessor;

@MockBean
ResultWriter resultWriter;

@InjectMocks
@Autowired
FileSharingService subject;

@Test
Expand Down Expand Up @@ -95,4 +106,22 @@ public void shouldNotCreateGenomicData() throws IOException {

assertFalse(actual);
}

@Test
void shouldCreatePatientsList() {
Query query = new Query();
query.setId("jasdijasd");
query.setPicSureId("jasdijasd");
query.setExpectedResultType(ResultType.PATIENTS);
AsyncResult result = new AsyncResult(query, patientProcessor, resultWriter);
result.setStatus(AsyncResult.Status.SUCCESS);
Mockito.when(queryService.getResultFor("jasdijasd"))
.thenReturn(result);
Mockito.when(fileWriter.writeResultToFile("patients.txt", result, "jasdijasd"))
.thenReturn(true);

boolean actual = subject.createPatientList(query);

Assertions.assertTrue(actual);
}
}
Loading