/** * A module of helper functions for the pipetter commands. * @module commands/pipetter/pipetterUtils */ var _ = require('lodash'); var assert = require('assert'); var math = require('mathjs'); import expect from '../../expect.js'; var misc = require('../../misc.js'); var wellsParser = require('../../parsers/wellsParser.js'); import * as WellContents from '../../WellContents.js'; /** * Get fully qualified syringe object name * @param {string|integer} syringeName - name or number of syringe * @param {object} data - The data object passed to command handlers. * @return {string} fully qualified syringe object name, if found */ export function getSyringeName(syringeName, equipmentName, data) { const syringeName2 = `${equipmentName}.syringe.${syringeName}`; if (_.isInteger(syringeName)) { return syringeName2; } else if (_.has(data.objects, syringeName)) { return syringeName; } else if (_.has(data.objects, syringeName2)) { return syringeName2; } return syringeName; } /** * Get an object representing the effects of pipetting, aspirating, or dispensing. * @param {object} params The parameters for the pipetter._aspirate command. * @param {object} data The data object passed to command handlers. * @param {object} effects an optional effects object for effects which have taken place during the command handler and aren't in the data object * @return {object} The effects caused by the `_aspirate`, `_dispense` or `_pipette` command. */ export function getEffects_pipette(parsed, data, effects) { const effects2 = (effects) ? _.cloneDeep(effects) : {}; const effectsNew = {}; function addEffect(name, obj) { effects2[name] = obj; effectsNew[name] = obj; } //console.log("getEffects_aspirate:\n"+JSON.stringify(parsed, null, '\t')); parsed.value.items.forEach((item, index) => { // console.log("item: "+JSON.stringify(item, null, '\t')); // Get initial contents of the syringe const syringe = _.isString(item.syringe) ? _.get(data.objects, item.syringe) : item.syringe; const syringeName = _.isString(item.syringe) ? item.syringe : parsed.objectName[`items.${index}.syringe`]; const syringeContentsName = `${syringeName}.contents`; const syringeContents00 = effects2[syringeContentsName] || syringe.contents || []; const syringeContaminantsName = `${syringeName}.contaminants`; const volume = item.volume; const source = item.source; if (!_.isUndefined(source)) { const syringeContents0 = syringeContents00; const syringeContaminants0 = effects2[syringeContaminantsName] || syringe.contaminants || []; //console.log({syringeName, syringeContents0}); // Get initial contents of the source well const [srcContents00, srcContentsName] = WellContents.getContentsAndName(source, data, effects2); const srcContents0 = (_.isEmpty(srcContents00)) ? ["Infinity l", source] : srcContents00; //console.log("srcContents0", srcContents0, srcContentsName); // Contents of source well and syringe after aspiration const [srcContents1a, syringeContents1a] = WellContents.transferContents(srcContents0, syringeContents0, item.volume); const srcContents1 = WellContents.mergeContents(srcContents1a); const syringeContents1 = WellContents.mergeContents(syringeContents1a); // console.log({srcContents1, syringeContents1a, syringeContents1}); // Update content effect for source addEffect(srcContentsName, srcContents1); // Get list of syringe contaminants const contaminants1 = _.keys(WellContents.flattenContents(syringeContents1)); //console.log({syringeContaminantsName, syringeContaminants0, contaminants1}); // Update contaminant effects if (!_.isEqual(syringeContaminants0, contaminants1)) addEffect(syringeContaminantsName, contaminants1); // Update content effect addEffect(syringeContentsName, syringeContents1); // Remove cleaned property //console.log(`syringe ${syringeName}: `+JSON.stringify(item.syringe)) if (!_.isUndefined(syringe.cleaned)) addEffect(`${syringeName}.cleaned`, null); // Update __WELLS__ effects for source const volume1 = math.eval(srcContents1[0]); const nameWELL = "__WELLS__."+srcContentsName; //console.log("nameWELL:", nameWELL) const well0 = misc.findObjectsValue(nameWELL, data.objects, effects2) || { isSource: true, volumeMin: srcContents0[0], volumeMax: srcContents0[0] }; const well1 = _.merge({}, well0, { volumeMax: math.max(math.eval(well0.volumeMax), volume1).format({precision: 14}), volumeMin: math.min(math.eval(well0.volumeMin), volume1).format({precision: 14}), volumeRemoved: (well0.volumeRemoved) ? math.chain(math.eval(well0.volumeRemoved)).add(volume).done().format({precision: 14}) : volume.format({precision: 14}) }); //console.log({well0, well1}); //console.log("x:\n"+JSON.stringify(x, null, ' ')); addEffect(nameWELL, well1); } const destination = item.destination; if (!_.isUndefined(destination)) { const syringeContents0 = effects2[syringeContentsName] || syringe.contents || []; const syringeContaminants0 = effects2[syringeContaminantsName] || syringe.contaminants || []; //console.log({syringeName, syringeContents0}); // Get initial contents of the destination well const [dstContents0, dstContentsName] = WellContents.getContentsAndName(destination, data, effects2); //console.log("dst contents", dstContents0, dstContentsName); expect.truthy({paramName: `items[${index}].syringe`}, !WellContents.isEmpty(syringeContents0), "syringe contents should not be empty when dispensing"); // Final contents of source well and syringe const [syringeContents1, dstContents1] = WellContents.transferContents(syringeContents0, dstContents0, item.volume); //console.log({syringeContents1, dstContents1}) //console.log({srcContents1, syringeContents1}); // Check for contact with the destination contents by looking for // the word "wet" in the program name. const isWetContact = !_.isEmpty(parsed.value.program) && /(_wet_|\bwet\b|_wet\b)/.test(parsed.value.program.toLowerCase()); if (isWetContact) { // Contaminate the syringe with source contents // FIXME: Contaminate the syringe with destination contents if there is wet contact, i.e., use dstContents1 instead of srcContents0 for flattenContents() const contaminantsB = _.keys(WellContents.flattenContents(dstContents0)); const contaminants1 = _.uniq(syringeContaminants0.concat(contaminantsB)); if (!_.isEqual(syringeContaminants0, contaminants1)) addEffect(syringeContaminantsName, contaminants1); } // Update content effect // If content volume = zero, set to null const syringeContents2 = (WellContents.isEmpty(syringeContents1)) ? null : syringeContents1; if (!_.isEqual(syringeContents0, syringeContents2)) addEffect(syringeContentsName, syringeContents2); // Update content effect for destination addEffect(dstContentsName, dstContents1); // Update __WELLS__ effects for destination // REFACTOR: lots of duplication with the same code in the source condition const volume0 = WellContents.getVolume(dstContents0); const volume1 = WellContents.getVolume(dstContents1); const nameWELL = "__WELLS__."+dstContentsName; //console.log({nameWELL, volume0, volume1}) //console.log("nameWELL:", nameWELL) const well0 = misc.findObjectsValue(nameWELL, data.objects, effects2) || { isSource: false, volumeMin: volume0.format({precision: 14}), volumeMax: volume0.format({precision: 14}) }; //console.log({well0}) const well1 = _.merge(well0, { volumeMax: math.max(math.eval(well0.volumeMax), volume1).format({precision: 14}), volumeMin: math.min(math.eval(well0.volumeMin), volume1).format({precision: 14}), volumeAdded: (well0.volumeAdded) ? math.chain(math.eval(well0.volumeAdded)).add(volume).done().format({precision: 14}) : volume.format({precision: 14}) }); //console.log("x:\n"+JSON.stringify(x, null, ' ')); addEffect(nameWELL, well1); } if (!_.isUndefined(item.well)) { // console.log({item}) const source = item.well; const syringeContents0 = syringeContents00; const syringeContaminants0 = effects2[syringeContaminantsName] || syringe.contaminants || []; //console.log({syringeName, syringeContents0}); // Get initial contents of the source well const [srcContents00, srcContentsName] = WellContents.getContentsAndName(source, data, effects2); const srcContents0 = (_.isEmpty(srcContents00)) ? ["Infinity l", source] : srcContents00; //console.log("srcContents0", srcContents0, srcContentsName); // Contents of source well and syringe after aspiration const [srcContents1, syringeContents1] = WellContents.transferContents(srcContents0, syringeContents0, item.volume || WellContents.emptyVolume); //console.log({srcContents1, syringeContents1}); // Get list of syringe contaminants const contaminants1 = _.keys(WellContents.flattenContents(syringeContents1)); //console.log({syringeContaminantsName, syringeContaminants0, contaminants1}); // Update contaminant effects if (!_.isEqual(syringeContaminants0, contaminants1)) addEffect(syringeContaminantsName, contaminants1); // Remove cleaned property //console.log(`syringe ${syringeName}: `+JSON.stringify(item.syringe)) if (!_.isUndefined(syringe.cleaned)) addEffect(`${syringeName}.cleaned`, null); } // Prevent superfluous 'nulling' of syringe contents //console.log({syringeContentsName, syringeContents00, effects2: effects2[syringeContentsName], effectsNew: effectsNew[syringeContentsName]}) if (effects2[syringeContentsName] === null && _.isEmpty(syringeContents00)) delete effectsNew[syringeContentsName]; }); //console.log("effectsNew:\n"+JSON.stringify(effectsNew)); return effectsNew; }