-
Notifications
You must be signed in to change notification settings - Fork 0
/
primer-workflow.aq
1 lines (1 loc) · 47 KB
/
primer-workflow.aq
1
{"config":{"title":"Primer Workflow","description":"A workflow to order primers","copyright":"University of Washington","version":"0.0.1","authors":[{"name":"Tileli Amimeur","affiliation":"University of Washington"},{"name":"Nick Bolten","affiliation":"University of Washington"},{"name":"Leandra Brettner","affiliation":"University of Washington"},{"name":"Cameron Cordray","affiliation":"University of Washington"},{"name":"Miles Gander","affiliation":"University of Washington"},{"name":"Samer Halabiya","affiliation":"University of Washington"},{"name":"Seunghee Jang","affiliation":"University of Washington"},{"name":"Yokesh Jayakumar","affiliation":"University of Washington"},{"name":"Benjamin Keller","affiliation":"University of Washington"},{"name":"Erriberto Lopez","affiliation":"University of Washington"},{"name":"Jon Luntzel","affiliation":"University of Washington"},{"name":"Abraham Miller","affiliation":"University of Washington"},{"name":"Garrett Newman","affiliation":"University of Washington"},{"name":"Michelle Parks","affiliation":"University of Washington"},{"name":"Sundipta Rao","affiliation":"University of Washington"},{"name":"Ayesha Saleem","affiliation":"University of Washington"},{"name":"Devin Strickland","affiliation":"University of Washington"},{"name":"Chris Takahashi","affiliation":"University of Washington"},{"name":"Yaoyu Yang","affiliation":"University of Washington"},{"name":"David Younger","affiliation":"University of Washington"}],"maintainer":{"name":"Ben Keller","email":"[email protected]"},"acknowledgements":null,"github":{"user":"bjkeller","repo":"primer-workflow","organization":"klavinslab"},"keywords":null,"aquadoc_version":"1.0.0","aquarium_version":"\u003c%= Bioturk::Application.config.aquarium_version %\u003e"},"components":[{"sample_types":[{"id":3,"name":"Primer","description":"A short double stranded piece of DNA for PCR and sequencing","created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.000-07:00","field_types":[{"id":19,"parent_id":3,"name":"Overhang Sequence","ftype":"string","choices":null,"array":false,"required":false,"created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.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":20,"parent_id":3,"name":"Anneal Sequence","ftype":"string","choices":null,"array":false,"required":true,"created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.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":21,"parent_id":3,"name":"T Anneal","ftype":"number","choices":null,"array":false,"required":true,"created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.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":[]}]}],"object_types":[{"id":24,"name":"Primer Stock","description":"rehydrated primer in tube","min":0,"max":1000,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2019-03-14T14:17:37.000-07:00","updated_at":"2019-03-14T14:17:37.000-07:00","unit":"Primer","cost":0.01,"release_method":"return","release_description":"","sample_type_id":3,"image":null,"prefix":"M20","rows":null,"columns":null,"sample_type_name":"Primer"},{"id":12,"name":"Primer Aliquot","description":"Primers at low concentration (10uM) for every day use","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"{ \"measure\": { \"type\": \"concentration\", \"unit\": \"micromolar\" } }","vendor":"No vendor information","created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.000-07:00","unit":"Primer","cost":0.01,"release_method":"return","release_description":"","sample_type_id":3,"image":null,"prefix":"M20","rows":null,"columns":null,"sample_type_name":"Primer"}],"operation_type":{"name":"Make Primer Aliquot","category":"Cloning","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"Stock","sample_types":["Primer"],"object_types":["Primer Stock"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"sample","role":"output","name":"Aliquot","sample_types":["Primer"],"object_types":["Primer Aliquot"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null}],"protocol":"# frozen_string_literal: true\n\n# Make Primer Aliquot protocol.\n#\n# For each input primer stock, transfers 10 uL of stock into 90 uL of water in\n# a 1.5 mL tube.\nclass Protocol\n def main\n operations.retrieve.make\n\n gather_tubes(\n count: operations.length,\n aliquot_ids: operations.map { |op| op.output('Aliquot').item.id }\n )\n create_aliquots(operations)\n\n operations.store\n end\n\n # Gather and prepare tubes for new aliquots.\n #\n # @param count [FixNum] the number of tubes to prepare\n # @param aliquot_ids [Array\u003cFixNum\u003e] the item IDs to label tubes\n def gather_tubes(count:, aliquot_ids:)\n show do\n title 'Prepare aliquot tubes'\n\n note \"Grab #{count} 1.5 mL tubes\"\n note \"Label each tube with the following ids: #{aliquot_ids.to_sentence}\"\n note 'Using the 100 uL pipette, pipette 90uL of water into each tube'\n end\n end\n\n # Displays instructions to transfer 10 uL of primer stock into the tube with\n # the corresponding label.\n #\n # @param operations [OperationList] the operations specifying transfer\n def create_aliquots(operations)\n show do\n title 'Transfer primer stock into primer aliquot'\n\n note 'Pipette 10 uL of the primer stock into a tube according to the ' \\\n 'following table:'\n table operations\n .start_table\n .input_item('Stock')\n .output_item('Aliquot', checkable: true)\n .end_table\n end\n end\nend\n","precondition":"def precondition(op)\n true\nend","cost_model":"def cost(op)\n { labor: 0.40, materials: 0.06 }\nend","documentation":"# Make Primer Aliquot\n\nMakes a 1:10 diluted 100L aliquot of a Primer stock from inventory.\n","test":"# frozen_string_literal: true\n\nclass ProtocolTest \u003c ProtocolTestBase\n def setup; end\n\n def analyze; end\nend\n","timing":null}},{"sample_types":[{"id":3,"name":"Primer","description":"A short double stranded piece of DNA for PCR and sequencing","created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.000-07:00","field_types":[{"id":19,"parent_id":3,"name":"Overhang Sequence","ftype":"string","choices":null,"array":false,"required":false,"created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.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":20,"parent_id":3,"name":"Anneal Sequence","ftype":"string","choices":null,"array":false,"required":true,"created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.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":21,"parent_id":3,"name":"T Anneal","ftype":"number","choices":null,"array":false,"required":true,"created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.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":[]}]}],"object_types":[{"id":25,"name":"Lyophilized Primer","description":"Some barely visible white powder","min":1,"max":10000,"handler":"sample_container","safety":"","cleanup":"","data":"","vendor":"","created_at":"2019-03-14T14:17:37.000-07:00","updated_at":"2019-03-14T14:17:37.000-07:00","unit":"tube","cost":5.0,"release_method":"return","release_description":"","sample_type_id":3,"image":null,"prefix":"","rows":null,"columns":null,"sample_type_name":"Primer"}],"operation_type":{"name":"Order Primer","category":"Cloning","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"output","name":"Primer","sample_types":["Primer"],"object_types":["Lyophilized Primer"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"string","role":"input","name":"Urgent?","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 'Standard Libs/Feedback'\nneeds 'Sample Models/Primer'\nneeds 'Cloning/Vendor'\n\n# Order Primer protocol.\n#\n# Protocol to order primers from a vendor selected by the user from vendors\n# supported by the Vendor module.\n# Uses the vendor module to provide instructions to order the primers, and\n# then adds the order number as an association for each of the primer outputs.\nclass Protocol\n include Feedback\n include Vendor\n\n def main\n operations.retrieve.make\n\n vendor = Vendor.determine_vendor(protocol: self)\n vendor.login(protocol: self)\n\n primers = operations.map { |op| create_primer(output: op.output('Primer')) }\n order_number = vendor.order_primers(protocol: self, primers: primers)\n add_order_number(operations: operations, order_number: order_number)\n\n get_protocol_feedback\n {}\n end\n\n # Create a Primer object from the Item of the given output.\n #\n # @param output [FieldValue] the output value\n # @return [Primer] the primer object for the Sample of the output Item\n def create_primer(output:)\n Primer.new(sample: output.item.sample)\n end\n\n # Add the given order number to all operations in the operations list\n # unless the order number is nil or the empty string.\n #\n # @param operations [OperationsList] the list of operations\n # @param order_number [String] the order number for \n def add_order_number(operations:, order_number:)\n return if order_number.blank?\n operations.each do |op|\n op.set_output_data('Primer', :order_number, order_number)\n end\n end\nend\n","precondition":"# frozen_string_literal: true\n\n# Test the precondition for the given Order Primer operation.\n#\n# The output primer may not be ordered if the Sample does not have an overhang\n# or annealing sequence. Otherwise, it may be ordered if one of the following\n# holds:\n# - the primer order is urgent,\n# - the total cost of all pending orders exceeds $50, or\n# - there are other orders that are urgent.\n#\n# @param operation [Operation] the Order Primer operation\n# @return [Boolean] true if the primer can be ordered, or false otherwise\ndef precondition(operation)\n # TODO: let user know that primers don't have necessary structure\n primer_sample = operation.output('Primer').sample\n return false unless primer_sample.properties['Overhang Sequence'].present?\n return false unless primer_sample.properties['Anneal Sequence'].present?\n\n return true if urgent?(operation)\n\n pending_orders = Operation.where('status IN (?) \u0026\u0026 operation_type_id IN (?)',\n %w[waiting pending delayed],\n operation.operation_type.id)\n total_cost = pending_orders.inject(0) do |sum, order|\n sum + order.nominal_cost[:materials]\n end\n return true if total_cost \u003e 50\n\n pending_orders.any? { |order_operation| urgent?(order_operation) }\nend\n\n# Indicate whether the operation has the urgent input parameter set.\n#\n# @param operation [Operation] the Order Primer operation\n# @return [Boolean] true if the urgent parameter is set, false otherwise\ndef urgent?(operation)\n urgent_parameter = operation.input('Urgent?').val\n\n urgent_parameter.present? \u0026\u0026 urgent_parameter.casecmp?('yes')\nend\n","cost_model":"# Kilroy was here 2017-09-22 18:54:13\ndef cost(op)\n \n props = op.output(\"Primer\").sample.properties\n seq = props[\"Overhang Sequence\"] + props[\"Anneal Sequence\"]\n n = seq.length\n \n if n \u003c= 60\n c = n * Parameter.get_float('short primer cost')\n elsif n \u003c= 90\n c = n * Parameter.get_float('medium primer cost')\n else\n c = n * Parameter.get_float('long primer cost')\n end\n \n { labor: 1.8, materials: c }\n \nend","documentation":"# OrderPrimer\n\nDisplays instructions to order DNA oligos for the specified output primer from a vendor.\n\nIndicate whether the order is urgent using the `Urgent?` parameter.\n\nCurrently supported vendors are IDT and Sigma-Aldrich.\nUse the **Parameters** menu to set the user and password for the vendor(s) you want to be able to use.\nSet values for `IDT User` and `IDT Password` for IDT; and `Sigma-Aldrich User` and `Sigma-Aldrich Password` for Sigma-Aldrich.\n\nSee the `Cloning/Vendor` library to add a vendor.\n","test":"# frozen_string_literal: true\n\nclass ProtocolTest \u003c ProtocolTestBase\n def setup\n Parameter.make('IDT User', 'dummy_user')\n Parameter.make('IDT Password', 'dummy_password')\n sample = primer_sample(name: 'Test Primer')\n add_operation.with_output('Primer', sample)\n end\n\n def analyze\n assert_equal @backtrace.last[:operation], 'complete'\n end\n\n def primer_sample(name:, description: 'A primer for testing')\n sample_type(type_name: 'Primer',\n definition: [\n {\n name: 'Anneal Sequence',\n array: false,\n required: true,\n ftype: 'string'\n },\n {\n name: 'Overhang Sequence',\n array: false,\n required: false,\n ftype: 'string'\n },\n {\n name: 'T Anneal',\n array: false,\n required: true,\n ftype: 'number'\n }\n ]).save\n sample(name: name,\n description: description,\n type_name: 'Primer',\n attributes: [\n { name: 'Anneal Sequence', value: 'ATTCTA' },\n { name: 'Overhang Sequence', value: 'ATCTCGAGCT' },\n { name: 'T Anneal', value: 70 }\n ])\n end\n\n def sample(name:, description:, user: nil, type_name:, attributes:)\n sample_type = sample_type(type_name: type_name)\n return nil if sample_type.nil?\n\n user = User.all.last if user.nil?\n sample = Sample.creator(\n {\n sample_type_id: sample_type.id,\n description: description,\n name: name,\n project: 'Testing',\n field_values: attributes\n },\n user\n )\n sample\n end\n\n def sample_type(type_name:, description: 'test type', definition: nil)\n sample_type = SampleType.find_by_name(type_name)\n return sample_type unless sample_type.nil?\n\n SampleType.create_from_raw(\n name: type_name,\n description: description,\n field_types: definition\n )\n end\nend\n","timing":{"start":960,"stop":990,"days":"[\"Mo\",\"Tu\",\"We\",\"Th\",\"Fr\"]","active":true}}},{"sample_types":[{"id":3,"name":"Primer","description":"A short double stranded piece of DNA for PCR and sequencing","created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.000-07:00","field_types":[{"id":19,"parent_id":3,"name":"Overhang Sequence","ftype":"string","choices":null,"array":false,"required":false,"created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.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":20,"parent_id":3,"name":"Anneal Sequence","ftype":"string","choices":null,"array":false,"required":true,"created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.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":21,"parent_id":3,"name":"T Anneal","ftype":"number","choices":null,"array":false,"required":true,"created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.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":[]}]}],"object_types":[{"id":25,"name":"Lyophilized Primer","description":"Some barely visible white powder","min":1,"max":10000,"handler":"sample_container","safety":"","cleanup":"","data":"","vendor":"","created_at":"2019-03-14T14:17:37.000-07:00","updated_at":"2019-03-14T14:17:37.000-07:00","unit":"tube","cost":5.0,"release_method":"return","release_description":"","sample_type_id":3,"image":null,"prefix":"","rows":null,"columns":null,"sample_type_name":"Primer"},{"id":12,"name":"Primer Aliquot","description":"Primers at low concentration (10uM) for every day use","min":0,"max":1,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"{ \"measure\": { \"type\": \"concentration\", \"unit\": \"micromolar\" } }","vendor":"No vendor information","created_at":"2019-03-14T14:16:57.000-07:00","updated_at":"2019-03-14T14:16:57.000-07:00","unit":"Primer","cost":0.01,"release_method":"return","release_description":"","sample_type_id":3,"image":null,"prefix":"M20","rows":null,"columns":null,"sample_type_name":"Primer"},{"id":24,"name":"Primer Stock","description":"rehydrated primer in tube","min":0,"max":1000,"handler":"sample_container","safety":"No safety information","cleanup":"No cleanup information","data":"No data","vendor":"No vendor information","created_at":"2019-03-14T14:17:37.000-07:00","updated_at":"2019-03-14T14:17:37.000-07:00","unit":"Primer","cost":0.01,"release_method":"return","release_description":"","sample_type_id":3,"image":null,"prefix":"M20","rows":null,"columns":null,"sample_type_name":"Primer"}],"operation_type":{"name":"Rehydrate Primer","category":"Cloning","deployed":false,"on_the_fly":false,"field_types":[{"ftype":"sample","role":"input","name":"Primer","sample_types":["Primer"],"object_types":["Lyophilized Primer"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":328,"preferred_field_type_id":1811,"choices":null},{"ftype":"sample","role":"output","name":"Primer Aliquot","sample_types":["Primer"],"object_types":["Primer Aliquot"],"part":false,"array":false,"routing":"P","preferred_operation_type_id":null,"preferred_field_type_id":null,"choices":null},{"ftype":"sample","role":"output","name":"Primer Stock","sample_types":["Primer"],"object_types":["Primer Stock"],"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 'Standard Libs/Feedback'\n\n# Rehydrate Primers protocol.\n# Takes a primer and hydrates it yielding a primer aliquot and stock.\n#\n# Note: this protocol assumes details about vendor labeling that may be\n# IDT-specific.\n#\n# TODO: add use of Units\nclass Protocol\n include Feedback\n\n def main\n operations.retrieve interactive: false\n operations.make\n\n spin_down_primers(operations)\n get_primer_nm(operations)\n\n rehydrate_primers(operations)\n aliquot_ids = operations.map do |op|\n op.output('Primer Aliquot').item.id\n end\n prepare_aliquot_tubes(tube_ids: aliquot_ids)\n vortex_and_centrifuge(count: operations.length)\n make_aliquots(operations)\n\n operations.each { |op| op.input('Primer').item.mark_as_deleted }\n operations.store\n\n get_protocol_feedback\n {}\n end\n\n # Display instructions to spin down the primers\n #\n # @param primers [Array\u003cItem\u003e] the input primers\n def spin_down_primers(_primers)\n # TODO: reference the tubes\n show do\n title 'Quick spin down all the primer tubes'\n note 'Place the primer tubes in a table top centrifuge and spin down ' \\\n 'for 3 seconds.'\n warning 'Make sure to balance!'\n end\n end\n\n # Displays the primers and for each asks for the nMoles of primer on the tube\n # label. Stores the measurement in `operation.temporary[:n_moles]`\n def get_primer_nm(operations)\n show do\n title 'Enter the nMoles of the primer'\n\n note 'Enter the number of moles for each primer, in nM. ' \\\n 'This is written toward the bottom of the tube, below the MW.'\n note 'The ID of the primer is listed before the primer\\'s name on the ' \\\n 'side of the tube.'\n table operations\n .start_table\n .input_sample('Primer')\n .get(:n_moles, type: 'number', heading: 'nMoles', default: 10)\n .end_table\n end\n end\n\n # Displays instructions to label the primer tubes with a new ID, and then add\n # TE to rehydrate the primer. Adds 10uL for each nM.\n #\n # @param operations [OperationList] the operations with input primers\n def rehydrate_primers(operations)\n show do\n title 'Label and rehydrate'\n\n note 'Label each primer tube with the IDs shown in Primer Stock IDs ' \\\n 'and rehydrate with volume of TE shown in Rehydrate'\n table operations\n .start_table\n .input_sample('Primer')\n .output_item('Primer Stock')\n .custom_column(\n heading: 'Rehydrate (uL of TE)', checkable: true\n ) { |op| op.temporary[:n_moles] * 10 }\n .end_table\n end\n end\n\n # Displays instructions to vortex and centrifuge the rehydrated primers.\n #\n # @param count [Fixnum] the number of primers\n def vortex_and_centrifuge(count:)\n show do\n title 'Vortex and centrifuge'\n note 'Wait one minute for the primer to dissolve in TE.' if count \u003c 7\n note 'Vortex each tube on table-top vortexer for 5 seconds and then ' \\\n 'quick spin for 2 seconds on table top centrifuge.'\n end\n end\n\n # Displays instructions to prepare the aliquot tubes by labeling and adding\n # 90uL of water to each.\n #\n # @param tube_ids [Array\u003cFixnum\u003e] the list of item IDs\n def prepare_aliquot_tubes(tube_ids:)\n count = tube_ids.length\n id_string = tube_ids.map(\u0026:to_s).join(', ')\n show do\n title 'Prepare 1.5 mL tubes'\n\n note 'While the primer dissolves in the TE, prepare tubes for each aliquot'\n check \"Grab #{count} 1.5 mL tubes, label with following ids: #{id_string}\"\n check 'Add 90 uL of water into each above tube.'\n end\n end\n\n # Displays instructions to transfer 10uL of the primer stock into each output\n # tube.\n #\n # @param operations [OperationList] the operations with output primers\n def make_aliquots(operations)\n show do\n title 'Make primer aliquots'\n\n note 'Add 10 uL from each primer stock into each primer aliquot tube ' \\\n 'using the following table.'\n\n table operations\n .start_table\n .output_item('Primer Stock', heading: 'Primer Stock (10 uL)')\n .output_item('Primer Aliquot', checkable: true)\n .end_table\n note 'Vortex each tube after the primer has been added.'\n end\n end\nend\n","precondition":"def precondition(op)\n true\nend","cost_model":"def cost(op)\n { labor: 3.16, materials: 0.12 }\nend","documentation":"# Rehydrate Primer\n\nTakes a primer and hydrates it, yielding a primer aliquot and a primer stock.\n\nThis protocol assumes that all primers in the job are from the same order.\nIn practice, the BIOFAB ensures this by telling IDT to only send an order once\nall primers are complete.\nThis allows the lab managers to identify and schedule the appropriate job.\n","test":"# frozen_string_literal: true\n\nclass ProtocolTest \u003c ProtocolTestBase\n def setup\n sample = primer_sample(name: 'Test Primer')\n add_operation.with_input('Primer', sample)\n .with_output('Primer Aliquot', sample)\n .with_output('Primer Stock', sample)\n end\n\n def analyze\n assert_equal(@backtrace.last[:operation], 'complete')\n end\n\n def primer_sample(name:, description: 'A primer for testing')\n sample_type(type_name: 'Primer',\n definition: [\n {\n name: 'Anneal Sequence',\n array: false,\n required: true,\n ftype: 'string'\n },\n {\n name: 'Overhang Sequence',\n array: false,\n required: false,\n ftype: 'string'\n },\n {\n name: 'T Anneal',\n array: false,\n required: true,\n ftype: 'number'\n }\n ]).save\n sample(name: name,\n description: description,\n type_name: 'Primer',\n attributes: [\n { name: 'Anneal Sequence', value: 'ATTCTA' },\n { name: 'Overhang Sequence', value: 'ATCTCGAGCT' },\n { name: 'T Anneal', value: 70 }\n ])\n end\n\n def sample(name:, description:, user: nil, type_name:, attributes:)\n sample_type = sample_type(type_name: type_name)\n return nil if sample_type.nil?\n\n user = User.all.last if user.nil?\n sample = Sample.creator(\n {\n sample_type_id: sample_type.id,\n description: description,\n name: name,\n project: 'Testing',\n field_values: attributes\n },\n user\n )\n sample\n end\n\n def sample_type(type_name:, description: 'test type', definition: nil)\n sample_type = SampleType.find_by_name(type_name)\n return sample_type unless sample_type.nil?\n\n SampleType.create_from_raw(\n name: type_name,\n description: description,\n field_types: definition\n )\n end\nend\n","timing":{"start":690,"stop":870,"days":"[\"Mo\",\"Tu\",\"We\",\"Th\",\"Fr\"]","active":true}}},{"library":{"name":"Vendor","category":"Cloning","code_source":"# frozen_string_literal: true\n\n# Module to manage details of ordering primers from a vendor using the\n# `Cloning/Order Primer` protocol.\n#\n# Each vendor corresponds to a sub-module of the Vendor module implementing a\n# method `order_primer(Primer)`, where `Primer` is defined in the library\n# `Sample Models/Primer`\n#\nmodule Vendor\n # Returns the vendor selected by the user.\n #\n # @param protocol [Protocol] the protocol where output should be displayed\n # TODO: allow for user to specify which vendors they use with Parameters\n def self.determine_vendor(protocol:)\n vendors = %w[IDT Sigma-Aldrich]\n # vendors = vendors.reject { |v| Parameter.get(\"#{v} User\").blank? }\n\n vendor_show_hash = protocol.show do\n select(vendors,\n var: 'vendor',\n label: 'Select the vendor you will be ordering primers from',\n default: 0)\n end\n\n if vendor_show_hash.value?('IDT')\n IDT\n elsif vendor_show_hash.value?('Sigma-Aldrich')\n SigmaAldrich\n end\n end\n\n # Defines common methods for primer vendors.\n #\n # If you add a vendor, extend the module with this one.\n module PrimerVendor\n # Displays login instructions for the vendor's website.\n #\n # @param protocol [Protocol] the protocol where output should be displayed\n # @param url [String] the URL for the vendor website\n # @param name [String] the login name for the vendor website\n def login_helper(protocol:, url:, name:)\n user = Parameter.get(\"#{name} User\")\n password = Parameter.get(\"#{name} Password\")\n protocol.show do\n title 'Prepare to order primer'\n\n check \"Go to the \u003ca href='#{url}'\u003e#{name} website\u003c/a\u003e, log in with \" \\\n \"the account (Username: #{user}, password is #{password}).\"\n warning 'Ensure that you are logged in to this exact username ' \\\n 'and password!'\n end\n end\n\n # Creates a primer table with entries for this vendor.\n # Note: uses vendor module definitions of primer_table_row.\n #\n # @param primers [Array] the primers to be ordered\n def build_primer_table(primers:)\n primers.map do |primer|\n primer_table_row(sample: primer.sample, sequence: primer.sequence)\n end\n end\n end\n\n # Module for making orders from IDT.\n module IDT\n extend PrimerVendor\n\n LOWER_LENGTH = 60\n UPPER_LENGTH = 90\n\n # Displays instructions to login to IDT website.\n #\n # @param protocol [Protocol] the protocol where output should be displayed\n def self.login(protocol:)\n login_helper(\n protocol: protocol,\n url: 'https://www.idtdna.com/site/account',\n name: 'IDT'\n )\n end\n\n # Returns an array representing a table row.\n #\n # @param sample [Sample] the sample object\n # @param sequence [String] the sequence string for the primer\n def self.primer_table_row(sample:, sequence:)\n [sample.id.to_s + ' ' + sample.name, sequence]\n end\n\n # Create strings containing identity of primers based on length\n def self.build_primer_lists(primers)\n short_primers = []\n long_primers = []\n\n primers.each_index do |index|\n primer = primers[index]\n primer_string = \"#{primer} (##{index + 1})\"\n if primer.length \u003e LOWER_LENGTH \u0026\u0026 primer.length \u003c= UPPER_LENGTH\n short_primers.push(primer_string)\n elsif primer.length \u003e UPPER_LENGTH\n long_primers.push(primer_string)\n end\n end\n\n [short_primers.join(', '), long_primers.join(', ')]\n end\n\n # Shows the primer table that was created in an earlier call and sets the\n # output data.\n #\n # @param protocol [Protocol] the protocol where output should be displayed\n # @param primer_tab [Array\u003cArray\u003cString\u003e\u003e] matrix for order table\n # @param short_primers [String] the string of short primers\n # @param long_primers [String] the string of long primers\n def self.display_primer_table(protocol:, primer_tab:, short_primers:, long_primers:)\n data = protocol.show do\n title 'Create an IDT DNA oligos order'\n\n if short_primers != ''\n warning \"Oligo concentration for primer(s) #{short_primers} will \" \\\n 'have to be set to \"100 nmole DNA oligo.\"'\n end\n if long_primers != ''\n warning \"Oligo concentration for primer(s) #{long_primers} will \" \\\n 'have to be set to \"250 nmole DNA oligo.\"'\n end\n\n check 'Under \"Custom DNA Oligos\", click \"DNA Oligos\", ' \\\n 'then click \"Order now\", and click \"Bulk input\". ' \\\n 'Copy and paste the following table there. '\n table primer_tab\n\n check 'Click Add to Order, review the shopping cart to double check ' \\\n 'that you entered correctly. ' \\\n \"There should be #{operations.length} primers in the cart.\"\n check 'Click Checkout, then click Continue.'\n check 'Enter the payment information, click the oligo card tab, ' \\\n 'select the Card1 in Choose Payment and then click Submit Order.'\n check 'Go back to the main page, let it sit for 5-10 minutes, ' \\\n 'return and refresh, and find the order number for the order ' \\\n 'you just placed.'\n\n get('text',\n var: 'order_number',\n label: 'Enter the IDT order number below',\n default: 100)\n end\n\n data[:order_number]\n end\n\n # Displays the instructions to order the list of primers from IDT.\n #\n # @param primers [Array\u003cPrimer\u003e] the list of primers to order\n # @return [String] the order number for the order including the primers\n def self.order_primers(protocol:, primers:)\n primer_table = build_primer_table(primers: primers)\n short_primers, long_primers = build_primer_lists(primers)\n order_number = display_primer_table(protocol: protocol,\n primer_tab: primer_table,\n short_primers: short_primers,\n long_primers: long_primers)\n\n order_number\n end\n end\n\n # Module for making primer orders to Sigma-Aldrich.\n module SigmaAldrich\n extend PrimerVendor\n\n # Displays instructions to login to the Sigma-Adrich website.\n def self.login(protocol:)\n login_helper(\n protocol: protocol,\n url: 'https://www.sigmaaldrich.com/webapp/wcs/stores/servlet/LogonForm?storeId=11001',\n name: 'Sigma-Aldrich'\n )\n end\n\n # Returns an array representing a table row.\n #\n # @param sample [Sample] the sample object\n # @param sequence [String] the sequence string for the primer\n def self.primer_table_row(sample:, sequence:)\n [\n sample.id.to_s + ' ' + sample.name,\n 'None',\n sequence,\n 'None',\n '0.025',\n 'Desalt',\n 'Dry',\n 'None',\n '1'\n ].join('\\t')\n end\n\n # Displays the primer table for vendor Sigma-Aldrich.\n #\n # @param primer_table [Array\u003cArray\u003cString\u003e\u003e] the table of primer details\n def self.show_primer_table(protocol:, primer_table:)\n protocol.show do\n title 'Create a Sigma-Aldrich DNA oligos order'\n\n check 'Under \"Products\", click \"Custom DNA Oligos\", and then under ' \\\n '\"Standard DNA Oligos\", click \"Order\" under \"Tubes\".'\n check 'Click \"Upload or Copy \u0026 Paste\".'\n check 'Copy and paste the following table and click submit.'\n\n table primer_table\n\n check 'Click Add to Cart.'\n check 'Proceed to Check Out.'\n check 'Click Check Out and confirm the order.'\n end\n end\n\n # Displays the instructions to order the list of primers from Sigma-Aldrich.\n #\n # @param primers [Array\u003cPrimer\u003e] the list of primers to order\n # @return [nil] since no order number is given\n def self.order_primers(protocol:, primers:)\n primer_table = build_primer_table(primers: primers)\n show_primer_table(protocol: protocol, primer_table: primer_table)\n\n nil\n end\n end\nend\n"}},{"library":{"name":"AbstractSample","category":"Sample Models","code_source":"# frozen_string_literal: true\n\n# Defines a superclass of sample objects to encapsulate Aquarium Sample objects.\nclass AbstractSample\n attr_accessor :item, :sample, :properties\n\n # Instantiates a new AbstractSample.\n #\n # @param sample [Sample] Sample\n # @param expected_sample_type [String] the name of the expected sample type\n # @raises [WrongSampleTypeError] if the type of the sample is not\n def initialize(sample:, expected_sample_type:)\n @sample = sample\n @properties = sample.properties\n\n return if type?(expected_sample_type)\n\n msg = \"Sample #{sample.id}, #{sample.sample_type.name}, \" \\\n \"is not a #{expected_sample_type}.\"\n raise WrongSampleTypeError.new(\n msg: msg, sample: sample, expected_type: expected_sample_type\n )\n end\n\n # Test whether this Sample has the given sample type.\n #\n # @param sample_type [String] the name of the sample type\n # @return [Boolean] true if the sample has the named type, and false otherwise.\n def type?(expected_sample_type)\n sample \u0026\u0026 sample.sample_type.name == expected_sample_type\n end\n\n # The name of the sample.\n #\n # @return [String] the name of the sample\n def name\n sample.name\n end\n\n # Fetches a property of this sample by name.\n #\n # @param property [String] the name of the property\n # @return [Object] the value of the named property for this sample\n def fetch(property)\n properties.fetch(property)\n end\nend\n\n# Exception class for an sample with the wrong sample type.\n#\n# @attr_reader [Sample] sample the sample object\n# @attr_reader [String] expected_type the name of the expected sample type\nclass WrongSampleTypeError \u003c StandardError\n attr_reader :sample, :expected_type\n\n def initialize(msg: 'Sample is not the expected type', sample:, expected_type:)\n @sample = sample\n @expected_type = expected_type\n super(msg)\n end\nend\n"}},{"library":{"name":"Primer","category":"Sample Models","code_source":"needs 'Sample Models/AbstractSample'\n\n# frozen_string_literal: true\n\n# Defines the Primer class with methods to manage the properties of primers,\n# such as sequence, length, priming site, etc.\nclass Primer \u003c AbstractSample\n THIS_SAMPLE_TYPE = 'Primer'\n OVERHANG_SEQUENCE = 'Overhang Sequence'\n ANNEAL_SEQUENCE = 'Anneal Sequence'\n T_ANNEAL = 'T Anneal'\n private_constant(\n :THIS_SAMPLE_TYPE, :OVERHANG_SEQUENCE, :ANNEAL_SEQUENCE, :T_ANNEAL\n )\n\n # Instantiates a new Primer.\n #\n # @param sample [Sample] Sample of SampleType \"Primer\"\n # @return [Primer]\n def initialize(sample:)\n super(sample: sample, expected_sample_type: THIS_SAMPLE_TYPE)\n end\n\n # Instantiates a new Primer from an Item.\n #\n # @param item [Item] Item of a Sample of SampleType \"Primer\"\n # @return [Primer]\n def self.from_item(item)\n Primer.new(sample: item.sample)\n end\n\n # Return the overhang sequence for this Primer.\n #\n # @return [String] the overhang sequence for this Primer\n def overhang_sequence\n fetch(OVERHANG_SEQUENCE).strip\n end\n\n # Return the anneal sequence for this Primer.\n #\n # @return [String] the anneal sequence for this Primer\n def anneal_sequence\n fetch(ANNEAL_SEQUENCE).strip\n end\n\n # Return the sequence for this Primer determined as the composition of the\n # overhang and anneal sequences.\n #\n # @return the composition of the overhang and anneal sequences of this Primer\n def sequence\n overhang_sequence + anneal_sequence\n end\n\n # Return the length of the primer primer sequence in nt.\n #\n # @return [FixNum] the length of the sequence of this primer\n def length\n sequence.length\n end\n\n # The annealing temperature of the primer\n #\n # @note This is the temperature as it is entered into the database.\n # It is not calculated and may be inaccurate depending on the template.\n # @return [FixNum] the annealing temperature\n def t_anneal\n fetch(T_ANNEAL)\n end\n\n # Finds binding sites for a set of primers on a set of templates\n #\n # @param primers [Array\u003cPrimer\u003e] the primers\n # @param sites [Array\u003cString\u003e] the templates to be scanned\n # @return [Array\u003cHash\u003e]\n def self.get_bindings(primers:, sites:)\n bindings = []\n primers.each do |primer|\n sites.each do |site|\n offset = primer.detect_priming_site(template: site)\n next if offset.blank?\n added_length = primer.sequence.length - offset[1]\n\n if added_length.negative?\n raise 'Detected binding site is longer than the primer sequence.'\n end\n\n b = { primer: primer,\n site: site,\n offset: offset,\n added_length: added_length }\n bindings.append(b)\n sites.delete(site)\n break\n end\n end\n bindings\n end\n\n # Finds the first binding site for a Primer on a template.\n #\n # @param template [String] the template sequence\n # @param min_length [FixNum] the minimum length of the binding site\n # @param require_perfect [FixNum] the number of nt from the 3' end that must match perfectly\n # @param allow_mismatch [FixNum] the number of mismatches allowed (doesn't do anything currently)\n # @return [Array\u003cFixNum\u003e] a length 2 array with the start and end position\n def detect_priming_site(template:, min_length: 16, require_perfect: 3, allow_mismatch: 1)\n # TODO: make this work with multiple matches and internal matches\n matches = scan(template, last(require_perfect))\n matches.delete_if { |m| m.offset(0)[1] \u003c min_length }\n return if matches.blank?\n\n i = 1\n while matches.length \u003e 1\n query = last(require_perfect + i)\n matches.keep_if { |m| expand_match(template, m, i) =~ /#{query}/i }\n i += 1\n end\n\n _start, stop = matches[0].offset(0)\n return [] unless template[0..stop] =~ /#{last(stop)}/i\n [0, stop]\n end\n\n # Return the suffix of the sequence of the given length.\n #\n # @param length [FixNum] the number of nucleotides\n # @return [String] the last n nucleotides of the primer sequence\n def last(length)\n sequence[-length..-1]\n end\n\n # Returns all matches of the pattern in the template sequence.\n #\n # @param template [String] the sequence to be scanned\n # @param pattern [String] the sequence to scan for\n # @return [Array\u003cMatchData\u003e]\n def scan(template, pattern)\n template.to_enum(:scan, /#{pattern}/i).map { Regexp.last_match }\n end\n\n # Returns the subsequence of the template constructed extended by extending\n # the the matching range by i nucleotides at the front.\n #\n # @param template [String] the template sequence\n # @param match [MatchData] a matching subsequence\n # @param length [FixNum] the length to extend\n def expand_match(template, match, length)\n start, stop = match.offset(0)\n template[(start - length)..stop]\n end\nend\n"}},{"library":{"name":"Feedback","category":"Standard Libs","code_source":"module Feedback\n CONTAINER_NAME = \"Feedback (Virtual)\"\n SAMPLE_TYPE_NAME = \"Operation Feedback\"\n \n # This method will prompt the technician to write feedback for the operations\n # that they complete on each job. This feedback will be associated to an item\n # that represents each operation type.\n def get_protocol_feedback\n \n # Gets feedback from the user\n if debug\n feedback = \"testing for job id\"\n else\n feedback = ask_for_feedback\n end\n \n if(!feedback.blank?)\n associate_feedback feedback\n end\n \n if debug\n print_association\n end\n \n end\n \n # Associates the feedback entered by the lab technician to the OperationType of the protocol\n # that uses this library.\n #\n # @param [String] the feedback entered by the lab technician.\n def associate_feedback feedback\n operation = OperationType.find(operation_type.id)\n feedback = feedback + \"- job #{jid}\"\n \n feedback_array = []\n if(!operation.get(:feedback).nil?)\n feedback_array = operation.get(:feedback)\n end\n feedback_array.push(feedback)\n operation.associate :feedback, feedback_array\n end\n \n # Debugging method that prints all associations\n def print_association\n operation = OperationType.find(operation_type.id)\n feedback_array = operation.get(:feedback)\n if feedback_array\n show do\n title \"This is printing because debug is on\"\n note \"#{feedback_array}\"\n end\n end\n end\n \n # Returns the feedback entered by a lab technician.\n #\n # @return [Hash] the information returned by the feedback show block\n def ask_for_feedback\n feedback = show do\n title \"We want your feedback\"\n \n note \"Notice anything weird with this protocol? Tell us below!\"\n \n get \"text\", var: \"feedback_user\", label: \"Enter your feedback here\", default: \"\"\n end\n feedback[:feedback_user] # return\n end\n\nend"}},{"library":{"name":"Units","category":"Standard Libs","code_source":"# frozen_string_literal: true\n\nmodule Units\n # Volume\n MICROLITERS = 'µl'\n MILLILITERS = 'ml'\n\n # Weight\n NANOGRAMS = 'ng'\n\n # Concentration\n PICOMOLAR = 'pM'\n NANOMOLAR = 'nM'\n MICROMOLAR = 'µM'\n MILLIMOLAR = 'mM'\n MOLAR = 'M'\n\n # Temperature\n DEGREES_C = '°C'\n\n # Time\n MINUTES = 'min'\n SECONDS = 'sec'\n HOURS = 'hr'\n # Force\n TIMES_G = 'x g'\n\n # R/DNA Length\n BASEPAIRS = 'bp'\n KILOBASEPAIRS = 'kbp'\n MEGABASEPAIRS = 'mbp'\n GIGABASEPAIRS = 'gbp'\n\n # Voltage\n VOLTS = 'V'\n\n def self.qty_display(qty)\n \"#{qty[:qty]} #{qty[:units]}\"\n end\n\n def qty_display(qty)\n \"#{qty[:qty]} #{qty[:units]}\"\n end\n\n def add_qty_display(options)\n new_items = {}\n\n options.each do |key, value|\n key =~ /^(.+_)+([a-z]+)$/\n\n case Regexp.last_match(2)\n when 'microliters'\n units = MICROLITERS\n when 'milliliters'\n units = MILLILITERS\n when 'minutes'\n units = MINUTES\n else\n next\n end\n\n qty = value.to_f\n\n new_items[\"#{Regexp.last_match(1)}qty\".to_sym] = {\n qty: qty, units: units\n }\n end\n\n options.update(new_items)\n end\n\n # Return the unit constant for the the unit name if there is one.\n #\n # @param unit_name [String] the name of the unit\n # @returns the value of the constant with the given name\n # @raises BadUnitNameError if the name is not the name of a defined unit\n def self.get_unit(unit_name:)\n const_get(unit_name.upcase)\n rescue StandardError\n raise BadUnitNameError.new(name: unit_name)\n end\n\n # Exception class for bad unit name arguments to Units::get_unit.\n #\n # @attr_reader [String] name the bad unit name\n class BadUnitNameError \u003c StandardError\n attr_reader :name\n\n def initialize(msg: 'Unknown unit name', name:)\n @name = name\n super(msg)\n end\n end\n\n # Return a key for the measure hash defined on the given object type.\n #\n # The measure hash must be defined in the data property of the object\n # type as JSON.\n # For instance\n #\n # { \"measure\": { \"type\": \"concentration\", \"unit\": \"micromolar\" } }\n #\n # The key is constructed as the type name, an underscore, and the unit name.\n #\n # \"concentration_µM\"\n #\n # @param object_type [ObjectType] the object type\n # @returns the key for the measure of the the object type if there is one\n # @raises MissingObjectTypeMeasure if the object type has no measure data_object\n def self.get_measure_key(object_type:)\n data_object = object_type.data_object\n raise MissingObjectTypeMeasureError.new(name: object_type.name) unless data_object.key?(:measure)\n\n measure = object_type.data_object[:measure]\n type_name = measure[:type]\n unit_name = measure[:unit]\n \"#{type_name}_#{get_unit(unit_name: unit_name)}\"\n end\n\n # Exception class for an object type without a measure hash definition.\n #\n # @attr_reader [String] name the name of the object type\n class MissingObjectTypeMeasureError \u003c StandardError\n attr_reader :name\n\n def initialize(msg: 'ObjectType has no measure in data object', name:)\n @name = name\n super(msg)\n end\n end\nend\n"}}]}