-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcovid19-test.aq
1 lines (1 loc) · 97.4 KB
/
covid19-test.aq
1
{"config":{"title":"covid19-test","description":"No description given","copyright":"No copyright declared","version":"no version info","authors":[],"maintainer":{"name":"No maintainer","email":"noone@nowehere"},"acknowledgements":[],"github":{"repo":"none","user":"none","access_token":"none"},"keywords":[],"aquadoc_version":"1.0.2"},"components":[{"sample_types":[{"id":3,"name":"qPCR Reaction","description":"qPCR Reaction","created_at":"2020-07-26T17:08:31.000-07:00","updated_at":"2020-07-26T17:08:31.000-07:00","field_types":[]}],"object_types":[{"id":5,"name":"96-well qPCR Plate","description":"96-well qPCR Reaction","min":0,"max":1,"handler":"collection","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:08:31.000-07:00","updated_at":"2020-07-26T17:08:31.000-07:00","unit":"qPCR Reaction","cost":0.01,"release_method":"return","release_description":"","sample_type_id":3,"image":null,"prefix":"","rows":8,"columns":12,"sample_type_name":"qPCR Reaction"}],"operation_type":{"name":"Add No Template Control","category":"Diagnostic RT-qPCR","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"PCR Plate","sample_types":["qPCR Reaction"],"object_types":["96-well qPCR Plate"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"sample","role":"output","name":"PCR Plate","sample_types":["qPCR Reaction"],"object_types":["96-well qPCR Plate"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"json","role":"input","name":"Options","sample_types":[],"object_types":[],"part":false,"array":false,"routing":null,"preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null}],"protocol":"# typed: false\n# frozen_string_literal: true\n\nneeds 'Diagnostic RT-qPCR/DiagnosticRTqPCRHelper'\nneeds 'Microtiter Plates/MicrotiterPlates'\n\n# Protocol for setting up a master mix plate for RT-qPCR\n# @note Instructions adapted from the CDC COVID-19 detection protocol\n# https://www.fda.gov/media/134922/download\n#\n# 12) Prior to moving to the nucleic acid handling area, prepare the\n# No Template Control (NTC) reactions for column #1 in the\n# assay preparation area.\n#\n# 13) Pipette 5 uL of nuclease-free water into the NTC sample wells\n# (Figure 2, column 1). Securely cap NTC wells before proceeding.\n#\n# 14) Cover the entire reaction plate and move the reaction plate to\n# the specimen nucleic acid handling area.\n#\n# @author Devin Strickland \[email protected]\u003e\nclass Protocol\n include DiagnosticRTqPCRHelper\n\n ########## DEFAULT PARAMS ##########\n\n # Default parameters that are applied equally to all operations.\n # Can be overridden by:\n # * Associating a JSON-formatted list of key, value pairs to the `Plan`.\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_job_params\n {}\n end\n\n # Default parameters that are applied to individual operations.\n # Can be overridden by:\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_operation_params\n {\n program_name: 'CDC_TaqPath_CG',\n group_size: 3,\n layout_method: 'cdc_sample_layout'\n }\n end\n\n ########## MAIN ##########\n\n def main\n setup_test_plates(operations: operations, method: :master_mix) if debug\n\n @job_params = update_all_params(\n operations: operations,\n default_job_params: default_job_params,\n default_operation_params: default_operation_params\n )\n return {} if operations.errored.any?\n\n prepare_materials(operations: operations)\n\n operations.each do |op|\n op.pass(PLATE)\n add_no_template_controls(operation: op)\n end\n\n operations.store\n\n {}\n end\n\n # Prepare workspace and materials\n #\n # @todo Make this handle master mix or enzyme with separate\n # buffer dynamically\n # @param operations [OperationList]\n # @return [void]\n def prepare_materials(operations:)\n show_prepare_workspace\n build_ntc_compositions(operations: operations)\n retrieve_by_compositions(operations: operations)\n end\n\n # Add the no template controls to an Operation's putput collection\n #\n # @param operation [Operation]\n # @return [void]\n def add_no_template_controls(operation:)\n # Group_Size and Program name are attributes of the plate\n # and should be associated to the plate from Prepare Master Mix\n # This may not work because group size is different depending on whether\n # talking about samples or primers\n # group_size = op.input(PLATE).collection.get(GROUP_SIZE_KEY)\n # program_name = op.input(PLATE).collection.get(COMPOSITION_NAME_KEY)\n\n collection = operation.output(PLATE).collection\n composition = operation.temporary[:compositions].first\n\n microtiter_plate = MicrotiterPlateFactory.build(\n collection: collection,\n group_size: operation.temporary[:options][:group_size],\n method: operation.temporary[:options][:layout_method]\n )\n\n layout_group = microtiter_plate.next_empty_group(key: TEMPLATE_KEY)\n\n show_add_ntc(\n collection: collection,\n volume: composition.water.qty_display,\n layout_group: layout_group\n )\n\n composition.template.added = true\n\n microtiter_plate.associate_provenance_group(\n group: layout_group,\n key: TEMPLATE_KEY,\n data: added_component_data(composition: composition)\n )\n\n show_result(collection: collection) if debug\n inspect_data_associations(collection: collection) if debug\n end\n\n # Instruct technician to add the no template control samples to the plate\n #\n # @param collection [Collection]\n # @param volume [Fixnum]\n # @param layout_group [Array\u003cArray\u003cFixnum\u003e\u003e]\n # @return [void]\n def show_add_ntc(collection:, volume:, layout_group:)\n show do\n title \"Pipet No Template Control (NTC) samples into plate #{collection}\"\n\n note \"Pipet #{volume} of #{WATER} into the indicated wells of\" \\\n \" plate #{collection}\"\n table highlight_collection_rc(collection, layout_group, check: true)\n end\n end\nend","precondition":"def precondition(_op)\n true\nend","cost_model":"def cost(_op)\n { labor: 0, materials: 0 }\nend","documentation":"Adds no-template control (NTC) samples to the input collection,\ntypically a 96-well plate. For example, according to the CDC protocol, pipettes \n5 uL of nuclease-free water into the NTC sample wells (the first column of sample wells).\n\n#### Job Options\nNone\n\n#### Operation Options\n`program_name [String]` The protocol to be followed, based on the qPCR\nmaster mix used. Defaults to CDC_TaqPath_CG. For other supported protocols, see\n[this document](https://github.com/aquariumbio/pcr-models/blob/master/pcr_libs/libraries/pcrcompositiondefinitions/source.rb).\n\n`group_size [Int]` The number of NTC samples to add for each primer group.\nDefaults to 3.\n\n`layout_method [String]` The method to use in laying out the plate. Defaults to `cdc_sample_layout`.\n","test":"# frozen_string_literal: true\n\nclass ProtocolTest \u003c ProtocolTestBase\n def setup\n ops = add_random_operations(1)\n end\n\n def analyze\n log('Hello from Nemo')\n assert_equal(@backtrace.last[:operation], 'complete')\n end\nend","timing":null}},{"sample_types":[{"id":4,"name":"RNA","description":"A sample of RNA","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","field_types":[]}],"object_types":[{"id":6,"name":"Lyophilized RNA","description":"Lyophilized RNA","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","unit":"RNA","cost":0.01,"release_method":"return","release_description":"","sample_type_id":4,"image":null,"prefix":"","rows":null,"columns":null,"sample_type_name":"RNA"},{"id":7,"name":"Purified RNA in 1.5 mL tube","description":"Purified RNA in 1.5 mL tube","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","unit":"RNA","cost":0.01,"release_method":"return","release_description":"","sample_type_id":4,"image":null,"prefix":"M80","rows":null,"columns":null,"sample_type_name":"RNA"}],"operation_type":{"name":"Aliquot Positive Template","category":"Diagnostic RT-qPCR","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"Template","sample_types":["RNA"],"object_types":["Lyophilized RNA"],"part":false,"array":false,"routing":"T","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"sample","role":"output","name":"Template","sample_types":["RNA"],"object_types":["Purified RNA in 1.5 mL tube"],"part":false,"array":false,"routing":"T","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null}],"protocol":"# Aliquot Positive Template\n# Written By Rita Chen 2020-05-04\n# Updated by Dany Fu 2020-06-01\n\nneeds 'Diagnostic RT-qPCR/DiagnosticRTqPCRHelper'\n\n# 2019-nCoV Positive Control (nCoVPC) Preparation:\n# 1) Precautions: This reagent should be handled with caution in a dedicated\n# nucleic acid handling area to prevent possible contamination. Freeze-thaw\n# cycles should be avoided. Maintain on ice when thawed.\n# 2) Resuspend dried reagent in each tube in 1 mL of nuclease-free water to\n# achieve the proper concentration. Make single use aliquots (approximately 30\n# uL) and store at less than and equal to -70C.\n# 3) Thaw a single aliquot of diluted positive control for each experiment and\n# hold on ice until adding to plate. Discard any unused portion of the aliquot.\nclass Protocol\n include DiagnosticRTqPCRHelper\n\n VOL_WATER = { qty: 1, units: MILLILITERS }.freeze\n VOL_SUSPENSION = { qty: 30, units: MICROLITERS }.freeze\n OUTPUT_ITEMS_NUM = { qty: 33, units: TUBE_MICROFUGE }.freeze\n\n ########## DEFAULT PARAMS ##########\n\n # Default parameters that are applied equally to all operations.\n # Can be overridden by:\n # * Associating a JSON-formatted list of key, value pairs to the `Plan`.\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_job_params\n {}\n end\n\n # Default parameters that are applied to individual operations.\n # Can be overridden by:\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_operation_params\n {}\n end\n\n ########## MAIN ##########\n\n def main\n @job_params = update_all_params(\n operations: operations,\n default_job_params: default_job_params,\n default_operation_params: default_operation_params\n )\n return {} if operations.errored.any?\n\n update_operation_params(\n operations: operations,\n default_operation_params: default_operation_params\n )\n\n # 1. Get 33 1.5 mL tube for each operation\n get_tubes(count: operations.length)\n operations.retrieve\n\n # 2. For each Lyophilized Postive Control, resuspend in 1 mL of\n # nuclease-free waterand, aliquot 33 use aliquots (approximately\n # 30 uL) and store at less than and equal to -70C.\n save_output(ops: operations)\n\n suspend_lyophilized_RNA\n keep_tubes = [] # Empty array for storing single use aliquots\n\n # Group the operations by the input reagent\n ops_by_input = operations.group_by { |op| op.input(TEMPLATE).item }\n ops_by_input.each do |lyophilized_rna, ops|\n keep_tubes.push(make_aliquots(ops: ops, lyophilized_rna: lyophilized_rna))\n end\n\n prepare_plating(keep_tubes: keep_tubes)\n\n # 3. Thaw a single aliquot of diluted positive control for each\n # experiment and hold on ice until adding to plate.\n # Discard any unused portion of the aliquot.\n operations.store(interactive: true, io: 'output', method: 'boxes')\n end\n\n # Get 33 1.5 mL tubes per dried positive control\n #\n # @param count [Integer] the number of operations\n def get_tubes(count:)\n show do\n title \"Get new #{TUBE_MICROFUGE}\"\n check \"Please get #{count * OUTPUT_ITEMS_NUM[:qty]} #{TUBE_MICROFUGE}\"\n end\n end\n\n # Label the tubes so that the same reagents have consecutive IDs\n # And move the output tubes to the right storage locations\n # @param operations [OperationList] The list of operations\n def save_output(ops:)\n ops.make\n\n # Declare references to output objects\n ops.each do |op|\n op.output(TEMPLATE).item.associate :volume, VOL_SUSPENSION[:qty]\n\n output_RNA = op.output(TEMPLATE).sample\n # makes 32 additional aliquots per op\n (OUTPUT_ITEMS_NUM[:qty] - 1).times do\n new_aliquot = output_RNA.make_item('Purified RNA in 1.5 mL tube')\n new_aliquot.associate :volume, VOL_SUSPENSION[:qty]\n link_output_item(operation: op, sample: output_RNA, item: new_aliquot)\n end\n end\n end\n\n # Manually link the item to the operation as an output\n #\n # @param op [Operation] the operation that creates the items\n # @param sample [Sample] the sample of the item\n # @param item [Item] the item that is created\n def link_output_item(operation:, sample:, item:)\n fv = FieldValue.new(\n name: TEMPLATE,\n child_item_id: item.id,\n child_sample_id: sample.id,\n role: 'output',\n parent_class: 'Operation',\n parent_id: operation.id,\n field_type_id: operation.output(TEMPLATE).field_type.id\n )\n fv.save\n end\n\n # Performs the resuspension protocol for a list of operations\n # that all use the given lyophilized_RNA input.\n def suspend_lyophilized_RNA()\n show do\n title 'Resuspend Positive Template'\n warning \"This reagent should be handled with caution in a dedicated \\\n nucleic acid handling area to prevent possible contamination.\"\n warning \"Freeze-thaw cycles should be avoided. Maintain on ice when \\\n thawed.\"\n check \"Resuspend dried Lyophilized Postive Control RNA in each tube in \\\n #{qty_display(VOL_WATER)} of nuclease-free water to achieve the \\\n proper concentration.\"\n end\n end\n\n # Performs the aliquote protocol for a list of operations\n # that all use the given lyophilized_RNA input.\n # @param make_aliquots [Item] the lyophilized_RNA\n # @param operations [OperationList] the list of operations\n def make_aliquots(ops:, lyophilized_rna:)\n last_tube = nil\n\n ops.each do |op|\n input_rnas = Array.new(OUTPUT_ITEMS_NUM[:qty], lyophilized_rna)\n aliquot_items = op.outputs.map(\u0026:item)\n table = Table.new\n .add_column('Lyophilized RNA', input_rnas.map(\u0026:to_s))\n .add_column('Output RNA Aliquot', aliquot_items.map(\u0026:to_s))\n\n show do\n title 'Aliquot Single Used Aliquot Positive Template'\n check \"Make single use aliquot by transfering \\\n #{qty_display(VOL_SUSPENSION)} of the diluted postive control into \\\n individual #{TUBE_MICROFUGE} and label it with the proper item ID.\"\n check 'Discard the empty input tube'\n table table\n end\n\n add_aliquot_provenance(\n stock_item: lyophilized_rna,\n aliquot_items: aliquot_items\n )\n\n lyophilized_rna.mark_as_deleted\n\n if debug\n aliquot = aliquot_items.last\n inspect(lyophilized_rna.associations, lyophilized_rna.id)\n inspect(aliquot.associations, aliquot.id)\n end\n\n # Retrieve the last item of the single use aliquot\n last_tube = aliquot_items.last\n end\n last_tube.id\n end\n\n # Prepare for plating protocol for a list of operations\n # that all use the given Purified RNA in 1.5 mL tube output.\n # @param keep_tubes [Item] Items to be kept on bench for immediate use\n def prepare_plating(keep_tubes:)\n show do\n title 'Preparation of Single Aliquot for Plating'\n warning \"This reagent should be handled with caution in a dedicated\\\n nucleic acid handling area to prevent possible contamination.\"\n warning 'Avoid freeze-thaw cycles. Maintain on ice when thawed.'\n # Retrieve the last item of the single use aliquot\n check \"Thaw a single aliquot of diluted positive control #{keep_tubes} \\\n for each experiment and hold on ice until adding to plate.\"\n check 'Discard any unused portion of the aliquot.'\n end\n\n # Don't store the single aliquot being used during plating\n keep_tubes.each do |id| Item.find(id).mark_as_deleted end\n end\nend\n","precondition":"def precondition(_op)\n true\nend","cost_model":"def cost(_op)\n { labor: 0, materials: 0 }\nend","documentation":"Documentation here. Start with a paragraph, not a heading or title, as in most views, the title will be supplied by the view.","test":"# frozen_string_literal: true\n\nclass ProtocolTest \u003c ProtocolTestBase\n def setup\n add_random_operations(1)\n end\n\n def analyze\n log('Hello from Nemo')\n assert_equal(@backtrace.last[:operation], 'complete')\n end\nend","timing":null}},{"sample_types":[{"id":5,"name":"Primer Mix","description":"Primer Mix","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","field_types":[]}],"object_types":[{"id":3,"name":"Lyophilized Primer Mix","description":"Lyophilized Primer Mix","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:06:20.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","unit":"Primer Mix","cost":0.01,"release_method":"return","release_description":"","sample_type_id":5,"image":null,"prefix":"","rows":null,"columns":null,"sample_type_name":"Primer Mix"},{"id":4,"name":"Primer Mix Aliquot","description":"Primer Mix Aliquot","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:06:20.000-07:00","updated_at":"2020-07-27T16:46:44.000-07:00","unit":"Primer Mix","cost":0.01,"release_method":"return","release_description":"","sample_type_id":1,"image":null,"prefix":"M20","rows":null,"columns":null,"sample_type_name":"Primer/Probe Mix"}],"operation_type":{"name":"Aliquot Primer/Probe","category":"Diagnostic RT-qPCR","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"Primer Set","sample_types":["Primer Mix"],"object_types":["Lyophilized Primer Mix"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"sample","role":"output","name":"Primer Set","sample_types":["Primer Mix"],"object_types":["Primer Mix Aliquot"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null}],"protocol":"# Aliquot Primer Probe\n# Written By Dany Fu 2020-05-05\n\nneeds 'Diagnostic RT-qPCR/DiagnosticRTqPCRHelper'\n\n# 1) Upon receipt, store dried primers and probes at 2-8C.\n# 2) Precautions: These reagents should only be handled in a clean area and\n# stored at appropriate temperatures (see below) in the dark. Freeze-thaw cycles\n# should be avoided. Maintain cold when thawed.\n# 3) Using aseptic technique, suspend dried reagents in 1.5 mL of nuclease-free\n# water (50X working concentration) and allow to rehydrate for 15 min at room\n# temperature in the dark.\n# 4) Mix gently and aliquot primers/probe in 300 uL volumes into 5 pre-labeled\n# tubes. Store a single aliquot of primers/probe at 2-8oC in the dark. Do not\n# refreeze (stable for up to 4 months). Store remaining aliquots at \u003c= -20oC\n# in a non-frost-free freezer.\nclass Protocol\n include DiagnosticRTqPCRHelper\n\n OUTPUT_ITEMS_NUM = { qty: 5, units: TUBE_MICROFUGE }.freeze\n TIME_REHYDRATE = { qty: 15, units: MINUTES }.freeze\n VOL_WATER = { qty: 1.5, units: MILLILITERS }.freeze\n VOL_SUSPENSION = { qty: 300, units: MICROLITERS }.freeze\n\n COLD_ROOM = 'M4'.freeze\n\n ########## DEFAULT PARAMS ##########\n\n # Default parameters that are applied equally to all operations.\n # Can be overridden by:\n # * Associating a JSON-formatted list of key, value pairs to the `Plan`.\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_job_params\n {}\n end\n\n # Default parameters that are applied to individual operations.\n # Can be overridden by:\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_operation_params\n {}\n end\n\n ########## MAIN ##########\n\n def main\n @job_params = update_all_params(\n operations: operations,\n default_job_params: default_job_params,\n default_operation_params: default_operation_params\n )\n return {} if operations.errored.any?\n\n update_operation_params(\n operations: operations,\n default_operation_params: default_operation_params\n )\n\n get_tubes(count: operations.length)\n operations.retrieve\n\n save_output(operations)\n suspend_primer_mix\n\n # Group the operations by the input reagent\n ops_by_input = operations.group_by { |op| op.input(PRIMER_MIX).item }\n ops_by_input.each do |primer, ops|\n make_aliquots(ops: ops, primer: primer)\n end\n\n operations.store(interactive: true, io: 'output', method: 'boxes')\n end\n\n # Get 5 1.5 mL tubes per dried reagent\n # @param count [Integer] the number of operations currently running\n def get_tubes(count:)\n show do\n title \"Get new #{TUBE_MICROFUGE}\"\n check \"Please get #{count * OUTPUT_ITEMS_NUM[:qty]} #{TUBE_MICROFUGE}\"\n end\n end\n\n # Create and save multiple output Items per operation\n # @param operations [OperationList] List of operations\n def save_output(operations)\n operations.make\n\n operations.each do |op|\n op.output(PRIMER_MIX).item.associate :volume, VOL_SUSPENSION[:qty]\n\n output_primer = op.output(PRIMER_MIX).sample\n # makes 4 additional aliquots per op\n (OUTPUT_ITEMS_NUM[:qty] - 1).times do\n new_aliquot = output_primer.make_item('Primer Mix Aliquot')\n new_aliquot.associate :volume, VOL_SUSPENSION[:qty]\n link_output_item(operation: op, sample: output_primer, item: new_aliquot)\n end\n end\n end\n\n # Manually link the item to the operation as an output\n # @param op [Operation] the operation that creates the items\n # @param sample [Sample] the sample of the item\n # @param item [Item] the item that is created\n def link_output_item(operation:, sample:, item:)\n fv = FieldValue.new(\n name: PRIMER_MIX,\n child_item_id: item.id,\n child_sample_id: sample.id,\n role: 'output',\n parent_class: 'Operation',\n parent_id: operation.id,\n field_type_id: operation.output(PRIMER_MIX).field_type.id\n )\n fv.save\n end\n\n # Instructions for suspending dried reagents\n def suspend_primer_mix\n show do\n title 'Suspend Primer Mix'\n warning 'These reagents should only be handled in a clean area.'\n warning 'Avoid reeze-thaw cycles. Maintain on ice when thawed.'\n check \"Using aseptic technique, suspend each dried primer in \\\n #{qty_display(VOL_WATER)} of nuclease-free water.\"\n check \"Rehydrate for #{qty_display(TIME_REHYDRATE)} at room temperature \\\n in the dark.\"\n timer initial: { minutes: TIME_REHYDRATE[:qty] }\n end\n end\n\n # Make 5 aliquots for each primer\n # @param operations [OperationList] Array of operations grouped by primer\n def make_aliquots(ops:, primer:)\n # last_tube_id = '' # keep one of each primer\n ops.each do |op|\n input_primers = Array.new(OUTPUT_ITEMS_NUM[:qty], primer)\n aliquot_items = op.outputs.map(\u0026:item)\n table = Table.new\n .add_column('Primer Mix ID', input_primers.map(\u0026:to_s))\n .add_column('Destination Tube ID', aliquot_items.map(\u0026:to_s))\n\n show do\n title 'Make Aliquots'\n check 'Label destination tube IDs according to the table.'\n check \"Mix solution gently and aliquot #{qty_display(VOL_SUSPENSION)} \\\n of rehydrated primer into each tube according to the table.\"\n table table\n end\n\n add_aliquot_provenance(stock_item: primer, aliquot_items: aliquot_items)\n\n primer.mark_as_deleted\n\n if debug\n aliquot = aliquot_items.last\n inspect(primer.associations, primer.id)\n inspect(aliquot.associations, aliquot.id)\n end\n end\n\n # One aliquot of each primer mix should be stored in the cold room instead\n # of freezer. However, this code doesn't actually work as one ObjectType\n # can only be associated with one location.\n # Leaving this here for completeness.\n # last_tube = Item.find(last_tube_id)\n # last_tube.move(COLD_ROOM)\n # last_tube.store\n end\nend\n","precondition":"def precondition(_op)\n true\nend","cost_model":"def cost(_op)\n { labor: 0, materials: 0 }\nend","documentation":"Documentation here. Start with a paragraph, not a heading or title, as in most views, the title will be supplied by the view.","test":"# frozen_string_literal: true\n\nclass ProtocolTest \u003c ProtocolTestBase\n def setup\n add_random_operations(1)\n end\n\n def analyze\n log('Hello from Nemo')\n assert_equal(@backtrace.last[:operation], 'complete')\n end\nend","timing":null}},{"sample_types":[{"id":6,"name":"Respiratory Specimen","description":"Incoming sample for testing","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","field_types":[{"id":13,"parent_id":6,"name":"Tracking ID","ftype":"string","choices":null,"array":false,"required":true,"created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","parent_class":"SampleType","role":null,"part":null,"routing":null,"preferred_operation_type_id":null,"preferred_field_type_id":null,"allowable_field_types":[],"sample_types":[],"object_types":[]}]},{"id":4,"name":"RNA","description":"A sample of RNA","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","field_types":[]}],"object_types":[{"id":8,"name":"Nasopharyngeal Swab","description":"Nasopharyngeal Swab","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","unit":"each","cost":0.01,"release_method":"return","release_description":"","sample_type_id":6,"image":null,"prefix":"M80","rows":null,"columns":null,"sample_type_name":"Respiratory Specimen"},{"id":7,"name":"Purified RNA in 1.5 mL tube","description":"Purified RNA in 1.5 mL tube","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","unit":"RNA","cost":0.01,"release_method":"return","release_description":"","sample_type_id":4,"image":null,"prefix":"M80","rows":null,"columns":null,"sample_type_name":"RNA"}],"operation_type":{"name":"Extract RNA","category":"Diagnostic RT-qPCR","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"Specimen","sample_types":["Respiratory Specimen"],"object_types":["Nasopharyngeal Swab"],"part":false,"array":false,"routing":"S","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"sample","role":"output","name":"Specimen","sample_types":["RNA"],"object_types":["Purified RNA in 1.5 mL tube"],"part":false,"array":false,"routing":"T","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"string","role":"input","name":"Method","sample_types":[],"object_types":[],"part":false,"array":false,"routing":null,"preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":"QIAamp DSP Viral RNA Mini Kit,Alternative Method"},{"ftype":"json","role":"input","name":"Options","sample_types":[],"object_types":[],"part":false,"array":false,"routing":null,"preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null}],"protocol":"# frozen_string_literal: true\n\nneeds 'RNA Extraction Kits/RNAExtractionHelper'\nneeds 'Diagnostic RT-qPCR/DiagnosticRTqPCRHelper'\n\n# Extract RNA Protocol\n#\n# @author Devin Strickland \[email protected]\u003e\nclass Protocol\n include RNAExtractionHelper\n include DiagnosticRTqPCRHelper\n\n ########## DEFAULT PARAMS ##########\n\n # Default parameters that are applied equally to all operations.\n # Can be overridden by:\n # * Associating a JSON-formatted list of key, value pairs to the `Plan`.\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_job_params\n {\n rna_extraction_kit: QiagenRNeasyMiniKit::NAME\n }\n end\n\n # Default parameters that are applied to individual operations.\n # Can be overridden by:\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_operation_params\n {\n sample_volume: { qty: 300, units: MICROLITERS }\n }\n end\n\n ########## MAIN ##########\n\n def main\n setup_test_options(operations: operations) if debug\n @job_params = update_all_params(\n operations: operations,\n default_job_params: default_job_params,\n default_operation_params: default_operation_params\n )\n return {} if operations.errored.any?\n\n operations.retrieve.make\n\n set_kit(name: @job_params[:rna_extraction_kit])\n\n sample_volumes = operations.map { |op| sample_volume(op) }\n if sample_volumes.uniq.length == 1\n run_rna_extraction_kit(\n operations: operations,\n sample_volume: sample_volumes.first\n )\n else\n run_rna_extraction_kit(\n operations: operations,\n use_operations: true\n )\n end\n\n add_specimen_provenance(operations: operations)\n\n operations.store\n\n {}\n end\n\n # Add provenance to SPECIMEN inputs and outputs of operations\n #\n # @param operations [OperationList]\n # @return [void]\n def add_specimen_provenance(operations:)\n operations.each do |op|\n add_one_to_one_provenance(\n from_item: op.input(SPECIMEN).item,\n to_item: op.output(SPECIMEN).item\n )\n end\n return unless debug\n\n inspect(operations.last.input(SPECIMEN).item.associations, 'input')\n inspect(operations.last.output(SPECIMEN).item.associations, 'output')\n end\nend\n","precondition":"def precondition(_op)\n true\nend","cost_model":"def cost(_op)\n { labor: 0, materials: 0 }\nend","documentation":"Documentation here. Start with a paragraph, not a heading or title, as in most views, the title will be supplied by the view.","test":"# frozen_string_literal: true\n\nclass ProtocolTest \u003c ProtocolTestBase\n def setup\n add_random_operations(1)\n end\n\n def analyze\n log('Hello from Nemo')\n assert_equal(@backtrace.last[:operation], 'complete')\n end\nend\n","timing":null}},{"sample_types":[],"object_types":[],"operation_type":{"name":"Extract RNA HTP","category":"Diagnostic RT-qPCR","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"Specimen","sample_types":[],"object_types":[],"part":false,"array":false,"routing":"S","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"sample","role":"output","name":"Specimen","sample_types":[],"object_types":[],"part":false,"array":false,"routing":"T","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"json","role":"input","name":"Options","sample_types":[],"object_types":[],"part":false,"array":false,"routing":null,"preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null}],"protocol":"# frozen_string_literal: true\n\nneeds 'Diagnostic RT-qPCR/RNAExtractionHelper'\n\n# Extract RNA Protocol\n#\n# @author Devin Strickland \[email protected]\u003e\nclass Protocol\n include RNAExtractionHelper\n\n ########## DEFAULT PARAMS ##########\n\n # Default parameters that are applied equally to all operations.\n # Can be overridden by:\n # * Associating a JSON-formatted list of key, value pairs to the `Plan`.\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_job_params\n {\n rna_extraction_kit: TestRNAExtractionKit::NAME\n }\n end\n\n # Default parameters that are applied to individual operations.\n # Can be overridden by:\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_operation_params\n {\n sample_volume: { qty: 300, units: MICROLITERS }\n }\n end\n\n ########## MAIN ##########\n\n def main\n setup_test_options(operations: operations) if debug\n @job_params = update_all_params(\n operations: operations,\n default_job_params: default_job_params,\n default_operation_params: default_operation_params\n )\n return {} if operations.errored.any?\n\n operations.retrieve.make\n\n set_kit(name: @job_params[:rna_extraction_kit])\n\n sample_volumes = operations.map { |op| sample_volume(op) }\n if sample_volumes.uniq.length == 1\n run_rna_extraction_kit(\n operations: operations,\n sample_volume: sample_volumes.first\n )\n else\n run_rna_extraction_kit(\n operations: operations,\n use_operations: true\n )\n end\n\n operations.store\n\n {}\n end\nend\n","precondition":"def precondition(_op)\n true\nend","cost_model":"def cost(_op)\n { labor: 0, materials: 0 }\nend","documentation":"Documentation here. Start with a paragraph, not a heading or title, as in most views, the title will be supplied by the view.","test":"# test","timing":null}},{"sample_types":[{"id":3,"name":"qPCR Reaction","description":"qPCR Reaction","created_at":"2020-07-26T17:08:31.000-07:00","updated_at":"2020-07-26T17:08:31.000-07:00","field_types":[]},{"id":1,"name":"Primer/Probe Mix","description":"Primer/Probe Mix for qPCR","created_at":"2020-07-26T17:06:20.000-07:00","updated_at":"2020-07-26T17:06:20.000-07:00","field_types":[]}],"object_types":[{"id":5,"name":"96-well qPCR Plate","description":"96-well qPCR Reaction","min":0,"max":1,"handler":"collection","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:08:31.000-07:00","updated_at":"2020-07-26T17:08:31.000-07:00","unit":"qPCR Reaction","cost":0.01,"release_method":"return","release_description":"","sample_type_id":3,"image":null,"prefix":"","rows":8,"columns":12,"sample_type_name":"qPCR Reaction"},{"id":4,"name":"Primer Mix Aliquot","description":"Primer Mix Aliquot","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:06:20.000-07:00","updated_at":"2020-07-27T16:46:44.000-07:00","unit":"Primer Mix","cost":0.01,"release_method":"return","release_description":"","sample_type_id":1,"image":null,"prefix":"M20","rows":null,"columns":null,"sample_type_name":"Primer/Probe Mix"}],"operation_type":{"name":"Prepare Master Mix","category":"Diagnostic RT-qPCR","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"output","name":"PCR Plate","sample_types":["qPCR Reaction"],"object_types":["96-well qPCR Plate"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"sample","role":"input","name":"Primer/Probe Mix","sample_types":["Primer/Probe Mix"],"object_types":["Primer Mix Aliquot"],"part":false,"array":true,"routing":"M","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"json","role":"input","name":"Options","sample_types":[],"object_types":[],"part":false,"array":false,"routing":null,"preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null}],"protocol":"# typed: false\n# frozen_string_literal: true\n\nneeds 'Diagnostic RT-qPCR/DiagnosticRTqPCRHelper'\nneeds 'Microtiter Plates/MicrotiterPlates'\n\n# Protocol for setting up a master mix plate for RT-qPCR\n# @note Instructions adapted from the CDC COVID-19 detection protocol\n# https://www.fda.gov/media/134922/download\n#\n# 1) In the reagent set-up room clean hood, place rRT-PCR buffer, enzyme,\n# and primer/probes on ice or cold-block. Keep cold during preparation\n# and use.\n#\n# 2) Mix buffer, enzyme, and primer/probes by inversion 5 times.\n#\n# 3) Centrifuge reagents and primers/probes for 5 seconds to collect\n# contents at the bottom of the tube, and then place the tube in\n# a cold rack.\n#\n# 4) Label one 1.5 mL microcentrifuge tube for each primer/probe set.\n#\n# 5) Determine the number of reactions (N) to set up per assay.\n# It is necessary to make excess reaction mix for the NTC, nCoVPC,\n# HSC (if included in the RT-PCR run), and RP reactions and for pipetting\n# error. Use the following guide to determine N:\n# - If number of samples (n) including controls equals 1 through 14,\n# then N = n + 1\n# - If number of samples (n) including controls is 15 or greater,\n# then N = n + 2\n#\n# 7) For each primer/probe set, calculate the amount of each reagent\n# to be added for each reaction mixture (N = # of reactions).\n#\n# 8) Dispense reagents into each respective labeled 1.5 mL microcentrifuge\n# tube. After addition of the reagents, mix reaction mixtures by\n# pipetting up and down. Do not vortex.\n#\n# 9) Centrifuge for 5 seconds to collect contents at the bottom of\n# the tube, and then place the tube in a cold rack.\n#\n# 10) Set up reaction strip tubes or plates in a 96-well cooler rack.\n#\n# 11) Dispense 15 uL of each master mix into the appropriate wells going\n# across the row\n#\n# @author Devin Strickland \[email protected]\u003e\nclass Protocol\n include DiagnosticRTqPCRHelper\n\n ########## DEFAULT PARAMS ##########\n\n # Default parameters that are applied equally to all operations.\n # Can be overridden by:\n # * Associating a JSON-formatted list of key, value pairs to the `Plan`.\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_job_params\n {}\n end\n\n # Default parameters that are applied to individual operations.\n # Can be overridden by:\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_operation_params\n {\n group_size: 24,\n program_name: 'CDC_TaqPath_CG',\n layout_method: 'cdc_primer_layout'\n }\n end\n\n ########## MAIN ##########\n\n def main\n @job_params = update_all_params(\n operations: operations,\n default_job_params: default_job_params,\n default_operation_params: default_operation_params\n )\n return {} if operations.errored.any?\n\n provision_plates(\n operations: operations,\n object_type: PLATE_OBJECT_TYPE\n )\n\n prepare_materials(operations: operations)\n\n assemble_master_mix_plates(operations: operations)\n\n operations.store\n\n inspect_data_associations(operation: operations.first) if debug\n\n {}\n end\n\n # Creates and assigns an output collection for each operation, and fills it\n # with the output sample according to the provided PlateLayoutGenerator\n # method\n # @note In debug mode, displays the matrix of each collection\n #\n # @param operations [OperationList]\n # @param object_type [String] the ObjectType of the collection to be made\n # @return [void]\n def provision_plates(operations:, object_type:)\n operations.each do |op|\n collection = make_new_plate(object_type, label_plate: true)\n op.output(PLATE).set(collection: collection)\n\n set_parts(\n collection: collection,\n group_size: op.temporary[:options][:group_size],\n method: op.temporary[:options][:layout_method],\n sample: op.output(PLATE).sample\n )\n\n inspect op.output(PLATE).collection.matrix if debug\n end\n end\n\n # Fills a collection with the provided sample according to the provided\n # PlateLayoutGenerator method\n #\n # @param collection [Collection]\n # @param group_size [Fixnum]\n # @param method [String] a PlateLayoutGenerator method\n # @param sample [Sample] the Sample to add to the collection\n # @return [void]\n def set_parts(collection:, group_size:, method:, sample:)\n layout_generator = PlateLayoutGeneratorFactory.build(\n group_size: group_size,\n method: method\n )\n\n loop do\n index = layout_generator.next\n break unless index.present?\n\n collection.set(index[0], index[1], sample)\n end\n end\n\n # Prepare workspace and materials\n #\n # @todo Make this handle master mix or enzyme with separate\n # buffer dynamically\n # @param operations [OperationList]\n # @return [void]\n def prepare_materials(operations:)\n show_prepare_workspace\n build_master_mix_compositions(operations: operations)\n retrieve_by_compositions(operations: operations)\n show_mix_and_spin_reagents\n end\n\n # Assembles a master mix plate for each operation\n #\n # @param operations [OperationList]\n # @return [void]\n def assemble_master_mix_plates(operations:)\n operations.each { |op| assemble_master_mix_plate(operation: op) }\n end\n\n # Assembles a master mix plate for an operation\n # @todo this isn't very efficient for multiple operations that all make the\n # same plate\n # @todo refactor to take collection and compositions as arguments, not\n # operation\n #\n # @param operation [Operation]\n # @return [void]\n def assemble_master_mix_plate(operation:)\n compositions = operation.temporary[:compositions]\n pp_labels = compositions.map { |c| c.primer_probe_mix.sample.name }\n show_label_mmix_tubes(labels: pp_labels)\n\n group_size = operation.temporary[:options][:group_size]\n program_name = operation.temporary[:options][:program_name]\n\n output_collection = operation.output(PLATE).collection\n output_collection.associate(GROUP_SIZE_KEY, group_size)\n output_collection.associate(COMPOSITION_NAME_KEY, program_name)\n\n make_master_mixes(\n compositions: compositions,\n sample_number: sample_number_with_excess(sample_number: group_size)\n )\n\n microtiter_plate = MicrotiterPlateFactory.build(\n collection: output_collection,\n group_size: group_size,\n method: :cdc_primer_layout\n )\n\n pipet_master_mixes(\n compositions: compositions,\n microtiter_plate: microtiter_plate\n )\n end\n\n # Makes large volume master mixes to be distributed among wells\n # @todo Make this address each composition in full\n #\n # @param compositions [Array\u003cPCRComposition\u003e]\n # @param sample_number [Fixnum] the number samples for each primer\n # @return [void]\n def make_master_mixes(compositions:, sample_number:)\n show_make_master_mixes(\n compositions: compositions,\n sample_number: sample_number\n )\n end\n\n # Distribute each master mix among wells on the plate\n #\n # @param compositions [Array\u003cPCRComposition\u003e]\n # @param microtiter_plate [MicrotiterPlate]\n # @return [void]\n def pipet_master_mixes(compositions:, microtiter_plate:)\n compositions.each do |composition|\n pipet_master_mix(\n composition: composition,\n microtiter_plate: microtiter_plate\n )\n end\n end\n\n # Pipet the master mixes to individual wells\n #\n # @param composition [PCRComposition]\n # @param microtiter_plate [MicrotiterPlate]\n # @return [void]\n def pipet_master_mix(composition:, microtiter_plate:)\n data = added_component_data(composition: composition)\n layout_group = microtiter_plate.associate_provenance_next_empty_group(\n key: MASTER_MIX_KEY,\n data: data\n )\n\n show_pipet_mmix(\n primer_mix_name: composition.primer_probe_mix.sample.name,\n volume: composition.sum_added_components(0),\n collection: microtiter_plate.collection,\n layout_group: layout_group\n )\n end\n\n ########## DATA METHODS ##########\n\n # Compute the number of 'extra' master mixes to make to account for\n # pipetting error\n #\n # @param sample_number [Fixnum]\n # @return [Fixnum]\n def sample_number_with_excess(sample_number:)\n sample_number \u003c 15 ? sample_number + 1 : sample_number + 2\n end\n\n ########## SHOW METHODS ##########\n\n # Instruct technician to mix the incoming reagents and spin down the tubes\n #\n # @return [void]\n def show_mix_and_spin_reagents\n show do\n title 'Mix and spin down reagents'\n\n note 'Mix buffer, enzyme, and primer/probes by inversion 5 times.'\n note 'Centrifuge reagents and primers/probes for 5 seconds to collect' \\\n ' contents at the bottom of the tube.'\n note 'Place the tubes on ice or in a cold-block.'\n end\n end\n\n # Instruct technician to label the tubes that will contain the master mixes\n #\n # @param labels [Array\u003cString\u003e]\n # @return [void]\n def show_label_mmix_tubes(labels:)\n n = labels.length\n labels = labels.map { |label| \"\u003cb\u003e#{label}\u003c/b\u003e\" }\n show do\n title 'Label master mix tubes'\n\n note \"Take out #{n} #{TUBE_MICROFUGE.pluralize(n)}\"\n note \"Write #{labels.to_sentence} on the tops of each tube\"\n end\n end\n\n # Instruct technician to make large volume master mixes to be\n # distributed among wells\n #\n # @param compositions [Array\u003cPCRComposition\u003e]\n # @param sample_number [Fixnum]\n # @return [void]\n def show_make_master_mixes(compositions:, sample_number:)\n show do\n title 'Pipet master mix components'\n\n note 'Pipet the following components into each labeled master mix tube'\n table master_mix_table(\n compositions: compositions,\n sample_number: sample_number\n )\n separator\n\n note 'Pipet the primer/probe mixes into each corresponding' \\\n ' master mix tube'\n table primer_probe_table(\n compositions: compositions,\n sample_number: sample_number\n )\n end\n end\n\n # Instruct technician to pipet a master mix into the plate\n #\n # @param primer_mix_name [String]\n # @param volume [Numeric]\n # @param collection [Collection]\n # @param layout_group [Array\u003cFixnum\u003e]\n # @return [void]\n def show_pipet_mmix(primer_mix_name:, volume:, collection:, layout_group:)\n show do\n title \"Pipet #{primer_mix_name} master mix into plate\"\n\n note \"Pipet #{volume} #{MICROLITERS} of #{primer_mix_name}\" \\\n \" master mix into the plate #{collection}\"\n table highlight_collection_rc(collection, layout_group, check: false)\n end\n end\n\n ########## TABLE METHODS ##########\n\n # Build table for volumes of master mix components\n #\n # @param compositions [Array\u003cPCRComposition\u003e]\n # @param sample_number [Fixnum]\n # @return [Array\u003cArray\u003e] a 2D array formatted for the `table` method in Krill\n def master_mix_table(compositions:, sample_number:)\n # This is a hack because otherwise only the first composition gets\n # marked as added\n compositions.each do |composition|\n composition.master_mix.added = true\n composition.water.added = true\n end\n\n composition = compositions.first\n header = [\n 'Component',\n composition.master_mix.sample.name,\n composition.water.display_name\n ]\n row = [\n \"Volume (#{MICROLITERS})\",\n composition.master_mix.add_in_table(sample_number),\n composition.water.add_in_table(sample_number)\n ]\n [header, row].transpose\n end\n\n # Build table for volumes of master mix components\n #\n # @param compositions [Array\u003cPCRComposition\u003e]\n # @param sample_number [Fixnum]\n # @return [Array\u003cArray\u003e] a 2D array formatted for the `table` method in Krill\n def primer_probe_table(compositions:, sample_number:)\n table = [[\n compositions.first.primer_probe_mix.display_name,\n 'Item',\n \"Volume (#{MICROLITERS})\"\n ]]\n compositions.each do |composition|\n pp_mix = composition.primer_probe_mix\n row = [\n pp_mix.sample.name,\n pp_mix.item.to_s,\n pp_mix.add_in_table(sample_number)\n ]\n table.append(row)\n end\n table\n end\n\n private\n\n def inspect_data_associations(operation:)\n collection = operation.output(PLATE).collection\n [[0, 0], [1, 3], [2, 8]].each do |r, c|\n part = collection.part(r, c)\n inspect part, \"part at #{[r, c]}\"\n inspect part.associations, \"data at #{[r, c]}\"\n end\n end\nend\n","precondition":"def precondition(_op)\n true\nend","cost_model":"def cost(_op)\n { labor: 0, materials: 0 }\nend","documentation":"Creates a collection, typically a 96-well plate, filled with qPCR master mixes. \n\n#### Job Options\nNone\n\n#### Operation Options\n`program_name [String]` The protocol to be followed, based on the qPCR\nmaster mix used. Defaults to CDC_TaqPath_CG. For other supported protocols, see\n[this document](https://github.com/aquariumbio/pcr-models/blob/master/pcr_libs/libraries/pcrcompositiondefinitions/source.rb).\n\n`group_size [Int]` The number of master mix samples to make and add for each \ninput primer. Defaults to 24.\n\n`layout_method [String]` The method to use in laying out the plate. Defaults to `cdc_primer_layout`.","test":"# frozen_string_literal: true\n\nclass ProtocolTest \u003c ProtocolTestBase\n def setup\n add_random_operations(1)\n\n # mm = Sample.find_by_name('TaqPath 1-Step RT-qPCR Master Mix')\n # p1 = Sample.find_by_name('2019-nCoV_N1')\n # p2 = Sample.find_by_name('2019-nCoV_N2')\n # p3 = Sample.find_by_name('RNase P')\n\n # add_operation\n # .with_property(\"Options\", '{ \"magic_number\": 24, \"foo\": \"baz\" }')\n # .with_input('Enzyme Master Mix', mm)\n # .with_input('Primer/Probe Mix', p1)\n # .with_input('Primer/Probe Mix', p2)\n # .with_input('Primer/Probe Mix', p3)\n end\n\n def analyze\n log('Hello from Nemo')\n assert_equal(@backtrace.last[:operation], 'complete')\n end\nend","timing":null}},{"sample_types":[{"id":4,"name":"RNA","description":"A sample of RNA","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","field_types":[]},{"id":3,"name":"qPCR Reaction","description":"qPCR Reaction","created_at":"2020-07-26T17:08:31.000-07:00","updated_at":"2020-07-26T17:08:31.000-07:00","field_types":[]}],"object_types":[{"id":7,"name":"Purified RNA in 1.5 mL tube","description":"Purified RNA in 1.5 mL tube","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","unit":"RNA","cost":0.01,"release_method":"return","release_description":"","sample_type_id":4,"image":null,"prefix":"M80","rows":null,"columns":null,"sample_type_name":"RNA"},{"id":9,"name":"96-well qPCR Reaction","description":"96-well qPCR Reaction","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","unit":"qPCR Reaction","cost":0.01,"release_method":"return","release_description":"","sample_type_id":3,"image":null,"prefix":"","rows":null,"columns":null,"sample_type_name":"qPCR Reaction"}],"operation_type":{"name":"Prepare RT-qPCR Plate","category":"Diagnostic RT-qPCR","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"Template","sample_types":["RNA"],"object_types":["Purified RNA in 1.5 mL tube"],"part":false,"array":true,"routing":"T","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"sample","role":"output","name":"qPCR Reactions","sample_types":["qPCR Reaction"],"object_types":["96-well qPCR Reaction"],"part":false,"array":false,"routing":"","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null}],"protocol":"# typed: false\n# frozen_string_literal: true\n\nneeds 'Diagnostic RT-qPCR/DiagnosticRTqPCRHelper'\nneeds 'Microtiter Plates/MicrotiterPlates'\n\n# Protocol for setting up a plate with extracted RNA samples\n#\n# @author Devin Strickland \[email protected]\u003e\n# @author Cannon Mallory \[email protected]\u003e\nclass Protocol\n include DiagnosticRTqPCRHelper\n\n CONTROL_TYPE_STUBS = %w[negative_template positive_template].freeze\n\n # Default parameters that are applied equally to all operations.\n # Can be overridden by:\n # * Associating a JSON-formatted list of key, value pairs to the `Plan`.\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_job_params\n {\n max_inputs: 24\n }\n end\n\n # Default parameters that are applied to individual operations.\n # Can be overridden by:\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_operation_params\n {\n negative_template_control: nil,\n negative_template_location: nil,\n positive_template_control: 'Test nCoVPC',\n positive_template_location: [0, 11],\n program_name: 'CDC_TaqPath_CG',\n group_size: 3,\n layout_method: 'cdc_sample_layout'\n }\n end\n\n def main\n setup_test_plates(operations: operations) if debug\n @job_params = update_all_params(\n operations: operations,\n default_job_params: default_job_params,\n default_operation_params: default_operation_params\n )\n\n validate(operations: operations)\n return {} if operations.errored.any?\n\n prepare_materials(operations: operations)\n # operations.make\n\n operations.each do |op|\n op.pass(PLATE)\n add_all_samples(operation: op)\n end\n\n operations.store\n\n {}\n end\n\n def add_all_samples(operation:)\n collection = operation.output(PLATE).collection\n remaining_compositions = operation.temporary[:compositions].dup\n operation_parameters = operation.temporary[:options]\n\n microtiter_plate = MicrotiterPlateFactory.build(\n collection: collection,\n group_size: operation_parameters[:group_size],\n method: operation_parameters[:layout_method]\n )\n\n remaining_compositions = add_control_samples(\n compositions: remaining_compositions,\n microtiter_plate: microtiter_plate,\n operation_parameters: operation_parameters\n )\n\n add_diagnostic_samples(\n compositions: remaining_compositions,\n microtiter_plate: microtiter_plate\n )\n\n seal_plate(collection)\n show_result(collection: collection) if debug\n end\n\n # Prepare workspace and materials\n #\n # @todo Make this handle master mix or enzyme with separate\n # buffer dynamically\n # @param operations [OperationList]\n # @return [void]\n def prepare_materials(operations:)\n rnase_warning\n safety_warning\n build_template_compositions(operations: operations)\n retrieve_by_compositions(operations: operations)\n end\n\n # Provides instructions and handling for addition of control\n # samples. Returns all inputs that are NOT control inputs\n #\n # @param compositions [Array\u003cPCRCompostion\u003e]\n # @param microtiter_plate [MicrotiterPlate]\n # @param operation_parameters [Hash]\n # @return operation_inputs [Array\u003citem\u003e]\n def add_control_samples(compositions:, microtiter_plate:,\n operation_parameters:)\n remaining_compositions = []\n CONTROL_TYPE_STUBS.each do |stub|\n name = operation_parameters[\"#{stub}_control\".to_sym]\n loc = operation_parameters[\"#{stub}_location\".to_sym]\n\n next unless name.present? \u0026\u0026 loc.present?\n\n compositions, remaining_compositions = compositions.partition do |c|\n c.template.sample.name == name\n end\n\n add_samples(\n compositions: compositions,\n microtiter_plate: microtiter_plate,\n column: loc[1]\n )\n\n # negative control samples need to be covered after addition\n next unless stub.include?('negative')\n\n seal_plate(collection, rc_list: get_rna_samples(collection))\n end\n remaining_compositions\n end\n\n # Provides instructions to add diagnostic samples to collection\n #\n # @param compositions [Array\u003cPCRCompostion\u003e]\n # @param microtiter_plate [MicrotiterPlate]\n def add_diagnostic_samples(compositions:, microtiter_plate:)\n add_samples(\n compositions: compositions,\n microtiter_plate: microtiter_plate\n )\n end\n\n # validates operations and ensures that they are formatted as expected\n #\n # @param operations [OperationList]\n def validate(operations:)\n operations.each do |op|\n if op.input_array(TEMPLATE).length \u003e @job_params[:max_inputs]\n raise IncompatibleInputsError, \"Too many inputs for Operation #{op.id}\"\n end\n end\n rescue IncompatibleInputsError =\u003e e\n error_operations(operations: operations, err: e)\n end\n\n # Say you're quitting due to an error and error all the operations\n #\n def error_operations(operations:, err:)\n show do\n title 'Incompatible Inputs Detected'\n warning err.message\n end\n\n operations.each { |op| op.error(:incompatible_inputs, err.message) }\n end\n\n class IncompatibleInputsError \u003c ProtocolError; end\n class NoAvailableWells \u003c ProtocolError; end\nend\n","precondition":"def precondition(_op)\n true\nend","cost_model":"def cost(_op)\n { labor: 0, materials: 0 }\nend","documentation":"Documentation here. Start with a paragraph, not a heading or title, as in most views, the title will be supplied by the view.","test":"class ProtocolTest \u003c ProtocolTestBase\n def setup\n add_random_operations(1)\n end\n \n def analyze\n log('Hello from Nemo')\n assert_equal(@backtrace.last[:operation], 'complete')\n end\n end","timing":null}},{"sample_types":[{"id":3,"name":"qPCR Reaction","description":"qPCR Reaction","created_at":"2020-07-26T17:08:31.000-07:00","updated_at":"2020-07-26T17:08:31.000-07:00","field_types":[]}],"object_types":[{"id":9,"name":"96-well qPCR Reaction","description":"96-well qPCR Reaction","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2020-07-26T17:09:27.000-07:00","updated_at":"2020-07-26T17:09:27.000-07:00","unit":"qPCR Reaction","cost":0.01,"release_method":"return","release_description":"","sample_type_id":3,"image":null,"prefix":"","rows":null,"columns":null,"sample_type_name":"qPCR Reaction"}],"operation_type":{"name":"Run qPCR","category":"Diagnostic RT-qPCR","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"qPCR Reactions","sample_types":["qPCR Reaction"],"object_types":["96-well qPCR Reaction"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"json","role":"input","name":"Options","sample_types":[],"object_types":[],"part":false,"array":false,"routing":null,"preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"sample","role":"output","name":"qPCR Reactions","sample_types":["qPCR Reaction"],"object_types":["96-well qPCR Reaction"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null}],"protocol":"# frozen_string_literal: true\n\nneeds 'PCR Libs/PCRComposition'\nneeds 'PCR Libs/PCRProgram'\nneeds 'Thermocyclers/Thermocyclers'\nneeds 'Standard Libs/PlanParams'\nneeds 'Standard Libs/Debug'\nneeds 'Standard Libs/UploadHelper'\nneeds 'Diagnostic RT-qPCR/DataAssociationKeys'\n\n# Protocol for loading samples into a qPCR thermocycler and running it\n#\n# @author Devin Strickland \[email protected]\u003e\n# @todo Decide whether this is actually qPCR specific\nclass Protocol\n include ThermocyclerHelper\n include PlanParams\n include Debug\n include UploadHelper\n include DataAssociationKeys\n\n INPUT_REACTIONS = 'qPCR Reactions'\n\n THERMOCYCLER_KEY = 'thermocycler'.to_sym\n\n ########## DEFAULT PARAMS ##########\n\n # Default parameters that are applied equally to all operations.\n # Can be overridden by:\n # * Associating a JSON-formatted list of key, value pairs to the `Plan`.\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_job_params\n {\n program_name: 'CDC_TaqPath_CG',\n qpcr: true\n }\n end\n\n # Default parameters that are applied to individual operations.\n # Can be overridden by:\n # * Adding a JSON-formatted list of key, value pairs to an `Operation`\n # input of type JSON and named `Options`.\n #\n def default_operation_params\n {\n thermocycler_model: TestThermocycler::MODEL,\n }\n end\n\n ########## MAIN ##########\n\n def main\n setup_test_options(operations: operations) if debug\n\n @job_params = update_all_params(\n operations: operations,\n default_job_params: default_job_params,\n default_operation_params: default_operation_params\n )\n return {} if operations.errored.any?\n\n available_thermocyclers = get_available_thermocyclers\n\n remove_unavailable_operations(available_thermocyclers)\n\n return {} if operations.empty?\n\n operations.retrieve.make\n\n composition = PCRCompositionFactory.build(\n program_name: @job_params[:program_name]\n )\n program = PCRProgramFactory.build(\n program_name: @job_params[:program_name],\n volume: composition.volume\n )\n\n running_thermocyclers = start_thermocyclers(program: program,\n composition: composition)\n\n get_data(running_thermocyclers: running_thermocyclers)\n\n operations.store\n\n {}\n end\n\n def get_data(running_thermocyclers:)\n running_thermocyclers.each do |op, thermocycler|\n go_to_thermocycler(thermocycler_name: op.get(THERMOCYCLER_KEY)['name'])\n export_measurements(thermocycler: thermocycler)\n\n associate_measurement(file_name: op.get(RAW_QPCR_DATA_KEY),\n plate: op.input(INPUT_REACTIONS).collection)\n end\n end\n\n def start_thermocyclers(program:, composition:)\n running_thermocyclers = []\n operations.each do |op|\n plate = op.input(INPUT_REACTIONS).item\n\n file_name = experiment_filename(plate)\n\n thermo_type = op.get(THERMOCYCLER_KEY)\n\n op.associate(RAW_QPCR_DATA_KEY, file_name)\n\n thermocycler = ThermocyclerFactory.build(\n model: thermo_type['model']\n )\n\n go_to_thermocycler(thermocycler_name: thermo_type['name'], plate: plate)\n\n set_up_program(\n thermocycler: thermocycler,\n program: program,\n composition: composition,\n qpcr: @job_params[:qpcr]\n )\n\n load_plate_and_start_run(\n thermocycler: thermocycler,\n items: plate,\n experiment_filename: file_name\n )\n running_thermocyclers.push([op, thermocycler])\n end\n running_thermocyclers\n end\n\n def associate_measurement(file_name:, plate:)\n file = upload_data(file_name, 1, 4)\n plate.associate(RAW_QPCR_DATA_KEY, file)\n end\n\n def go_to_thermocycler(thermocycler_name:, plate: nil)\n show do\n title 'Go to Thermocycler'\n note \"Take the #{plate.object_type.name} \u003cb\u003e#{plate.id}\u003c/b\u003e\"\\\n \" to Thermocycler #{thermocycler_name}\" unless plate.nil?\n note \"Complete the next few steps at Thermocycler #{thermocycler_name}\"\n end\n end\n\n def get_available_thermocyclers\n thermocyclers = find_thermocyclers\n available_key = 'available'\n response = show do\n title 'Check Available Thermocyclers'\n note 'Please check which thermocyclers are currently available'\n thermocyclers.each_with_index do |thermo|\n select([available_key, 'unavailable'],\n var: thermo['name'].to_s,\n label: \"Thermocycler #{thermo['name']}\")\n end\n end\n available_thermo = []\n thermocyclers.map do |thermo|\n next unless response[thermo['name'].to_s] == available_key || debug\n\n available_thermo.push(thermo)\n end\n available_thermo\n end\n\n def find_thermocyclers\n Parameter.where(key: 'thermocycler').map { |thr| JSON.parse(thr.value) }\n end\n\n def remove_unavailable_operations(available_thermocyclers)\n ops_to_remove = find_unavailable_ops(available_thermocyclers)\n operations.reject! { |op| ops_to_remove.include?(op) }\n error_op_warning(ops_to_remove)\n ops_to_remove.each do |op|\n op.error(:unavailablethermocycler, 'No thermocyclers were available')\n op.set_status_recursively('pending')\n end\n end\n\n def error_op_warning(ops_to_remove)\n show do\n title 'Thermocyclers Unavailable'\n note 'There are not enough available thermocyclers for this job'\n warning 'The following plates were removed from this job'\n ops_to_remove.each do |op|\n note op.input(INPUT_REACTIONS).collection.id.to_s\n end\n end\n end\n\n def find_unavailable_ops(thermocyclers)\n ops_to_remove = []\n operations.each do |op|\n available = false\n thermocyclers.each do |thermo|\n next unless thermo['model'] == op.temporary[:options][:thermocycler_model] || true\n\n op.associate(THERMOCYCLER_KEY, thermo)\n thermocyclers.delete(thermo)\n available = true\n break\n end\n ops_to_remove.push(op) unless available \n end\n ops_to_remove\n end\n\n class UnavailableThermocycler \u003c ProtocolError; end\n\n ########## NAMING METHODS ##########\n\n # Constructs a name for the experiment file.\n #\n # @return [String]\n def experiment_filename(plate)\n date = DateTime.now.strftime('%Y-%m-%d')\n \"#{date}_Job_#{job.id}_#{plate.id}\"\n end\n\n # Gets the currently active `Job`\n #\n # @return [Job]\n def job\n operation_ids = operations.map(\u0026:id)\n ja_ids = JobAssociation.where(operation_id: operation_ids).map(\u0026:job_id).uniq\n jobs = Job.find(ja_ids).select(\u0026:active?)\n raise ProtocolError, 'Cannot resolve the current Job' if jobs.length \u003e 1\n\n jobs.last\n end\nend\n","precondition":"def precondition(_op)\n true\nend","cost_model":"def cost(_op)\n { labor: 0, materials: 0 }\nend","documentation":"Documentation here. Start with a paragraph, not a heading or title, as in most views, the title will be supplied by the view.","test":"# frozen_string_literal: true\n\nclass ProtocolTest \u003c ProtocolTestBase\n def setup\n add_random_operations(1)\n # [1..3].each do |i|\n # s = Sample.find_by_name(\"Test qPCR Reaction #{i}\")\n # add_operation\n # .with_input('qPCR Reactions', s)\n # .with_property('Options', '{\"thermocycler_model\": \"BioRad CFX96\", \"program_name\": \"CDC_qScript_XLT_ToughMix\"}')\n # .with_output('qPCR Reactions', s)\n # end\n end\n\n def analyze\n log('Hello from Nemo')\n assert_equal(@backtrace.last[:operation], 'complete')\n end\nend\n","timing":null}},{"library":{"name":"DataAssociationKeys","category":"Diagnostic RT-qPCR","code_source":"# frozen_string_literal: true\n\nmodule DataAssociationKeys\n COMPOSITION_NAME_KEY = 'composition_name'\n PRIMER_GROUP_SIZE_KEY = 'primer_group_size'\n PRIMER_METHOD_KEY = 'primer_method'\n SAMPLE_GROUP_SIZE_KEY = 'sample_group_size'\n SAMPLE_METHOD_KEY = 'sample_method'\n MASTER_MIX_KEY = 'master_mix'\n MASTER_MIX_STOCK_KEY = 'master_mix_stock'\n PRIMER_PROBE_MIX_KEY = 'primer_probe_mix'\n TEMPLATE_KEY = 'template'\n RAW_QPCR_DATA_KEY = 'raw_qpcr_data'\n LOT_NUM_KEY = 'lot_number'\nend\n"}},{"library":{"name":"DiagnosticRTqPCRCompositions","category":"Diagnostic RT-qPCR","code_source":"# typed: false\n# frozen_string_literal: true\n\nneeds 'PCR Libs/PCRComposition'\n\n# Module for working with PCRCompositions used in Diagnostic RT qPCR\n#\n# @author Devin Strickland \[email protected]\u003e\nmodule DiagnosticRTqPCRCompositions\n PRIMER_PROBE_MIX = 'Primer/Probe Mix' # Should converge with PRIMER_PROBE_MIX\n # in PCRCOmpositionDefinitions\n TEMPLATE = 'Template'\n\n # Initialize all `PCRComposition`s for each operation stripwell\n #\n # @param operations [OperationList]\n # @return [void]\n def build_stripwell_master_mix_compositions(operations:)\n operations.each do |op|\n primer_mixes = []\n op.input_array(PRIMER_PROBE_MIX).each do |fv|\n primer_mixes += fv.collection.parts\n end\n\n compositions = []\n\n primer_mixes.each do |primer_mix|\n composition = build_modified_master_mix_composition(\n primer_mix: primer_mix,\n program_name: op.temporary[:options][:program_name]\n )\n compositions.append(composition)\n end\n\n op.temporary[:compositions] = compositions\n end\n end\n\n # Initialize a `PCRComposition` for a given primer mix and program\n # stripwell\n #\n # @param primer_mix [Item]\n # @param program_name [String]\n # @return [PCRComposition]\n def build_modified_master_mix_composition(primer_mix:, program_name:)\n composition = PCRCompositionFactory.build(program_name: program_name)\n composition.primer_probe_mix.item = primer_mix\n composition\n end\n\n # Initialize all `PCRComposition`s for each operation\n #\n # @param operations [OperationList]\n # @return [void]\n def build_master_mix_compositions(operations:)\n operations.each do |operation|\n primer_mixes = operation.input_array(PRIMER_PROBE_MIX).map(\u0026:item)\n\n compositions = []\n\n primer_mixes.each do |primer_mix|\n composition = build_master_mix_composition(\n primer_mix: primer_mix,\n program_name: operation.temporary[:options][:program_name]\n )\n compositions.append(composition)\n end\n\n operation.temporary[:compositions] = compositions\n end\n end\n\n # Initialize a `PCRComposition` for a given primer mix and program\n #\n # @param primer_mix [Item]\n # @param program_name [String]\n # @return [PCRComposition]\n def build_master_mix_composition(primer_mix:, program_name:)\n composition = PCRCompositionFactory.build(program_name: program_name)\n mm_item = master_mix_item(sample: composition.master_mix.sample)\n composition.master_mix.item = mm_item\n composition.primer_probe_mix.item = primer_mix\n composition.water.item = water_item\n composition\n end\n\n # Initialize all `PCRComposition`s for each operation\n #\n # @param operations [OperationList]\n # @return [void]\n def build_template_compositions(operations:)\n operations.each do |operation|\n templates = operation.input_array(TEMPLATE).map(\u0026:item)\n compositions = []\n\n templates.each do |template|\n composition = build_template_composition(\n template: template,\n program_name: operation.temporary[:options][:program_name]\n )\n compositions.append(composition)\n end\n operation.temporary[:compositions] = compositions\n end\n end\n\n # Initialize all `PCRComposition`s for each operation\n #\n # @param operations [OperationList]\n # @return [void]\n def build_ntc_compositions(operations:)\n operations.each do |operation|\n composition = build_template_composition(\n template: no_template_control_item,\n program_name: operation.temporary[:options][:program_name]\n )\n operation.temporary[:compositions] = [composition]\n end\n end\n\n # Initialize a `PCRComposition` for the given program\n #\n # @param program_name [String]\n # @return [PCRComposition]\n def build_template_composition(template:, program_name:)\n composition = PCRCompositionFactory.build(program_name: program_name)\n composition.template.item = template\n composition\n end\n\n # Retrieve `Item`s required for the protocol based on what's in\n # the compositions that are attached to the operations\n #\n # @param operations [OperationList]\n # @return [void]\n def retrieve_by_compositions(operations:)\n compositions = operations.map { |op| op.temporary[:compositions] }.flatten\n items = compositions.map(\u0026:items).flatten.compact.uniq\n items = items.sort_by(\u0026:object_type_id)\n take(items, interactive: true)\n end\n\n # Build the data structure that documents the provenance of a\n # master mix\n #\n # @param primer_mix [Item]\n # @param composition [PCRComposition]\n # @return [Hash] a data structure that documents the provenance of a\n # master mix\n def added_component_data(composition:)\n composition.added_components.map { |component| collect_data(component) }\n end\n\n # Reduce a `ReactionComponent` (part of a `PCRComposition`) to a simplified\n # Hash of selected attributes\n #\n # @param component [ReactionComponent]\n # @return [Hash]\n def collect_data(component)\n {\n name: component.input_name,\n item: component.item,\n volume: component.volume_hash\n }\n end\nend\n"}},{"library":{"name":"DiagnosticRTqPCRDebug","category":"Diagnostic RT-qPCR","code_source":"# typed: false\n# frozen_string_literal: true\n\nneeds 'Collection Management/CollectionTransfer'\nneeds 'Diagnostic RT-qPCR/DataAssociationKeys'\n\nmodule DiagnosticRTqPCRDebug\n include CollectionTransfer\n include DataAssociationKeys\n\n VERBOSE = false\n PLATE = 'PCR Plate'\n\n def debug_parameters\n {\n group_size: 3,\n program_name: 'CDC_TaqPath_CG',\n debug_primer: [5, 8, 9], # rp, n1, n2,\n debug_template: 'template',\n debug_item_ids: [257] + Array.new(22, 256)\n }\n end\n\n # Populate all input plates with qPCR Reactions\n #\n # @param operations [OperationList]\n # @param method [String]\n # @return [void]\n def setup_test_plates(operations:, method: nil)\n operations.each do |op|\n setup_test_plate(collection: op.input(PLATE).collection, method: method)\n op.set_input('Template', Item.find(debug_parameters[:debug_item_ids]))\n end\n end\n\n # Populate a collection with qPCR Reactions\n #\n # @param collection [Collection]\n # @param method [String]\n # @return [void]\n def setup_test_plate(collection:, method:)\n collection.associate(:program_name, debug_parameters[:program_name])\n collection.associate(:group_size, debug_parameters[:group_size])\n qpcr_reaction = Sample.find_by_name('Test qPCR Reaction')\n\n layout_generator = PlateLayoutGeneratorFactory.build(\n group_size: 24,\n method: :cdc_primer_layout\n )\n\n loop do\n layout_group = layout_generator.next_group\n break unless layout_group.present?\n\n layout_group.each do |r, c|\n collection.set(r, c, qpcr_reaction)\n part = collection.part(r, c)\n part.associate(MASTER_MIX_KEY, 'added')\n end\n end\n\n if method == :master_mix\n show_result(collection: collection) if VERBOSE\n inspect_data_associations(collection: collection) if VERBOSE\n return\n end\n\n layout_generator = PlateLayoutGeneratorFactory.build(\n group_size: debug_parameters[:group_size],\n method: :cdc_sample_layout\n )\n layout_group = layout_generator.next_group\n layout_group.each do |r, c|\n part = collection.part(r, c)\n part.associate(TEMPLATE_KEY, 'added')\n end\n\n show_result(collection: collection) if VERBOSE\n inspect_data_associations(collection: collection) if VERBOSE\n end\n\n # Show all the non-empty wells of the test plate\n # @todo figure out what you really want to show here\n #\n # @param collection [Collection]\n # @return [void]\n def show_result(collection:)\n show do\n title 'Test Plate Setup'\n table highlight_non_empty(collection)\n end\n end\n\n # Inspect a subset of the parts and their data associations\n #\n # @param collection [Collection]\n # @return [void]\n def inspect_data_associations(collection:)\n [[0, 0], [0, 3], [0, 8]].each do |r, c|\n part = collection.part(r, c)\n inspect part, \"part at #{[r, c]}\"\n inspect part.associations, \"data at #{[r, c]}\"\n end\n end\nend\n"}},{"library":{"name":"DiagnosticRTqPCRHelper","category":"Diagnostic RT-qPCR","code_source":"# typed: false\n# frozen_string_literal: true\n\nneeds 'Standard Libs/PlanParams'\nneeds 'Standard Libs/CommonInputOutputNames'\nneeds 'Standard Libs/Debug'\nneeds 'Standard Libs/Pipettors'\nneeds 'Standard Libs/LabwareNames'\nneeds 'Collection Management/CollectionActions'\nneeds 'Collection Management/CollectionDisplay'\nneeds 'Collection Management/CollectionTransfer'\nneeds 'Diagnostic RT-qPCR/DataAssociationKeys'\nneeds 'Diagnostic RT-qPCR/DiagnosticRTqPCRDebug'\nneeds 'Diagnostic RT-qPCR/DiagnosticRTqPCRCompositions'\n\n# Module for elements that are common throughout Diagnostic RT qPCR\n#\n# @author Devin Strickland \[email protected]\u003e\nmodule DiagnosticRTqPCRHelper\n # Standard Libs\n include Units\n include PlanParams\n include CommonInputOutputNames\n include Debug\n include Pipettors\n include LabwareNames\n\n # Collection Management\n include CollectionActions\n include CollectionDisplay\n include CollectionTransfer\n\n # Diagnostic RT-qPCR\n include DataAssociationKeys\n include DiagnosticRTqPCRDebug\n include DiagnosticRTqPCRCompositions\n\n WATER = 'Molecular Grade Water'\n WATER_OBJECT_TYPE = 'Reagent Aliquot'\n PLATE = 'PCR Plate'\n PLATE_OBJECT_TYPE = '96-well qPCR Plate'\n PRIMER_MIX = 'Primer/Probe Mix'\n MASTER_MIX_OBJECT_TYPE = 'qPCR Master Mix Stock'\n SPECIMEN = 'Specimen'\n\n RNA_FREE_WORKSPACE = 'reagent set-up room'\n\n def rnase_warning\n show do\n title 'RNase degrades RNA'\n note 'RNA is prone to degradation by RNase present in our eyes, skin, and breath.'\n note 'Avoid opening tubes outside the Biosafety Cabinet (BSC).'\n bullet 'Change gloves whenever you suspect potential RNAse contamination'\n end\n end\n\n def safety_warning\n show do\n title 'Review Safety Warnings'\n note '\u003cb\u003eAlways\u003c/b\u003e pay attention to orange warning blocks throughout the protocol.'\n warning '\u003cb\u003eINFECTIOUS MATERIALS\u003c/b\u003e'\n note 'You will be working with infectious materials.'\n note 'Do \u003cb\u003eALL\u003c/b\u003e work in a biosafety cabinet (BSC).'\n note '\u003cb\u003ePPE is required\u003c/b\u003e'\n check 'Put on lab coat.'\n check 'Put on 2 layers of gloves.'\n bullet 'Make sure to use tight gloves. Tight gloves reduce the chance of the gloves getting caught on the tubes when closing their lids.'\n bullet 'Change outer layer of gloves after handling infectious sample and before touching surfaces outside of the BSC (such as a refrigerator door handle).'\n end\n end\n\n # Adds samples to to collections, provides instructions to tech\n #\n # @param compositions [Array\u003cPCRCompostion\u003e]\n # @param microtiter_plate [MicrotiterPlate]\n # @param column [int]\n def add_samples(compositions:, microtiter_plate:, column: nil)\n compositions.each do |composition|\n layout_group = microtiter_plate.next_empty_group(\n key: TEMPLATE_KEY,\n column: column\n )\n\n # inspect 'Skipping method single_channel_item_to_collection'\n single_channel_item_to_collection(\n to_collection: microtiter_plate.collection,\n source: composition.template.item,\n volume: composition.template.volume_hash,\n association_map: layout_group.map { |r, c| { to_loc: [r, c] } }\n )\n\n composition.template.added = true\n\n microtiter_plate.associate_provenance_group(\n group: layout_group,\n key: TEMPLATE_KEY,\n data: added_component_data(composition: composition)\n )\n end\n end\n\n # Instruct technician to do everything necessary to prepare the workspace\n #\n # @return [void]\n def show_prepare_workspace\n show do\n title 'Prepare workspace'\n\n note \"All tasks in this protocol occur in the #{RNA_FREE_WORKSPACE}.\"\n note 'As you retrieve reagents, place them on ice or in a cold-block.'\n end\n end\n\n ########## ITEM METHODS ##########\n\n # Finds a master mix Item in inventory\n #\n # @param sample [Sample] of qPCR Master Mix\n # @return [Item]\n def master_mix_item(sample:)\n get_item(\n sample: sample,\n object_type_name: MASTER_MIX_OBJECT_TYPE\n )\n end\n\n # Finds a water Item in inventory\n #\n # @return [Item]\n def water_item\n get_item(\n sample: Sample.find_by_name(WATER),\n object_type_name: WATER_OBJECT_TYPE\n )\n end\n\n # Finds a water Item in inventory for no template control\n #\n # @return [Item]\n def no_template_control_item\n water_item\n end\n\n # Finds an Item in inventory for the given `Sample` and `ObjectType`\n # @todo replace with a back-end method such as `Sample.in`\n #\n # @param sample [Sample]\n # @param object_type_name [String]\n # @return [Item]\n def get_item(sample:, object_type_name:)\n Item.with_sample(sample: sample)\n .where(object_type: ObjectType.find_by_name(object_type_name))\n .reject(\u0026:deleted?)\n .first\n end\n\n ########## PROVENANCE METHODS ##########\n\n # Add provenance metadata to a stock item and aliquots made from that stock\n #\n # @param stock_item [Item] the source item for the aliquot operation\n # @param aliquot_items [Array\u003cItem\u003e] the aliqouts made in the operation\n # @return [void]\n def add_aliquot_provenance(stock_item:, aliquot_items:)\n from_map = AssociationMap.new(stock_item)\n to_maps = aliquot_items.map { |a| [a, AssociationMap.new(a)] }\n\n to_maps.each do |aliquot_item, to_map|\n add_provenance(\n from: stock_item, from_map: from_map,\n to: aliquot_item, to_map: to_map\n )\n from_map.save\n to_map.save\n end\n end\n\n def add_one_to_one_provenance(from_item:, to_item:)\n from_map = AssociationMap.new(from_item)\n to_map = AssociationMap.new(to_item)\n\n add_provenance(\n from: from_item, from_map: from_map,\n to: to_item, to_map: to_map\n )\n from_map.save\n to_map.save\n end\n\n ################### Provision Plate Methods ################\n\n # Creates and assigns an output collection for each operation, and fills it\n # with the output sample according to the provided PlateLayoutGenerator\n # method\n # @note In debug mode, displays the matrix of each collection\n #\n # @param operations [OperationList]\n # @param object_type [String] the ObjectType of the collection to be made\n # @return [void]\n def provision_plates(operations:)\n operations.each do |op|\n collection = op.output(PLATE).make_collection\n get_and_label_new_plate(collection)\n\n set_parts(\n collection: collection,\n group_size: op.temporary[:options][:group_size],\n method: op.temporary[:options][:layout_method],\n sample: op.output(PLATE).sample\n )\n\n inspect op.output(PLATE).collection.matrix if debug\n end\n end\n\n # Fills a collection with the provided sample according to the provided\n # PlateLayoutGenerator method\n #\n # @param collection [Collection]\n # @param group_size [Fixnum]\n # @param method [String] a PlateLayoutGenerator method\n # @param sample [Sample] the Sample to add to the collection\n # @return [void]\n def set_parts(collection:, group_size:, method:, sample:)\n layout_generator = PlateLayoutGeneratorFactory.build(\n group_size: group_size,\n method: method,\n dimensions: collection.dimensions\n )\n\n loop do\n index = layout_generator.next\n break unless index.present?\n\n collection.set(index[0], index[1], sample)\n end\n end\n\n ############# STRIPWELL METHODS ############\n \n # Adds a stripwell to a microtiter plate\n #\n # @param composition_group [Array\u003cCompositions\u003e] list of compositions that are\n # all contained in the same stripwell\n # @param microtiter_plate [MicrotiterPlate]\n def add_stripwell(composition_group:, microtiter_plate:, stripwell:)\n layout_group = microtiter_plate.next_empty_group(key: PRIMER_PROBE_MIX_KEY)\n composition_group.zip(layout_group).each do |composition, lyt|\n data = added_component_data(composition: composition)\n microtiter_plate.associate_provenance(index: lyt,\n key: PRIMER_PROBE_MIX_KEY,\n data: data)\n end\n show_add_stripwell(layout_group: layout_group,\n stripwell: stripwell,\n collection: microtiter_plate.collection)\n end\n\n # Show instructions to place stripwell in rack\n # @param layout_group [Array\u003c[r,c]\u003e]\n # @param stripwell [Collection]\n # @param collection [Collection] the collection of the microtiter plate\n def show_add_stripwell(layout_group:, stripwell:, collection:)\n show_mark_stripwell(stripwell: stripwell)\n show do \n title 'Add Stripwell to Stripwell Rack'\n note \"Please place stripwell #{stripwell.id} in\"\\\n \" stripwell rack #{collection.id} per table below\"\n note 'Make sure column 1 of the stripwell lines up with column 1 of the rack'\n table highlight_collection_rc(collection, layout_group){ stripwell.parts.first.sample.name }\n end\n end\n\n # Directions to mark a stripwell correctly\n #\n # @param stripwell [Colleciton]\n def show_mark_stripwell(stripwell:)\n show do\n title 'Mark Stripwell'\n note 'Using a felt tip marker please mark'\\\n \" stripwell #{stripwell.id}\"\n note \"Mark one end \u003cb\u003e1\u003c/b\u003e and the other \u003cb\u003e#{stripwell.dimensions[1]}\"\n warning 'Do NOT mark the lids of the stripwell!'\n end\n end\nend\n"}},{"library":{"name":"PrepareRTqPCRValidation","category":"Diagnostic RT-qPCR","code_source":"# frozen_string_literal: true\n\nmodule PrepareRTqPCRValidation\n\n # Validates that all operations are as expected\n #\n # @param operations [OperationList] list of operations\n # @raise rescue will Error any operations that do not pass validation\n def validate(operations:)\n input_errors = []\n content_errors = []\n all_errors = []\n\n operations.each do |op|\n valid_input, input_error = validate_inputs(op)\n valid_contents, content_error = validate_contents(op)\n unless valid_input\n all_errors.push(op)\n input_errors.push({op: op, error: input_error})\n op.error(:IncompatibleInputsError, input_error)\n end\n\n unless valid_contents\n all_errors.push(op)\n content_errors.push({op: op, error: content_error})\n op.error(:InvalidContentsError, content_error)\n end\n end\n\n display_error(input_errors) unless input_errors.empty?\n display_error(content_errors) unless content_errors.empty?\n\n all_errors\n end\n\n def display_error(error_list)\n error_type = error_list.first[:error]\n tab = [['\u003cb\u003eOperation\u003cb\u003e', '\u003cb\u003eError Message\u003c/b\u003e']]\n error_list.each do |err|\n tab.push([err[:op].id, err[:error]])\n end\n\n show do\n title 'Operation Error'\n note \"The following operations had #{error_type}\"\n table tab\n end\n end\n\n def validate_contents(op)\n [true, nil]\n end\n\n def validate_inputs(op)\n #[failed/passed, message]\n [true, nil]\n end\n\n class IncompatibleInputsError \u003c ProtocolError; end\n class InvalidContentsError \u003c ProtocolError; end\n\n \nend"}},{"library":{"name":"PrepareqPCRPlateHelper","category":"Diagnostic RT-qPCR","code_source":"# frozen_string_literal: true\n\nneeds 'Standard Libs/PlanParams'\nneeds 'Standard Libs/CommonInputOutputNames'\nneeds 'Standard Libs/Debug'\nneeds 'Standard Libs/InputOutput'\nneeds 'Standard Libs/Pipettors'\nneeds 'Standard Libs/Units'\nneeds 'Collection Management/CollectionActions'\nneeds 'Collection Management/CollectionDisplay'\nneeds 'Collection Management/CollectionTransfer'\n# needs 'Collection Management/CollectionLocation'\nneeds 'Collection Management/CollectionData'\nneeds 'Microtiter Plates/PlateLayoutGenerator'\n\n# Protocol methods for setting up plate withe extracted RNA Samples\n#\n# @author Devin Strickland \[email protected]\u003e\n# @author Cannon Mallory \[email protected]\u003e\nmodule PrepareqPCRPlateHelper\n include PlanParams\n include Debug\n include InputOutput\n include Pipettors\n include Units\n include CommonInputOutputNames\n include CollectionActions\n include CollectionDisplay\n include CollectionTransfer\n # include CollectionLocation\n include CollectionData\n\n attr_reader :job_params\n\n TEMPLATE = 'Template'\n OPTIONS = 'options'\n INPUT_PLATE = 'Input PCR Plate'\n OUTPUT_PLATE = 'Output PCR Plate'\n OUTPUT_C_TYPE = '96-well qPCR Reaction'\n\n # An assortment of items for debugging\n # Make sure these exist and are right item type\n # [Positive Control, human sample, human sample]\n DEBUG_ITEMS = [425, 8627, 262]\n DEBUG_CONTAINER = 18007 # Make sure that proper samples exist in plate (currently\n # Validation steps rubber stamp everything)\n\n def run_job(default_job_params:, default_operation_params:, op:)\n @job_params = update_plan_params(plan_params: default_job_params,\n opts: default_operation_params)\n\n @job_params = update_plan_params(plan_params: job_params,\n opts: get_op_opts(op))\n\n input_collection = op.input(INPUT_PLATE).collection\n input_collection = Collection.find(DEBUG_CONTAINER) if debug\n output_collection = relabel_plate(input_collection)\n op.output(OUTPUT_PLATE).set(collection: output_collection)\n\n input_collection.mark_as_deleted unless debug\n\n layout_generator = PlateLayoutGeneratorFactory.build(\n group_size: job_params[:group_size],\n method: job_params[:method]\n )\n\n if debug\n operation_inputs = generate_samples\n else\n operation_inputs = op.input_array(TEMPLATE).map { |fv| fv.item }\n end\n\n sample_inputs, negative_controls, positive_controls =\n sort_inputs(neg_control: job_params[:negative_controls],\n pos_control: job_params[:positive_controls],\n operation_inputs: operation_inputs)\n\n setup_control_samples(operation_inputs: negative_controls,\n collection: output_collection,\n layout_generator: layout_generator,\n controls: job_params[:negative_controls])\n\n setup_control_samples(operation_inputs: positive_controls,\n collection: output_collection,\n layout_generator: layout_generator,\n controls: job_params[:positive_controls])\n\n setup_samples(operation_inputs: sample_inputs,\n collection: output_collection,\n layout_generator: layout_generator)\n\n associate_plate_to_plate(to_collection: output_collection,\n from_collection: input_collection)\n\n\n add_additional_components(rc_list: make_sample_rc_list(output_collection,\n operation_inputs),\n collection: output_collection,\n recipe_list: job_params[:additional_inputs],\n operation: op)\n \n add_negative_controls(collection: output_collection, samples: negative_controls,\n recipe: job_params[:negative_controls], operation: op)\n\n add_inputs(collection: output_collection, samples: sample_inputs,\n recipe: job_params[:standard_samples], operation: op)\n\n add_inputs(collection: output_collection, samples: positive_controls,\n recipe: job_params[:positive_controls], operation: op)\n\n {}\n end\n\n def add_negative_controls(collection:, samples:, recipe:, operation:)\n return if samples.length == 0\n\n show do\n title 'Adding Negative Controls'\n note 'It is \u003cb\u003eVERY\u003c/b\u003e important to ensure that negative controls are not\n contaminated'\n note 'Take extra time to ensure proper sterile techniques are used'\n end\n\n add_inputs(collection: collection, samples: samples,\n recipe: recipe, operation: operation)\n\n show do\n title 'Cover Negative Control Wells'\n note 'Carefully cap control wells per table below'\n table highlight_collection_rc(collection,\n make_sample_rc_list(collection, samples)){ |r, c|\n convert_coordinates_to_location([r,c]) }\n end\n end\n\n def add_inputs(collection:, samples:, recipe:, operation:)\n unless recipe[:additional_inputs].nil?\n add_additional_components(rc_list: make_sample_rc_list(collection,\n samples),\n collection: collection,\n recipe_list: recipe[:additional_inputs],\n operation: operation)\n end\n\n return if samples.length == 0 \n\n move_to_station(recipe[:station])\n layout_materials(samples + [collection])\n\n samples.each do |item|\n add_to_plate(rc_list: make_sample_rc_list(collection, [item]),\n collection: collection,\n source: item,\n volume: recipe[:volume])\n end\n end\n\n def add_additional_components(rc_list:, collection:, recipe_list:, operation:)\n recipe_list.each do |recipe|\n add_static_inputs([operation], recipe[:name],\n recipe[:name], recipe[:container])\n item = operation.input(recipe[:name]).item\n\n move_to_station(recipe[:station])\n layout_materials([item, collection])\n\n add_to_plate(rc_list: rc_list, collection: collection, source: item,\n volume: recipe[:volume])\n end\n end\n\n def add_to_plate(rc_list:, collection:, source:, volume:)\n show do\n title 'Pipette into Plate'\n note pipet(volume: volume, source: source,\n destination: 'the highlighted wells below')\n table highlight_collection_rc(collection, rc_list) { |r, c|\n convert_coordinates_to_location([r,c])\n }\n end\n end\n\n def move_to_station(station)\n show do \n title 'Move to Station'\n note \"Please move to #{station} to complete the following steps\"\n end\n end\n\n def layout_materials(materials)\n show do\n title 'Layout Materials'\n note 'Please set out the following items for easy access'\n table create_location_table(materials)\n end\n end\n\n def make_sample_rc_list(collection, inputs)\n rc_list = []\n inputs.each do |item|\n item = item.sample unless item.is_a? Sample\n rc_list += collection.find(item)\n end\n rc_list\n end\n\n\n def setup_control_samples(operation_inputs:, collection:,\n layout_generator:, controls:)\n setup_samples(\n operation_inputs: operation_inputs,\n collection: collection,\n layout_generator: layout_generator,\n column: controls[:first_well][1]\n )\n end\n\n def generate_samples\n debug_items = []\n DEBUG_ITEMS.each do |id|\n debug_items.push(Item.find(id))\n end\n debug_items\n end\n\n def sort_inputs(neg_control:, pos_control:, operation_inputs:)\n remaining_inputs = []\n negative_controls = []\n positive_controls = []\n operation_inputs.each do |input|\n input_name = input.sample.name\n\n if input_name.include?(neg_control[:name])\n negative_controls.push(input)\n elsif input_name.include?(pos_control[:name])\n positive_controls.push(input)\n else\n remaining_inputs.push(input)\n end\n end\n [remaining_inputs, negative_controls, positive_controls]\n end\n\n def setup_samples(operation_inputs:, collection:, layout_generator:, column: nil)\n operation_inputs.each do |fv|\n\n job_params[:max_inputs].times do\n\n retry_group = true\n\n layout_group = layout_generator.next_group(column: column)\n\n layout_group.each do |r, c|\n existing_part = collection.part(r, c)\n\n if existing_part.sample.name.include?(job_params[:negative_controls][:name]); break; end\n if existing_part.sample.name.include?(job_params[:positive_controls][:name]); break; end\n\n collection.set(r, c, fv.sample)\n\n from_obj_to_obj_provenance(collection.part(r, c), existing_part)\n\n retry_group = false\n end\n\n column = layout_generator.iterate_column(column)\n break unless retry_group\n end\n end\n end\n\nend"}}]}