diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/RequisitionQcGroup.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/AnalysisQcGroup.java similarity index 84% rename from cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/RequisitionQcGroup.java rename to cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/AnalysisQcGroup.java index c8bbf0d..f4daa53 100644 --- a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/RequisitionQcGroup.java +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/AnalysisQcGroup.java @@ -9,20 +9,18 @@ /** * Immutable RequisitionQcGroup */ -@JsonDeserialize(builder = RequisitionQcGroup.Builder.class) -public class RequisitionQcGroup { +@JsonDeserialize(builder = AnalysisQcGroup.Builder.class) +public class AnalysisQcGroup { private final BigDecimal callability; private final BigDecimal collapsedCoverage; - private final Donor donor; private final String groupId; private final String libraryDesignCode; private final BigDecimal purity; private final String tissueOrigin; private final String tissueType; - private RequisitionQcGroup(Builder builder) { - this.donor = requireNonNull(builder.donor); + private AnalysisQcGroup(Builder builder) { this.tissueOrigin = requireNonNull(builder.tissueOrigin); this.tissueType = requireNonNull(builder.tissueType); this.libraryDesignCode = requireNonNull(builder.libraryDesignCode); @@ -40,10 +38,6 @@ public BigDecimal getCollapsedCoverage() { return collapsedCoverage; } - public Donor getDonor() { - return donor; - } - public String getGroupId() { return groupId; } @@ -69,15 +63,14 @@ public static class Builder { private BigDecimal callability; private BigDecimal collapsedCoverage; - private Donor donor; private String groupId; private String libraryDesignCode; private BigDecimal purity; private String tissueOrigin; private String tissueType; - public RequisitionQcGroup build() { - return new RequisitionQcGroup(this); + public AnalysisQcGroup build() { + return new AnalysisQcGroup(this); } public Builder callability(BigDecimal callability) { @@ -90,11 +83,6 @@ public Builder collapsedCoverage(BigDecimal collapsedCoverage) { return this; } - public Builder donor(Donor donor) { - this.donor = donor; - return this; - } - public Builder groupId(String groupId) { this.groupId = groupId; return this; diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Case.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Case.java index 64ea945..e731cb5 100644 --- a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Case.java +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Case.java @@ -1,5 +1,6 @@ package ca.on.oicr.gsi.cardea.data; +import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; @@ -31,7 +32,8 @@ public class Case { private final Requisition requisition; private final LocalDate startDate; private final List tests; - private final List deliverables; + private final List qcGroups; + private final List deliverables; private final String timepoint; private final String tissueOrigin; private final String tissueType; @@ -56,15 +58,19 @@ private Case(Builder builder) { this.timepoint = builder.timepoint; this.receipts = unmodifiableList(builder.receipts); this.tests = unmodifiableList(builder.tests); + this.qcGroups = builder.qcGroups == null ? emptyList() : unmodifiableList(builder.qcGroups); this.deliverables = - builder.deliverables == null ? null : unmodifiableList(builder.deliverables); + builder.deliverables == null ? emptyList() : unmodifiableList(builder.deliverables); this.requisition = requireNonNull(builder.requisition); this.startDate = requireNonNull(builder.startDate); + this.latestActivityDate = Stream - .of(receipts.stream().map(Sample::getLatestActivityDate), - tests.stream().map(Test::getLatestActivityDate), - Stream.of(requisition.getLatestActivityDate())) - .flatMap(Function.identity()).filter(Objects::nonNull).max(LocalDate::compareTo) + .concat(Stream.of(receipts.stream().map(Sample::getLatestActivityDate), + tests.stream().map(Test::getLatestActivityDate)) + .flatMap(Function.identity()), + deliverables == null ? Stream.empty() + : deliverables.stream().map(CaseDeliverable::getLatestActivityDate)) + .filter(Objects::nonNull).max(LocalDate::compareTo) .orElse(null); this.receiptDaysSpent = builder.receiptDaysSpent; this.analysisReviewDaysSpent = builder.analysisReviewDaysSpent; @@ -118,7 +124,11 @@ public List getTests() { return tests; } - public List getDeliverables() { + public List getQcGroups() { + return qcGroups; + } + + public List getDeliverables() { return deliverables; } @@ -174,7 +184,8 @@ public static class Builder { private List receipts; private Requisition requisition; private List tests; - private List deliverables; + private List qcGroups; + private List deliverables; private String timepoint; private String tissueOrigin; private String tissueType; @@ -235,7 +246,12 @@ public Builder requisition(Requisition requisition) { return this; } - public Builder deliverables(List deliverables) { + public Builder qcGroups(List qcGroups) { + this.qcGroups = qcGroups; + return this; + } + + public Builder deliverables(List deliverables) { this.deliverables = deliverables; return this; } diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Deliverable.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/CaseDeliverable.java similarity index 73% rename from cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Deliverable.java rename to cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/CaseDeliverable.java index e3d911d..869e868 100644 --- a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Deliverable.java +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/CaseDeliverable.java @@ -3,11 +3,15 @@ import static java.util.Objects.requireNonNull; import java.time.LocalDate; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -@JsonDeserialize(builder = Deliverable.Builder.class) -public class Deliverable { +@JsonDeserialize(builder = CaseDeliverable.Builder.class) +public class CaseDeliverable { private final DeliverableType deliverableType; private final LocalDate analysisReviewQcDate; @@ -18,12 +22,10 @@ public class Deliverable { private final Boolean releaseApprovalQcPassed; private final String releaseApprovalQcUser; private final String releaseApprovalQcNote; - private final LocalDate releaseQcDate; - private final Boolean releaseQcPassed; - private final String releaseQcUser; - private final String releaseQcNote; + private final List releases; + private final LocalDate latestActivityDate; - private Deliverable(Builder builder) { + private CaseDeliverable(Builder builder) { this.deliverableType = requireNonNull(builder.deliverableType); this.analysisReviewQcDate = builder.analysisReviewQcDate; this.analysisReviewQcPassed = builder.analysisReviewQcPassed; @@ -33,10 +35,14 @@ private Deliverable(Builder builder) { this.releaseApprovalQcPassed = builder.releaseApprovalQcPassed; this.releaseApprovalQcUser = builder.releaseApprovalQcUser; this.releaseApprovalQcNote = builder.releaseApprovalQcNote; - this.releaseQcDate = builder.releaseQcDate; - this.releaseQcPassed = builder.releaseQcPassed; - this.releaseQcUser = builder.releaseQcUser; - this.releaseQcNote = builder.releaseQcNote; + this.releases = builder.releases == null ? Collections.emptyList() + : Collections.unmodifiableList(builder.releases); + this.latestActivityDate = Stream + .concat(releases == null ? Stream.empty() : releases.stream().map(CaseRelease::getQcDate), + Stream.of(analysisReviewQcDate, releaseApprovalQcDate)) + .filter(Objects::nonNull) + .max(LocalDate::compareTo) + .orElse(null); } public DeliverableType getDeliverableType() { @@ -75,21 +81,14 @@ public String getReleaseApprovalQcNote() { return releaseApprovalQcNote; } - public LocalDate getReleaseQcDate() { - return releaseQcDate; + public List getReleases() { + return releases; } - public Boolean getReleaseQcPassed() { - return releaseQcPassed; + public LocalDate getLatestActivityDate() { + return latestActivityDate; } - public String getReleaseQcUser() { - return releaseQcUser; - } - - public String getReleaseQcNote() { - return releaseQcNote; - } @JsonPOJOBuilder(withPrefix = "") public static class Builder { @@ -103,10 +102,7 @@ public static class Builder { private Boolean releaseApprovalQcPassed; private String releaseApprovalQcUser; private String releaseApprovalQcNote; - private LocalDate releaseQcDate; - private Boolean releaseQcPassed; - private String releaseQcUser; - private String releaseQcNote; + private List releases; public Builder deliverableType(DeliverableType deliverableType) { this.deliverableType = deliverableType; @@ -153,28 +149,13 @@ public Builder releaseApprovalQcNote(String releaseApprovalQcNote) { return this; } - public Builder releaseQcDate(LocalDate releaseQcDate) { - this.releaseQcDate = releaseQcDate; - return this; - } - - public Builder releaseQcPassed(Boolean releaseQcPassed) { - this.releaseQcPassed = releaseQcPassed; - return this; - } - - public Builder releaseQcUser(String releaseQcUser) { - this.releaseQcUser = releaseQcUser; - return this; - } - - public Builder releaseQcNote(String releaseQcNote) { - this.releaseQcNote = releaseQcNote; + public Builder releases(List releases) { + this.releases = releases; return this; } - public Deliverable build() { - return new Deliverable(this); + public CaseDeliverable build() { + return new CaseDeliverable(this); } } diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/CaseRelease.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/CaseRelease.java new file mode 100644 index 0000000..4027815 --- /dev/null +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/CaseRelease.java @@ -0,0 +1,84 @@ +package ca.on.oicr.gsi.cardea.data; + +import java.time.LocalDate; +import java.util.Objects; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +@JsonDeserialize(builder = CaseRelease.Builder.class) +public class CaseRelease { + + private final String deliverable; + private final LocalDate qcDate; + private final Boolean qcPassed; + private final String qcUser; + private final String qcNote; + + private CaseRelease(Builder builder) { + this.deliverable = Objects.requireNonNull(builder.deliverable); + this.qcDate = builder.qcDate; + this.qcPassed = builder.qcPassed; + this.qcUser = builder.qcUser; + this.qcNote = builder.qcNote; + } + + public String getDeliverable() { + return deliverable; + } + + public LocalDate getQcDate() { + return qcDate; + } + + public Boolean getQcPassed() { + return qcPassed; + } + + public String getQcUser() { + return qcUser; + } + + public String getQcNote() { + return qcNote; + } + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + + private String deliverable; + private LocalDate qcDate; + private Boolean qcPassed; + private String qcUser; + private String qcNote; + + public Builder deliverable(String deliverable) { + this.deliverable = deliverable; + return this; + } + + public Builder qcDate(LocalDate qcDate) { + this.qcDate = qcDate; + return this; + } + + public Builder qcPassed(Boolean qcPassed) { + this.qcPassed = qcPassed; + return this; + } + + public Builder qcUser(String qcUser) { + this.qcUser = qcUser; + return this; + } + + public Builder qcNote(String qcNote) { + this.qcNote = qcNote; + return this; + } + + public CaseRelease build() { + return new CaseRelease(this); + } + + } +} diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Project.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Project.java index f46ba05..4c882b7 100644 --- a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Project.java +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Project.java @@ -1,6 +1,10 @@ package ca.on.oicr.gsi.cardea.data; import static java.util.Objects.requireNonNull; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; @@ -12,10 +16,15 @@ public class Project { private final String name; private final String pipeline; + private final Map> deliverables; private Project(Builder builder) { this.name = requireNonNull(builder.name); this.pipeline = requireNonNull(builder.pipeline); + Map> tempDeliverables = builder.deliverables.entrySet().stream() + .collect(Collectors.toMap(entry -> entry.getKey(), + entry -> Collections.unmodifiableList(entry.getValue()))); + this.deliverables = Collections.unmodifiableMap(tempDeliverables); } public String getName() { @@ -26,11 +35,16 @@ public String getPipeline() { return pipeline; } + public Map> getDeliverables() { + return deliverables; + } + @JsonPOJOBuilder(withPrefix = "") public static class Builder { private String name; private String pipeline; + private Map> deliverables; public Project build() { return new Project(this); @@ -46,5 +60,10 @@ public Builder pipeline(String pipeline) { return this; } + public Builder deliverables(Map> deliverables) { + this.deliverables = deliverables; + return this; + } + } } diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Requisition.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Requisition.java index 84daa1b..adf6b9e 100644 --- a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Requisition.java +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/Requisition.java @@ -1,14 +1,8 @@ package ca.on.oicr.gsi.cardea.data; -import static java.util.Collections.emptyList; -import static java.util.Collections.unmodifiableList; import static java.util.Objects.requireNonNull; -import java.time.LocalDate; -import java.util.List; import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Stream; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; @@ -19,13 +13,8 @@ public class Requisition { private final Long assayId; - private final List releaseApprovals; - private final List releases; private final long id; - private final List analysisReviews; - private final LocalDate latestActivityDate; private final String name; - private final List qcGroups; private final String stopReason; private final boolean stopped; private final boolean paused; @@ -39,17 +28,6 @@ private Requisition(Builder builder) { this.stopReason = builder.stopReason; this.paused = builder.paused; this.pauseReason = builder.pauseReason; - this.qcGroups = builder.qcGroups == null ? emptyList() : unmodifiableList(builder.qcGroups); - this.analysisReviews = builder.analysisReviews == null ? emptyList() - : unmodifiableList(builder.analysisReviews); - this.releaseApprovals = - builder.releaseApprovals == null ? emptyList() : unmodifiableList(builder.releaseApprovals); - this.releases = - builder.releases == null ? emptyList() : unmodifiableList(builder.releases); - this.latestActivityDate = - Stream.of(analysisReviews.stream(), releaseApprovals.stream(), releases.stream()) - .flatMap(Function.identity()).map(RequisitionQc::getQcDate).max(LocalDate::compareTo) - .orElse(null); } @Override @@ -68,34 +46,14 @@ public Long getAssayId() { return assayId; } - public List getReleaseApprovals() { - return releaseApprovals; - } - - public List getReleases() { - return releases; - } - public long getId() { return id; } - public List getAnalysisReviews() { - return analysisReviews; - } - - public LocalDate getLatestActivityDate() { - return latestActivityDate; - } - public String getName() { return name; } - public List getQcGroups() { - return qcGroups; - } - public boolean isStopped() { return stopped; } @@ -121,12 +79,8 @@ public int hashCode() { public static class Builder { private Long assayId; - private List releaseApprovals; - private List releases; private long id; - private List analysisReviews; private String name; - private List qcGroups; private String stopReason; private boolean stopped; private boolean paused; @@ -141,36 +95,16 @@ public Requisition build() { return new Requisition(this); } - public Builder releaseApprovals(List releaseApprovals) { - this.releaseApprovals = releaseApprovals; - return this; - } - - public Builder releases(List releases) { - this.releases = releases; - return this; - } - public Builder id(long id) { this.id = id; return this; } - public Builder analysisReviews(List analysisReviews) { - this.analysisReviews = analysisReviews; - return this; - } - public Builder name(String name) { this.name = name; return this; } - public Builder qcGroups(List qcGroups) { - this.qcGroups = qcGroups; - return this; - } - public Builder stopReason(String stopReason) { this.stopReason = stopReason; return this; diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/RequisitionQc.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/RequisitionQc.java deleted file mode 100644 index 64c5e98..0000000 --- a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/RequisitionQc.java +++ /dev/null @@ -1,63 +0,0 @@ -package ca.on.oicr.gsi.cardea.data; - -import static java.util.Objects.requireNonNull; - -import java.time.LocalDate; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; - -/** - * Immutable RequisitionQc - */ -@JsonDeserialize(builder = RequisitionQc.Builder.class) -public class RequisitionQc { - - private final LocalDate qcDate; - private final boolean qcPassed; - private final String qcUser; - - private RequisitionQc(Builder builder) { - this.qcPassed = requireNonNull(builder.qcPassed); - this.qcUser = requireNonNull(builder.qcUser); - this.qcDate = requireNonNull(builder.qcDate); - } - - public LocalDate getQcDate() { - return qcDate; - } - - public String getQcUser() { - return qcUser; - } - - public boolean isQcPassed() { - return qcPassed; - } - - @JsonPOJOBuilder(withPrefix = "") - public static class Builder { - - private LocalDate qcDate; - private boolean qcPassed; - private String qcUser; - - public RequisitionQc build() { - return new RequisitionQc(this); - } - - public Builder qcDate(LocalDate qcDate) { - this.qcDate = qcDate; - return this; - } - - public Builder qcPassed(boolean qcPassed) { - this.qcPassed = qcPassed; - return this; - } - - public Builder qcUser(String qcUser) { - this.qcUser = qcUser; - return this; - } - } -} diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuCase.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuCase.java index a971494..3272c09 100644 --- a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuCase.java +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuCase.java @@ -1,7 +1,5 @@ package ca.on.oicr.gsi.cardea.data; -import ca.on.oicr.gsi.cardea.data.CaseStatus; - import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; diff --git a/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/CaseLoader.java b/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/CaseLoader.java index 565bc49..27c4299 100644 --- a/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/CaseLoader.java +++ b/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/CaseLoader.java @@ -4,7 +4,8 @@ import ca.on.oicr.gsi.cardea.data.AssayTargets; import ca.on.oicr.gsi.cardea.data.Case; import ca.on.oicr.gsi.cardea.data.CaseData; -import ca.on.oicr.gsi.cardea.data.Deliverable; +import ca.on.oicr.gsi.cardea.data.CaseDeliverable; +import ca.on.oicr.gsi.cardea.data.CaseRelease; import ca.on.oicr.gsi.cardea.data.DeliverableType; import ca.on.oicr.gsi.cardea.data.Donor; import ca.on.oicr.gsi.cardea.data.Lane; @@ -14,8 +15,7 @@ import ca.on.oicr.gsi.cardea.data.OmittedSample; import ca.on.oicr.gsi.cardea.data.Project; import ca.on.oicr.gsi.cardea.data.Requisition; -import ca.on.oicr.gsi.cardea.data.RequisitionQc; -import ca.on.oicr.gsi.cardea.data.RequisitionQcGroup; +import ca.on.oicr.gsi.cardea.data.AnalysisQcGroup; import ca.on.oicr.gsi.cardea.data.Run; import ca.on.oicr.gsi.cardea.data.Sample; import ca.on.oicr.gsi.cardea.data.Test; @@ -228,7 +228,8 @@ private List loadCases(FileReader fileReader, Map project .timepoint(parseString(json, "timepoint")) .receipts(parseIdsAndGet(json, "receipt_ids", JsonNode::asText, samplesById)) .tests(parseTests(json, "assay_tests", samplesById)) - .deliverables(parseDeliverables(json.get("deliverables"))) + .qcGroups(parseAnalysisQcGroups(json.get("qc_groups"), donorsById)) + .deliverables(parseCaseDeliverables(json.get("deliverables"))) .requisition(requisitionsById.get(requisitionId)) .startDate(parseDate(json, "start_date")) .receiptDaysSpent(parseInteger(json, "receipt_days_spent", true)) @@ -287,7 +288,9 @@ protected Map loadProjects(FileReader fileReader) throws DataParseException, IOException { List projects = loadFromJsonArrayFile(fileReader, json -> new Project.Builder().name(parseString(json, "name", true)) - .pipeline(parseString(json, "pipeline", true)).build()); + .pipeline(parseString(json, "pipeline", true)) + .deliverables(parseProjectDeliverables(json.get("deliverables"))) + .build()); return projects.stream().collect(Collectors.toMap(Project::getName, Function.identity())); } @@ -303,10 +306,7 @@ protected Map loadRequisitions(FileReader fileReader, .stopReason(parseString(json, "stop_reason", false)) .paused(parseBoolean(json, "paused")) .pauseReason(parseString(json, "pause_reason", false)) - .qcGroups(parseRequisitionQcGroups(json.get("qc_groups"), donorsById)) - .analysisReviews(parseRequisitionQcs(json, "analysis_reviews")) - .releaseApprovals(parseRequisitionQcs(json, "release_approvals")) - .releases(parseRequisitionQcs(json, "releases")).build(); + .build(); return requisition; }); @@ -507,15 +507,14 @@ private static Boolean parseQcPassed(JsonNode json, String fieldName, boolean re } } - private static List parseRequisitionQcGroups(JsonNode json, + private static List parseAnalysisQcGroups(JsonNode json, Map donorsById) throws DataParseException { if (json == null || !json.isArray()) { throw new DataParseException("Invalid requisition qc_groups"); } - List qcGroups = new ArrayList<>(); + List qcGroups = new ArrayList<>(); for (JsonNode node : json) { - qcGroups.add(new RequisitionQcGroup.Builder() - .donor(donorsById.get(parseString(node, "donor_id", true))) + qcGroups.add(new AnalysisQcGroup.Builder() .tissueOrigin(parseString(node, "tissue_origin", true)) .tissueType(parseString(node, "tissue_type", true)) .libraryDesignCode(parseString(node, "library_design", true)) @@ -528,23 +527,6 @@ private static List parseRequisitionQcGroups(JsonNode json, return qcGroups; } - private static List parseRequisitionQcs(JsonNode json, String fieldName) - throws DataParseException { - JsonNode arr = json.get(fieldName); - if (arr == null || !arr.isArray()) { - throw new DataParseException(String.format("%s is not an array", fieldName)); - } - List qcs = new ArrayList<>(); - for (JsonNode node : arr) { - qcs.add(new RequisitionQc.Builder() - .qcPassed(parseQcPassed(node, "qc_state", true)) - .qcUser(parseString(node, "qc_user")) - .qcDate(parseDate(node, "qc_date")) - .build()); - } - return qcs; - } - private static LocalDate parseSampleCreatedDate(JsonNode json) throws DataParseException { LocalDate receivedDate = parseDate(json, "received"); if (receivedDate != null) { @@ -736,13 +718,14 @@ private List parseTests(JsonNode json, String fieldName, Map parseDeliverables(JsonNode deliverablesNode) throws DataParseException { + private List parseCaseDeliverables(JsonNode deliverablesNode) + throws DataParseException { if (deliverablesNode == null) { return null; } - List deliverables = new ArrayList<>(); + List deliverables = new ArrayList<>(); for (JsonNode node : deliverablesNode) { - deliverables.add(new Deliverable.Builder() + deliverables.add(new CaseDeliverable.Builder() .deliverableType(DeliverableType.valueOf(parseString(node, "deliverable_type"))) .analysisReviewQcDate(parseDate(node, "analysis_review_qc_date")) .analysisReviewQcPassed(parseQcPassed(node, "analysis_review_qc_state", false)) @@ -752,15 +735,54 @@ private List parseDeliverables(JsonNode deliverablesNode) throws Da .releaseApprovalQcPassed(parseQcPassed(node, "release_approval_qc_state", false)) .releaseApprovalQcUser(parseString(node, "release_approval_qc_user")) .releaseApprovalQcNote(parseString(node, "release_approval_qc_note")) - .releaseQcDate(parseDate(node, "release_qc_date")) - .releaseQcPassed(parseQcPassed(node, "release_qc_state", false)) - .releaseQcUser(parseString(node, "release_qc_user")) - .releaseQcNote(parseString(node, "release_qc_note")) + .releases(parseReleases(node.get("releases"))) .build()); } return deliverables; } + private List parseReleases(JsonNode json) throws DataParseException { + if (json == null) { + return null; + } else if (!json.isArray()) { + throw new DataParseException("Invalid case releases"); + } + List list = new ArrayList<>(); + for (JsonNode node : json) { + list.add(new CaseRelease.Builder() + .deliverable(parseString(node, "deliverable", true)) + .qcDate(parseDate(node, "qc_date")) + .qcPassed(parseQcPassed(node, "qc_state", false)) + .qcUser(parseString(node, "qc_user")) + .qcNote(parseString(node, "qc_note")) + .build()); + } + return list; + } + + private Map> parseProjectDeliverables(JsonNode json) + throws DataParseException { + if (json == null || !json.isObject()) { + throw new DataParseException("Invalid project deliverable types"); + } + Map> map = new HashMap<>(); + Iterator> iterator = json.fields(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + DeliverableType deliverableType = DeliverableType.valueOf(entry.getKey()); + JsonNode listJson = entry.getValue(); + if (listJson == null || !listJson.isArray()) { + throw new DataParseException("Invalid project deliverables"); + } + List deliverables = new ArrayList<>(); + for (JsonNode node : listJson) { + deliverables.add(node.asText()); + } + map.put(deliverableType, deliverables); + } + return map; + } + @FunctionalInterface private static interface ParseFunction { R apply(T input) throws DataParseException; diff --git a/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/service/CaseService.java b/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/service/CaseService.java index 788acdf..fa583df 100644 --- a/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/service/CaseService.java +++ b/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/service/CaseService.java @@ -3,16 +3,15 @@ import ca.on.oicr.gsi.cardea.data.Assay; import ca.on.oicr.gsi.cardea.data.Case; import ca.on.oicr.gsi.cardea.data.CaseData; +import ca.on.oicr.gsi.cardea.data.CaseDeliverable; +import ca.on.oicr.gsi.cardea.data.CaseRelease; import ca.on.oicr.gsi.cardea.data.CaseStatus; import ca.on.oicr.gsi.cardea.data.CaseStatusesForRun; import ca.on.oicr.gsi.cardea.data.CasesForRequisition; -import ca.on.oicr.gsi.cardea.data.Requisition; -import ca.on.oicr.gsi.cardea.data.RequisitionQc; import ca.on.oicr.gsi.cardea.data.Run; import ca.on.oicr.gsi.cardea.data.Sample; import ca.on.oicr.gsi.cardea.data.ShesmuCase; import ca.on.oicr.gsi.cardea.server.CaseLoader; - import ca.on.oicr.gsi.Pair; import java.time.Duration; @@ -23,7 +22,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -69,7 +67,7 @@ public CaseStatusesForRun getCaseStatusesForRun(String runName) { .collect(Collectors.toSet()); List> statusForCaseList = casesMatchingRunName.stream() - .map(k -> new Pair(getReqStatus(k.getRequisition()).getLabel(), k.getId())) + .map(k -> new Pair(getCaseStatus(k).getLabel(), k.getId())) .collect(Collectors.toList()); Map> statusesForRun = new HashMap>(); statusForCaseList.forEach((p) -> { @@ -97,15 +95,11 @@ private int getRefreshFailures() { return refreshFailures; } - private CaseStatus getReqStatus(Requisition req) { - if (req.isStopped()) { + private CaseStatus getCaseStatus(Case kase) { + if (kase.isStopped()) { return CaseStatus.STOPPED; - } - var reqQcs = - req.getReleases().stream().map(RequisitionQc::isQcPassed).collect(Collectors.toSet()); - if (reqQcs.isEmpty()) { - return CaseStatus.ACTIVE; - } else if (reqQcs.stream().anyMatch(status -> status.equals(true))) { + } else if (kase.getDeliverables().stream().anyMatch( + deliverable -> deliverable.getReleases().stream().anyMatch(CaseRelease::getQcPassed))) { return CaseStatus.COMPLETED; } else { return CaseStatus.ACTIVE; @@ -169,16 +163,34 @@ private Set getLimsIusIdsForShesmu(Case kase) { .collect(Collectors.toSet()); } + private LocalDate getCompletedDate(Case kase) { + if (kase.getDeliverables().isEmpty()) { + return null; + } + LocalDate completedDate = null; + for (CaseDeliverable deliverable : kase.getDeliverables()) { + if (deliverable.getReleases().isEmpty()) { + return null; + } + for (CaseRelease release : deliverable.getReleases()) { + if (Boolean.TRUE.equals(release.getQcPassed())) { + return null; + } + if (completedDate == null || completedDate.isBefore(release.getQcDate())) { + completedDate = release.getQcDate(); + } + } + } + return completedDate; + } + private ShesmuCase convertCaseToShesmuCase(Case kase) { - Optional completedDate = kase.getRequisition().getReleases().stream() - .map(qc -> qc.getQcDate()) - .max(LocalDate::compareTo); return new ShesmuCase.Builder() .assayName(caseData.getAssaysById().get(kase.getAssayId()).getName()) .assayVersion(caseData.getAssaysById().get(kase.getAssayId()).getVersion()) .caseIdentifier(kase.getId()) - .caseStatus(getReqStatus(kase.getRequisition())) - .completedDate(completedDate.orElse(null)) + .caseStatus(getCaseStatus(kase)) + .completedDate(getCompletedDate(kase)) .limsIds(getLimsIusIdsForShesmu(kase)) .requisitionId(kase.getRequisition().getId()) .requisitionName(kase.getRequisition().getName()) diff --git a/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/CaseLoaderTest.java b/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/CaseLoaderTest.java index 0d67550..b650bdb 100644 --- a/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/CaseLoaderTest.java +++ b/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/CaseLoaderTest.java @@ -3,12 +3,12 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; - import ca.on.oicr.gsi.cardea.data.Assay; import ca.on.oicr.gsi.cardea.data.AssayTargets; import ca.on.oicr.gsi.cardea.data.Case; import ca.on.oicr.gsi.cardea.data.CaseData; -import ca.on.oicr.gsi.cardea.data.Deliverable; +import ca.on.oicr.gsi.cardea.data.CaseDeliverable; +import ca.on.oicr.gsi.cardea.data.CaseRelease; import ca.on.oicr.gsi.cardea.data.DeliverableType; import ca.on.oicr.gsi.cardea.data.Donor; import ca.on.oicr.gsi.cardea.data.Metric; @@ -17,8 +17,7 @@ import ca.on.oicr.gsi.cardea.data.OmittedSample; import ca.on.oicr.gsi.cardea.data.Project; import ca.on.oicr.gsi.cardea.data.Requisition; -import ca.on.oicr.gsi.cardea.data.RequisitionQc; -import ca.on.oicr.gsi.cardea.data.RequisitionQcGroup; +import ca.on.oicr.gsi.cardea.data.AnalysisQcGroup; import ca.on.oicr.gsi.cardea.data.Run; import ca.on.oicr.gsi.cardea.data.Sample; import org.junit.jupiter.api.BeforeAll; @@ -74,6 +73,13 @@ private void assertProject(Project project) { assertNotNull(project); assertEquals(testProjectName, project.getName()); assertEquals("Research", project.getPipeline()); + assertNotNull(project.getDeliverables()); + assertEquals(1, project.getDeliverables().size()); + List deliverables = project.getDeliverables().get(DeliverableType.DATA_RELEASE); + assertNotNull(deliverables); + assertEquals(2, deliverables.size()); + assertTrue(deliverables.contains("Full Pipeline")); + assertTrue(deliverables.contains("cBioPortal Submission")); } private void assertSample(Sample sample) { @@ -141,11 +147,23 @@ public void testLoad() throws Exception { assertNotNull(kase.getDeliverables()); assertEquals(1, kase.getDeliverables().size()); - Deliverable deliverable = kase.getDeliverables().get(0); + CaseDeliverable deliverable = kase.getDeliverables().get(0); assertEquals(DeliverableType.CLINICAL_REPORT, deliverable.getDeliverableType()); assertEquals("Person", deliverable.getAnalysisReviewQcUser()); assertEquals(LocalDate.of(2021, 8, 10), deliverable.getAnalysisReviewQcDate()); assertTrue(deliverable.getAnalysisReviewQcPassed()); + List releases = deliverable.getReleases(); + assertNotNull(releases); + assertEquals(1, releases.size()); + assertEquals("Clinical Report", releases.get(0).getDeliverable()); + + assertNotNull(kase.getQcGroups()); + assertEquals(3, kase.getQcGroups().size()); + AnalysisQcGroup qcGroup = kase.getQcGroups().stream() + .filter(x -> "M".equals(x.getTissueType()) && "WG".equals(x.getLibraryDesignCode())) + .findAny().orElse(null); + assertNotNull(qcGroup); + assertEquals(new BigDecimal("87.6189"), qcGroup.getCallability()); } @Test @@ -244,17 +262,6 @@ public void testLoadRequisitions() throws Exception { assertNotNull(requisition); assertEquals(requisitionId, requisition.getId()); assertEquals("REQ-1", requisition.getName()); - List qcs = requisition.getAnalysisReviews(); - assertEquals(1, qcs.size()); - RequisitionQc qc = qcs.get(0); - assertTrue(qc.isQcPassed()); - assertNotNull(requisition.getQcGroups()); - assertEquals(3, requisition.getQcGroups().size()); - RequisitionQcGroup qcGroup = requisition.getQcGroups().stream() - .filter(x -> "M".equals(x.getTissueType()) && "WG".equals(x.getLibraryDesignCode())) - .findAny().orElse(null); - assertNotNull(qcGroup); - assertEquals(new BigDecimal("87.6189"), qcGroup.getCallability()); } } diff --git a/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/MockCase.java b/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/MockCase.java index b216c1d..d8a2e0d 100644 --- a/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/MockCase.java +++ b/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/MockCase.java @@ -2,14 +2,14 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; - -import ca.on.oicr.gsi.cardea.data.Assay; 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.DeliverableType; import ca.on.oicr.gsi.cardea.data.Donor; import ca.on.oicr.gsi.cardea.data.MetricCategory; import ca.on.oicr.gsi.cardea.data.Project; import ca.on.oicr.gsi.cardea.data.Requisition; -import ca.on.oicr.gsi.cardea.data.RequisitionQc; import ca.on.oicr.gsi.cardea.data.Run; import ca.on.oicr.gsi.cardea.data.Sample; import java.util.ArrayList; @@ -31,21 +31,10 @@ private static Requisition addRequisition(Case kase, int caseNumber, String name when(requisition.getId()).thenReturn(Long.valueOf(caseNumber)); when(requisition.isStopped()).thenReturn(caseNumber == 23); when(requisition.getName()).thenReturn(name); - when(requisition.getAnalysisReviews()).thenReturn(new ArrayList<>()); - when(requisition.getReleaseApprovals()).thenReturn(new ArrayList<>()); - when(requisition.getReleases()).thenReturn(new ArrayList<>()); when(kase.getRequisition()).thenReturn(requisition); return requisition; } - private static void addRequisitionQc(List qcs, boolean qcPassed) { - RequisitionQc qc = mock(RequisitionQc.class); - when(qc.isQcPassed()).thenReturn(qcPassed); - when(qc.getQcUser()).thenReturn("User"); - when(qc.getQcDate()).thenReturn(LocalDate.now()); - qcs.add(qc); - } - private static Sample addRunLibrary(List gateItems, String id, Boolean qcPassed, String qcReason, Boolean dataReviewPassed) { Sample sample = addSample(gateItems, id, qcPassed, qcReason); @@ -132,6 +121,15 @@ private static Case makeCase(String donorName, String assayName, String projectN addSample(kase.getReceipts(), receiptSampleId, true, "Good"); when(kase.getTests()).thenReturn(new ArrayList<>()); addRequisition(kase, caseNumber, requisitionName); + when(kase.getDeliverables()).thenReturn(new ArrayList<>()); + CaseDeliverable deliverable = mock(CaseDeliverable.class); + when(deliverable.getDeliverableType()).thenReturn(DeliverableType.DATA_RELEASE); + when(deliverable.getReleases()).thenReturn(new ArrayList<>()); + CaseRelease release = mock(CaseRelease.class); + when(release.getDeliverable()).thenReturn("Full Pipeline"); + deliverable.getReleases().add(release); + kase.getDeliverables().add(deliverable); + return kase; } @@ -357,23 +355,29 @@ private static Case makeCase4() { private static Case makeCase5() { final int caseNumber = 5; - // Case is pending draft report + // Case is pending release approval Case kase = makeCase("PRO4_0001", "Single Test", "PRO4", "REQ04", caseNumber); addTest(kase, caseNumber, 1, "Test", true, true, true, true); - Requisition requisition = kase.getRequisition(); - addRequisitionQc(requisition.getAnalysisReviews(), true); + CaseDeliverable deliverable = kase.getDeliverables().get(0); + when(deliverable.getAnalysisReviewQcPassed()).thenReturn(Boolean.TRUE); + when(deliverable.getAnalysisReviewQcUser()).thenReturn("User"); + when(deliverable.getAnalysisReviewQcDate()).thenReturn(LocalDate.now()); return kase; } private static Case makeCase6() { final int caseNumber = 6; - // Case is pending final report + // Case is pending release Case kase = makeCase("PRO5_0001", "Single Test", "PRO5", "REQ04", caseNumber); addTest(kase, caseNumber, 1, "Test", true, true, true, true); addTest(kase, caseNumber, 2, "Test", true, true, true, true); - Requisition requisition = kase.getRequisition(); - addRequisitionQc(requisition.getAnalysisReviews(), true); - addRequisitionQc(requisition.getReleaseApprovals(), true); + CaseDeliverable deliverable = kase.getDeliverables().get(0); + when(deliverable.getAnalysisReviewQcPassed()).thenReturn(Boolean.TRUE); + when(deliverable.getAnalysisReviewQcUser()).thenReturn("User"); + when(deliverable.getAnalysisReviewQcDate()).thenReturn(LocalDate.now()); + when(deliverable.getReleaseApprovalQcPassed()).thenReturn(Boolean.TRUE); + when(deliverable.getReleaseApprovalQcUser()).thenReturn("User"); + when(deliverable.getReleaseApprovalQcDate()).thenReturn(LocalDate.now()); return kase; } diff --git a/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/service/CaseServiceTest.java b/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/service/CaseServiceTest.java index 1847441..62fb338 100644 --- a/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/service/CaseServiceTest.java +++ b/cardea-server/src/test/java/ca/on/oicr/gsi/cardea/server/service/CaseServiceTest.java @@ -2,20 +2,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import ca.on.oicr.gsi.cardea.data.CaseStatusesForRun; -import ca.on.oicr.gsi.cardea.data.RequisitionQc; import ca.on.oicr.gsi.cardea.data.Run; import java.time.LocalDate; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import org.junit.jupiter.api.BeforeEach; import ca.on.oicr.gsi.cardea.data.Case; import ca.on.oicr.gsi.cardea.data.CaseData; +import ca.on.oicr.gsi.cardea.data.CaseDeliverable; +import ca.on.oicr.gsi.cardea.data.CaseRelease; import ca.on.oicr.gsi.cardea.data.Donor; import ca.on.oicr.gsi.cardea.data.Requisition; import ca.on.oicr.gsi.cardea.data.Sample; @@ -37,6 +36,19 @@ private void addCase(CaseData data, int caseNumber, int requisitionNumber) { addTest(kase, caseNumber, "A"); addTest(kase, caseNumber, "B"); when(kase.getRequisition()).thenReturn(makeRequisition(requisitionNumber)); + when(kase.isStopped()).thenReturn(requisitionNumber % 2 == 1 ? true : false); + when(kase.getDeliverables()).thenReturn(new ArrayList<>()); + CaseDeliverable deliverable = mock(CaseDeliverable.class); + when(deliverable.getReleases()).thenReturn(new ArrayList<>()); + CaseRelease release = mock(CaseRelease.class); + when(release.getDeliverable()).thenReturn("Full Pipeline"); + if (requisitionNumber % 1 != 1) { + when(release.getQcPassed()).thenReturn(true); + when(release.getQcUser()).thenReturn("User"); + when(release.getQcDate()).thenReturn(LocalDate.now()); + } + deliverable.getReleases().add(release); + kase.getDeliverables().add(deliverable); data.getCases().add(kase); } @@ -88,9 +100,6 @@ private Requisition makeRequisition(int requisitionNumber) { .name(String.format("REQ_%d", requisitionNumber)) .assayId(2L) .stopped(requisitionNumber % 2 == 1 ? true : false) - .releases(requisitionNumber % 2 == 1 ? null - : Arrays.asList(new RequisitionQc.Builder().qcPassed(true).qcUser("test").qcDate( - LocalDate.now()).build())) .build(); } diff --git a/cardea-server/src/test/resources/testdata/cases.json b/cardea-server/src/test/resources/testdata/cases.json index cedf59a..3f491fb 100644 --- a/cardea-server/src/test/resources/testdata/cases.json +++ b/cardea-server/src/test/resources/testdata/cases.json @@ -73,6 +73,35 @@ "full_depth_sequencing_days_spent": 0 } ], + "qc_groups": [ + { + "tissue_origin": "Ly", + "tissue_type": "R", + "library_design": "WG", + "group_id": null, + "purity": null, + "collapsed_coverage": null, + "callability": null + }, + { + "tissue_origin": "Lv", + "tissue_type": "M", + "library_design": "WT", + "group_id": null, + "purity": 65.0, + "collapsed_coverage": null, + "callability": null + }, + { + "tissue_origin": "Lv", + "tissue_type": "M", + "library_design": "WG", + "group_id": null, + "purity": 65.0, + "collapsed_coverage": null, + "callability": 87.6189 + } + ], "deliverables": [ { "deliverable_type": "CLINICAL_REPORT", @@ -84,10 +113,15 @@ "release_approval_qc_user": null, "release_approval_qc_date": null, "release_approval_qc_note": null, - "release_qc_state": null, - "release_qc_user": null, - "release_qc_date": null, - "release_qc_note": null + "releases": [ + { + "deliverable": "Clinical Report", + "qc_state": null, + "qc_user": null, + "qc_date": null, + "qc_note": null + } + ] } ], "requisition_id": 512, diff --git a/cardea-server/src/test/resources/testdata/projects.json b/cardea-server/src/test/resources/testdata/projects.json index 7121d03..39c13b8 100644 --- a/cardea-server/src/test/resources/testdata/projects.json +++ b/cardea-server/src/test/resources/testdata/projects.json @@ -1,6 +1,9 @@ [ { "name": "PROJ", - "pipeline": "Research" + "pipeline": "Research", + "deliverables": { + "DATA_RELEASE": ["Full Pipeline", "cBioPortal Submission"] + } } -] \ No newline at end of file +] diff --git a/cardea-server/src/test/resources/testdata/requisitions.json b/cardea-server/src/test/resources/testdata/requisitions.json index b457325..dfa70d5 100644 --- a/cardea-server/src/test/resources/testdata/requisitions.json +++ b/cardea-server/src/test/resources/testdata/requisitions.json @@ -6,49 +6,7 @@ "stopped": false, "stop_reason": null, "paused": false, - "pause_reason": null, - "qc_groups": [ - { - "donor_id": "SAM413576", - "tissue_origin": "Ly", - "tissue_type": "R", - "library_design": "WG", - "group_id": null, - "purity": null, - "collapsed_coverage": null, - "callability": null - }, - { - "donor_id": "SAM413576", - "tissue_origin": "Lv", - "tissue_type": "M", - "library_design": "WT", - "group_id": null, - "purity": 65.0, - "collapsed_coverage": null, - "callability": null - }, - { - "donor_id": "SAM413576", - "tissue_origin": "Lv", - "tissue_type": "M", - "library_design": "WG", - "group_id": null, - "purity": 65.0, - "collapsed_coverage": null, - "callability": 87.6189 - } - ], - "analysis_reviews": [ - { - "qcable_type": "analysis_review", - "qc_state": "Ready", - "qc_user": "Person", - "qc_date": "2021-08-10" - } - ], - "release_approvals": [], - "releases": [] + "pause_reason": null }, { "id": 999, @@ -57,10 +15,6 @@ "stopped": false, "stop_reason": null, "paused": false, - "pause_reason": null, - "qc_groups": [], - "analysis_reviews": [], - "release_approvals": [], - "releases": [] + "pause_reason": null } ] diff --git a/changes/change_deliverables.md b/changes/change_deliverables.md new file mode 100644 index 0000000..4035909 --- /dev/null +++ b/changes/change_deliverables.md @@ -0,0 +1 @@ +Case deliverables now contain all QC information for analysis review, release approval, and release diff --git a/changes/remove_requisitionQc.md b/changes/remove_requisitionQc.md new file mode 100644 index 0000000..891e8fb --- /dev/null +++ b/changes/remove_requisitionQc.md @@ -0,0 +1 @@ +Requisition QCs