Skip to content

Commit fc22d4b

Browse files
authored
fix(protocol-designer, step-generation): fix getTransferPlanAndReferenceVolumes multiAspirate logic (#19047)
This PR fixes logic to correctly determine source wells to fit in tip for a multiAspirate transfer in `getTransferPlanAndReferenceVolumes` planning utility. We pass the number of source wells to find the correct volume in the tip, considering the max working volume of the tip. Closes RQA-4431
1 parent d46293b commit fc22d4b

File tree

8 files changed

+51
-4
lines changed

8 files changed

+51
-4
lines changed

protocol-designer/src/load-file/migration/8_5_0.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ const getClippedFlowRateForMoveLiquid = (args: {
109109
tiprackDefinition: tiprackDef,
110110
volume,
111111
path,
112+
numAspirateWells:
113+
'aspirate_wells' in formData ? formData.aspirate_wells : formData.wells,
112114
numDispenseWells:
113115
'dispense_wells' in formData ? formData.dispense_wells : formData.wells,
114116
aspirateAirGapByVolume: tipLiquidSpecs.aspirate.retract

protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/PipetteFields/FlowRateField.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,14 @@ export function FlowRateField(props: FlowRateFieldProps): JSX.Element {
101101
? getTransferPlanAndReferenceVolumes({
102102
volume: Number(formData.volume),
103103
path: (formData.path as PathOption) ?? 'single',
104+
numAspirateWells:
105+
formData.stepType === 'moveLiquid'
106+
? formData.aspirate_wells.length
107+
: formData.wells.length,
104108
numDispenseWells:
105109
formData.stepType === 'moveLiquid'
106110
? formData.dispense_wells.length
107-
: 1,
111+
: formData.wells.length,
108112
pipetteSpecs: pipette?.spec,
109113
tiprackDefinition: tiprackDef,
110114
// multi-dispense is valid on OT-2, even though liquid class values are null

protocol-designer/src/steplist/formLevel/handleFormChange/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ const getNoLiquidClassValuesMoveLiquid = (args: {
659659
>,
660660
volume,
661661
path: rawForm.path as PathOption,
662+
numAspirateWells: rawForm.aspirate_wells.length,
662663
numDispenseWells: rawForm.dispense_wells.length,
663664
aspirateAirGapByVolume: aspirate.retract.airGapByVolume as Array<
664665
[number, number]
@@ -1032,6 +1033,7 @@ const getLiquidClassValuesMoveLiquid = (args: {
10321033
disposalByVolume,
10331034
volume,
10341035
path: rawForm.path as PathOption,
1036+
numAspirateWells: rawForm.aspirate_wells.length,
10351037
numDispenseWells: rawForm.dispense_wells.length,
10361038
aspirateAirGapByVolume: aspirate.retract.airGapByVolume as Array<
10371039
[number, number]

protocol-designer/src/steplist/formLevel/stepFormToArgs/moveLiquidFormToArgs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ const getCheckedPath = (
117117
tiprackDefinition: tiprackDef,
118118
volume,
119119
path,
120+
numAspirateWells: hydratedFormData.aspirate_wells.length,
120121
numDispenseWells: hydratedFormData.dispense_wells.length,
121122
aspirateAirGapByVolume: liquidClassValuesForTip.aspirate.retract
122123
.airGapByVolume as Array<[number, number]>,
@@ -128,6 +129,7 @@ const getCheckedPath = (
128129
tiprackDefinition: tiprackDef,
129130
volume,
130131
path,
132+
numAspirateWells: hydratedFormData.aspirate_wells.length,
131133
numDispenseWells: hydratedFormData.dispense_wells.length,
132134
aspirateAirGapByVolume: liquidClassValuesForTip.aspirate.retract
133135
.airGapByVolume as Array<[number, number]>,

step-generation/src/commandCreators/compound/consolidate.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,8 @@ export const consolidate: CommandCreator<ConsolidateArgs> = (
234234
tiprackDefinition,
235235
volume,
236236
path: 'multiAspirate',
237-
numDispenseWells: sourceWells.length,
237+
numAspirateWells: sourceWells.length,
238+
numDispenseWells: 1,
238239
aspirateAirGapByVolume:
239240
(aspirate?.retract.airGapByVolume as Array<[number, number]>) ?? [],
240241
conditioningByVolume: null,

step-generation/src/commandCreators/compound/distribute.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ export const distribute: CommandCreator<DistributeArgs> = (
241241
tiprackDefinition,
242242
volume,
243243
path: 'multiDispense',
244+
numAspirateWells: 1,
244245
numDispenseWells: destWells.length,
245246
conditioningByVolume: (multiDispense?.conditioningByVolume ?? []) as Array<
246247
[number, number]

step-generation/src/utils/__tests__/misc.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
8080
tiprackDefinition: fixtureTiprack10ul,
8181
volume: 5,
8282
path: 'single',
83+
numAspirateWells: 1,
8384
numDispenseWells: 1,
8485
aspirateAirGapByVolume: [],
8586
conditioningByVolume: null,
@@ -113,6 +114,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
113114
tiprackDefinition: fixtureTiprack10ul,
114115
volume: 15,
115116
path: 'single',
117+
numAspirateWells: 1,
116118
numDispenseWells: 1,
117119
aspirateAirGapByVolume: [],
118120
conditioningByVolume: null,
@@ -132,6 +134,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
132134
tiprackDefinition: fixtureTiprack10ul,
133135
volume: 15,
134136
path: 'single',
137+
numAspirateWells: 1,
135138
numDispenseWells: 1,
136139
aspirateAirGapByVolume: [],
137140
conditioningByVolume: null,
@@ -151,6 +154,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
151154
tiprackDefinition: fixtureTiprack10ul,
152155
volume: 10,
153156
path: 'single',
157+
numAspirateWells: 1,
154158
numDispenseWells: 1,
155159
aspirateAirGapByVolume: [[10, 2]],
156160
conditioningByVolume: null,
@@ -170,6 +174,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
170174
tiprackDefinition: fixtureTiprack10ul,
171175
volume: 3,
172176
path: 'multiAspirate',
177+
numAspirateWells: 3,
173178
numDispenseWells: 1,
174179
aspirateAirGapByVolume: [],
175180
conditioningByVolume: null,
@@ -205,6 +210,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
205210
volume: 4,
206211
path: 'multiAspirate',
207212
numDispenseWells: 1,
213+
numAspirateWells: 3,
208214
aspirateAirGapByVolume: [],
209215
conditioningByVolume: null,
210216
disposalByVolume: null,
@@ -220,6 +226,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
220226
volume: 4,
221227
path: 'multiAspirate',
222228
numDispenseWells: 1,
229+
numAspirateWells: 3,
223230
aspirateAirGapByVolume: [],
224231
conditioningByVolume: null,
225232
disposalByVolume: null,
@@ -234,6 +241,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
234241
tiprackDefinition: fixtureTiprack300ul,
235242
volume: 10,
236243
path: 'multiDispense',
244+
numAspirateWells: 1,
237245
numDispenseWells: 3,
238246
aspirateAirGapByVolume: [],
239247
conditioningByVolume: [
@@ -264,6 +272,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
264272
tiprackDefinition: fixtureTiprack10ul,
265273
volume: 3,
266274
path: 'multiDispense',
275+
numAspirateWells: 1,
267276
numDispenseWells: 4,
268277
aspirateAirGapByVolume: [],
269278
conditioningByVolume: [
@@ -293,6 +302,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
293302
tiprackDefinition: fixtureTiprack10ul,
294303
volume: 3,
295304
path: 'multiDispense',
305+
numAspirateWells: 1,
296306
numDispenseWells: 4,
297307
aspirateAirGapByVolume: [],
298308
conditioningByVolume: [
@@ -322,6 +332,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
322332
tiprackDefinition: fixtureTiprack10ul,
323333
volume: 6,
324334
path: 'multiDispense',
335+
numAspirateWells: 1,
325336
numDispenseWells: 2,
326337
aspirateAirGapByVolume: [],
327338
conditioningByVolume: [[10, 1]],
@@ -349,6 +360,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
349360
tiprackDefinition: fixtureTiprack10ul,
350361
volume: 1,
351362
path: 'single',
363+
numAspirateWells: 1,
352364
numDispenseWells: 1,
353365
aspirateAirGapByVolume: [],
354366
conditioningByVolume: null,
@@ -372,6 +384,7 @@ describe('getTransferPlanAndReferenceVolumes', () => {
372384
tiprackDefinition: fixtureTiprack10ul,
373385
volume: 0.8,
374386
path: 'single',
387+
numAspirateWells: 1,
375388
numDispenseWells: 1,
376389
aspirateAirGapByVolume: [],
377390
conditioningByVolume: null,

step-generation/src/utils/misc.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,7 @@ export const getTransferPlanAndReferenceVolumes = (args: {
956956
tiprackDefinition: LabwareDefinition2 | null
957957
volume: number
958958
path: PathOption
959+
numAspirateWells: number
959960
numDispenseWells: number
960961
aspirateAirGapByVolume: Array<[number, number]>
961962
conditioningByVolume: Array<[number, number]> | null
@@ -975,6 +976,7 @@ export const getTransferPlanAndReferenceVolumes = (args: {
975976
conditioningByVolume,
976977
disposalByVolume,
977978
numDispenseWells,
979+
numAspirateWells,
978980
aspirateAirGapByVolume,
979981
} = args
980982
const { liquids } = pipetteSpecs
@@ -1016,6 +1018,20 @@ export const getTransferPlanAndReferenceVolumes = (args: {
10161018
const isMultiAspirateAvailable =
10171019
maxWorkingVolume > minVolumeForMultiAspirateDispense
10181020

1021+
if (path === 'multiAspirate' && numAspirateWells <= numDispenseWells) {
1022+
console.warn(
1023+
'Invalid combination of source and destination wells for multiAspirate path'
1024+
)
1025+
} else if (path === 'multiDispense' && numAspirateWells >= numDispenseWells) {
1026+
console.warn(
1027+
'Invalid combination of source and destination wells for multiDispense path'
1028+
)
1029+
} else if (path === 'single' && numAspirateWells !== numDispenseWells) {
1030+
console.warn(
1031+
'Invalid combination of source and destination wells for single path'
1032+
)
1033+
}
1034+
10191035
// early return if multiAspirate/multiDispense cannot be accommodated
10201036
if (
10211037
path === 'single' ||
@@ -1111,9 +1127,15 @@ export const getTransferPlanAndReferenceVolumes = (args: {
11111127
},
11121128
}
11131129
}
1130+
11141131
// path is valid multiAspirate
11151132
const maxSourcesPerAspiration = Math.floor(maxWorkingVolume / volume)
1116-
const volumeTotalAspiration = maxSourcesPerAspiration * volume
1133+
const sourcesPerAspiration = Math.min(
1134+
maxSourcesPerAspiration,
1135+
numAspirateWells
1136+
)
1137+
const volumeTotalAspiration = sourcesPerAspiration * volume
1138+
11171139
return {
11181140
referenceVolumes: {
11191141
airGap: {
@@ -1134,7 +1156,7 @@ export const getTransferPlanAndReferenceVolumes = (args: {
11341156
},
11351157
multiWellHandling: {
11361158
isSupported: true,
1137-
numWellsToFitInTip: maxSourcesPerAspiration,
1159+
numWellsToFitInTip: sourcesPerAspiration,
11381160
},
11391161
}
11401162
}

0 commit comments

Comments
 (0)