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

[GLT-4068] added case TAT report #194

Merged
merged 8 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions changes/add_GLT-4068.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Case TAT report generated from the Cases list and uses the same filters that are applied to the list
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.io.IOException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
Expand All @@ -13,10 +12,12 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.JsonNode;
import ca.on.oicr.gsi.dimsum.controller.BadRequestException;
import ca.on.oicr.gsi.dimsum.service.CaseService;
import ca.on.oicr.gsi.dimsum.util.reporting.Report;
import ca.on.oicr.gsi.dimsum.util.reporting.ReportFormat;
import ca.on.oicr.gsi.dimsum.util.reporting.reports.CaseTatReport;
import ca.on.oicr.gsi.dimsum.util.reporting.reports.DareInputSheet;
import ca.on.oicr.gsi.dimsum.util.reporting.reports.FullDepthSummary;
import ca.on.oicr.gsi.dimsum.util.reporting.reports.TglTrackingReport;
Expand All @@ -30,7 +31,7 @@ public class DownloadRestController {

@PostMapping("/reports/{reportName}")
public HttpEntity<byte[]> generateReport(@PathVariable String reportName,
@RequestBody Map<String, String> parameters, HttpServletResponse response)
@RequestBody JsonNode parameters, HttpServletResponse response)
throws IOException {

ReportFormat format = Report.getFormat(parameters);
Expand All @@ -55,6 +56,8 @@ private static Report getReport(String reportName) {
return FullDepthSummary.INSTANCE;
case "dare-input-sheet":
return DareInputSheet.INSTANCE;
case "case-tat-report":
return CaseTatReport.INSTANCE;
default:
throw new BadRequestException("Invalid report name");
}
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/ca/on/oicr/gsi/dimsum/util/reporting/Column.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ public String getDelimitedColumnString(String delimiter, T object) {
};
}

public static <T> Column<T> forInteger(String title, Function<T, Integer> getter) {
return new Column<T>(title) {

@Override
public void writeExcelCell(Cell cell, T object) {
Integer value = getter.apply(object);
if (value != null) {
cell.setCellValue(value.doubleValue());
}
}

@Override
public String getDelimitedColumnString(String delimiter, T object) {
Integer value = getter.apply(object);
return value != null ? value.toString() : "";
}
};
}

private final String title;

public Column(String title) {
Expand Down
16 changes: 8 additions & 8 deletions src/main/java/ca/on/oicr/gsi/dimsum/util/reporting/Report.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import com.fasterxml.jackson.databind.JsonNode;
import ca.on.oicr.gsi.dimsum.controller.BadRequestException;
import ca.on.oicr.gsi.dimsum.service.CaseService;

Expand All @@ -28,8 +27,8 @@ public String getTitle() {
return title;
}

public static ReportFormat getFormat(Map<String, String> parameters) {
String format = parameters.get(PARAM_FORMAT);
public static ReportFormat getFormat(JsonNode parameters) {
String format = parameters.has(PARAM_FORMAT) ? parameters.get(PARAM_FORMAT).asText() : null;
if (format == null) {
return ReportFormat.EXCEL;
}
Expand All @@ -45,7 +44,7 @@ public static ReportFormat getFormat(Map<String, String> parameters) {
}
}

public byte[] writeFile(CaseService caseService, Map<String, String> parameters)
public byte[] writeFile(CaseService caseService, JsonNode parameters)
throws IOException {
ReportFormat format = getFormat(parameters);

Expand All @@ -61,7 +60,7 @@ public byte[] writeFile(CaseService caseService, Map<String, String> parameters)
}
}

private byte[] writeExcelFile(CaseService caseService, Map<String, String> parameters)
private byte[] writeExcelFile(CaseService caseService, JsonNode parameters)
throws IOException {
XSSFWorkbook workbook = new XSSFWorkbook();
for (ReportSection<?> section : sections) {
Expand All @@ -74,9 +73,10 @@ private byte[] writeExcelFile(CaseService caseService, Map<String, String> param
}

private byte[] writeDelimitedFile(CaseService caseService, String delimiter,
Map<String, String> parameters) {
JsonNode parameters) {
StringBuilder sb = new StringBuilder();
boolean includeHeadings = Objects.equals("true", parameters.get(PARAM_HEADINGS));
boolean includeHeadings =
parameters.has(PARAM_HEADINGS) && "true".equals(parameters.get(PARAM_HEADINGS).asText());
wuall826 marked this conversation as resolved.
Show resolved Hide resolved
// This does not support multiple sections
sections.get(0).createDelimitedText(sb, caseService, delimiter, includeHeadings, parameters);
return sb.toString().getBytes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import com.fasterxml.jackson.databind.JsonNode;
import ca.on.oicr.gsi.dimsum.controller.BadRequestException;
import ca.on.oicr.gsi.dimsum.service.CaseService;

Expand Down Expand Up @@ -84,7 +84,7 @@ public List<Column<T>> getColumns() {
}

public void createExcelSheet(XSSFWorkbook workbook, CaseService caseService,
Map<String, String> parameters) {
JsonNode parameters) {
List<T> objects = getData(caseService, parameters);
XSSFSheet worksheet = workbook.createSheet(getTitle());
writeExcelSheet(worksheet, objects);
Expand All @@ -93,7 +93,7 @@ public void createExcelSheet(XSSFWorkbook workbook, CaseService caseService,
protected abstract void writeExcelSheet(XSSFSheet worksheet, List<T> objects);

public void createDelimitedText(StringBuilder sb, CaseService caseService,
String delimiter, boolean includeHeadings, Map<String, String> parameters) {
String delimiter, boolean includeHeadings, JsonNode parameters) {
List<T> objects = getData(caseService, parameters);
writeDelimitedText(sb, objects, delimiter, includeHeadings);
}
Expand All @@ -110,10 +110,11 @@ protected abstract void writeDelimitedText(StringBuilder sb, List<T> objects, St
*
* @throws BadRequestException if there are invalid parameters
*/
public abstract List<T> getData(CaseService caseService, Map<String, String> parameters);
public abstract List<T> getData(CaseService caseService, JsonNode parameters);

protected static Set<String> getParameterStringSet(Map<String, String> parameters, String name) {
String value = parameters.get(name);
protected static Set<String> getParameterStringSet(JsonNode parameters, String name) {
JsonNode valueNode = parameters.get(name);
String value = (valueNode != null) ? valueNode.asText() : null;
if (value == null || value.isEmpty()) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package ca.on.oicr.gsi.dimsum.util.reporting.reports;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.JsonNode;
import ca.on.oicr.gsi.cardea.data.Case;
import ca.on.oicr.gsi.cardea.data.CaseDeliverable;
import ca.on.oicr.gsi.cardea.data.CaseRelease;
import ca.on.oicr.gsi.cardea.data.Project;
import ca.on.oicr.gsi.cardea.data.Requisition;
import ca.on.oicr.gsi.cardea.data.Sample;
import ca.on.oicr.gsi.cardea.data.Test;
import ca.on.oicr.gsi.dimsum.controller.BadRequestException;
import ca.on.oicr.gsi.dimsum.service.CaseService;
import ca.on.oicr.gsi.dimsum.service.filtering.CaseFilter;
import ca.on.oicr.gsi.dimsum.service.filtering.CaseFilterKey;
import ca.on.oicr.gsi.dimsum.util.SampleUtils;
import ca.on.oicr.gsi.dimsum.util.reporting.Column;
import ca.on.oicr.gsi.dimsum.util.reporting.Report;
import ca.on.oicr.gsi.dimsum.util.reporting.ReportSection;
import ca.on.oicr.gsi.dimsum.util.reporting.ReportSection.TableReportSection;

public class CaseTatReport extends Report {

private static class RowData {

private final Case kase;
private final Test test;

public RowData(Case kase, Test test) {
this.kase = kase;
this.test = test;
}

public Case getCase() {
return kase;
}

public Test getTest() {
return test;
}
}

private static final ReportSection<RowData> caseSection =
new TableReportSection<>("Case Report",
Arrays.asList(
Column.forString("Case ID", x -> x.getCase().getId()),
Column.forString("Projects",
x -> getSortedProjectNameOrPipeline(x.getCase(), Project::getName)),
Column.forString("Pipeline",
x -> getSortedProjectNameOrPipeline(x.getCase(), Project::getPipeline)),
Column.forString("Requisition", x -> x.getCase().getRequisition().getName()),
Column.forString("Assay", x -> x.getCase().getAssayName()),
Column.forString("Start Date",
x -> x.getCase().getStartDate().format(DateTimeFormatter.ISO_LOCAL_DATE)),
Column.forString("Receipt Completed",
x -> findLatestCompletionDate(x.getCase().getReceipts())),
Column.forInteger("Receipt Days", x -> x.getCase().getReceiptDaysSpent()),
Column.forString("Test", x -> x.getTest().getName()),
Column.forString("Supplemental Only",
x -> isSupplementalOnly(x.getTest(), x.getCase().getRequisition()) ? "Yes"
: "No"),
Column.forString("Extraction Completed",
x -> findLatestCompletionDate(x.getTest().getExtractions())),
Column.forInteger("Extraction Days", x -> x.getTest().getExtractionDaysSpent()),
Column.forString("Library Prep Completed",
x -> findLatestCompletionDate(x.getTest().getLibraryPreparations())),
Column.forInteger("Library Prep. Days",
x -> x.getTest().getLibraryPreparationDaysSpent()),
Column.forString("Library Qual Completed",
x -> findLatestCompletionDate(x.getTest().getLibraryQualifications())),
Column.forInteger("Library Qual. Days",
x -> x.getTest().getLibraryQualificationDaysSpent()),
Column.forString("Full-Depth Completed",
x -> findLatestCompletionDate(x.getTest().getFullDepthSequencings())),
Column.forInteger("Full-Depth Days",
x -> x.getTest().getFullDepthSequencingDaysSpent()),
Column.forString("Analysis Review Completed",
x -> getMaxDateFromDeliverables(x.getCase(),
CaseDeliverable::getAnalysisReviewQcDate)),
Column.forInteger("Analysis Review Days",
x -> x.getCase().getAnalysisReviewDaysSpent()),
Column.forString("Release Approval Completed",
x -> getMaxDateFromDeliverables(x.getCase(),
CaseDeliverable::getReleaseApprovalQcDate)),
Column.forString("Release Completed", x -> {
if (!x.getCase().isStopped()) {
return getCompletionDate(x.getCase());
}
List<LocalDate> dates = x.getCase().getDeliverables().stream()
.flatMap(deliverable -> deliverable.getReleases().stream())
.map(CaseRelease::getQcDate)
.collect(Collectors.toList());
if (dates.contains(null)) {
return null;
}
return dates.stream().max(LocalDate::compareTo)
.map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)).orElse(null);
}),
Column.forInteger("Release Days", x -> x.getCase().getReleaseDaysSpent()),
Column.forString("Completion Date", x -> getCompletionDate(x.getCase())),
wuall826 marked this conversation as resolved.
Show resolved Hide resolved
Column.forInteger("Total Days", x -> x.getCase().getCaseDaysSpent()))) {

@Override
public List<RowData> getData(CaseService caseService, JsonNode parameters) {
List<CaseFilter> filters = convertParametersToFilters(parameters);
return caseService.getCaseStream(filters)
.flatMap(kase -> kase.getTests().stream().map(test -> new RowData(kase, test)))
.collect(Collectors.toList());
}
};

public static final CaseTatReport INSTANCE = new CaseTatReport();

private CaseTatReport() {
super("Case TAT Report", caseSection);
}

private static String getSortedProjectNameOrPipeline(Case kase,
Function<Project, String> function) {
return kase.getProjects().stream()
.map(function)
.sorted()
.collect(Collectors.joining(", "));
}

private static List<CaseFilter> convertParametersToFilters(JsonNode parameters) {
List<CaseFilter> filters = new ArrayList<>();
parameters.fields().forEachRemaining(entry -> {
try {
CaseFilterKey filterKey = CaseFilterKey.valueOf(entry.getKey().toUpperCase());
String[] values = entry.getValue().asText().split(",");
for (String value : values) {
CaseFilter filter = new CaseFilter(filterKey, value.trim());
filters.add(filter);
}
} catch (IllegalArgumentException e) {
throw new BadRequestException(
"Invalid filter key: " + entry.getKey() + ". " + e.getMessage());
}
wuall826 marked this conversation as resolved.
Show resolved Hide resolved
});
return filters;
}

private static boolean isSupplementalOnly(Test test, Requisition requisition) {
if (test.getExtractions().isEmpty()
|| test.getLibraryPreparations().isEmpty()
|| test.getLibraryQualifications().isEmpty()
|| test.getFullDepthSequencings().isEmpty()) {
wuall826 marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
return test.getExtractions().stream()
.allMatch(sample -> !sample.getRequisitionId().equals(requisition.getId()))
&& test.getLibraryPreparations().stream()
.allMatch(sample -> !sample.getRequisitionId().equals(requisition.getId()))
&& test.getLibraryQualifications().stream()
.allMatch(sample -> !sample.getRequisitionId().equals(requisition.getId()))
&& test.getFullDepthSequencings().stream()
.allMatch(sample -> !sample.getRequisitionId().equals(requisition.getId()));
wuall826 marked this conversation as resolved.
Show resolved Hide resolved
}

private static String findLatestCompletionDate(List<Sample> samples) {
LocalDate latestDate = null;
for (Sample sample : samples) {
if (SampleUtils.isPassed(sample)) {
LocalDate dateToCompare = null;
if (sample.getRun() != null) {
LocalDate sampleReviewDate = sample.getDataReviewDate();
LocalDate runReviewDate = sample.getRun().getDataReviewDate();
if (sampleReviewDate != null && runReviewDate != null) {
dateToCompare =
runReviewDate.isAfter(sampleReviewDate) ? runReviewDate : sampleReviewDate;
}
} else {
dateToCompare = sample.getQcDate();
}
if (dateToCompare != null && (latestDate == null || dateToCompare.isAfter(latestDate))) {
latestDate = dateToCompare;
}
}
}
return latestDate != null ? latestDate.format(DateTimeFormatter.ISO_LOCAL_DATE) : null;
}

private static String getMaxDateFromDeliverables(Case x,
Function<CaseDeliverable, LocalDate> dateExtractor) {
List<LocalDate> dates = x.getDeliverables().stream()
.map(dateExtractor)
.collect(Collectors.toList());
if (dates.contains(null)) {
return null;
}
return dates.stream().max(LocalDate::compareTo)
.map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)).orElse(null);
}

private static String getCompletionDate(Case x) {
if (x.isStopped()) {
return "STOPPED";
}
List<CaseDeliverable> deliverables = x.getDeliverables();
if (deliverables.isEmpty()) {
return null;
}
List<CaseRelease> releases = deliverables.stream()
.flatMap(deliverable -> deliverable.getReleases().stream())
.collect(Collectors.toList());
if (releases.isEmpty()) {
return null;
}
if (releases.stream()
.anyMatch(release -> release.getQcPassed() == null || !release.getQcPassed())) {
return null;
}
LocalDate latestQcDate = releases.stream()
.map(CaseRelease::getQcDate)
.max(LocalDate::compareTo)
.orElse(null);
return latestQcDate != null ? latestQcDate.toString() : null;
}
}
Loading
Loading