Skip to content

Commit

Permalink
fix(protocol-designer): fix staging area logic (#17403)
Browse files Browse the repository at this point in the history
* fix(protocol-designer): fix staging area logic
  • Loading branch information
koji authored Feb 3, 2025
1 parent 2525a63 commit dc48e10
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ describe('getNumSlotsAvailable', () => {
mockAdditionalEquipment,
'stagingArea'
)
// Note: the return value is 2 because trashBin can be placed slot1
expect(result).toBe(2)

expect(result).toBe(1)
})

it('should return 1 when there are 8 modules with 2 magnetic blocks and one trash for staging area', () => {
Expand Down Expand Up @@ -285,14 +285,14 @@ describe('getNumSlotsAvailable', () => {
mockAdditionalEquipment,
'stagingArea'
)
expect(result).toBe(2)
expect(result).toBe(1)
})
it('should return 4 when there are 12 magnetic blocks for staging area', () => {
it('should return 0 when there are 11 magnetic blocks for staging area', () => {
const mockModules = {
0: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'C2',
slot: 'D2',
},
1: {
model: MAGNETIC_BLOCK_V1,
Expand All @@ -302,52 +302,47 @@ describe('getNumSlotsAvailable', () => {
2: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'C2',
slot: 'B2',
},
3: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'C2',
slot: 'A2',
},
4: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'C2',
slot: 'D3',
},
5: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'C2',
slot: 'C3',
},
6: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'D2',
slot: 'B3',
},
7: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'C2',
slot: 'D1',
},
8: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'C2',
slot: 'C1',
},
9: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'C2',
slot: 'B1',
},
10: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'C2',
},
11: {
model: MAGNETIC_BLOCK_V1,
type: MAGNETIC_BLOCK_TYPE,
slot: 'C2',
slot: 'A1',
},
} as any
const mockAdditionalEquipment: AdditionalEquipment[] = []
Expand All @@ -356,9 +351,39 @@ describe('getNumSlotsAvailable', () => {
mockAdditionalEquipment,
'stagingArea'
)
expect(result).toBe(4)
// Note: the return value is 0 because trashBin A3
expect(result).toBe(0)
})

it('should return 3 when slots in column 1 are occupied', () => {
const mockModules = {
0: {
model: TEMPERATURE_MODULE_V2,
type: TEMPERATURE_MODULE_TYPE,
slot: 'D1',
},
1: {
model: HEATERSHAKER_MODULE_V1,
type: HEATERSHAKER_MODULE_TYPE,
slot: 'C1',
},
2: {
model: THERMOCYCLER_MODULE_V2,
type: THERMOCYCLER_MODULE_TYPE,
slot: 'B1',
},
} as any
const mockAdditionalEquipment: AdditionalEquipment[] = ['trashBin']
const result = getNumSlotsAvailable(
mockModules,
mockAdditionalEquipment,
'stagingArea'
)

expect(result).toBe(3)
})
it('should return 12 when there are 4 staging area for magnetic block', () => {

it('should return 11 when there are 4 staging area for magnetic block', () => {
const mockAdditionalEquipment: AdditionalEquipment[] = [
'stagingArea',
'stagingArea',
Expand All @@ -370,7 +395,7 @@ describe('getNumSlotsAvailable', () => {
mockAdditionalEquipment,
MAGNETIC_BLOCK_V1
)
expect(result).toBe(12)
expect(result).toBe(11)
})
it('should return 8 when there are 4 modules, 4 staging area for magnetic block since magnetic blocks can now go on staging areas', () => {
const mockModules = {
Expand Down Expand Up @@ -401,7 +426,7 @@ describe('getNumSlotsAvailable', () => {
mockAdditionalEquipment,
MAGNETIC_BLOCK_V1
)
expect(result).toBe(8)
expect(result).toBe(7)
})
})

Expand Down
89 changes: 78 additions & 11 deletions protocol-designer/src/pages/CreateNewProtocolWizard/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import {
ABSORBANCE_READER_TYPE,
ABSORBANCE_READER_V1,
getLabwareDefURI,
getLabwareDisplayName,
getPipetteSpecsV2,
HEATERSHAKER_MODULE_TYPE,
HEATERSHAKER_MODULE_V1,
MAGNETIC_BLOCK_TYPE,
MAGNETIC_BLOCK_V1,
MAGNETIC_MODULE_V1,
MAGNETIC_MODULE_V2,
TEMPERATURE_MODULE_TYPE,
TEMPERATURE_MODULE_V1,
TEMPERATURE_MODULE_V2,
THERMOCYCLER_MODULE_TYPE,
Expand All @@ -32,8 +35,9 @@ import type { AdditionalEquipment, WizardFormState } from './types'

const NUM_SLOTS_OUTER = 8
const NUM_SLOTS_MIDDLE = 4
const NUM_SLOTS_COLUMN3 = 4
const NUM_SLOTS_MAGNETIC_BLOCK = 12
const NUM_SLOTS_COLUMN1 = 4
// Note (1/31/25): change the max from 12 to 11 because of a fixture(trash bin/waste chute)
const NUM_SLOTS_MAGNETIC_BLOCK = 11

export const getNumOptions = (length: number): DropdownOption[] => {
return Array.from({ length }, (_, i) => ({
Expand All @@ -42,6 +46,43 @@ export const getNumOptions = (length: number): DropdownOption[] => {
}))
}

// Note (1/31/25): at this moment, users allow to set one about thermocycler and need to count 2
interface ModuleCounts {
magneticBlockCount: number
heaterShakerCount: number
temperatureCount: number
plateReaderCount: number
}

const countModules = (modules: WizardFormState['modules']): ModuleCounts => {
return Object.values(modules || {}).reduce(
(acc, module) => {
switch (module.type) {
case MAGNETIC_BLOCK_TYPE:
acc.magneticBlockCount += 1
break
case HEATERSHAKER_MODULE_TYPE:
acc.heaterShakerCount += 1
break
case TEMPERATURE_MODULE_TYPE:
acc.temperatureCount += 1
break
case ABSORBANCE_READER_TYPE:
acc.plateReaderCount += 1
break
default:
break
}
return acc
},
{
magneticBlockCount: 0,
heaterShakerCount: 0,
temperatureCount: 0,
plateReaderCount: 0,
}
)
}
export const getNumSlotsAvailable = (
modules: WizardFormState['modules'],
additionalEquipment: WizardFormState['additionalEquipment'],
Expand Down Expand Up @@ -115,16 +156,42 @@ export const getNumSlotsAvailable = (
}

case 'stagingArea': {
const modulesWithColumn3 =
modules !== null
? Object.values(modules).filter(module => module.slot?.includes('3'))
.length
: 0
const fixtureSlotsWithColumn3 =
additionalEquipment !== null
? additionalEquipment.filter(slot => slot.includes('3')).length
const {
magneticBlockCount,
heaterShakerCount,
temperatureCount,
plateReaderCount,
} = countModules(modules)

// Note (kk: 1/31/25) magnetic modules are placed in the middle slots first
// then it will be placed in the column 1 slots and column 3 slots
// the way to distribute magnetic modules like D1 -> D3 -> C1 -> C3
const adjustMagneticBlockCount =
magneticBlockCount - NUM_SLOTS_MIDDLE > 0
? magneticBlockCount - NUM_SLOTS_MIDDLE
: 0
return NUM_SLOTS_COLUMN3 - modulesWithColumn3 - fixtureSlotsWithColumn3

const thermocyclerModuleCount = hasTC ? 2 : 0

const totalModules =
adjustMagneticBlockCount +
heaterShakerCount +
temperatureCount +
thermocyclerModuleCount

// if the following is more than 0, pd will need to keep one slot in column 3 for trash bin/waste chute
const requiredSlotInColumn3 =
totalModules - NUM_SLOTS_COLUMN1 >= 0 ? 1 : 0

// there is two cases pd considers
// 1. stating area can slots in column 3 because trash bin can be a slot in column 1
// 2. not case 1 which is very limited
return totalModules <= NUM_SLOTS_COLUMN1
? NUM_SLOTS_COLUMN1 - plateReaderCount - requiredSlotInColumn3
: NUM_SLOTS_OUTER -
totalModules -
plateReaderCount -
requiredSlotInColumn3
}

case 'wasteChute': {
Expand Down

0 comments on commit dc48e10

Please sign in to comment.