@@ -3,16 +3,25 @@ import reduce from 'lodash/reduce'
33import type { LoadLiquidRunTimeCommand , RunTimeCommand } from '../../command'
44
55export interface LabwareByLiquidId {
6- [ liquidId : string ] : Array < {
7- labwareId : string
8- volumeByWell : { [ well : string ] : number }
9- } >
6+ [ liquidId : string ] : LabwareVolumeEntry [ ]
107}
8+
9+ interface WellVolumeMap {
10+ [ well : string ] : number
11+ }
12+
13+ interface LabwareVolumeEntry {
14+ labwareId : string
15+ volumeByWell : WellVolumeMap
16+ }
17+
1118interface WellVolumeEntry {
1219 labwareId : string
1320 volume : number
1421 liquidId : string
1522}
23+
24+ // Filter for loadLiquid commands with non-empty liquids, then build and consolidate them
1625export function getLabwareInfoByLiquidId (
1726 commands : RunTimeCommand [ ]
1827) : LabwareByLiquidId {
@@ -25,19 +34,29 @@ export function getLabwareInfoByLiquidId(
2534 )
2635 : [ ]
2736
28- const initialLabwareByLiquidId = reduce <
29- LoadLiquidRunTimeCommand ,
30- LabwareByLiquidId
31- > (
32- loadLiquidCommands ,
37+ const initialLabwareByLiquidId =
38+ buildInitialLabwareByLiquidId ( loadLiquidCommands )
39+
40+ return consolidateSharedWells ( initialLabwareByLiquidId )
41+ }
42+
43+ // Group labware by liquidId, merging volumeByWell entries when the same labware
44+ // appears multiple times for the same liquid
45+ function buildInitialLabwareByLiquidId (
46+ commands : LoadLiquidRunTimeCommand [ ]
47+ ) : LabwareByLiquidId {
48+ return reduce < LoadLiquidRunTimeCommand , LabwareByLiquidId > (
49+ commands ,
3350 ( acc , command ) => {
3451 const { liquidId, labwareId, volumeByWell } = command . params
3552 if ( ! ( liquidId in acc ) ) acc [ liquidId ] = [ ]
3653
3754 const labwareIndex = acc [ liquidId ] . findIndex (
3855 i => i . labwareId === labwareId
3956 )
57+
4058 if ( labwareIndex >= 0 ) {
59+ // Merge wells into existing labware entry
4160 acc [ liquidId ] [ labwareIndex ] . volumeByWell = {
4261 ...acc [ liquidId ] [ labwareIndex ] . volumeByWell ,
4362 ...volumeByWell ,
@@ -49,72 +68,75 @@ export function getLabwareInfoByLiquidId(
4968 } ,
5069 { }
5170 )
71+ }
72+
73+ // Build a reverse mapping of wells to all liquids that occupy them
74+ function consolidateSharedWells (
75+ liquidsByIdForLabware : LabwareByLiquidId
76+ ) : LabwareByLiquidId {
77+ const wellToLabwareVolumes : Record < string , WellVolumeEntry [ ] > = { }
5278
53- const consolidateSharedWells = (
54- liquidsByIdForLabware : LabwareByLiquidId
55- ) : LabwareByLiquidId => {
56- const wellToLabwareVolumes : Record < string , WellVolumeEntry [ ] > = { }
57-
58- // Track all wells with their labware, volume, and source liquid
59- Object . entries ( liquidsByIdForLabware ) . forEach (
60- ( [ liquidId , labwareArray ] ) => {
61- labwareArray . forEach ( labware => {
62- Object . entries ( labware . volumeByWell ) . forEach ( ( [ well , volume ] ) => {
63- wellToLabwareVolumes [ well ] ??= [ ]
64- wellToLabwareVolumes [ well ] . push ( {
65- labwareId : labware . labwareId ,
66- volume,
67- liquidId,
68- } )
69- } )
79+ Object . entries ( liquidsByIdForLabware ) . forEach ( ( [ liquidId , labwareArray ] ) => {
80+ labwareArray . forEach ( labware => {
81+ Object . entries ( labware . volumeByWell ) . forEach ( ( [ well , volume ] ) => {
82+ const compositeKey = `${ labware . labwareId } :${ well } `
83+ wellToLabwareVolumes [ compositeKey ] ??= [ ]
84+ wellToLabwareVolumes [ compositeKey ] . push ( {
85+ labwareId : labware . labwareId ,
86+ volume,
87+ liquidId,
7088 } )
71- }
72- )
73-
74- // Identify overlapping wells
75- const overlappingWells = Object . entries ( wellToLabwareVolumes ) . filter (
76- ( [ , entries ] ) => entries . length > 1
77- )
78- const consolidatedWells : LabwareByLiquidId = { }
79-
80- overlappingWells . forEach ( ( [ well , entries ] ) => {
81- const totalVolume = entries . reduce ( ( sum , entry ) => sum + entry . volume , 0 )
82- const labwareIdName = entries [ 0 ] . labwareId
83- const liquidIds = entries . map ( e => e . liquidId ) . sort ( )
84- const mixedLiquidId = `mixed-${ liquidIds . join ( '-' ) } `
85-
86- consolidatedWells [ mixedLiquidId ] ??= [
87- { labwareId : labwareIdName , volumeByWell : { } } ,
88- ]
89- consolidatedWells [ mixedLiquidId ] [ 0 ] . volumeByWell [ well ] = totalVolume
89+ } )
9090 } )
91- // Remove consolidated wells from the original liquids
92- const cleanedOriginal : LabwareByLiquidId = { }
93- const overlappingWellSet = new Set ( overlappingWells . map ( ( [ well ] ) => well ) )
94-
95- Object . entries ( liquidsByIdForLabware ) . forEach (
96- ( [ liquidId , labwareArray ] ) => {
97- labwareArray . forEach ( labware => {
98- const remainingWells = Object . fromEntries (
99- Object . entries ( labware . volumeByWell ) . filter (
100- ( [ well ] ) => ! overlappingWellSet . has ( well )
101- )
102- )
103-
104- if ( Object . keys ( remainingWells ) . length > 0 ) {
105- cleanedOriginal [ liquidId ] ??= [ ]
106- cleanedOriginal [ liquidId ] . push ( {
107- labwareId : labware . labwareId ,
108- volumeByWell : remainingWells ,
109- } )
110- }
91+ } )
92+
93+ // Find wells that have been loaded with multiple different liquids
94+ const overlappingWells = Object . entries ( wellToLabwareVolumes ) . filter (
95+ ( [ , entries ] ) => entries . length > 1
96+ )
97+
98+ // Create consolidated entries for wells with mixed liquids
99+ const consolidatedWells : LabwareByLiquidId = { }
100+
101+ overlappingWells . forEach ( ( [ compositeKey , entries ] ) => {
102+ const totalVolume = entries . reduce ( ( sum , entry ) => sum + entry . volume , 0 )
103+ const labwareIdName = entries [ 0 ] . labwareId
104+ const liquidIds = entries . map ( e => e . liquidId ) . sort ( )
105+ const mixedLiquidId = `mixed-${ liquidIds . join ( '-' ) } `
106+
107+ consolidatedWells [ mixedLiquidId ] ??= [
108+ { labwareId : labwareIdName , volumeByWell : { } } ,
109+ ]
110+ const wellName = compositeKey . split ( ':' ) [ 1 ]
111+ consolidatedWells [ mixedLiquidId ] [ 0 ] . volumeByWell [ wellName ] = totalVolume
112+ } )
113+
114+ // Remove overlapping wells from original liquid entries, keeping only non-overlapping wells
115+ const cleanedOriginal : LabwareByLiquidId = { }
116+ const overlappingWellSet = new Set (
117+ overlappingWells . map ( ( [ compositeKey ] ) => compositeKey )
118+ )
119+
120+ Object . entries ( liquidsByIdForLabware ) . forEach ( ( [ liquidId , labwareArray ] ) => {
121+ labwareArray . forEach ( labware => {
122+ const remainingWells = Object . fromEntries (
123+ Object . entries ( labware . volumeByWell ) . filter (
124+ ( [ well ] ) => ! overlappingWellSet . has ( `${ labware . labwareId } :${ well } ` )
125+ )
126+ )
127+
128+ if ( Object . keys ( remainingWells ) . length > 0 ) {
129+ cleanedOriginal [ liquidId ] ??= [ ]
130+ cleanedOriginal [ liquidId ] . push ( {
131+ labwareId : labware . labwareId ,
132+ volumeByWell : remainingWells ,
111133 } )
112134 }
113- )
114- return {
115- ...cleanedOriginal ,
116- ...consolidatedWells ,
117- }
135+ } )
136+ } )
137+
138+ return {
139+ ...cleanedOriginal ,
140+ ...consolidatedWells ,
118141 }
119- return consolidateSharedWells ( initialLabwareByLiquidId )
120142}
0 commit comments