Module: Krill::Base

Defined in:
lib/krill/base.rb,
lib/krill/find.rb,
lib/krill/inventory.rb,
lib/krill/transfers.rb,
lib/krill/operations.rb,
lib/krill/operation_list_input_table.rb

Instance Method Summary collapse

Instance Method Details

#box_interactive(items, method, user_shows) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/krill/inventory.rb', line 120

def box_interactive(items, method, user_shows)

  boxes, extras = boxes_for items

  if method == :take
    show_title = 'Take from '
    box_note = 'Collect Item(s)'
    extra_title = 'Gather the Following Additional Item(s)'
  elsif method == :return
    show_title = 'Return to '
    box_note = 'Return Item(s)'
    extra_title = 'Return the Following Additional Item(s)'
  else
    show_title = ''
    box_note = ''
    extra_title = ''
  end

  unless boxes.empty?
    contents = boxes.keys.collect { |b| { content: b, check: true } }
    show do
      title 'Boxes Required'
      note 'You will need the following boxes from the freezer(s)'
      table contents.each_slice(6).to_a
    end
  end

  boxes.each do |name, box|
    show do
      title show_title + name
      note box_note
      table box.table
      raw user_shows
    end
  end

  return if extras.empty?

  takes = extras.collect(&:features)
  show do
    title extra_title
    takes.each do |t|
      item t
    end
    raw user_shows
  end
end

#boxes_for(items) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/krill/inventory.rb', line 95

def boxes_for(items)

  boxes = {}

  r = Regexp.new '(M20|M80|SF[0-9]*)\.[0-9]+\.[0-9]+\.[0-9]+'

  loc_matched_items = items.select { |i| r.match(i.location) }
  extras = items - loc_matched_items

  # make boxes for the items with valid box locations, sorted by location
  (sort_by_location loc_matched_items).each do |i|

    freezer, hotel, box, slot = i.location.split('.')
    slot = slot.to_i
    name = "#{freezer}.#{hotel}.#{box}"

    boxes[name] = Box.new unless boxes[name]
    boxes[name].highlight slot, i.id

  end

  [boxes, extras]

end

#collection_from(id) ⇒ Collection

Upgrade an item to a Collection.

Parameters:

  • id (Item/Fixnum)

    An Item, or an id of an item to be upgraded

Returns:

  • (Collection)

    the Collection that the item corresponds to, if any



66
67
68
# File 'lib/krill/inventory.rb', line 66

def collection_from(id)
  Collection.find id
end

#debugBoolean

Returns true if and only if the protocol is being run in debug mode.

Returns:

  • (Boolean)


12
13
14
# File 'lib/krill/base.rb', line 12

def debug
  false
end

#distribute(col, object_type_name, options = {}) {|block| ... } ⇒ Array<Item>

Opposite of load_samples, displays how to transfer sample from each part of a collection into distinct new Items.

Examples:

suppose you had a gel with ladder in lanes (1,1) and (2,1) and you wanted to make gel fragments from the lanes.

slices = distribute( gel, "Gel Slice", except: [ [0,0], [1,0] ], interactive: true ) {
  title "Cut gel slices and place them in new 1.5 mL tubes"
  note "Label the tubes with the id shown"
}

Parameters:

  • col (Collection)

    the collection to distribute from

  • object_type_name (String)

    the object type of the new items that will be made

Yields:

Returns:

  • (Array<Item>)

    new items that are made from the samples in the collection



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/krill/transfers.rb', line 180

def distribute(col, object_type_name, options = {})

  opts = { except: [], interactive: false }.merge options

  object_type = ObjectType.find_by(name: object_type_name)
  raise "Could not find object type #{object_type_name} in distribute" unless object_type

  user_shows = if block_given?
                 ShowBlock.new.run(&Proc.new)
               else
                 []
               end

  m = col.matrix
  items = []
  routes = []

  (0..m.length - 1).each do |i|
    (0..m[i].length - 1).each do |j|
      next unless m[i][j] > 0 && !(opts[:except].include? [i, j])

      s = find(:sample, id: m[i][j])[0]
      item = Item.make({ quantity: 1, inuse: 0 }, sample: s, object_type: object_type)
      items.push item
      routes.push from: [i, j], to: item
    end
  end

  if opts[:interactive]
    show do
      table [
        ['Row', 'Column', 'New ' + object_type_name + ' id']
      ].concat(routes.collect { |r| [r[:from][0] + 1, r[:from][1] + 1, r[:to].id] })
      raw user_shows
    end
  end

  items

end

#error(e) ⇒ Object



72
73
74
# File 'lib/krill/base.rb', line 72

def error(e)
  Job.find(jid).reload.append_step operation: 'error', message: e.to_s, backtrace: e.backtrace[0, 10]
end

#finish_show(page) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/krill/base.rb', line 24

def finish_show(page)

  # increment pc
  @job ||= Job.find(jid)
  @job.append_step operation: 'display', content: page
  # @job.pc += 1
  # @job.save

  if !debug

    # stop and wait for technician to click OK
    mutex.synchronize { thread_status.running = false }
    Thread.stop

    # get technician input
    @job.reload
    job_state = @job.job_state
    input = ShowResponse.new(job_state.last[:inputs])

    # populate operations with table input data
    input[:table_inputs].each do |table_input|
      operation = operations.find { |op| op.id == table_input[:opid] }
      next unless operation

      operation.temporary[table_input[:key].to_sym] = if table_input[:type] == 'number'
                                                        table_input[:value].to_f
                                                      else
                                                        table_input[:value]
                                                      end
    end

    # return the technician input
    input

  else

    # figure out default technician response
    i = ShowResponse.new(simulated_input_for(page))
    @job.append_step operation: 'next', time: Time.now, inputs: i

    raise "Job #{jid} executed too many steps (50) in debug mode. Could be an infinite loop." if @job.pc > 500

    i

  end

end

#insert_operation(index, element) ⇒ Object



36
37
38
39
40
41
42
43
44
# File 'lib/krill/operations.rb', line 36

def insert_operation(index, element)
  before = @operations[0, index]
  after = @operations[index, @operations.length - index]
  @operations = before + [element] + after
  @operations.extend(OperationList)
  @operations.protocol = self
  @operations.length # force db query
  @operations
end

#load_samples(headings, ingredients, collections) {|block| ... } ⇒ Object

Displays a table to the user that describes how to load a number of samples into a collection.

Examples:

shows the user a table that describes how to arrays of templates, forward primers, and reverse primers into a set of stripwell tubes

load_samples(
  [ "Template, 1 µL", "Forward Primer, 2.5 µL", "Reverse Primer, 2.5 µL" ],
  [  templates,        forward_primers,          reverse_primers         ],
  stripwells ) {
    note "Load templates first, then forward primers, then reverse primers."
    warning "Use a fresh pipette tip for each transfer."
  }

Parameters:

  • headings (Array<String>)

    describes how much to transfer of each ingredient

  • ingredients (Array<Array<Item>>)

    items to be loaded from

  • collections (Array<Collections>)

    the collections that will be loaded into

Yields:



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/krill/transfers.rb', line 22

def load_samples(headings, ingredients, collections) # needs a better name

  user_shows = if block_given?
                 ShowBlock.new.run(&Proc.new)
               else
                 []
               end

  raise 'Empty collection list' if collections.empty?

  heading = [[collections[0].object_type.name.to_s, 'Location'] + headings]
  i = 0

  collections.each do |col|

    tab = []
    m = col.matrix

    (0..m.length - 1).each do |r|
      (0..m[r].length - 1).each do |c|
        if i < ingredients[0].length
          loc = if m.length == 1
                  (c + 1).to_s
                else
                  "#{r + 1},#{c + 1}"
                end
          tab.push([col.id, loc] + ingredients.collect { |ing| { content: ing[i].is_a?(Item) ? ing[i].id : ing[i], check: true } })
        end
        i += 1
      end
    end

    show do
      title "Load #{col.object_type.name} #{col.id}"
      table heading + tab
      raw user_shows
    end

  end

end

#new_collection(name) ⇒ Object

This is the same as Collection.new_collection.



58
59
60
# File 'lib/krill/inventory.rb', line 58

def new_collection(name)
  Collection.new_collection name
end

#new_object(name) ⇒ Object

Create a new item with only an ObjectType

Parameters:

  • name (String)

    the name of the ObjectType that will be instantiated into item



38
39
40
# File 'lib/krill/inventory.rb', line 38

def new_object(name)
  Item.new_object name
end

#new_sample(name, spec) ⇒ Object

Create a new item with a Sample and ObjectType.

Parameters:

  • name (String)

    the name of the sample that will be instantiated into item

  • spec (String)

    the name of the ObjectType that will be instantiated into item



46
47
48
49
50
51
52
53
# File 'lib/krill/inventory.rb', line 46

def new_sample(name, spec)
  s = Sample.find_by(name: name)
  ot = ObjectType.find_by(name: spec[:as])
  raise "Unknown sample #{name}" unless s
  raise "Unknown container #{spec[:as]}" unless ot

  Item.make({ quantity: 1, inuse: 0 }, sample: s, object_type: ot)
end

#operation_typeOperationType

Gets the OperationType for this job.

Returns:



28
29
30
31
32
33
34
# File 'lib/krill/operations.rb', line 28

def operation_type

  ops = operations

  ops[0].operation_type unless ops.empty?

end

#operations(opts = { force: false }) ⇒ OperationsList

Gets the OperationList for this job.

Returns:

  • (OperationsList)

    the list of operations for this job



11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/krill/operations.rb', line 11

def operations(opts = { force: false })

  if opts[:force] || !@operations
    op_ids = JobAssociation.where(job_id: jid).collect(&:operation_id)
    @operations = Operation.includes(:operation_type).find(op_ids)
    @operations.extend(OperationList)
    @operations.protocol = self
    @operations.length # force db query
  end

  @operations

end

#produce(items) ⇒ Object



264
265
266
267
268
269
270
271
# File 'lib/krill/inventory.rb', line 264

def produce(items)
  if items.class == Array
    take items
  else
    (take [items])[0]
  end

end

#release(items, args = {}) ⇒ Object

The other side of #take, releases a list of items from being associated to the job.

Examples:

releasing a long list of items that goes through freezer boxes

release items, interactive: true,  method: "boxes"

Parameters:

  • items (Array<Items>)

    the items that will be unassociated with this job

  • args (Hash) (defaults to: {})

    additional optional arguments

Options Hash (args):

  • :interactive (Boolean)

    decide whether to show instructions to technician to put items away, or to silently release the items

  • :method (String)

    decide how to show instructions to technician



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/krill/inventory.rb', line 226

def release(items, args = {})

  user_shows = if block_given?
                 ShowBlock.new.run(&Proc.new)
               else
                 []
               end

  options = {
    interactive: false
  }.merge args

  if options[:interactive]

    case options[:method]

    when 'boxes'

      box_interactive items, :return, user_shows

    else

      rels = items.collect(&:features)
      show do
        title 'Return the Following Item(s)'
        rels.each do |r|
          item r
        end
        raw user_shows
      end
    end

  end

  items

end

#showObject

Show instructions to technician.

See Also:



19
20
21
22
# File 'lib/krill/base.rb', line 19

def show
  page = ShowBlock.new(self).run(&Proc.new)
  finish_show(page)
end

#show_with_input_table(ops, create_block, num_tries = 5) ⇒ Object

Create a Proc that details how to create the table Pass in operations (virtual or non-virtual) Pass optional block with additional instructions



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/krill/operation_list_input_table.rb', line 127

def show_with_input_table(ops, create_block, num_tries = 5)
  ops.extend(OperationList)
  counter = 0
  results = nil
  continue = true
  msgs = []
  while continue && (counter < num_tries)
    counter += 1
    input_table = create_block.call(ops)
    extra = ShowBlock.new(self).run(&Proc.new) if block_given?

    results = show do
      raw extra if block_given?
      if msgs.any?
        msgs.each do |m|
          warning m
        end
      end
      table input_table.render
    end

    msgs = ops.cleanup_input_table
    continue = if msgs.any?
                 true
               else
                 false
               end
  end
  results
end

#sort_by_location(items) ⇒ Object

Sorts items in place alphanumerically by freezer, hotel, box, then slot.

Parameters:

  • items (Array<Item>)

    list of items to sort



81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/krill/inventory.rb', line 81

def sort_by_location(items)
  return [] if items.empty?

  locations = items.map { |item| item.location.split('.') }
  sorted_locations = locations.sort do |loc1, loc2|
    comp = loc1[0] <=> loc2[0]
    comp = comp.zero? ? loc1[1].to_i <=> loc2[1].to_i : comp
    comp = comp.zero? ? loc1[2].to_i <=> loc2[2].to_i : comp
    comp.zero? ? loc1[3].to_i <=> loc2[3].to_i : comp
  end
  loc_strings = sorted_locations.map { |loc| "#{loc[0]}.#{loc[1]}.#{loc[2]}.#{loc[3]}" }
  items.sort_by! { |item| loc_strings.index(item.location) }
end

#spread(samples, name, options = {}) ⇒ Object

This is the same as Collection.spread.

See Also:



73
74
75
76
# File 'lib/krill/inventory.rb', line 73

def spread(samples, name, options = {})
  opts = { reverse: false }.merge(options)
  Collection.spread samples, name, opts
end

#take(items, args = {}) ⇒ Object

Associate the item with the job running the protocol, until it is released (see #release). It also "touches" the item by the job, so that one can later determine that the item was used by the job.

Examples:

taking a long list of items that goes through freezer boxes

take items, interactive: true,  method: "boxes"

Parameters:

  • items (Array<Items>)

    the items that will be associated with this job

  • args (Hash) (defaults to: {})

    additional optional arguments

Options Hash (args):

  • :interactive (Boolean)

    decide whether to show instructions to technician to find items, or to silently take the items

  • :method (String)

    decide how to show instructions to technician



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/krill/inventory.rb', line 178

def take(items, args = {})

  user_shows = if block_given?
                 ShowBlock.new.run(&Proc.new)
               else
                 []
               end

  options = {
    interactive: false,
    method: 'list'
  }.merge args

  if options[:interactive]

    case options[:method]

    when 'boxes'

      box_interactive items, :take, user_shows

    else

      takes = items.collect(&:features)
      show do
        title 'Gather the Following Item(s)'
        takes.each do |t|
          item t
        end
        raw user_shows
      end
    end

  end

  items

end

#transfer(sources, destinations, _options = {}) {|block| ... } ⇒ Object

Displays a set of pages using the transfer method from show that describe to the user how to transfer individual parts of some quantity of source wells to some quantity of destination wells. Routing is computed automatically.

Examples:

transfer all the wells in a set of stripwell tubes into the non-empty lanes of a set of gels

transfer( stripwells, gels ) {
  note "Use a 100 µL pipetter to transfer 10 µL from the PCR results to the gel as indicated."
}

Parameters:

  • sources (Array<Collection>)

    collections that will be transfered from

  • destinations (Array<Collection>)

    collections that will recieve new parts from the source collections

Yields:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/krill/transfers.rb', line 77

def transfer(sources, destinations, _options = {})

  # go through each well of the sources and transfer it to the next empty well of
  # destinations. Every time a source or destination is used up, advance to
  # another step.

  user_shows = if block_given?
                 ShowBlock.new.run(&Proc.new)
               else
                 []
               end

  # source and destination indices
  s = 0
  d = 0

  # matrix indices
  sr = 0
  sc = 0
  dr = 0
  dc = 0
  dr, dc = destinations[0].next 0, 0, skip_non_empty: true unless destinations[0].matrix[dr][dc] == -1

  routing = []

  until sr.nil?

    # add to routing table
    routing.push(from: [sr, sc], to: [dr, dc])

    # increase sr,sc,dr,dc
    sr, sc = sources[s].next sr, sc, skip_non_empty: false
    dr, dc = destinations[d].next dr, dc, skip_non_empty: true

    # if either is nil or if the source well is empty
    next unless !sr || !dr || sources[s].matrix[sr][sc] == -1

    # display
    show do
      title "Transfer from #{sources[s].object_type.name} #{sources[s].id} to #{destinations[d].object_type.name} #{destinations[d].id}"
      transfer sources[s], destinations[d], routing
      raw user_shows
    end

    # update destination collection
    routing.each do |r|
      destinations[d].set r[:to][0], r[:to][1], Sample.find(sources[s].matrix[r[:from][0]][r[:from][1]])
    end

    destinations[d].save

    # clear routing for next step
    routing = []

    # BUGFIX by Yaoyu Yang
    # return if sources[s].matrix[sr][sc] == -1
    #
    if sr && sources[s].matrix[sr][sc] == -1
      s += 1
      return unless s < sources.length

      sr = 0
      sc = 0
    end
    # END BUGFIX

    # update source indices
    unless sr
      s += 1
      return unless s < sources.length

      sr = 0
      sc = 0
    end

    # update destination indices
    next if dc

    d += 1
    return unless d < destinations.length

    dr = 0
    dc = 0
    dr, dc = destinations[d].next 0, 0, skip_non_empty: true unless destinations[d].matrix[dr][dc] == -1

  end

  nil

end