diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/CaseData.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/CaseData.java index 713960d..ac9e145 100644 --- a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/CaseData.java +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/CaseData.java @@ -16,12 +16,14 @@ public class CaseData { private final Map assaysById; private final List cases; private final List omittedSamples; + private final List omittedRunSamples; private final ZonedDateTime timestamp; private CaseData(Builder builder) { this.assaysById = unmodifiableMap(builder.assaysById); this.cases = unmodifiableList(builder.cases); this.omittedSamples = unmodifiableList(builder.omittedSamples); + this.omittedRunSamples = unmodifiableList(builder.omittedRunSamples); this.timestamp = requireNonNull(builder.timestamp); } @@ -37,6 +39,10 @@ public List getOmittedSamples() { return omittedSamples; } + public List getOmittedRunSamples() { + return omittedRunSamples; + } + public ZonedDateTime getTimestamp() { return timestamp; } @@ -46,6 +52,7 @@ public static class Builder { private Map assaysById; private List cases; private List omittedSamples; + private List omittedRunSamples; private ZonedDateTime timestamp; public CaseData build() { @@ -67,6 +74,11 @@ public Builder omittedSamples(List omittedSamples) { return this; } + public Builder omittedRunSamples(List omittedRunSamples) { + this.omittedRunSamples = omittedRunSamples; + return this; + } + public Builder timestamp(ZonedDateTime timestamp) { this.timestamp = timestamp; return this; diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/OmittedRunSample.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/OmittedRunSample.java new file mode 100644 index 0000000..4af2feb --- /dev/null +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/OmittedRunSample.java @@ -0,0 +1,172 @@ +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 OmittedRunSample + */ +@JsonDeserialize(builder = OmittedRunSample.Builder.class) +public class OmittedRunSample { + + private final String id; + private final String name; + private final long runId; + private final int sequencingLane; + private final LocalDate qcDate; + private final Boolean qcPassed; + private final String qcReason; + private final String qcNote; + private final String qcUser; + private final LocalDate dataReviewDate; + private final Boolean dataReviewPassed; + private final String dataReviewUser; + + private OmittedRunSample(Builder builder) { + this.id = requireNonNull(builder.id); + this.name = requireNonNull(builder.name); + this.runId = requireNonNull(builder.runId); + this.sequencingLane = requireNonNull(builder.sequencingLane); + this.qcPassed = builder.qcPassed; + this.qcReason = builder.qcReason; + this.qcNote = builder.qcNote; + this.qcUser = builder.qcUser; + this.qcDate = builder.qcDate; + this.dataReviewPassed = builder.dataReviewPassed; + this.dataReviewUser = builder.dataReviewUser; + this.dataReviewDate = builder.dataReviewDate; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public long getRunId() { + return runId; + } + + public int getSequencingLane() { + return sequencingLane; + } + + public LocalDate getQcDate() { + return qcDate; + } + + public Boolean getQcPassed() { + return qcPassed; + } + + public String getQcReason() { + return qcReason; + } + + public String getQcNote() { + return qcNote; + } + + public String getQcUser() { + return qcUser; + } + + public LocalDate getDataReviewDate() { + return dataReviewDate; + } + + public Boolean getDataReviewPassed() { + return dataReviewPassed; + } + + public String getDataReviewUser() { + return dataReviewUser; + } + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + + private String id; + private String name; + private Long runId; + private Integer sequencingLane; + private LocalDate qcDate; + private Boolean qcPassed; + private String qcReason; + private String qcNote; + private String qcUser; + private LocalDate dataReviewDate; + private Boolean dataReviewPassed; + private String dataReviewUser; + + public OmittedRunSample build() { + return new OmittedRunSample(this); + } + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder runId(Long runId) { + this.runId = runId; + return this; + } + + public Builder sequencingLane(Integer sequencingLane) { + this.sequencingLane = sequencingLane; + return this; + } + + public Builder qcDate(LocalDate qcDate) { + this.qcDate = qcDate; + return this; + } + + public Builder qcPassed(Boolean qcPassed) { + this.qcPassed = qcPassed; + return this; + } + + public Builder qcReason(String qcReason) { + this.qcReason = qcReason; + return this; + } + + public Builder qcNote(String qcNote) { + this.qcNote = qcNote; + return this; + } + + public Builder qcUser(String qcUser) { + this.qcUser = qcUser; + return this; + } + + public Builder dataReviewDate(LocalDate dataReviewDate) { + this.dataReviewDate = dataReviewDate; + return this; + } + + public Builder dataReviewPassed(Boolean dataReviewPassed) { + this.dataReviewPassed = dataReviewPassed; + return this; + } + + public Builder dataReviewUser(String dataReviewUser) { + this.dataReviewUser = dataReviewUser; + return this; + } + + } + +} diff --git a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/RunAndLibraries.java b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/RunAndLibraries.java index f2bba52..e9efa65 100644 --- a/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/RunAndLibraries.java +++ b/cardea-data/src/main/java/ca/on/oicr/gsi/cardea/data/RunAndLibraries.java @@ -10,8 +10,12 @@ /** * Immutable RunAndLibraries + * + * This is being moved to Dimsum because its only intended use is for data transformation within + * Dimsum */ @JsonDeserialize(builder = RunAndLibraries.Builder.class) +@Deprecated(forRemoval = true) public class RunAndLibraries { private Set fullDepthSequencings; 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 a9eaa12..b5cc6cf 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 @@ -165,6 +165,14 @@ public void testCaseSerializeDeserialize() throws Exception { assertCaseEqual(original, deserialized); } + @org.junit.jupiter.api.Test + public void testOmittedRunSampleSerializeDeserialize() throws Exception { + OmittedRunSample original = makeOmittedRunSample(); + String serialized = mapper.writeValueAsString(original); + OmittedRunSample deserialized = mapper.readerFor(OmittedRunSample.class).readValue(serialized); + assertOmittedRunSampleEqual(original, deserialized); + } + @org.junit.jupiter.api.Test public void testShesmuCaseSerializeDeserialize() throws Exception { ShesmuCase original = makeShesmuCase(); @@ -177,7 +185,8 @@ public void testShesmuCaseSerializeDeserialize() throws Exception { public void testShesmuDetailedCaseSerializeDeserialize() throws Exception { ShesmuDetailedCase original = makeShesmuDetailedCase(); String serialized = mapper.writeValueAsString(original); - ShesmuDetailedCase deserialized = mapper.readerFor(ShesmuDetailedCase.class).readValue(serialized); + ShesmuDetailedCase deserialized = + mapper.readerFor(ShesmuDetailedCase.class).readValue(serialized); assertShesmuDetailedCaseEqual(original, deserialized); } @@ -479,6 +488,21 @@ private static void assertCaseEqual(Case one, Case two) { assertEquals(one.getPauseDays(), two.getPauseDays()); } + private static void assertOmittedRunSampleEqual(OmittedRunSample one, OmittedRunSample two) { + assertEquals(one.getId(), two.getId()); + assertEquals(one.getName(), two.getName()); + assertEquals(one.getRunId(), two.getRunId()); + assertEquals(one.getSequencingLane(), two.getSequencingLane()); + assertEquals(one.getQcPassed(), two.getQcPassed()); + assertEquals(one.getQcReason(), two.getQcReason()); + assertEquals(one.getQcNote(), two.getQcNote()); + assertEquals(one.getQcUser(), two.getQcUser()); + assertEquals(one.getQcDate(), two.getQcDate()); + assertEquals(one.getDataReviewPassed(), two.getDataReviewPassed()); + assertEquals(one.getDataReviewUser(), two.getDataReviewUser()); + assertEquals(one.getDataReviewDate(), two.getDataReviewDate()); + } + private static void assertShesmuCaseEqual(ShesmuCase one, ShesmuCase two) { assertEquals(one.getAssayName(), two.getAssayName()); assertEquals(one.getAssayVersion(), two.getAssayVersion()); @@ -490,7 +514,8 @@ private static void assertShesmuCaseEqual(ShesmuCase one, ShesmuCase two) { assertEquals(one.getRequisitionName(), two.getRequisitionName()); } - private static void assertShesmuDetailedCaseEqual(ShesmuDetailedCase one, ShesmuDetailedCase two) { + private static void assertShesmuDetailedCaseEqual(ShesmuDetailedCase one, + ShesmuDetailedCase two) { assertEquals(one.getAssayName(), two.getAssayName()); assertEquals(one.getAssayVersion(), two.getAssayVersion()); assertEquals(one.getCaseIdentifier(), two.getCaseIdentifier()); @@ -499,7 +524,8 @@ private static void assertShesmuDetailedCaseEqual(ShesmuDetailedCase one, Shesmu 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()); + assertShesmuTestEqual(one.getSequencing().iterator().next(), + two.getSequencing().iterator().next()); assertEquals(one.getRequisitionId(), two.getRequisitionId()); assertEquals(one.getRequisitionName(), two.getRequisitionName()); } @@ -796,6 +822,23 @@ private static Case makeCase() { .build(); } + private static OmittedRunSample makeOmittedRunSample() { + return new OmittedRunSample.Builder() + .id("123_4_LDI567") + .name("Joe") + .runId(123L) + .sequencingLane(4) + .qcPassed(true) + .qcReason("Good stuff") + .qcNote("This is my note") + .qcUser("Me") + .qcDate(LocalDate.now().minusDays(1L)) + .dataReviewPassed(true) + .dataReviewUser("Someone else") + .dataReviewDate(LocalDate.now()) + .build(); + } + private static ShesmuCase makeShesmuCase() { Set limsIds = new HashSet<>(); limsIds.add("ID1"); @@ -817,28 +860,28 @@ private static ShesmuDetailedCase makeShesmuDetailedCase() { Set sequencing = new HashSet<>(); Set limsIds = new HashSet<>(); limsIds.add(new ShesmuSample.Builder() - .id("ID1") - .supplemental(false) - .build()); + .id("ID1") + .supplemental(false) + .build()); sequencing.add(new ShesmuSequencing.Builder() - .test("Some Test") - .limsIds(limsIds) - .complete(true) - .type(MetricCategory.LIBRARY_QUALIFICATION) - .build()); + .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(); + .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/CaseLoader.java b/cardea-server/src/main/java/ca/on/oicr/gsi/cardea/server/CaseLoader.java index 5119b54..40bbbc2 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 @@ -15,6 +15,7 @@ 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.OmittedRunSample; import ca.on.oicr.gsi.cardea.data.OmittedSample; import ca.on.oicr.gsi.cardea.data.Project; import ca.on.oicr.gsi.cardea.data.Requisition; @@ -61,6 +62,7 @@ public class CaseLoader { private File requisitionFile; private File runFile; private File sampleFile; + private File noCaseRunlibFile; private File timestampFile; private Timer refreshTimer = null; @@ -87,6 +89,7 @@ public CaseLoader( assayFile = new File(dataDirectory, "assays.json"); timestampFile = new File(dataDirectory, "timestamp"); noCaseFile = new File(dataDirectory, "receipts_nocase.json"); + noCaseRunlibFile = new File(dataDirectory, "run_samples_nocase.json"); } /** @@ -127,7 +130,8 @@ public CaseData load(ZonedDateTime previousTimestamp) throws DataParseException, FileReader runReader = getRunReader(); FileReader assayReader = getAssayReader(); FileReader caseReader = getCaseReader(); - FileReader nocaseReader = getNoCaseReader();) { + FileReader nocaseReader = getNoCaseReader(); + FileReader noCaseRunlibReader = getNoCaseRunlibReader()) { ZonedDateTime afterTimestamp = getDataTimestamp(); if (afterTimestamp == null) { log.debug("New case data is currently being written; aborting load."); @@ -145,6 +149,7 @@ public CaseData load(ZonedDateTime previousTimestamp) throws DataParseException, loadSamples(sampleReader, donorsById, runsById, requisitionsById); List omittedSamples = loadOmittedSamples(nocaseReader, donorsById, requisitionsById); + List omittedRunSamples = loadOmittedRunSamples(noCaseRunlibReader); Map assaysById = loadAssays(assayReader); List cases = loadCases(caseReader, projectsByName, samplesById, donorsById, requisitionsById, assaysById); @@ -156,6 +161,7 @@ public CaseData load(ZonedDateTime previousTimestamp) throws DataParseException, .assaysById(assaysById) .cases(cases) .omittedSamples(omittedSamples) + .omittedRunSamples(omittedRunSamples) .timestamp(afterTimestamp) .build(); @@ -181,6 +187,10 @@ protected FileReader getNoCaseReader() throws FileNotFoundException { return new FileReader(noCaseFile); } + protected FileReader getNoCaseRunlibReader() throws FileNotFoundException { + return new FileReader(noCaseRunlibFile); + } + protected FileReader getProjectReader() throws FileNotFoundException { return new FileReader(projectFile); } @@ -291,6 +301,26 @@ protected List loadOmittedSamples(FileReader fileReader, }); } + protected List loadOmittedRunSamples(FileReader fileReader) + throws DataParseException, IOException { + return loadFromJsonArrayFile(fileReader, json -> { + return new OmittedRunSample.Builder() + .id(parseString(json, "sample_id", true)) + .name(parseString(json, "oicr_internal_name", true)) + .runId(parseLong(json, "sequencing_run_id", true)) + .sequencingLane(parseInteger(json, "sequencing_lane", true)) + .qcPassed(parseQcPassed(json, "qc_state", true)) + .qcReason(parseString(json, "qc_reason")) + .qcNote(parseString(json, "qc_note")) + .qcUser(parseString(json, "qc_user")) + .qcDate(parseDate(json, "qc_date")) + .dataReviewPassed(parseDataReviewPassed(json, "data_review_state")) + .dataReviewUser(parseString(json, "data_review_user")) + .dataReviewDate(parseDate(json, "data_review_date")) + .build(); + }); + } + protected Map loadProjects(FileReader fileReader) throws DataParseException, IOException { List projects = loadFromJsonArrayFile(fileReader, 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 1c63105..f55fd1e 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 @@ -15,6 +15,7 @@ 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.OmittedRunSample; import ca.on.oicr.gsi.cardea.data.OmittedSample; import ca.on.oicr.gsi.cardea.data.Project; import ca.on.oicr.gsi.cardea.data.Requisition; @@ -118,6 +119,21 @@ private static void assertRun(Run run) { assertEquals(new BigDecimal("0.65"), lane1.getPercentPfixRead2()); } + private static void assertOmittedRunSample(OmittedRunSample sample) { + assertEquals("5459_1_LDI36185", sample.getId()); + assertEquals("MISS_011408_Co_P_PE_490_WG", sample.getName()); + assertEquals(5459L, sample.getRunId()); + assertEquals(1, sample.getSequencingLane()); + assertNull(sample.getQcPassed()); + assertEquals("Not Ready", sample.getQcReason()); + assertNull(sample.getQcUser()); + assertNull(sample.getQcDate()); + assertNull(sample.getQcNote()); + assertNull(sample.getDataReviewPassed()); + assertNull(sample.getDataReviewUser()); + assertNull(sample.getDataReviewDate()); + } + @BeforeEach public void setup() { sut = new CaseLoader(dataDirectory, null); @@ -203,6 +219,15 @@ public void testLoad() throws Exception { .findAny().orElse(null); assertNotNull(qcGroup); assertEquals(new BigDecimal("87.6189"), qcGroup.getCallability()); + + List omittedRunSamples = data.getOmittedRunSamples(); + assertNotNull(omittedRunSamples); + assertEquals(2, omittedRunSamples.size()); + OmittedRunSample omittedRunSample = omittedRunSamples.stream() + .filter(x -> "MISS_011408_Co_P_PE_490_WG".equals(x.getName())) + .findFirst().orElse(null); + assertNotNull(omittedRunSample); + assertOmittedRunSample(omittedRunSample); } @Test @@ -339,4 +364,16 @@ public void testLoadRuns() throws Exception { } } + @Test + public void testLoadOmittedRunSamples() throws Exception { + try (FileReader reader = sut.getNoCaseRunlibReader()) { + List samples = sut.loadOmittedRunSamples(reader); + assertEquals(2, samples.size()); + OmittedRunSample sample = samples.stream() + .filter(x -> "MISS_011408_Co_P_PE_490_WG".equals(x.getName())) + .findFirst().orElse(null); + assertOmittedRunSample(sample); + } + } + } diff --git a/cardea-server/src/test/resources/testdata/run_samples_nocase.json b/cardea-server/src/test/resources/testdata/run_samples_nocase.json new file mode 100644 index 0000000..dd4e624 --- /dev/null +++ b/cardea-server/src/test/resources/testdata/run_samples_nocase.json @@ -0,0 +1,30 @@ +[ + { + "sample_id": "5459_1_LDI36185", + "oicr_internal_name": "MISS_011408_Co_P_PE_490_WG", + "sequencing_run_id": 5459, + "sequencing_lane": 1, + "qc_state": "Not Ready", + "qc_reason": "Not Ready", + "qc_note": null, + "qc_user": null, + "qc_date": null, + "data_review_state": "Pending", + "data_review_user": null, + "data_review_date": null + }, + { + "sample_id": "5459_1_LDI36190", + "oicr_internal_name": "MISS_011408_Ly_R_PE_522_WG", + "sequencing_run_id": 5459, + "sequencing_lane": 1, + "qc_state": "Not Ready", + "qc_reason": "Not Ready", + "qc_note": null, + "qc_user": null, + "qc_date": null, + "data_review_state": "Pending", + "data_review_user": null, + "data_review_date": null + } +] diff --git a/changes/add_runlib_omissions.md b/changes/add_runlib_omissions.md new file mode 100644 index 0000000..79ef85e --- /dev/null +++ b/changes/add_runlib_omissions.md @@ -0,0 +1 @@ +Omitted run samples in Dimsum case data