Skip to content

Commit

Permalink
x1237: Add reagent lot to cell segmentation op
Browse files Browse the repository at this point in the history
  • Loading branch information
khelwood committed Sep 11, 2024
1 parent e02b0f8 commit a110bd1
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@ public Validator<String> samplePrepReagentLotValidator() {
return new StringValidator("Sample prep reagent lot", 6, 6, CharacterType.DIGIT);
}

@Bean
public Validator<String> reagentLotValidator() {
return new StringValidator("Reagent lot", 6, 6, CharacterType.DIGIT);
}

@Bean
public Validator<String> cytAssistBarcodeValidator() {
Set<CharacterType> charTypes = EnumSet.of(CharacterType.DIGIT, CharacterType.ALPHA, CharacterType.HYPHEN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public static class SegmentationLabware {
private List<Integer> commentIds = List.of();
private SlideCosting costing;
private LocalDateTime performed;
private String reagentLot;

/**
* The barcode of the labware.
Expand Down Expand Up @@ -134,6 +135,14 @@ public void setPerformed(LocalDateTime performed) {
this.performed = performed;
}

public String getReagentLot() {
return this.reagentLot;
}

public void setReagentLot(String reagentLot) {
this.reagentLot = reagentLot;
}

@Override
public String toString() {
return BasicUtils.describe(this)
Expand All @@ -142,6 +151,7 @@ public String toString() {
.add("commentIds", commentIds)
.add("costing", costing)
.add("performed", performed==null ? null : performed.toString())
.add("reagentLot", reagentLot)
.reprStringValues()
.toString();
}
Expand All @@ -155,7 +165,9 @@ public boolean equals(Object o) {
&& Objects.equals(this.workNumber, that.workNumber)
&& Objects.equals(this.commentIds, that.commentIds)
&& this.costing == that.costing
&& Objects.equals(this.performed, that.performed));
&& Objects.equals(this.performed, that.performed)
&& Objects.equals(this.reagentLot, that.reagentLot)
);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package uk.ac.sanger.sccp.stan.service;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import uk.ac.sanger.sccp.stan.model.*;
import uk.ac.sanger.sccp.stan.repo.*;
Expand All @@ -14,11 +15,11 @@

import java.time.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toSet;
import static uk.ac.sanger.sccp.utils.BasicUtils.inMap;
import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty;
import static uk.ac.sanger.sccp.utils.BasicUtils.*;

/**
* @author dr6
Expand All @@ -39,10 +40,13 @@ public class SegmentationServiceImp implements SegmentationService {
private final OperationCommentRepo opComRepo;
private final LabwareNoteRepo noteRepo;

private final Validator<String> reagentLotValidator;

public SegmentationServiceImp(Clock clock, ValidationHelperFactory valHelperFactory,
OperationService opService, WorkService workService,
OperationRepo opRepo, OperationTypeRepo opTypeRepo,
OperationCommentRepo opComRepo, LabwareNoteRepo noteRepo) {
OperationCommentRepo opComRepo, LabwareNoteRepo noteRepo,
@Qualifier("reagentLotValidator") Validator<String> reagentLotValidator) {
this.clock = clock;
this.valHelperFactory = valHelperFactory;
this.opService = opService;
Expand All @@ -51,6 +55,7 @@ public SegmentationServiceImp(Clock clock, ValidationHelperFactory valHelperFact
this.opTypeRepo = opTypeRepo;
this.opComRepo = opComRepo;
this.noteRepo = noteRepo;
this.reagentLotValidator = reagentLotValidator;
}

@Override
Expand Down Expand Up @@ -92,6 +97,7 @@ SegmentationData validate(User user, SegmentationRequest request) {
checkCostings(problems, data.opType, request.getLabware());
UCMap<LocalDateTime> priorOpTimes = checkPriorOps(problems, data.opType, data.labware.values());
checkTimestamps(val, clock, request.getLabware(), data.labware, priorOpTimes);
checkReagentLots(problems, request.getLabware());
return data;
}

Expand Down Expand Up @@ -221,6 +227,25 @@ void checkTimestamps(ValidationHelper val, Clock clock, List<SegmentationLabware
}
}

/**
* Checks the reagent lots
* @param problems receptacle for problems
* @param lwReqs details of the request
*/
void checkReagentLots(final Collection<String> problems, final Collection<SegmentationLabware> lwReqs) {
final Consumer<String> problemAdd = problems::add;
for (SegmentationLabware lwReq : lwReqs) {
String lot = lwReq.getReagentLot();
if (lot != null) {
lot = emptyToNull(lot.trim());
lwReq.setReagentLot(lot);
}
if (lot != null) {
reagentLotValidator.validate(lot, problemAdd);
}
}
}

/**
* Records the operations and all associated information for the request
* @param lwReqs details of the request
Expand Down Expand Up @@ -281,6 +306,9 @@ Operation recordOp(User user, SegmentationData data, SegmentationLabware lwReq,
newNotes.add(new LabwareNote(null, lw.getId(), op.getId(), "costing",
lwReq.getCosting().name()));
}
if (lwReq.getReagentLot()!=null) {
newNotes.add(new LabwareNote(null, lw.getId(), op.getId(), "reagent lot", lwReq.getReagentLot()));
}
if (!lwReq.getCommentIds().isEmpty()) {
lwReq.getCommentIds().stream()
.map(data.comments::get)
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -1908,6 +1908,8 @@ input SegmentationLabware {
costing: SlideCosting
"""The time with which the operation should be recorded."""
performed: Timestamp
"""The reagent lot number."""
reagentLot: String
}

"""A request to record segmentation on one or more labware."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
import uk.ac.sanger.sccp.stan.EntityCreator;
import uk.ac.sanger.sccp.stan.GraphQLTester;
import uk.ac.sanger.sccp.stan.model.*;
import uk.ac.sanger.sccp.stan.repo.LabwareNoteRepo;
import uk.ac.sanger.sccp.utils.UCMap;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand All @@ -32,6 +35,8 @@ public class TestSegmentationMutation {
private EntityCreator entityCreator;
@Autowired
private EntityManager entityManager;
@Autowired
private LabwareNoteRepo lwNoteRepo;

@Test
@Transactional
Expand Down Expand Up @@ -68,6 +73,13 @@ public void testSegmentation() throws Exception {
new Work.SampleSlotId(sam2.getId(), slot2.getId())
);

List<LabwareNote> notes = lwNoteRepo.findAllByOperationIdIn(List.of(opId));
assertThat(notes).hasSize(2);
notes.forEach(note -> assertEquals(lw.getId(), note.getLabwareId()));
UCMap<String> noteValues = notes.stream().collect(UCMap.toUCMap(LabwareNote::getName, LabwareNote::getValue));
assertEquals("Faculty", noteValues.get("costing"));
assertEquals("123456", noteValues.get("reagent lot"));

testSegmentationQC(work, lw);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
import uk.ac.sanger.sccp.stan.service.work.WorkService;
import uk.ac.sanger.sccp.stan.service.work.WorkService.WorkOp;
import uk.ac.sanger.sccp.utils.UCMap;
import uk.ac.sanger.sccp.utils.Zip;

import java.time.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.IntStream;
import java.util.stream.Stream;
Expand Down Expand Up @@ -52,6 +54,8 @@ class TestSegmentationService {
private OperationCommentRepo mockOpComRepo;
@Mock
private LabwareNoteRepo mockNoteRepo;
@Mock
private Validator<String> mockReagentLotValidator;

@InjectMocks
private SegmentationServiceImp service;
Expand Down Expand Up @@ -362,6 +366,35 @@ void testCheckPriorOps(boolean anyMissing) {
}
}

@Test
public void testCheckReagentLots() {
final List<String> problems = new ArrayList<>(2);
when(mockReagentLotValidator.validate(any(), any())).then(invocation -> {
String lot = invocation.getArgument(0);
if (lot != null && lot.indexOf('!') >= 0) {
Consumer<String> addProblem = invocation.getArgument(1);
addProblem.accept("Bad lot: "+lot);
return false;
}
return true;
});
String[] inputLots = {null, " ", "", "123456", " 23456 ", "Alpha!", "Beta! "};
String[] expectedLots = {null, null, null, "123456", "23456", "Alpha!", "Beta!"};
List<SegmentationLabware> sls = Arrays.stream(inputLots)
.map(lot -> {
SegmentationLabware sl = new SegmentationLabware();
sl.setReagentLot(lot);
return sl;
})
.toList();
service.checkReagentLots(problems, sls);
verify(mockReagentLotValidator, times(4)).validate(any(), any());
Arrays.stream(expectedLots).filter(Objects::nonNull)
.forEach(lot -> verify(mockReagentLotValidator).validate(eq(lot), any()));
Zip.forEach(sls.stream(), Arrays.stream(expectedLots), (sl, lot) -> assertEquals(lot, sl.getReagentLot()));
assertThat(problems).containsExactlyInAnyOrder("Bad lot: Alpha!", "Bad lot: Beta!");
}

@Test
void testGreater() {
for (Object[] args : new Object[][]{
Expand Down Expand Up @@ -480,12 +513,13 @@ void testRecord() {

@ParameterizedTest
@CsvSource({
"false,false,false,false",
"false,true,false,true",
"false,false,true,true",
"true,true,false,false",
"false,false,false,false,false",
"false,true,false,true,false",
"false,false,true,true,false",
"true,true,false,false,false",
"true,true,false,false,true",
})
void testRecordOp(boolean hasTime, boolean hasCosting, boolean hasCommentIds, boolean hasWork) {
void testRecordOp(boolean hasTime, boolean hasCosting, boolean hasCommentIds, boolean hasWork, boolean hasLot) {
SegmentationLabware lwReq = new SegmentationLabware();
SegmentationData data = new SegmentationData(List.of());
data.opType = EntityFactory.makeOperationType("opname", null);
Expand Down Expand Up @@ -519,6 +553,9 @@ void testRecordOp(boolean hasTime, boolean hasCosting, boolean hasCommentIds, bo
} else {
work = null;
}
if (hasLot) {
lwReq.setReagentLot("123456");
}

when(mockOpService.createOperationInPlace(data.opType, user, lw, null, null)).thenReturn(op);

Expand All @@ -531,7 +568,14 @@ void testRecordOp(boolean hasTime, boolean hasCosting, boolean hasCommentIds, bo
verify(mockOpService).createOperationInPlace(data.opType, user, lw, null, null);

assertMayContain(opsToUpdate, hasTime ? op : null);
assertMayContain(newNotes, hasCosting ? new LabwareNote(null, lw.getId(), op.getId(), "costing", "Faculty") : null);
final List<LabwareNote> expectedNotes = new ArrayList<>(2);
if (hasCosting) {
expectedNotes.add(new LabwareNote(null, lw.getId(), op.getId(), "costing", "Faculty"));
}
if (hasLot) {
expectedNotes.add(new LabwareNote(null, lw.getId(), op.getId(), "reagent lot", "123456"));
}
assertThat(newNotes).containsExactlyInAnyOrderElementsOf(expectedNotes);
assertMayContain(newWorkOps, hasWork ? new WorkOp(work, op) : null);
if (hasCommentIds) {
final int slotId = lw.getFirstSlot().getId();
Expand Down
1 change: 1 addition & 0 deletions src/test/resources/graphql/segmentation.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mutation {
workNumber: "[WORK]"
commentIds: [1]
costing: Faculty
reagentLot: "123456"
}]
}) {
labware {
Expand Down

0 comments on commit a110bd1

Please sign in to comment.