Skip to content

Commit

Permalink
refactor(app, api-client): add utilities for parsing deck config via …
Browse files Browse the repository at this point in the history
…addressable areas (#13947)

Instead of explicit load fixture commands, extrapolate the simplest possible setup for the deck
given the included addressable areas referenced in a set of protocol commands

Closes RAUT-853
  • Loading branch information
b-cooper authored Nov 9, 2023
1 parent e03e988 commit 7258da8
Show file tree
Hide file tree
Showing 22 changed files with 591 additions and 39 deletions.
58 changes: 58 additions & 0 deletions api-client/src/protocols/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
ModuleModel,
PipetteName,
RunTimeCommand,
AddressableAreaName,
} from '@opentrons/shared-data'

interface PipetteNamesByMount {
Expand Down Expand Up @@ -244,6 +245,63 @@ export function parseInitialLoadedFixturesByCutout(
)
}

export function parseAllAddressableAreas(
commands: RunTimeCommand[]
): AddressableAreaName[] {
return commands.reduce<AddressableAreaName[]>((acc, command) => {
if (
command.commandType === 'moveLabware' &&
command.params.newLocation !== 'offDeck' &&
'slotName' in command.params.newLocation &&
!acc.includes(command.params.newLocation.slotName as AddressableAreaName)

Check warning on line 256 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
) {
return [
...acc,
command.params.newLocation.slotName as AddressableAreaName,

Check warning on line 260 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
]
} else if (
command.commandType === 'moveLabware' &&
command.params.newLocation !== 'offDeck' &&
'addressableAreaName' in command.params.newLocation &&
!acc.includes(
command.params.newLocation.addressableAreaName as AddressableAreaName

Check warning on line 267 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
)
) {
return [
...acc,
command.params.newLocation.addressableAreaName as AddressableAreaName,

Check warning on line 272 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
]
} else if (
(command.commandType === 'loadLabware' ||
command.commandType === 'loadModule') &&
command.params.location !== 'offDeck' &&
'slotName' in command.params.location &&
!acc.includes(command.params.location.slotName as AddressableAreaName)

Check warning on line 279 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
) {
return [...acc, command.params.location.slotName as AddressableAreaName]

Check warning on line 281 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
} else if (
command.commandType === 'loadLabware' &&
command.params.location !== 'offDeck' &&
'addressableAreaName' in command.params.location &&
!acc.includes(
command.params.location.addressableAreaName as AddressableAreaName

Check warning on line 287 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
)
) {
return [
...acc,
command.params.location.addressableAreaName as AddressableAreaName,

Check warning on line 292 in api-client/src/protocols/utils.ts

View workflow job for this annotation

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
]
}
// TODO(BC, 11/6/23): once moveToAddressableArea command exists add it back here
// else if (command.commandType === 'moveToAddressableArea') {
// ...
// }
else {
return acc
}
}, [])
}

export interface LiquidsById {
[liquidId: string]: {
displayName: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ export function getLabwareLocationCombos(
})
} else {
return appendLocationComboIfUniq(acc, {
location: command.params.location,
location: {
slotName:
'addressableAreaName' in command.params.location
? command.params.location.addressableAreaName
: command.params.location.slotName,
},
definitionUri,
labwareId: command.result.labwareId,
})
Expand Down Expand Up @@ -107,7 +112,12 @@ export function getLabwareLocationCombos(
})
} else {
return appendLocationComboIfUniq(acc, {
location: command.params.newLocation,
location: {
slotName:
'addressableAreaName' in command.params.newLocation
? command.params.newLocation.addressableAreaName
: command.params.newLocation.slotName,
},
definitionUri: labwareEntity.definitionUri,
labwareId: command.params.labwareId,
})
Expand Down Expand Up @@ -191,7 +201,10 @@ function resolveAdapterLocation(
} else {
adapterOffsetLocation = {
definitionUri: labwareDefUri,
slotName: labwareEntity.location.slotName,
slotName:
'addressableAreaName' in labwareEntity.location
? labwareEntity.location.addressableAreaName
: labwareEntity.location.slotName,
}
}
return {
Expand Down
5 changes: 4 additions & 1 deletion app/src/organisms/CommandText/LoadCommandText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ export const LoadCommandText = ({
? t('load_labware_info_protocol_setup_off_deck', { labware })
: t('load_labware_info_protocol_setup_no_module', {
labware,
slot_name: command.params.location?.slotName,
slot_name:
'addressableAreaName' in command.params.location
? command.params.location.addressableAreaName
: command.params.location.slotName,
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export const getLabwareOffsetLocation = (
slotName: adapter.location.slotName,
definitionUri: adapter.definitionUri,
}
} else if ('addressableAreaName' in adapter.location) {
return {
slotName: adapter.location.addressableAreaName,
definitionUri: adapter.definitionUri,
}
} else if ('moduleId' in adapter.location) {
const moduleIdUnderAdapter = adapter.location.moduleId
const moduleModel = modules.find(
Expand All @@ -52,7 +57,12 @@ export const getLabwareOffsetLocation = (
return { slotName, moduleModel, definitionUri: adapter.definitionUri }
}
} else {
return { slotName: labwareLocation.slotName }
return {
slotName:
'addressableAreaName' in labwareLocation
? labwareLocation.addressableAreaName
: labwareLocation.slotName,
}
}
return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ export const getLabwareRenderInfo = (
)
}
// TODO(bh, 2023-10-19): convert this to deck definition v4 addressableAreas
const slotName = location.slotName.toString()
const slotName =
'addressableAreaName' in location
? location.addressableAreaName
: location.slotName
// TODO(bh, 2023-10-19): remove slotPosition when render info no longer relies on directly
const slotPosition = getSlotPosition(deckDef, slotName)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export function getLocationInfoNames(
return { slotName: 'Off deck', labwareName }
} else if ('slotName' in labwareLocation) {
return { slotName: labwareLocation.slotName, labwareName }
} else if ('addressableAreaName' in labwareLocation) {
return { slotName: labwareLocation.addressableAreaName, labwareName }
} else if ('moduleId' in labwareLocation) {
const loadModuleCommandUnderLabware = loadModuleCommands?.find(
command => command.result?.moduleId === labwareLocation.moduleId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ export function getRunLabwareRenderInfo(
}

if (location !== 'offDeck') {
const slotName = location.slotName
const slotName =
'addressableAreaName' in location
? location.addressableAreaName
: location.slotName
const slotPosition =
deckDef.locations.orderedSlots.find(slot => slot.id === slotName)
?.position ?? []
Expand Down
5 changes: 4 additions & 1 deletion app/src/organisms/LabwarePositionCheck/utils/labware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,10 @@ export const getLabwareIdsInOrder = (
).location.slotName
}
} else {
slot = loc.slotName
slot =
'addressableAreaName' in loc
? loc.addressableAreaName
: loc.slotName
}
return [
...innerAcc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
useModulesQuery,
} from '@opentrons/react-api-client'
import { renderWithProviders } from '@opentrons/components'
import { getDeckDefFromRobotType } from '@opentrons/shared-data'
import ot3StandardDeckDef from '@opentrons/shared-data/deck/definitions/3/ot3_standard.json'

import { i18n } from '../../../i18n'
Expand All @@ -25,7 +24,6 @@ import {
} from '../__fixtures__'

jest.mock('@opentrons/react-api-client')
jest.mock('@opentrons/shared-data/js/helpers')
jest.mock(
'../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis'
)
Expand All @@ -37,9 +35,6 @@ const mockUseCreateLiveCommandMutation = useCreateLiveCommandMutation as jest.Mo
const mockUseModulesQuery = useModulesQuery as jest.MockedFunction<
typeof useModulesQuery
>
const mockGetDeckDefFromRobotType = getDeckDefFromRobotType as jest.MockedFunction<
typeof getDeckDefFromRobotType
>
const mockUseMostRecentCompletedAnalysis = useMostRecentCompletedAnalysis as jest.MockedFunction<
typeof useMostRecentCompletedAnalysis
>
Expand Down Expand Up @@ -72,9 +67,6 @@ describe('ProtocolSetupLabware', () => {
when(mockUseMostRecentCompletedAnalysis)
.calledWith(RUN_ID)
.mockReturnValue(mockRecentAnalysis)
when(mockGetDeckDefFromRobotType)
.calledWith('OT-3 Standard')
.mockReturnValue(ot3StandardDeckDef as any)
when(mockGetProtocolModulesInfo)
.calledWith(mockRecentAnalysis, ot3StandardDeckDef as any)
.mockReturnValue(mockProtocolModuleInfo)
Expand Down
142 changes: 142 additions & 0 deletions app/src/resources/deck_configuration/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { RunTimeCommand } from '@opentrons/shared-data'
import {
FLEX_SIMPLEST_DECK_CONFIG,
getSimplestDeckConfigForProtocolCommands,
} from '../utils'

const RUN_TIME_COMMAND_STUB_MIXIN: Pick<
RunTimeCommand,
'id' | 'createdAt' | 'startedAt' | 'completedAt' | 'status'
> = {
id: 'fake_id',
createdAt: 'fake_createdAt',
startedAt: 'fake_startedAt',
completedAt: 'fake_createdAt',
status: 'succeeded',
}

describe('getSimplestDeckConfigForProtocolCommands', () => {
it('returns simplest deck if no commands alter addressable areas', () => {
expect(getSimplestDeckConfigForProtocolCommands([])).toEqual(
FLEX_SIMPLEST_DECK_CONFIG
)
})
it('returns staging area fixtures if commands address column 4 areas', () => {
const cutoutConfigs = getSimplestDeckConfigForProtocolCommands([
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'loadLabware',
params: {
loadName: 'fake_load_name',
location: { slotName: 'A4' },
version: 1,
namespace: 'fake_namespace',
},
},
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'loadLabware',
params: {
loadName: 'fake_load_name',
location: { slotName: 'B4' },
version: 1,
namespace: 'fake_namespace',
},
},
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'loadLabware',
params: {
loadName: 'fake_load_name',
location: { slotName: 'C4' },
version: 1,
namespace: 'fake_namespace',
},
},
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'loadLabware',
params: {
loadName: 'fake_load_name',
location: { slotName: 'D4' },
version: 1,
namespace: 'fake_namespace',
},
},
])
expect(cutoutConfigs).toEqual([
...FLEX_SIMPLEST_DECK_CONFIG.slice(0, 8),
{
cutoutId: 'cutoutA3',
cutoutFixtureId: 'stagingAreaRightSlot',
requiredAddressableAreas: ['A4'],
},
{
cutoutId: 'cutoutB3',
cutoutFixtureId: 'stagingAreaRightSlot',
requiredAddressableAreas: ['B4'],
},
{
cutoutId: 'cutoutC3',
cutoutFixtureId: 'stagingAreaRightSlot',
requiredAddressableAreas: ['C4'],
},
{
cutoutId: 'cutoutD3',
cutoutFixtureId: 'stagingAreaRightSlot',
requiredAddressableAreas: ['D4'],
},
])
})
it('returns simplest cutout fixture where many are possible', () => {
const cutoutConfigs = getSimplestDeckConfigForProtocolCommands([
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'moveLabware',
params: {
newLocation: { addressableAreaName: 'gripperWasteChute' },
labwareId: 'fake_labwareId',
strategy: 'usingGripper',
},
},
])
expect(cutoutConfigs).toEqual([
...FLEX_SIMPLEST_DECK_CONFIG.slice(0, 11),
{
cutoutId: 'cutoutD3',
cutoutFixtureId: 'wasteChuteRightAdapterNoCover',
requiredAddressableAreas: ['gripperWasteChute'],
},
])
})
it('returns compatible cutout fixture where multiple addressable requirements present', () => {
const cutoutConfigs = getSimplestDeckConfigForProtocolCommands([
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'moveLabware',
params: {
newLocation: { addressableAreaName: 'gripperWasteChute' },
labwareId: 'fake_labwareId',
strategy: 'usingGripper',
},
},
{
...RUN_TIME_COMMAND_STUB_MIXIN,
commandType: 'moveLabware',
params: {
newLocation: { addressableAreaName: 'D4' },
labwareId: 'fake_labwareId',
strategy: 'usingGripper',
},
},
])
expect(cutoutConfigs).toEqual([
...FLEX_SIMPLEST_DECK_CONFIG.slice(0, 11),
{
cutoutId: 'cutoutD3',
cutoutFixtureId: 'stagingAreaSlotWithWasteChuteRightAdapterNoCover',
requiredAddressableAreas: ['gripperWasteChute', 'D4'],
},
])
})
})
Loading

0 comments on commit 7258da8

Please sign in to comment.