aac-metrics 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6eb16ba6004490123feaa056a8f47b22e9f0bb2680d4d3d512666084062fb683
4
- data.tar.gz: 38e012659e66d1117839f7b29c82dbc1dac797c6af2e67411eff93bacf069836
3
+ metadata.gz: 554fc52ede0b5dffdcf9315ec279a9f42a3ef2a44a324195018eda56538336b1
4
+ data.tar.gz: 2b082df38edf52ddf495045969db894ef02f7718641658d8d1ca4b012456e14c
5
5
  SHA512:
6
- metadata.gz: b2d23f02943d8011727dc3979405111137b119cc254021f77916fa4a62df524ec9d84d078b29f8fa360d1b46a267d65e2ae10291c87c97b2457671e591665c7e
7
- data.tar.gz: '087f78505783f40b5c6f5bfaedc421833881bee629e2db39292cf8b6b23a115c6b3e332ae325d8de4d1fcce9e76b83cc4b5a44fa35787bc1b17eeb3b2acc99b3'
6
+ metadata.gz: 58c3c7e8128b0bee8754baa3b218a36491cf985ed752b917733a0508bc9a4c8abe8c3e2c40ddcb7cd9098bea51a6c3d9143f5ba4cbd2d9098f59f16899bf1e0b
7
+ data.tar.gz: c9e2ec540feee357aaf6b1a20b11e5b938d2f4f8d28ba6d03eee80245167b1eb339ab5a2e1440588db6a31cffbe27bee37a44d6ef35f0078f5381d363c06226c
@@ -37,6 +37,29 @@ module AACMetrics::Loader
37
37
  end
38
38
  if obfset
39
39
  json = JSON.parse(File.read(obfset))
40
+ relations_hash = {}
41
+ json.each do |board|
42
+ board['grid']['order'].each_with_index do |row, row_idx|
43
+ row.each_with_index do |id, col_idx|
44
+ button = id && board['buttons'].detect{|b| b['id'] == id }
45
+ if button && button['label'] && !button['clone_id']
46
+ ref = "#{board['grid']['rows']}x#{board['grid']['columns']}-#{row_idx}.#{col_idx}"
47
+ pre = 'c'
48
+ relations_hash["cpre#{ref}-#{button['label']}"] ||= []
49
+ relations_hash["cpre#{ref}-#{button['label']}"] << [board, button]
50
+ end
51
+ end
52
+ end
53
+ end
54
+ relations_hash.each do |id, cells|
55
+ if cells.length > 1
56
+ cells.each do |board, button|
57
+ board['clone_ids'] ||= []
58
+ board['clone_ids'] << id
59
+ button['clone_id'] ||= id
60
+ end
61
+ end
62
+ end
40
63
  elsif analysis
41
64
  json = JSON.parse(File.read(analysis))
42
65
  end
@@ -84,6 +107,7 @@ module AACMetrics::Loader
84
107
  path = paths.shift
85
108
  visited_paths[path] = idx
86
109
  new_json = {
110
+ "format" => 'obs',
87
111
  "id" => "brd#{idx}",
88
112
  "buttons" => [],
89
113
  "grid" => {},
@@ -158,28 +182,29 @@ module AACMetrics::Loader
158
182
  # treat action buttons for metrics
159
183
  new_btn = nil
160
184
  end
161
- # temporarily save semantic_id and possible clone_id for later use
185
+ # temporarily save semantic_id and possibly clone_id for later use
162
186
  # 1. Buttons in the same location with the same
163
187
  # semantic_id should be marked in the obfset as having
164
188
  # the same semantic_id
165
189
  # 2. Buttons in the same location with the same label & voc
166
190
  # and same load_board setting
167
191
  # should be marked in the obfset as having the same clone_id
168
- ref = "#{new_json['grid']['rows']}x#{new_json['grid']['columns']}-#{row_ids}.#{col_id}"
192
+ ref = "#{new_json['grid']['rows']}x#{new_json['grid']['columns']}-#{row_idx}.#{col_idx}"
169
193
  if btn['semantic_id']
170
194
  relations_hash["s#{ref}-#{btn['semantic_id']}"] ||= []
171
195
  relations_hash["s#{ref}-#{btn['semantic_id']}"] << [new_json['id'], new_btn['id']]
172
196
  end
173
- if new_btn['label']
174
- # TODO: currently doesn't enforce same-location on links, just whether it's a linked button or not
175
- pre = new_btn['load_board'] ? 'cl' : 'c'
197
+ if new_btn && new_btn['label']
198
+ #pre = new_btn['load_board'] ? 'cl' : 'c'
199
+ pre = 'c'
176
200
  relations_hash["#{pre}#{ref}-#{new_btn['label']}"] ||= []
177
- relations_hash["#{pre}#{ref}-#{new_btn['label']}"] ||= [new_json['id'], new_btn['id']]
201
+ relations_hash["#{pre}#{ref}-#{new_btn['label']}"] << [new_json['id'], new_btn['id']]
178
202
  end
179
- if do_ingest && new_btn['label']
203
+ if do_ingest && new_btn && new_btn['label']
180
204
  str = new_btn['label'].downcase.sub(/^\s+/, '').sub(/\s+$/, '')
181
205
  if str.scan(/\s+/).length < 2
182
206
  word_hash = Digest::MD5.hexdigest(str)[0, 10]
207
+ # Will need to re-evaluate hash process if it ever finds a collision with an already-saved word
183
208
  raise "collision!" if words[word_hash] && words[word_hash] != str
184
209
  if add_words || words[word_hash]
185
210
  words[word_hash] = str
@@ -197,9 +222,18 @@ module AACMetrics::Loader
197
222
  boards << new_json
198
223
  end
199
224
  end
225
+ # finalize all board paths once done iterating
226
+ boards.each do |brd|
227
+ brd['buttons'].each do |btn|
228
+ if btn['load_board'] && btn['load_board']['tmp_path']
229
+ btn['load_board']['id'] = "brd#{visited_paths[btn['load_board']['tmp_path']]}" if visited_paths[btn['load_board']['tmp_path']]
230
+ btn['load_board'].delete('tmp_path')
231
+ end
232
+ end
233
+ end
200
234
  # any semantic_id or clone_id repeats must be recorded
201
235
  relations_hash.each do |id, btns|
202
- if btns && btns.length > 0
236
+ if btns && btns.length > 1
203
237
  btns.each do |brd_id, btn_id|
204
238
  brd = boards.detect{|b| b['id'] == brd_id }
205
239
  if brd && brd['buttons']
@@ -220,32 +254,18 @@ module AACMetrics::Loader
220
254
  #
221
255
  end
222
256
  end
223
- boards.each do |brd|
224
- brd['buttons'].each do |btn|
225
- if btn['load_board'] && btn['load_board']['tmp_path']
226
- btn['load_board']['id'] = "brd#{visited_paths[btn['load_board']['tmp_path']]}" if visited_paths[btn['load_board']['tmp_path']]
227
- btn['load_board'].delete('tmp_path')
228
- end
229
- end
230
- end
231
257
  # TODO: record whether the board set is expected to have auto-home
232
258
  {boards: boards, words: words, words_path: words_path}
233
259
  end
234
260
 
235
- # TODO: Qualitative assessments of common vocabularies,
236
- # gather perspectives on what makes a "good" vocabulary
237
- # and collect reviews from field experts, also free
238
- # response sections.
239
- # Some criteria:
240
- # - works well for age group X, Y, Z
241
- # - works well for a beginning communicator
242
- # - allows long-term growth as-is
243
- # - comprehensive core
244
- # -
245
-
246
261
  def self.ingest(fn, token=nil)
247
262
  output = nil
248
263
  boards = nil
264
+ if fn.match(/manifest.json/)
265
+ json = JSON.parse(File.read(fn))
266
+ root_fn = json['root']
267
+ fn = fn.sub(/manifest.json/, root_fn)
268
+ end
249
269
  if fn.match(/\.obfset$/)
250
270
  boards = retrieve(fn, false)
251
271
  output = fn
@@ -268,7 +288,8 @@ module AACMetrics::Loader
268
288
  end
269
289
  end
270
290
  if boards
271
- analysis = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', fn.sub(/\.obfset$/, '.analysis')))
291
+ analysis = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', output.sub(/\.obfset$/, '.analysis')))
292
+ analysis = output.sub(/\.obfset$/, '.analysis')
272
293
  res = AACMetrics::Metrics.analyze(boards, false)
273
294
  f = File.open(analysis, 'w')
274
295
  f.write(JSON.pretty_generate(res))
@@ -5,34 +5,38 @@
5
5
  # - the vocabulary organization of this set makes sense
6
6
  # - this set provides clear opportunities for user-specific words to be added
7
7
  # - grammatical forms, growth over time
8
+ # - works well for age group X, Y, Z
9
+ # - works well for a beginning communicator
10
+ # - allows long-term growth as-is
11
+ # - comprehensive core
12
+
8
13
  # Effort algorithms for scanning/eyes
9
- # TODO: manual way to flag button as conceptually
10
- # related to the same-locaed button on the
11
- # prior board, allowing for a discounted penalty
12
14
  module AACMetrics::Metrics
13
- # TODO:
14
- # 1. When navigating from one board to the next, grid locations
15
+ # A. When navigating from one board to the next, grid locations
15
16
  # with the same clone_id or semantic_id should result in a
16
17
  # discount to overall search based more on the number of
17
18
  # uncloned/unsemantic buttons than the number of total buttons
18
19
  # (perhaps also factoring in the percent of board with that
19
20
  # id present in the full board set)
20
- # 2. When selecting a button with a semantic_id or clone_id,
21
+ # B. When selecting a button with a semantic_id or clone_id,
21
22
  # a discount to both search and selection should
22
23
  # be applied based on the percent of boards that
23
24
  # contain the same id at that grid location
24
- # 3.5 When selecting a button with a semantic_id or clone_id,
25
+ # C. When selecting a button with a semantic_id or clone_id,
25
26
  # if the same id was present on the previous board,
26
27
  # an additional discount to search and selection should be applied
27
- def self.analyze(obfset, output=true, include_obfset=false)
28
+ # D When selecting a button with a semantic_id or clone_id,
29
+ # apply a steep discount to the button in the same location
30
+ # as the link used to get there if they share an id
31
+ def self.analyze(obfset, output=true, include_obfset=false)
28
32
  locale = nil
29
33
  buttons = []
30
- refs = {}
34
+ set_refs = {}
31
35
  grid = {}
32
36
 
33
37
  if obfset.is_a?(Hash) && obfset['buttons']
34
38
  locale = obfset['locale'] || 'en'
35
- refs = obfset['reference_counts']
39
+ set_refs = obfset['reference_counts']
36
40
  grid = obfset['grid']
37
41
  buttons = []
38
42
  obfset['buttons'].each do |btn|
@@ -49,13 +53,14 @@ module AACMetrics::Metrics
49
53
  else
50
54
  visited_board_ids = {}
51
55
  to_visit = [{board: obfset[0], level: 0, entry_x: 1.0, entry_y: 1.0}]
52
- refs = {}
56
+ set_refs = {}
53
57
  rows_tally = 0.0
54
58
  cols_tally = 0.0
55
59
  root_rows = nil
56
60
  root_cols = nil
57
61
  # Gather repeated words/concepts
58
62
  obfset.each do |board|
63
+ # try to figure out the average grid size for board set
59
64
  root_rows ||= board['grid']['rows']
60
65
  root_cols ||= board['grid']['columns']
61
66
  rows_tally += board['grid']['rows']
@@ -63,25 +68,27 @@ module AACMetrics::Metrics
63
68
  # determine frequency within the board set
64
69
  # for each semantic_id and clone_id
65
70
  if board['clone_ids']
66
- boards['clone_ids'].each do |id|
67
- refs[id] ||= 0
68
- refs[id] += 1
71
+ board['clone_ids'].each do |id|
72
+ set_refs[id] ||= 0
73
+ set_refs[id] += 1
69
74
  end
70
75
  end
71
76
  if board['semantic_ids']
72
- boards['semantic_ids'].each do |id|
73
- refs[id] ||= 0
74
- refs[id] += 1
77
+ board['semantic_ids'].each do |id|
78
+ set_refs[id] ||= 0
79
+ set_refs[id] += 1
75
80
  end
76
81
  end
77
82
  end
83
+ # If the average grid size is much different than the root
84
+ # grid size, only then use the average as the size for this board set
78
85
  if (rows_tally / obfset.length.to_f - root_rows).abs > 3 || (cols_tally / obfset.length.to_f - root_cols).abs > 3
79
86
  root_rows = (rows_tally / obfset.length.to_f).floor
80
87
  root_cols = (cols_tally / obfset.length.to_f).floor
81
88
  end
82
- pcts = {}
83
- refs.each do |id, cnt|
84
- pcts[id] = cnt.to_f / obfset.length.to_f
89
+ set_pcts = {}
90
+ set_refs.each do |id, cnt|
91
+ set_pcts[id] = cnt.to_f / obfset.length.to_f
85
92
  end
86
93
  locale = obfset[0]['locale']
87
94
  known_buttons = {}
@@ -102,20 +109,56 @@ module AACMetrics::Metrics
102
109
  # whose semantic_id or clone_id is repeated in the board set
103
110
  # -0.0025 (* pct of matching boards) for semantic_id
104
111
  # -0.005 (* pct of matching boards) for clone_id
112
+ reuse_discount = 0.0
105
113
  board[:board]['grid']['rows'].times do |row_idx|
106
114
  board[:board]['grid']['columns'].times do |col_idx|
107
115
  button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
108
116
  button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
109
- if button && button['clone_id'] && pcts[button['clone_id']]
110
- board_effort -= 0.005 * pcts[button['clone_id']]
111
- elsif button && button['semantic_id'] && pcts[button['semantic_id']]
112
- board_effort -= 0.0025 * pcts[button['semantic_id']]
117
+ if button && button['clone_id'] && set_pcts[button['clone_id']]
118
+ reuse_discount += REUSED_CLONE_FROM_OTHER_DISCOUNT * set_pcts[button['clone_id']]
119
+ elsif button && button['semantic_id'] && set_pcts[button['semantic_id']]
120
+ reuse_discount += REUSED_SEMANTIC_FROM_OTHER_DISCOUNT * set_pcts[button['semantic_id']]
113
121
  end
114
122
  end
115
123
  end
116
-
124
+ board_effort -= reuse_discount
117
125
  prior_buttons = 0
118
126
 
127
+ # Calculate the percent of links to this board
128
+ # that had or were linked by clone_ids or semantic_ids
129
+ board_pcts = {}
130
+ obfset.each do |brd|
131
+ brd['buttons'].each do |link_btn|
132
+ # For every board that links to this board
133
+ if link_btn['load_board'] && link_btn['load_board']['id'] == board[:board]['id']
134
+ board_pcts['all'] ||= 0
135
+ board_pcts['all'] += 1
136
+ # Count how many of those links have a clone_id or semantic_id
137
+ if link_btn['clone_id']
138
+ board_pcts[link_btn['clone_id']] ||= 0
139
+ board_pcts[link_btn['clone_id']] += 1
140
+ end
141
+ if link_btn['semantic_id']
142
+ board_pcts[link_btn['semantic_id']] ||= 0
143
+ board_pcts[link_btn['semantic_id']] += 1
144
+ end
145
+ # Also count all the clone_ids and semantic_ids
146
+ # anywhere on the boards that link to this one
147
+ (brd['clone_ids'] || []).uniq.each do |cid|
148
+ board_pcts["upstream-#{cid}"] ||= 0
149
+ board_pcts["upstream-#{cid}"] += 1
150
+ end
151
+ (brd['semantic_ids'] || []).uniq.each do |sid|
152
+ board_pcts["upstream-#{sid}"] ||= 0
153
+ board_pcts["upstream-#{sid}"] += 1
154
+ end
155
+ end
156
+ end
157
+ end
158
+ board_pcts.each do |id, cnt|
159
+ board_pcts[id] = board_pcts[id].to_f / board_pcts['all'].to_f
160
+ end
161
+
119
162
  board[:board]['grid']['rows'].times do |row_idx|
120
163
  board[:board]['grid']['columns'].times do |col_idx|
121
164
  button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
@@ -125,13 +168,34 @@ module AACMetrics::Metrics
125
168
  x = (btn_width / 2) + (btn_width * col_idx)
126
169
  y = (btn_height / 2) + (btn_height * row_idx)
127
170
  # prior_buttons = (row_idx * board[:board]['grid']['columns']) + col_idx
171
+ # calculate the percentage of links that point to this button
172
+ # and match on semantic_id or clone_id
128
173
  effort = 0
129
- # TODO: additional discount on board search effort
130
- # if this button's semantic_id or clone_id
131
- # was also present on the prior board
132
- # board_effort * 0.5 for semantic_id
133
- # board_effort * 0.33 for clone_id
134
- effort += board_effort
174
+ # Additional discount on board search effort,
175
+ # remember that semantic_id and clone_id are
176
+ # keyed to the same grid location, so matches only
177
+ # apply to that specific location
178
+ # - if this button's semantic_id or clone_id
179
+ # was also present anywhere on the prior board
180
+ # board_effort * 0.5 for semantic_id
181
+ # board_effort * 0.33 for clone_id
182
+ # - if this button's semantic_id or clone_id
183
+ # is directly used to navigate to this board
184
+ # board_effort * 0.1 for semantic_id
185
+ # board_effort * 0.1 for clone_id
186
+ button_effort = board_effort
187
+ if board_pcts[button['semantic_id']]
188
+ # TODO: Pull out these magic numbers
189
+ button_effort = [button_effort, button_effort * SAME_LOCATION_AS_PRIOR_DISCOUNT / board_pcts[button['semantic_id']]].min
190
+ elsif board_pcts["upstream-#{button['semantic_id']}"]
191
+ button_effort = [button_effort, button_effort * RECOGNIZABLE_SEMANTIC_FROM_PRIOR_DISCOUNT / board_pcts["upstream-#{button['semantic_id']}"]].min
192
+ end
193
+ if board_pcts[button['clone_id']]
194
+ button_effort = [button_effort, button_effort * SAME_LOCATION_AS_PRIOR_DISCOUNT / board_pcts[button['clone_id']]].min
195
+ elsif board_pcts["upstream-#{button['clone_id']}"]
196
+ button_effort = [button_effort, button_effort * RECOGNIZABLE_CLONE_FROM_PRIOR_DISCOUNT / board_pcts["upstream-#{button['clone_id']}"]].min
197
+ end
198
+ effort += button_effort
135
199
  # add effort for percent distance from entry point
136
200
  distance = distance_effort(x, y, board[:entry_x], board[:entry_y])
137
201
  # TODO: decrease effective distance if the semantic_id or clone_id:
@@ -141,6 +205,24 @@ module AACMetrics::Metrics
141
205
  # - was also present on the prior board (total)
142
206
  # distance * 0.5 for semantic_id
143
207
  # distance * 0.33 for clone_id
208
+ # - is directly used to navigate to this board
209
+ # distance * 0.1 * (pct of links that match) for semantic_id
210
+ # distance * 0.1 * (pct of links that match) for clone_id
211
+ if board_pcts[button['semantic_id']]
212
+ distance = [distance, distance * SAME_LOCATION_AS_PRIOR_DISCOUNT / board_pcts[button['semantic_id']]].min
213
+ elsif board_pcts["upstream-#{button['semantic_id']}"]
214
+ distance = [distance, distance * RECOGNIZABLE_SEMANTIC_FROM_PRIOR_DISCOUNT / board_pcts["upstream-#{button['semantic_id']}"]].min
215
+ elsif set_pcts[button['semantic_id']]
216
+ distance = [distance, distance * RECOGNIZABLE_SEMANTIC_FROM_OTHER_DISCOUNT / set_pcts[button['semantic_id']]].min
217
+ end
218
+ if board_pcts[button['clone_id']]
219
+ distance = [distance, distance * SAME_LOCATION_AS_PRIOR_DISCOUNT / board_pcts[button['clone_id']]].min
220
+ elsif board_pcts["upstream-#{button['clone_id']}"]
221
+ distance = [distance, distance * RECOGNIZABLE_CLONE_FROM_PRIOR_DISCOUNT / board_pcts["upstream-#{button['clone_id']}"]].min
222
+ elsif set_pcts[button['clone_id']]
223
+ distance = [distance, distance * RECOGNIZABLE_CLONE_FROM_OTHER_DISCOUNT / set_pcts[button['clone_id']]].min
224
+ end
225
+
144
226
  effort += distance
145
227
  if distance > DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN || (board[:entry_x] == 1.0 && board[:entry_y] == 1.0)
146
228
  # add small effort for every prior (visible) button when visually scanning
@@ -170,31 +252,39 @@ module AACMetrics::Metrics
170
252
  end
171
253
  if try_visit
172
254
  next_board = obfset.detect{|brd| brd['id'] == button['load_board']['id'] }
173
- puts "LIKE[] #{effort}" if button['label'] == 'like'
255
+ change_effort = BOARD_CHANGE_PROCESSING_EFFORT
174
256
  if next_board
175
257
  to_visit.push({
176
258
  board: next_board,
177
259
  level: board[:level] + 1,
178
- prior_effort: effort + BOARD_CHANGE_PROCESSING_EFFORT,
260
+ prior_effort: effort + change_effort,
179
261
  entry_x: x,
180
- entry_y: y
262
+ entry_y: y,
263
+ entry_clone_id: button['clone_id'],
264
+ entry_semantic_id: button['semantic_id']
181
265
  })
182
266
  end
183
267
  end
184
268
  else
185
269
  word = button['label']
186
270
  existing = known_buttons[word]
187
- button['effort'] = effort
188
- if !existing || existing[:effort] < effort #board[:level] < existing[:level]
189
- puts "LIKE #{effort}" if button['label'] == 'like'
271
+ if !existing || effort < existing[:effort] #board[:level] < existing[:level]
272
+ if board_pcts[button['clone_id']]
273
+ effort -= [BOARD_CHANGE_PROCESSING_EFFORT, BOARD_CHANGE_PROCESSING_EFFORT * 0.3 / board_pcts[button['clone_id']]].min
274
+ elsif board_pcts[button['semantic_id']]
275
+ effort -= [BOARD_CHANGE_PROCESSING_EFFORT, BOARD_CHANGE_PROCESSING_EFFORT * 0.5 / board_pcts[button['semantic_id']]].min
276
+ end
277
+
190
278
  known_buttons[word] = {
191
279
  id: "#{button['id']}::#{board[:board]['id']}",
192
280
  label: word,
193
281
  level: board[:level],
194
- effort: effort
282
+ effort: effort,
195
283
  }
196
284
  end
197
285
  end
286
+ button['effort'] = effort
287
+
198
288
  end
199
289
  end
200
290
  end
@@ -212,7 +302,7 @@ module AACMetrics::Metrics
212
302
  locale: locale,
213
303
  total_boards: total_boards,
214
304
  total_buttons: buttons.length,
215
- reference_counts: refs,
305
+ reference_counts: set_refs,
216
306
  grid: {
217
307
  rows: root_rows,
218
308
  columns: root_cols
@@ -228,12 +318,19 @@ module AACMetrics::Metrics
228
318
 
229
319
  SQRT2 = Math.sqrt(2)
230
320
  BUTTON_SIZE_MULTIPLIER = 0.09
231
- FIELD_SIZE_MULTIPLIER = 0.017
232
- VISUAL_SCAN_MULTIPLIER = 0.02
321
+ FIELD_SIZE_MULTIPLIER = 0.005
322
+ VISUAL_SCAN_MULTIPLIER = 0.015
233
323
  BOARD_CHANGE_PROCESSING_EFFORT = 1.0
234
- DISTANCE_MULTIPLIER = 0.5
324
+ DISTANCE_MULTIPLIER = 0.4
235
325
  DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN = 0.1
236
326
  SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER = 0.5
327
+ SAME_LOCATION_AS_PRIOR_DISCOUNT = 0.1
328
+ RECOGNIZABLE_SEMANTIC_FROM_PRIOR_DISCOUNT = 0.5
329
+ RECOGNIZABLE_SEMANTIC_FROM_OTHER_DISCOUNT = 0.6
330
+ RECOGNIZABLE_CLONE_FROM_PRIOR_DISCOUNT = 0.33
331
+ RECOGNIZABLE_CLONE_FROM_OTHER_DISCOUNT = 0.4
332
+ REUSED_SEMANTIC_FROM_OTHER_DISCOUNT = 0.0025
333
+ REUSED_CLONE_FROM_OTHER_DISCOUNT = 0.005
237
334
 
238
335
  def self.button_size_effort(rows, cols)
239
336
  BUTTON_SIZE_MULTIPLIER * (rows + cols) / 2