From 9c63cf58b1e361ce59d1cf9b49ebfcff603369b6 Mon Sep 17 00:00:00 2001 From: Ally Wu Date: Mon, 1 Apr 2024 16:04:11 -0400 Subject: [PATCH] comments implemented --- .../rest/DownloadRestController.java | 3 + .../gsi/dimsum/util/reporting/Column.java | 19 ++ .../util/reporting/reports/CaseTatReport.java | 206 ++++++++++++++++++ ts/component/table-builder.ts | 6 +- ts/data/case.ts | 19 +- 5 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ca/on/oicr/gsi/dimsum/util/reporting/reports/CaseTatReport.java diff --git a/src/main/java/ca/on/oicr/gsi/dimsum/controller/rest/DownloadRestController.java b/src/main/java/ca/on/oicr/gsi/dimsum/controller/rest/DownloadRestController.java index 3784146d..a72a2ded 100644 --- a/src/main/java/ca/on/oicr/gsi/dimsum/controller/rest/DownloadRestController.java +++ b/src/main/java/ca/on/oicr/gsi/dimsum/controller/rest/DownloadRestController.java @@ -17,6 +17,7 @@ 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; @@ -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"); } diff --git a/src/main/java/ca/on/oicr/gsi/dimsum/util/reporting/Column.java b/src/main/java/ca/on/oicr/gsi/dimsum/util/reporting/Column.java index 639e3f1b..191b4b48 100644 --- a/src/main/java/ca/on/oicr/gsi/dimsum/util/reporting/Column.java +++ b/src/main/java/ca/on/oicr/gsi/dimsum/util/reporting/Column.java @@ -51,6 +51,25 @@ public String getDelimitedColumnString(String delimiter, T object) { }; } + public static Column forInteger(String title, Function getter) { + return new Column(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) { diff --git a/src/main/java/ca/on/oicr/gsi/dimsum/util/reporting/reports/CaseTatReport.java b/src/main/java/ca/on/oicr/gsi/dimsum/util/reporting/reports/CaseTatReport.java new file mode 100644 index 00000000..fce39f28 --- /dev/null +++ b/src/main/java/ca/on/oicr/gsi/dimsum/util/reporting/reports/CaseTatReport.java @@ -0,0 +1,206 @@ +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.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; +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.Requisition; +import ca.on.oicr.gsi.cardea.data.Sample; +import ca.on.oicr.gsi.cardea.data.Test; +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 caseSection = new TableReportSection<>( + "Case Report", + Arrays.asList( + Column.forString("Case ID", x -> x.getCase().getId()), + Column.forString("Projects", + x -> getSortedProjectNameAndPipeline(x.getCase()).stream() + .map(str -> str.split(" - ")[0]) + .collect(Collectors.joining(", "))), + Column.forString("Pipeline", + x -> getSortedProjectNameAndPipeline(x.getCase()).stream() + .map(str -> str.split(" - ")[1]) + .collect(Collectors.joining(", "))), + 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 Date", x -> Optional + .ofNullable(findLatestCompletionDate(x.getCase().getReceipts())) + .map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)) + .orElse("")), + 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 Date", x -> Optional + .ofNullable(findLatestCompletionDate(x.getTest().getExtractions())) + .map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)) + .orElse("")), + Column.forInteger("Extraction Days", x -> x.getTest().getExtractionDaysSpent()), + Column.forString("Library Prep Completed Date", x -> Optional.ofNullable( + findLatestCompletionDate(x.getTest().getLibraryPreparations())) + .map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)) + .orElse("")), + Column.forInteger("Library Prep. Days", + x -> x.getTest().getLibraryPreparationDaysSpent()), + Column.forString("Library Qual Completed Date", x -> Optional.ofNullable( + findLatestCompletionDate(x.getTest().getLibraryQualifications())) + .map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)) + .orElse("")), + Column.forInteger("Library Qual. Days", + x -> x.getTest().getLibraryQualificationDaysSpent()), + Column.forString("Full Depth Completed Date", x -> Optional + .ofNullable( + findLatestCompletionDate(x.getTest().getFullDepthSequencings())) + .map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)) + .orElse("")), + Column.forInteger("Full-Depth Days", + x -> x.getTest().getFullDepthSequencingDaysSpent()), + Column.forString("Analysis Review Completed", + x -> x.getCase().getDeliverables().stream() + .map(CaseDeliverable::getAnalysisReviewQcDate) + .filter(Objects::nonNull) + .max(LocalDate::compareTo) + .map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)) + .orElse("")), + Column.forInteger("Analysis Review Days", + x -> x.getCase().getAnalysisReviewDaysSpent()), + Column.forString("Release Approval Completed", + x -> x.getCase().getDeliverables().stream() + .map(CaseDeliverable::getReleaseApprovalQcDate) + .filter(Objects::nonNull) + .max(LocalDate::compareTo) + .map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)) + .orElse("")), + Column.forInteger("Release Approval Days", + x -> x.getCase().getReleaseApprovalDaysSpent()), + Column.forString("Completion Date", x -> getCompletionDate(x.getCase())), + Column.forInteger("Total Days", x -> x.getCase().getCaseDaysSpent()))) { + @Override + public List getData(CaseService caseService, Map parameters) { + List 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 Collection getSortedProjectNameAndPipeline(Case case1) { + return case1.getProjects() + .stream() + .map(project -> project.getName() + " - " + project.getPipeline()) + .sorted(Comparator + .comparing(str -> !str.contains("Accredited with Clinical Report"))) + .collect(Collectors.toList()); + } + + private static List convertParametersToFilters(Map parameters) { + List filters = new ArrayList<>(); + parameters.forEach((key, value) -> { + CaseFilterKey filterKey = CaseFilterKey.valueOf(key.toUpperCase()); + CaseFilter filter = new CaseFilter(filterKey, value); + filters.add(filter); + }); + return filters; + } + + private static boolean isSupplementalOnly(Test test, Requisition requisition) { + if (test == null || test.getExtractions().isEmpty()) { + return false; + } + return test.getExtractions().stream() + .allMatch(sample -> !sample.getRequisitionId().equals(requisition.getId())); + } + + private static LocalDate findLatestCompletionDate(List samples) { + return samples.stream() + .filter(SampleUtils::isPassed) + .map(sample -> { + if (sample.getRun() != null) { + LocalDate sampleReviewDate = sample.getDataReviewDate(); + LocalDate runReviewDate = sample.getRun().getDataReviewDate(); + return Stream.of(sampleReviewDate, runReviewDate) + .filter(Objects::nonNull) + .max(LocalDate::compareTo) + .orElse(null); + } else { + return sample.getQcDate(); + } + }) + .filter(Objects::nonNull) + .max(LocalDate::compareTo) + .orElse(null); + } + + private static String getCompletionDate(Case x) { + if (x.isStopped()) { + return "STOPPED"; + } + List deliverables = x.getDeliverables(); + if (deliverables.isEmpty()) { + return null; + } + List releases = deliverables.get(0).getReleases(); + 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; + } +} diff --git a/ts/component/table-builder.ts b/ts/component/table-builder.ts index 81bf2216..988c349d 100644 --- a/ts/component/table-builder.ts +++ b/ts/component/table-builder.ts @@ -53,7 +53,7 @@ export interface FilterDefinition { export interface StaticAction { title: string; - handler: () => void; + handler: (filters: { key: string; value: string }[]) => void; } export interface BulkAction { @@ -316,7 +316,9 @@ export class TableBuilder { } if (this.definition.staticActions) { this.definition.staticActions.forEach((action) => { - addActionButton(container, action.title, action.handler); + addActionButton(container, action.title, () => + action.handler(this.acceptedFilters) + ); }); } } diff --git a/ts/data/case.ts b/ts/data/case.ts index 8e338565..1c475b55 100644 --- a/ts/data/case.ts +++ b/ts/data/case.ts @@ -187,7 +187,24 @@ export const caseDefinition: TableDefinition = { } return null; }, - staticActions: [legendAction], + staticActions: [ + legendAction, + { + title: "TAT Report", + handler: (filters: { key: string; value: string }[]) => { + const currentFilters: { [key: string]: any } = {}; + for (const filter of filters) { + if (filter.value !== undefined && filter.value !== null) { + currentFilters[filter.key] = filter.value; + } + } + postDownload( + urls.rest.downloads.reports("case-tat-report"), + currentFilters + ); + }, + }, + ], bulkActions: [ { title: "Download",