Skip to content

Commit

Permalink
x1236 Add sample prep reagent lot to probe operation
Browse files Browse the repository at this point in the history
  • Loading branch information
khelwood committed Sep 10, 2024
1 parent 456bd25 commit e02b0f8
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ public Validator<String> reagentPlateBarcodeValidator() {
return new StringValidator("Reagent plate barcode", 24, 24, charTypes);
}

@Bean
public Validator<String> samplePrepReagentLotValidator() {
return new StringValidator("Sample prep 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 @@ -24,14 +24,16 @@ public static class ProbeOperationLabware {
private String barcode;
private String workNumber;
private SlideCosting kitCosting;
private String samplePrepReagentLot;
private List<ProbeLot> probes = List.of();

public ProbeOperationLabware() {}

public ProbeOperationLabware(String barcode, String workNumber, SlideCosting kitCosting, List<ProbeLot> probes) {
public ProbeOperationLabware(String barcode, String workNumber, SlideCosting kitCosting, String samplePrepReagentLot, List<ProbeLot> probes) {
setBarcode(barcode);
setWorkNumber(workNumber);
setKitCosting(kitCosting);
setSamplePrepReagentLot(samplePrepReagentLot);
setProbes(probes);
}

Expand Down Expand Up @@ -67,6 +69,15 @@ public void setKitCosting(SlideCosting kitCosting) {
this.kitCosting = kitCosting;
}

/** Sample prep reagent lot number. */
public String getSamplePrepReagentLot() {
return this.samplePrepReagentLot;
}

public void setSamplePrepReagentLot(String samplePrepReagentLot) {
this.samplePrepReagentLot = samplePrepReagentLot;
}

/**
* The probes used on this labware.
*/
Expand All @@ -86,6 +97,7 @@ public boolean equals(Object o) {
return (Objects.equals(this.barcode, that.barcode)
&& Objects.equals(this.workNumber, that.workNumber)
&& this.kitCosting==that.kitCosting
&& Objects.equals(this.samplePrepReagentLot, that.samplePrepReagentLot)
&& Objects.equals(this.probes, that.probes)
);
}
Expand All @@ -97,7 +109,8 @@ public int hashCode() {

@Override
public String toString() {
return String.format("(%s, %s, workNumber: %s, probes: %s)", repr(barcode), kitCosting, repr(workNumber), probes);
return String.format("(%s, %s, workNumber: %s, samplePrepReagentLot: %s, probes: %s)",
repr(barcode), kitCosting, repr(workNumber), repr(samplePrepReagentLot), probes);
}
}

Expand Down
52 changes: 52 additions & 0 deletions src/main/java/uk/ac/sanger/sccp/stan/service/ProbeServiceImp.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

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

import static uk.ac.sanger.sccp.utils.BasicUtils.*;
Expand All @@ -24,6 +25,7 @@
@Service
public class ProbeServiceImp implements ProbeService {
public static final String KIT_COSTING_NAME = "kit costing";
public static final String SAMPLE_PREP_REAGENT_LOT_NAME = "sample prep reagent lot";

private final LabwareValidatorFactory lwValFac;
private final LabwareRepo lwRepo;
Expand All @@ -35,6 +37,7 @@ public class ProbeServiceImp implements ProbeService {
private final OperationService opService;
private final WorkService workService;
private final Validator<String> probeLotValidator;
private final Validator<String> samplePrepReagentLotValidator;
private final Clock clock;

@Autowired
Expand All @@ -43,6 +46,7 @@ public ProbeServiceImp(LabwareValidatorFactory lwValFac,
ProbePanelRepo probePanelRepo, LabwareProbeRepo lwProbeRepo, LabwareNoteRepo noteRepo,
OperationService opService, WorkService workService,
@Qualifier("probeLotNumberValidator") Validator<String> probeLotValidator,
@Qualifier("samplePrepReagentLotValidator") Validator<String> samplePrepReagentLotValidator,
Clock clock) {
this.lwValFac = lwValFac;
this.lwRepo = lwRepo;
Expand All @@ -54,6 +58,7 @@ public ProbeServiceImp(LabwareValidatorFactory lwValFac,
this.opService = opService;
this.workService = workService;
this.probeLotValidator = probeLotValidator;
this.samplePrepReagentLotValidator = samplePrepReagentLotValidator;
this.clock = clock;
}

Expand All @@ -79,6 +84,7 @@ public OperationResult recordProbeOperation(User user, ProbeOperationRequest req
.toList();
UCMap<Work> work = workService.validateUsableWorks(problems, workNumbers);
checkKitCostings(problems, request.getLabware());
checkSamplePrepReagentLots(problems, request.getLabware());
UCMap<ProbePanel> probes = validateProbes(problems, request.getLabware());
if (request.getPerformed()!=null) {
validateTimestamp(problems, request.getPerformed(), labware);
Expand Down Expand Up @@ -160,6 +166,28 @@ public void checkKitCostings(Collection<String> problems, Collection<ProbeOperat
}
}

/**
* Checks sample prep reagent lots.
* Lots are trimmed; empty lots are nulled; missing lots are skipped.
* @param problems receptacle for problems
* @param pols details of the request
*/
public void checkSamplePrepReagentLots(Collection<String> problems, Collection<ProbeOperationLabware> pols) {
if (pols != null) {
final Consumer<String> addProblem = problems::add;
for (ProbeOperationLabware pol : pols) {
String lot = pol.getSamplePrepReagentLot();
if (lot != null) {
lot = emptyToNull(lot.trim());
pol.setSamplePrepReagentLot(lot);
if (lot != null) {
samplePrepReagentLotValidator.validate(lot, addProblem);
}
}
}
}
}

/**
* Loads and checks the probes indicated in the request, their lot numbers and plexes.
* @param problems receptacle for problems
Expand Down Expand Up @@ -240,6 +268,7 @@ public OperationResult perform(User user, Collection<ProbeOperationLabware> pols
UCMap<Work> workMap) {
UCMap<Operation> lwOps = makeOps(user, opType, pols, lwMap, time);
saveKitCostings(pols, lwMap, lwOps);
saveSamplePrepReagentLots(pols, lwMap, lwOps);
linkWork(pols, lwOps, workMap);
saveProbes(pols, lwOps, lwMap, probeMap);
return assembleResult(pols, lwMap, lwOps);
Expand Down Expand Up @@ -324,6 +353,29 @@ public void saveProbes(Collection<ProbeOperationLabware> pols, UCMap<Operation>
lwProbeRepo.saveAll(lwProbes);
}

/**
* Saves the indicated sample prep reagent lots against the indicated labware and ops
* @param pols request details
* @param lwMap map to look up labware by barcode
* @param lwOps map to look up operation by labware barcode
*/
public void saveSamplePrepReagentLots(Collection<ProbeOperationLabware> pols,
UCMap<Labware> lwMap, UCMap<Operation> lwOps) {
List<LabwareNote> notes = new ArrayList<>();
for (ProbeOperationLabware pol : pols) {
String lot = pol.getSamplePrepReagentLot();
if (!nullOrEmpty(lot)) {
String barcode = pol.getBarcode();
Labware lw = lwMap.get(barcode);
Operation op = lwOps.get(barcode);
notes.add(new LabwareNote(null, lw.getId(), op.getId(), SAMPLE_PREP_REAGENT_LOT_NAME, lot));
}
}
if (!notes.isEmpty()) {
noteRepo.saveAll(notes);
}
}

/**
* Assembles an operation result whose order matches the order given in the request.
* @param pols the request specifying labware barcodes
Expand Down
26 changes: 21 additions & 5 deletions src/main/java/uk/ac/sanger/sccp/utils/BasicUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -231,20 +231,36 @@ public static <E> ArrayList<E> newArrayList(Iterable<? extends E> items) {
}

/**
* Collector to a map where the values are the input objects
* Collector to a map whose values are the items in the stream
* @param keyMapper a mapping function to produce keys
* @param mapFactory a supplier providing a new empty {@code Map}
* into which the results will be inserted
* @param <T> the type of the input elements
* @param <K> the output type of the key mapping function
* @param <K> the key type of the resulting {@code Map}
* @param <M> the type of the resulting {@code Map}
* @return a {@code Collector} which collects elements into a {@code Map}
* whose keys are the result of applying a key mapping function to the input
* elements, and whose values are input elements
*/
public static <T, K, M extends Map<K, T>> Collector<T, ?, M> inMap(Function<? super T, ? extends K> keyMapper,
Supplier<M> mapFactory) {
return Collectors.toMap(keyMapper, Function.identity(), illegalStateMerge(), mapFactory);
return toMap(keyMapper, Function.identity(), mapFactory);
}

/**
* Collector to a map
* @param keyMapper a mapping function to produce keys
* @param valueMapper a mapping function to produce values
* @param mapFactory a supplier providing a new empty {@code Map}
* into which the results will be inserted
* @param <T> the type of the input elements
* @param <K> the key type of the resulting {@code Map}
* @param <V> the value type of the resulting {@code Map}
* @param <M> the type of the resulting {@code Map}
* @return a {@code Collector} which collects elements into a {@code Map}
*/
public static <T, K, V, M extends Map<K,V>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper,
Supplier<M> mapFactory) {
return Collectors.toMap(keyMapper, valueMapper, illegalStateMerge(), mapFactory);
}

/**
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/uk/ac/sanger/sccp/utils/UCMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
public class UCMap<V> implements Map<String, V> {
private final Map<String, V> inner;

/** Creates a new UCMap backed by a hashmap with the given initial capacity */
public UCMap(int initialCapacity) {
inner = new HashMap<>(initialCapacity);
}

/** Creates a new UCMap backed by a hashmap of the default capacity */
public UCMap() {
inner = new HashMap<>();
}

/** Creates a new UCMap containing the contents of the given map */
public UCMap(Map<String, V> contents) {
this(contents.size());
this.putAll(contents);
Expand Down Expand Up @@ -162,6 +165,13 @@ public int hashCode() {
return inner.hashCode();
}

/**
* Converts a given key to upper case. If it's not a string,
* returns the key.
* @param key the key to upcase
* @return the upcased key
* @param <K> the type of the key, typically a string
*/
private static <K> K upcase(K key) {
if (key instanceof String) {
//noinspection unchecked
Expand All @@ -185,6 +195,26 @@ public String toString() {
return BasicUtils.inMap(keyMapper, UCMap::new);
}

/**
* Collector to collect a {@code UCMap} from strings to values extracted from the input objects
* @param keyMapper mapper from input objects to strings
* @param valueMapper mapper from input objects to values in map
* @return a collector to collect to a {@code UCMap}
* @param <T> the type of input object
* @param <V> the type of value in the map
*/
public static <T, V> Collector<T, ?, UCMap<V>> toUCMap(Function<? super T, String> keyMapper,
Function<? super T, V> valueMapper) {
return BasicUtils.toMap(keyMapper, valueMapper, UCMap::new);
}

/**
* Creates a UCMap containing a single item
* @param keyMapper function to extract key from the item
* @param value the item to put into the map as a value
* @return a UCMap containing one item
* @param <T> the type of value to put in the map
*/
public static <T> UCMap<T> from(Function<T, String> keyMapper, T value) {
UCMap<T> map = new UCMap<>(1);
map.put(keyMapper.apply(value), value);
Expand All @@ -200,6 +230,13 @@ public static <T> UCMap<T> from(Function<T, String> keyMapper, T... values) {
return map;
}

/**
* Creates a UCMap containing the items from a stream, using the keys from the given function
* @param items a stream or items to put into a map
* @param keyMapper function to get keys for items
* @return map containing the given items as values
* @param <T> type of items to put into the map
*/
public static <T> UCMap<T> from(Collection<? extends T> items, Function<T, String> keyMapper) {
UCMap<T> map = new UCMap<>(items.size());
for (T item : items) {
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 @@ -1738,6 +1738,8 @@ input ProbeOperationLabware {
workNumber: String!
"""The costing for the kit used on this labware."""
kitCosting: SlideCosting!
"""Sample prep reagent lot number."""
samplePrepReagentLot: String
"""The probes used on this labware."""
probes: [ProbeLot!]!
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import uk.ac.sanger.sccp.stan.repo.*;
import uk.ac.sanger.sccp.stan.service.CompletionServiceImp;
import uk.ac.sanger.sccp.stan.service.operation.AnalyserServiceImp;
import uk.ac.sanger.sccp.utils.UCMap;

import javax.transaction.Transactional;
import java.util.*;
Expand Down Expand Up @@ -79,7 +80,7 @@ public void testRecordProbeOperation() throws Exception {
List<LabwareProbe> lwProbes = lwProbeRepo.findAllByOperationIdIn(List.of(opId));
assertThat(lwProbes).hasSize(2);
if (lwProbes.getFirst().getProbePanel().getName().equals("probe2")) {
lwProbes = IntStream.of(lwProbes.size()-1,-1,-1).mapToObj(lwProbes::get).collect(toList());
lwProbes = IntStream.of(lwProbes.size() - 1, -1, -1).mapToObj(lwProbes::get).collect(toList());
}
for (LabwareProbe lwp : lwProbes) {
assertEquals(opId, lwp.getOperationId());
Expand All @@ -95,7 +96,13 @@ public void testRecordProbeOperation() throws Exception {
assertEquals(2, lwp.getPlex());
assertEquals("LOT2", lwp.getLotNumber());
assertEquals(SlideCosting.SGP, lwp.getCosting());

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("kit costing"));
assertEquals("123456", noteValues.get("sample prep reagent lot"));
testCompletion(lw, work, sample);
testAnalyser(lw, work, sample);
testSampleMetrics(lw, work);
Expand Down
Loading

0 comments on commit e02b0f8

Please sign in to comment.