diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuDetailedCase.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuDetailedCase.java new file mode 100644 index 0000000..8635c9f --- /dev/null +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuDetailedCase.java @@ -0,0 +1,167 @@ +package ca.on.oicr.gsi.cardea.data; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import java.time.*; +import java.util.Optional; +import java.util.Set; + +import static java.util.Collections.unmodifiableSet; +import static java.util.Objects.requireNonNull; + +/** + * Immutable ShesmuCase + */ +@JsonDeserialize(builder = ShesmuDetailedCase.Builder.class) +public class ShesmuDetailedCase { + + private final String assayName; + private final String assayVersion; + private final String caseIdentifier; + private final CaseStatus caseStatus; + private final boolean stopped; + private final boolean paused; + private final Optional completedDate; + private final long requisitionId; + private final String requisitionName; + private final Set sequencing; + + private ShesmuDetailedCase(Builder builder) { + this.assayName = requireNonNull(builder.assayName); + this.assayVersion = requireNonNull(builder.assayVersion); + this.caseIdentifier = requireNonNull(builder.caseIdentifier); + this.caseStatus = requireNonNull(builder.caseStatus); + this.completedDate = builder.completedDate; + this.stopped = builder.stopped; + this.paused = builder.paused; + this.requisitionId = requireNonNull(builder.requisitionId); + this.requisitionName = requireNonNull(builder.requisitionName); + this.sequencing = unmodifiableSet(requireNonNull(builder.sequencing)); + } + + public String getAssayName() { + return assayName; + } + + public String getAssayVersion() { + return assayVersion; + } + + public String getCaseIdentifier() { + return caseIdentifier; + } + + public CaseStatus getCaseStatus() { + return caseStatus; + } + + public boolean isStopped() { + return stopped; + } + + public boolean isPaused() { + return paused; + } + + public Optional getCompletedDate() { + return completedDate; + } + + public long getRequisitionId() { + return requisitionId; + } + + public String getRequisitionName() { + return requisitionName; + } + + public Set getSequencing() { + return sequencing; + } + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + + private String assayName; + private String assayVersion; + private String caseIdentifier; + private CaseStatus caseStatus; + private boolean paused; + private boolean stopped; + private Optional completedDate; + private long requisitionId; + private String requisitionName; + private Set sequencing; + + public ShesmuDetailedCase build() { + return new ShesmuDetailedCase(this); + } + + public Builder assayName(String assayName) { + this.assayName = assayName; + return this; + } + + public Builder assayVersion(String assayVersion) { + this.assayVersion = assayVersion; + return this; + } + + public Builder caseStatus(CaseStatus caseStatus) { + this.caseStatus = caseStatus; + return this; + } + + public Builder paused(boolean paused) { + this.paused = paused; + return this; + } + + public Builder stopped(boolean stopped) { + this.stopped = stopped; + return this; + } + + public Builder caseIdentifier(String caseIdentifier) { + this.caseIdentifier = caseIdentifier; + return this; + } + + public Builder completedDate(Instant completedDate) { + this.completedDate = completedDate == null ? Optional.empty() : Optional.of(completedDate); + return this; + } + + public Builder completedDateLocal(LocalDate completedDate) { + this.completedDate = convertCompletedDate(completedDate); + return this; + } + + public Builder requisitionId(long requisitionId) { + this.requisitionId = requisitionId; + return this; + } + + public Builder requisitionName(String requisitionName) { + this.requisitionName = requisitionName; + return this; + } + + public Builder sequencing(Set sequencing){ + this.sequencing = sequencing; + return this; + } + + private Optional convertCompletedDate(LocalDate completedDate) { + if (completedDate == null) { + return Optional.empty(); + } + return Optional.of( + ZonedDateTime.of( + completedDate, + LocalTime.MIDNIGHT, + ZoneId.of("UTC")).toInstant()); + } + } +} diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuSample.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuSample.java new file mode 100644 index 0000000..274bade --- /dev/null +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuSample.java @@ -0,0 +1,59 @@ +package ca.on.oicr.gsi.cardea.data; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import java.util.Set; + +import static java.util.Collections.unmodifiableSet; +import static java.util.Objects.requireNonNull; + +/** + * Immutable Test + */ +@JsonDeserialize(builder = ShesmuSample.Builder.class) +public class ShesmuSample { + + private final String id; + private final boolean supplemental; + + + private ShesmuSample(Builder builder) { + this.id = requireNonNull(builder.id); + this.supplemental = requireNonNull(builder.supplemental); + } + + + public String getId() { + return id; + } + + public Boolean isSupplemental() { + return supplemental; + } + + + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + + private String id; + private boolean supplemental; + + public ShesmuSample build() { + return new ShesmuSample(this); + } + + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder supplemental(boolean supplemental) { + this.supplemental = supplemental; + return this; + } + + } +} diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuSequencing.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuSequencing.java new file mode 100644 index 0000000..16b891a --- /dev/null +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/ShesmuSequencing.java @@ -0,0 +1,82 @@ +package ca.on.oicr.gsi.cardea.data; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; + +import java.util.Set; + +import static java.util.Collections.*; +import static java.util.Objects.requireNonNull; + +/** + * Immutable Test + */ +@JsonDeserialize(builder = ShesmuSequencing.Builder.class) +public class ShesmuSequencing { + + private final String test; + private final MetricCategory type; + private final Set limsIds; + private final boolean complete; + + + private ShesmuSequencing(Builder builder) { + this.test = requireNonNull(builder.test); + this.type = requireNonNull(builder.type); + this.limsIds = unmodifiableSet(requireNonNull(builder.limsIds)); + this.complete = requireNonNull(builder.complete); + } + + + public String getTest() { + return test; + } + + public MetricCategory getType() { + return type; + } + + public Set getLimsIds() { + return limsIds; + } + + public Boolean isComplete() { + return complete; + } + + + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + + private String test; + private Set limsIds; + private MetricCategory type; + private boolean complete; + + public ShesmuSequencing build() { + return new ShesmuSequencing(this); + } + + public Builder test(String test) { + this.test = test; + return this; + } + + public Builder type(MetricCategory type) { + this.type = type; + return this; + } + + public Builder limsIds(Set limsIds) { + this.limsIds = limsIds; + return this; + } + + public Builder complete(boolean complete) { + this.complete = complete; + return this; + } + + } +} diff --git a/cardea-data/src/test/java/ca/on/oicr/gsi/cardea/JacksonTest.java b/cardea-data/src/test/java/ca/on/oicr/gsi/cardea/JacksonTest.java index 2fe13e0..a9eaa12 100644 --- a/cardea-data/src/test/java/ca/on/oicr/gsi/cardea/JacksonTest.java +++ b/cardea-data/src/test/java/ca/on/oicr/gsi/cardea/JacksonTest.java @@ -11,32 +11,11 @@ import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; + +import ca.on.oicr.gsi.cardea.data.*; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import com.fasterxml.jackson.databind.ObjectMapper; -import ca.on.oicr.gsi.cardea.data.AnalysisQcGroup; -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.CaseDeliverable; -import ca.on.oicr.gsi.cardea.data.CaseDeliverableImpl; -import ca.on.oicr.gsi.cardea.data.CaseImpl; -import ca.on.oicr.gsi.cardea.data.CaseRelease; -import ca.on.oicr.gsi.cardea.data.CaseReleaseImpl; -import ca.on.oicr.gsi.cardea.data.CaseStatus; -import ca.on.oicr.gsi.cardea.data.DeliverableType; -import ca.on.oicr.gsi.cardea.data.Donor; -import ca.on.oicr.gsi.cardea.data.Lane; -import ca.on.oicr.gsi.cardea.data.Metric; -import ca.on.oicr.gsi.cardea.data.MetricCategory; -import ca.on.oicr.gsi.cardea.data.MetricSubcategory; -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.Run; -import ca.on.oicr.gsi.cardea.data.Sample; -import ca.on.oicr.gsi.cardea.data.ShesmuCase; -import ca.on.oicr.gsi.cardea.data.Test; -import ca.on.oicr.gsi.cardea.data.ThresholdType; +import ca.on.oicr.gsi.cardea.data.ShesmuSequencing; public class JacksonTest { @@ -194,6 +173,14 @@ public void testShesmuCaseSerializeDeserialize() throws Exception { assertShesmuCaseEqual(original, deserialized); } + @org.junit.jupiter.api.Test + public void testShesmuDetailedCaseSerializeDeserialize() throws Exception { + ShesmuDetailedCase original = makeShesmuDetailedCase(); + String serialized = mapper.writeValueAsString(original); + ShesmuDetailedCase deserialized = mapper.readerFor(ShesmuDetailedCase.class).readValue(serialized); + assertShesmuDetailedCaseEqual(original, deserialized); + } + private static void assertOmittedSamplesEqual(OmittedSample one, OmittedSample two) { assertEquals(one.getId(), two.getId()); assertEquals(one.getName(), two.getName()); @@ -503,6 +490,34 @@ private static void assertShesmuCaseEqual(ShesmuCase one, ShesmuCase two) { assertEquals(one.getRequisitionName(), two.getRequisitionName()); } + private static void assertShesmuDetailedCaseEqual(ShesmuDetailedCase one, ShesmuDetailedCase two) { + assertEquals(one.getAssayName(), two.getAssayName()); + assertEquals(one.getAssayVersion(), two.getAssayVersion()); + assertEquals(one.getCaseIdentifier(), two.getCaseIdentifier()); + assertEquals(one.isPaused(), two.isPaused()); + assertEquals(one.isStopped(), two.isStopped()); + assertEquals(one.getCaseStatus(), two.getCaseStatus()); + assertEquals(one.getCompletedDate(), two.getCompletedDate()); + assertEquals(one.getSequencing().size(), two.getSequencing().size()); + assertShesmuTestEqual(one.getSequencing().iterator().next(), two.getSequencing().iterator().next()); + assertEquals(one.getRequisitionId(), two.getRequisitionId()); + assertEquals(one.getRequisitionName(), two.getRequisitionName()); + } + + private static void assertShesmuSampleEqual(ShesmuSample one, ShesmuSample two) { + assertEquals(one.getId(), two.getId()); + assertEquals(one.isSupplemental(), two.isSupplemental()); + } + + private static void assertShesmuTestEqual(ShesmuSequencing one, ShesmuSequencing two) { + assertEquals(one.getTest(), two.getTest()); + assertEquals(one.getType(), two.getType()); + assertEquals(one.isComplete(), two.isComplete()); + assertEquals(one.getLimsIds().size(), two.getLimsIds().size()); + assertShesmuSampleEqual(one.getLimsIds().iterator().next(), two.getLimsIds().iterator().next()); + + } + private static Donor makeDonor() { return new Donor.Builder() .id("SAM1") @@ -798,4 +813,32 @@ private static ShesmuCase makeShesmuCase() { .build(); } + private static ShesmuDetailedCase makeShesmuDetailedCase() { + Set sequencing = new HashSet<>(); + Set limsIds = new HashSet<>(); + limsIds.add(new ShesmuSample.Builder() + .id("ID1") + .supplemental(false) + .build()); + sequencing.add(new ShesmuSequencing.Builder() + .test("Some Test") + .limsIds(limsIds) + .complete(true) + .type(MetricCategory.LIBRARY_QUALIFICATION) + .build()); + + return new ShesmuDetailedCase.Builder() + .assayName("Assay") + .assayVersion("2.0") + .caseIdentifier("CASE10") + .stopped(false) + .paused(false) + .caseStatus(CaseStatus.COMPLETED) + .completedDateLocal(LocalDate.of(2024, 1, 13)) + .sequencing(sequencing) + .requisitionId(1L) + .requisitionName("Some Req") + .build(); + } + } diff --git a/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/controller/CardeaApiController.java b/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/controller/CardeaApiController.java index 7637485..5102879 100644 --- a/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/controller/CardeaApiController.java +++ b/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/controller/CardeaApiController.java @@ -5,6 +5,7 @@ import ca.on.oicr.gsi.cardea.data.CaseData; import ca.on.oicr.gsi.cardea.data.CaseStatusesForRun; import ca.on.oicr.gsi.cardea.data.ShesmuCase; +import ca.on.oicr.gsi.cardea.data.ShesmuDetailedCase; import java.time.ZonedDateTime; @@ -60,4 +61,9 @@ public Set getShesmuCases() { return caseService.getShesmuCases(); } + @GetMapping("/shesmu-detailed-cases") + public Set getShesmuDetailedCases() { + return caseService.getShesmuDetailedCases(); + } + } 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 7c6e610..d7868ac 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 @@ -1,14 +1,6 @@ package ca.on.oicr.gsi.cardea.server.service; -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.Run; -import ca.on.oicr.gsi.cardea.data.Sample; -import ca.on.oicr.gsi.cardea.data.ShesmuCase; +import ca.on.oicr.gsi.cardea.data.*; import ca.on.oicr.gsi.cardea.server.CaseLoader; import ca.on.oicr.gsi.Pair; @@ -133,6 +125,70 @@ public Set getShesmuCases() { .collect(Collectors.toSet()); } + public Set getShesmuDetailedCases() { + return caseData.getCases().stream() + .filter(kase -> kase.getTests().stream() + .anyMatch(test -> !test.getFullDepthSequencings().isEmpty() + || test.getLibraryQualifications().stream() + .anyMatch(sample -> sample.getRun() != null))) + .map(kase -> convertCaseToShesmuDetailedCase(kase)) + .collect(Collectors.toSet()); + } + + private Set getSequencingForShesmuCase(Case kase){ + + Set sequencings = new HashSet<>(); + final long reqId = kase.getRequisition().getId(); + + for (Test test : kase.getTests()) { + + ShesmuSequencing fullDepthSeq = makeShesmuSequencing(test.getFullDepthSequencings(), MetricCategory.FULL_DEPTH_SEQUENCING, reqId, test.getName()); + if (fullDepthSeq != null) { + sequencings.add(fullDepthSeq); + } + + ShesmuSequencing libraryQual = makeShesmuSequencing(test.getLibraryQualifications(), MetricCategory.LIBRARY_QUALIFICATION, reqId, test.getName()); + if (libraryQual != null) { + sequencings.add(libraryQual); + } + }; + + return sequencings; + } + + private ShesmuSequencing makeShesmuSequencing(List samples, MetricCategory type, long reqId, String name){ + + Set shesmuSamples = new HashSet<>(); + + boolean hasPassed = false; + boolean hasWaiting = false; + for (Sample sample : samples){ + + if (sample.getDataReviewPassed() == null || sample.getQcPassed() == null){ + hasWaiting = true; + } else if (sample.getDataReviewPassed() && sample.getQcPassed()) { + hasPassed = true; + } + + if (!(sample.getRun() == null && type.equals(MetricCategory.LIBRARY_QUALIFICATION))) { + shesmuSamples.add(new ShesmuSample.Builder() + .id(sample.getId()) + .supplemental(!Objects.equals(sample.getRequisitionId(), reqId)) + .build()); + } + } + + if (shesmuSamples.isEmpty()) { + return null; + } + return new ShesmuSequencing.Builder() + .test(name) + .limsIds(shesmuSamples) + .complete((hasPassed && !hasWaiting)) + .type(type) + .build(); + } + private Set getLimsIusIdsForShesmu(Case kase) { return kase.getTests().stream() .map(test -> { @@ -184,6 +240,21 @@ private ShesmuCase convertCaseToShesmuCase(Case kase) { .build(); } + private ShesmuDetailedCase convertCaseToShesmuDetailedCase(Case kase) { + return new ShesmuDetailedCase.Builder() + .assayName(caseData.getAssaysById().get(kase.getAssayId()).getName()) + .assayVersion(caseData.getAssaysById().get(kase.getAssayId()).getVersion()) + .caseIdentifier(kase.getId()) + .caseStatus(getCaseStatus(kase)) + .paused(kase.getRequisition().isPaused()) + .stopped(kase.getRequisition().isStopped()) + .completedDateLocal(getCompletedDate(kase)) + .requisitionId(kase.getRequisition().getId()) + .requisitionName(kase.getRequisition().getName()) + .sequencing(getSequencingForShesmuCase(kase)) + .build(); + } + @Scheduled(fixedDelay = 1L, timeUnit = TimeUnit.MINUTES) private void refreshData() { try { diff --git a/changes/add_shesmu_detailed_cases.md b/changes/add_shesmu_detailed_cases.md new file mode 100644 index 0000000..145f77b --- /dev/null +++ b/changes/add_shesmu_detailed_cases.md @@ -0,0 +1 @@ +Shesmu Detailed Cases endpoint