From fedd7808a795e63fb500a17c0ab0602b2de782f4 Mon Sep 17 00:00:00 2001 From: David Robinson <14000840+khelwood@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:44:59 +0000 Subject: [PATCH] x1476 include (ancestral) qpcr results comments in release file --- .../service/releasefile/ReleaseColumn.java | 1 + .../service/releasefile/ReleaseEntry.java | 9 +++++ .../releasefile/ReleaseFileService.java | 28 +++++++++++++++ .../integrationtest/TestReleaseMutation.java | 2 +- .../releasefile/TestReleaseFileService.java | 35 +++++++++++++++++++ 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseColumn.java b/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseColumn.java index 86bd77f90..75bd24e1e 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseColumn.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseColumn.java @@ -48,6 +48,7 @@ public enum ReleaseColumn implements TsvColumn { Tissue_coverage(ReleaseEntry::getCoverage, ReleaseFileOption.Visium), Permeabilisation_time(ReleaseEntry::getPermTime, ReleaseFileOption.Visium), Cq_value(ReleaseEntry::getCq, ReleaseFileOption.Visium), + qPCR_comment(ReleaseEntry::getQpcrComment, ReleaseFileOption.Visium), Number_of_amplification_cycles(ReleaseEntry::getAmplificationCycles, ReleaseFileOption.Visium), Visium_concentration(ReleaseEntry::getVisiumConcentration, ReleaseFileOption.Visium), Visium_concentration_type(ReleaseEntry::getVisiumConcentrationType, ReleaseFileOption.Visium), diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseEntry.java b/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseEntry.java index 324ab3213..57bf9cb52 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseEntry.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseEntry.java @@ -31,6 +31,7 @@ public class ReleaseEntry { private String reagentPlateType; private String reagentSource; private String cq; + private String qpcrComment; private String visiumConcentration; private String visiumConcentrationType; private String sizeRange; @@ -186,6 +187,14 @@ public void setCq(String cq) { this.cq = cq; } + public String getQpcrComment() { + return this.qpcrComment; + } + + public void setQpcrComment(String qpcrComment) { + this.qpcrComment = qpcrComment; + } + public String getVisiumConcentration() { return this.visiumConcentration; } diff --git a/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseFileService.java b/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseFileService.java index 7d228f215..73667b8b5 100644 --- a/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseFileService.java +++ b/src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseFileService.java @@ -132,6 +132,7 @@ public ReleaseFileContent getReleaseFileContent(Collection releaseIds, if (options.contains(ReleaseFileOption.Visium)) { loadVisiumBarcodes(entries, ancestry); loadSizeRanges(entries, slotIds); + loadQpcrComments(entries, ancestry); } if (options.contains(ReleaseFileOption.Histology)) { loadRnaAnalysis(entries, slotIds); @@ -984,6 +985,33 @@ public void loadImagingQcComments(Collection entries, Ancestry anc } } + + /** Loads comments from qpcr operations */ + public void loadQpcrComments(Collection entries, Ancestry ancestry) { + OperationType qpcrOpType = opTypeRepo.getByName("qPCR results"); + Set allSlotIds = ancestry.keySet().stream().map(SlotSample::slotId).collect(toSet()); + List ops = opRepo.findAllByOperationTypeAndDestinationSlotIdIn(qpcrOpType, allSlotIds); + if (ops.isEmpty()) { + return; + } + List opcoms = opComRepo.findAllByOperationIdIn(ops.stream().map(Operation::getId).collect(toList())); + Map> slotIdComs = opcoms.stream() + .collect(groupingBy(oc -> new SlotIdSampleId(oc.getSlotId(), oc.getSampleId()))); + for (ReleaseEntry entry : entries) { + SlotSample key = new SlotSample(entry.getSlot(), entry.getSample()); + Set entryOcs = new HashSet<>(); + for (SlotSample ss : ancestry.ancestors(key)) { + List ocs = slotIdComs.get(new SlotIdSampleId(ss.slotId(), ss.sampleId())); + if (!nullOrEmpty(ocs)) { + entryOcs.addAll(ocs); + } + } + if (!nullOrEmpty(entryOcs)) { + entry.setQpcrComment(joinComments(entryOcs.stream())); + } + } + } + /** * Sets various measurements for the release entries. * The measurements may be recorded on the specified slot, or any ancestral slot diff --git a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestReleaseMutation.java b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestReleaseMutation.java index 4d19d3aab..ca143cd0e 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestReleaseMutation.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestReleaseMutation.java @@ -76,7 +76,7 @@ public class TestReleaseMutation { @Transactional public void testRelease() throws Exception { Stream.of("Probe hybridisation Xenium", "Probe hybridisation QC", "Xenium analyser", - "Xenium analyser QC", "DV200 analysis", "RIN analysis", "Paraffin processing") + "Xenium analyser QC", "DV200 analysis", "RIN analysis", "Paraffin processing", "qPCR results") .forEach(name -> entityCreator.createOpType(name, null, OperationTypeFlag.IN_PLACE)); Work work1 = entityCreator.createWork(null, null, null, null, null); Donor donor = entityCreator.createDonor("DONOR1"); diff --git a/src/test/java/uk/ac/sanger/sccp/stan/service/releasefile/TestReleaseFileService.java b/src/test/java/uk/ac/sanger/sccp/stan/service/releasefile/TestReleaseFileService.java index 04a3a1764..011b88513 100644 --- a/src/test/java/uk/ac/sanger/sccp/stan/service/releasefile/TestReleaseFileService.java +++ b/src/test/java/uk/ac/sanger/sccp/stan/service/releasefile/TestReleaseFileService.java @@ -198,6 +198,7 @@ public void testGetReleaseFileContent(StorageDetail detail, boolean includeVisiu doNothing().when(service).loadSectionComments(any()); doNothing().when(service).loadVisiumBarcodes(any(), any()); doNothing().when(service).loadSizeRanges(any(), any()); + doNothing().when(service).loadQpcrComments(any(), any()); doNothing().when(service).loadXeniumFields(any(), any()); doNothing().when(service).loadSolutions(any()); doNothing().when(service).loadFlags(any()); @@ -227,6 +228,7 @@ public void testGetReleaseFileContent(StorageDetail detail, boolean includeVisiu verify(service).loadSectionComments(entries); verify(service, times(includeVisium ? 1 : 0)).loadVisiumBarcodes(entries, ancestry); verify(service, times(includeVisium ? 1 : 0)).loadSizeRanges(entries, slotIds); + verify(service, times(includeVisium ? 1 : 0)).loadQpcrComments(entries, ancestry); verify(service).loadXeniumFields(entries, slotIds); verify(service).loadSolutions(entries); verify(service).loadFlags(entries); @@ -814,6 +816,39 @@ public void testLoadStains() { verify(service).loadImagingQcComments(entries, ancestry, Set.of(100,101)); } + @Test + public void testLoadQpcrComments() { + setupLabware(); + OperationType opType = EntityFactory.makeOperationType("qPCR results", null); + when(mockOpTypeRepo.getByName(any())).thenReturn(opType); + Labware lw3 = EntityFactory.makeLabware(EntityFactory.getTubeType(), sample); + Labware lw4 = EntityFactory.makeLabware(EntityFactory.getTubeType(), sample); + Ancestry ancestry = makeAncestry(lw3, sample, lw2, sample, lw2, sample, lw1, sample); + ancestry.put(new SlotSample(lw4.getFirstSlot(), sample), Set.of()); + List qpcrOps = IntStream.of(3,4).mapToObj(i -> { + Operation op = new Operation(); + op.setId(i); + return op; + }).toList(); + when(mockOpRepo.findAllByOperationTypeAndDestinationSlotIdIn(any(), any())).thenReturn(qpcrOps); + List opComs = List.of( + new OperationComment(10, new Comment(1, "Banana", "c"), 3, sample.getId(), lw1.getFirstSlot().getId(), null), + new OperationComment(11, new Comment(2, "Custard", "c"), 4, sample.getId(), lw2.getFirstSlot().getId(), null) + ); + when(mockOpComRepo.findAllByOperationIdIn(any())).thenReturn(opComs); + List entries = List.of( + new ReleaseEntry(lw3, lw3.getFirstSlot(), sample), + new ReleaseEntry(lw4, lw4.getFirstSlot(), sample) + ); + service.loadQpcrComments(entries, ancestry); + verify(mockOpTypeRepo).getByName("qPCR results"); + Set allSlotIds = ancestry.keySet().stream().map(SlotSample::slotId).collect(toSet()); + verify(mockOpRepo).findAllByOperationTypeAndDestinationSlotIdIn(opType, allSlotIds); + verify(mockOpComRepo).findAllByOperationIdIn(List.of(3,4)); + assertThat(entries.get(0).getQpcrComment()).isIn("Banana. Custard.", "Custard. Banana."); + assertThat(entries.get(1).getQpcrComment()).isNullOrEmpty(); + } + @Test public void testLoadImagingQcComments() { setupLabware();