aac-metrics 0.1.1 → 0.1.3

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: 24e65891d54936aeecfeab18e673f39e27c8d85ed71131658118bf0764502dd6
4
- data.tar.gz: f3eea991fad67e0445a52462a96e5af2f7e72737464c22883e80222926f2dd16
3
+ metadata.gz: 57db00976963f424efaab981a2b5066280b2de8dd8d3b241301ba3880fe1ce5c
4
+ data.tar.gz: fc386c30321ad3f94f03b00bfde72c19ea20dfebf400f6cc8322c841c5b5950b
5
5
  SHA512:
6
- metadata.gz: 433769c391cb7039957b338c6263ad8c0c107b8e2c67f3bfe7e4e30276816290c859bad7add69bdb06ed58e88d25fc9400cd89dd9791b600d028f023483ff8f9
7
- data.tar.gz: cdb2251cfa3d2531ffb2fa8b8cff28b0dc9902b19c8f83a43c8faa641981f92ad7cb1655c86d4b6f510df907ad41546fa63bfdd0475cebb41330348110a20ca2
6
+ metadata.gz: 4b9778b4e5a1dce3c1571bdd3038025b7ca6acec1dc9359cc4a04c5183dfd6640fd11fc06651d3059a5561f20d5f22974e6fa35c72d6b6273ee326b38c09612b
7
+ data.tar.gz: a2762f3a27e48b3445495d0ff16e46da23d80bd4d8e4a08f347df957fd3b595bc93f7697c769b92ba61bd633d57ec07b8df83c0bc2a1d374b763e48be9506724
@@ -78,6 +78,7 @@ module AACMetrics::Loader
78
78
  words_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', "base_words"))
79
79
  words = nil
80
80
  do_ingest = true
81
+ relations_hash = {}
81
82
 
82
83
  while paths.length > 0
83
84
  path = paths.shift
@@ -130,17 +131,7 @@ module AACMetrics::Loader
130
131
  "id" => "btn#{btn_idx}",
131
132
  "label" => (btn['vocalization'] || '').length > 0 ? btn['vocalization'] : btn['label']
132
133
  }
133
- if do_ingest && new_btn['label']
134
- str = new_btn['label'].downcase.sub(/^\s+/, '').sub(/\s+$/, '')
135
- if str.scan(/\s+/).length < 2
136
- word_hash = Digest::MD5.hexdigest(str)[0, 10]
137
- raise "collision!" if words[word_hash] && words[word_hash] != str
138
- if add_words || words[word_hash]
139
- words[word_hash] = str
140
- new_btn['label'] = "$#{word_hash}"
141
- end
142
- end
143
- end
134
+ # record load_board reference
144
135
  btn_idx += 1
145
136
  if btn['load_board']
146
137
  if btn['load_board']['path']
@@ -167,6 +158,36 @@ module AACMetrics::Loader
167
158
  # treat action buttons for metrics
168
159
  new_btn = nil
169
160
  end
161
+ # temporarily save semantic_id and possible clone_id for later use
162
+ # 1. Buttons in the same location with the same
163
+ # semantic_id should be marked in the obfset as having
164
+ # the same semantic_id
165
+ # 2. Buttons in the same location with the same label & voc
166
+ # and same load_board setting
167
+ # 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}"
169
+ if btn['semantic_id']
170
+ relations_hash["s#{ref}-#{btn['semantic_id']}"] ||= []
171
+ relations_hash["s#{ref}-#{btn['semantic_id']}"] << [new_json['id'], new_btn['id']]
172
+ 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'
176
+ relations_hash["#{pre}#{ref}-#{new_btn['label']}"] ||= []
177
+ relations_hash["#{pre}#{ref}-#{new_btn['label']}"] ||= [new_json['id'], new_btn['id']]
178
+ end
179
+ if do_ingest && new_btn['label']
180
+ str = new_btn['label'].downcase.sub(/^\s+/, '').sub(/\s+$/, '')
181
+ if str.scan(/\s+/).length < 2
182
+ word_hash = Digest::MD5.hexdigest(str)[0, 10]
183
+ raise "collision!" if words[word_hash] && words[word_hash] != str
184
+ if add_words || words[word_hash]
185
+ words[word_hash] = str
186
+ new_btn['label'] = "$#{word_hash}"
187
+ end
188
+ end
189
+ end
190
+
170
191
  end
171
192
  new_row.push(new_btn ? new_btn['id'] : nil)
172
193
  new_json['buttons'].push(new_btn) if new_btn
@@ -176,6 +197,29 @@ module AACMetrics::Loader
176
197
  boards << new_json
177
198
  end
178
199
  end
200
+ # any semantic_id or clone_id repeats must be recorded
201
+ relations_hash.each do |id, btns|
202
+ if btns && btns.length > 0
203
+ btns.each do |brd_id, btn_id|
204
+ brd = boards.detect{|b| b['id'] == brd_id }
205
+ if brd && brd['buttons']
206
+ btn = brd['buttons'].detect{|b| b['id'] == btn_id }
207
+ if btn
208
+ if id.match(/^s/)
209
+ btn['semantic_id'] = id
210
+ brd['semantic_ids'] ||= []
211
+ brd['semantic_ids'] << id
212
+ elsif id.match(/^c/)
213
+ btn['clone_id'] = id
214
+ brd['clone_ids'] ||= []
215
+ brd['clone_ids'] << id
216
+ end
217
+ end
218
+ end
219
+ end
220
+ #
221
+ end
222
+ end
179
223
  boards.each do |brd|
180
224
  brd['buttons'].each do |btn|
181
225
  if btn['load_board'] && btn['load_board']['tmp_path']
@@ -184,9 +228,21 @@ module AACMetrics::Loader
184
228
  end
185
229
  end
186
230
  end
231
+ # TODO: record whether the board set is expected to have auto-home
187
232
  {boards: boards, words: words, words_path: words_path}
188
233
  end
189
234
 
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
+
190
246
  def self.ingest(fn, token=nil)
191
247
  output = nil
192
248
  boards = nil
@@ -292,7 +348,31 @@ module AACMetrics::Loader
292
348
  @@synonyms[locale] = res
293
349
  end
294
350
 
351
+ def self.sentences(locale)
352
+ @@sentences ||= {}
353
+ return @@sentences[locale] if @@sentences[locale]
354
+ locale = locale.split(/-|_/)[0]
355
+ path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', "sentences.#{locale}.json"))
356
+ res = JSON.parse(File.read(path))
357
+ @@sentences[locale] = res
358
+ end
295
359
 
360
+ def self.fringe_words(locale)
361
+ @@fringe_words ||= {}
362
+ return @@fringe_words[locale] if @@fringe_words[locale]
363
+ locale = locale.split(/-|_/)[0]
364
+ path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', "fringe.#{locale}.json"))
365
+ all_words = []
366
+ list = JSON.parse(File.read(path))
367
+ list.each do |set|
368
+ set['categories'].each do |cat|
369
+ all_words += cat['words']
370
+ end
371
+ end
372
+ all_words.uniq!
373
+ @@synonyms[locale] = all_words
374
+ end
375
+
296
376
  def self.base_words(locale)
297
377
  @@base_words ||= {}
298
378
  return @@base_words[locale] if @@base_words[locale]
@@ -2,31 +2,84 @@
2
2
  # Scores for average effort level for word sets (spelling if that's th only way)
3
3
  # Effort scores for sentence corpus
4
4
  # Effort algorithms for scanning/eyes
5
-
5
+ # TODO: manual way to flag button as conceptually
6
+ # related to the same-locaed button on the
7
+ # prior board, allowing for a discounted penalty
6
8
  module AACMetrics::Metrics
7
- def self.analyze(obfset, output=true)
9
+ # TODO:
10
+ # 1. When navigating from one board to the next, grid locations
11
+ # with the same clone_id or semantic_id should result in a
12
+ # discount to overall search based more on the number of
13
+ # uncloned/unsemantic buttons than the number of total buttons
14
+ # (perhaps also factoring in the percent of board with that
15
+ # id present in the full board set)
16
+ # 2. When selecting a button with a semantic_id or clone_id,
17
+ # a discount to both search and selection should
18
+ # be applied based on the percent of boards that
19
+ # contain the same id at that grid location
20
+ # 3.5 When selecting a button with a semantic_id or clone_id,
21
+ # if the same id was present on the previous board,
22
+ # an additional discount to search and selection should be applied
23
+ def self.analyze(obfset, output=true)
8
24
  locale = nil
9
25
  buttons = []
10
- total_boards = 1
11
-
26
+ refs = {}
27
+ grid = {}
28
+
12
29
  if obfset.is_a?(Hash) && obfset['buttons']
13
30
  locale = obfset['locale'] || 'en'
31
+ refs = obfset['reference_counts']
32
+ grid = obfset['grid']
14
33
  buttons = []
15
34
  obfset['buttons'].each do |btn|
16
35
  buttons << {
17
36
  id: btn['id'],
18
37
  label: btn['label'],
19
38
  level: btn['level'],
20
- effort: btn['effort']
39
+ effort: btn['effort'],
40
+ semantic_id: btn['semantic_id'],
41
+ clone_id: btn['clone_id']
21
42
  }
22
43
  end
23
44
  total_boards = obfset['total_boards']
24
45
  else
25
46
  visited_board_ids = {}
26
47
  to_visit = [{board: obfset[0], level: 0, entry_x: 1.0, entry_y: 1.0}]
48
+ refs = {}
49
+ rows_tally = 0.0
50
+ cols_tally = 0.0
51
+ root_rows = nil
52
+ root_cols = nil
53
+ obfset.each do |board|
54
+ root_rows ||= board['grid']['rows']
55
+ root_cols ||= board['grid']['columns']
56
+ rows_tally += board['grid']['rows']
57
+ cols_tally += board['grid']['columns']
58
+ # determine frequency within the board set
59
+ # for each semantic_id and clone_id
60
+ if board['clone_ids']
61
+ boards['clone_ids'].each do |id|
62
+ refs[id] ||= 0
63
+ refs[id] += 1
64
+ end
65
+ end
66
+ if board['semantic_ids']
67
+ boards['semantic_ids'].each do |id|
68
+ refs[id] ||= 0
69
+ refs[id] += 1
70
+ end
71
+ end
72
+ end
73
+ if (rows_tally / obfset.length.to_f - root_rows).abs > 3 || (cols_tally / obfset.length.to_f - root_cols).abs > 3
74
+ root_rows = (rows_tally / obfset.length.to_f).floor
75
+ root_cols = (cols_tally / obfset.length.to_f).floor
76
+ end
77
+ pcts = {}
78
+ refs.each do |id, cnt|
79
+ pcts[id] = cnt.to_f / obfset.length.to_f
80
+ end
27
81
  locale = obfset[0]['locale']
28
82
  known_buttons = {}
29
- sqrt2 = Math.sqrt(2)
30
83
  while to_visit.length > 0
31
84
  board = to_visit.shift
32
85
  visited_board_ids[board[:board]['id']] = board[:level]
@@ -35,32 +88,63 @@ module AACMetrics::Metrics
35
88
  btn_width = 1.0 / board[:board]['grid']['columns'].to_f
36
89
  board_effort = 0
37
90
  # add effort for level of complexity when new board is rendered
38
- board_effort += 0.003 * board[:board]['grid']['rows'] * board[:board]['grid']['columns']
91
+ button_size = button_size_effort(board[:board]['grid']['rows'], board[:board]['grid']['columns'])
92
+ board_effort += button_size
39
93
  # add effort for number of visible buttons
40
- board_effort += 0.007 * board[:board]['grid']['order'].flatten.length
94
+ field_size = field_size_effort(board[:board]['grid']['order'].flatten.length)
95
+ board_effort += field_size
96
+ # decrease effort here for every button on the board
97
+ # whose semantic_id or clone_id is repeated in the board set
98
+ # -0.0025 (* pct of matching boards) for semantic_id
99
+ # -0.005 (* pct of matching boards) for clone_id
100
+ board[:board]['grid']['rows'].times do |row_idx|
101
+ board[:board]['grid']['columns'].times do |col_idx|
102
+ button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
103
+ button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
104
+ if button && button['clone_id'] && pcts[button['clone_id']]
105
+ board_effort -= 0.005 * pcts[button['clone_id']]
106
+ elsif button && button['semantic_id'] && pcts[button['semantic_id']]
107
+ board_effort -= 0.0025 * pcts[button['semantic_id']]
108
+ end
109
+ end
110
+ end
111
+
41
112
  prior_buttons = 0
42
113
 
43
114
  board[:board]['grid']['rows'].times do |row_idx|
44
115
  board[:board]['grid']['columns'].times do |col_idx|
45
116
  button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
46
117
  button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
47
- prior_buttons += 0.1 if !button
118
+ # prior_buttons += 0.1 if !button
48
119
  next unless button
49
120
  x = (btn_width / 2) + (btn_width * col_idx)
50
121
  y = (btn_height / 2) + (btn_height * row_idx)
51
122
  # prior_buttons = (row_idx * board[:board]['grid']['columns']) + col_idx
52
123
  effort = 0
124
+ # TODO: additional discount on board search effort
125
+ # if this button's semantic_id or clone_id
126
+ # was also present on the prior board
127
+ # board_effort * 0.5 for semantic_id
128
+ # board_effort * 0.33 for clone_id
53
129
  effort += board_effort
54
130
  # add effort for percent distance from entry point
55
- distance = Math.sqrt((x - board[:entry_x]) ** 2 + (y - board[:entry_y]) ** 2) / sqrt2
131
+ distance = distance_effort(x, y, board[:entry_x], board[:entry_y])
132
+ # TODO: decrease effective distance if the semantic_id or clone_id:
133
+ # - are used on other boards in the set (semi)
134
+ # distance * 0.5 (* pct of matching boards) for semantic_id
135
+ # distance * 0.33 (* pct of matching boards) for clone_id
136
+ # - was also present on the prior board (total)
137
+ # distance * 0.5 for semantic_id
138
+ # distance * 0.33 for clone_id
56
139
  effort += distance
57
- if distance > 0.1 || (board[:entry_x] == 1.0 && board[:entry_y] == 1.0)
140
+ if distance > DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN || (board[:entry_x] == 1.0 && board[:entry_y] == 1.0)
58
141
  # add small effort for every prior (visible) button when visually scanning
59
- effort += prior_buttons * 0.001
142
+ visual_scan = visual_scan_effort(prior_buttons)
143
+ effort += visual_scan
60
144
  else
61
145
  # ..unless it's right by the previous button, then
62
146
  # add tiny effort for local scan
63
- effort += distance * 0.5
147
+ effort += distance * SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER
64
148
  end
65
149
  # add cumulative effort from previous sequence
66
150
  effort += board[:prior_effort] || 0
@@ -81,11 +165,12 @@ module AACMetrics::Metrics
81
165
  end
82
166
  if try_visit
83
167
  next_board = obfset.detect{|brd| brd['id'] == button['load_board']['id'] }
168
+ puts "LIKE[] #{effort}" if button['label'] == 'like'
84
169
  if next_board
85
170
  to_visit.push({
86
171
  board: next_board,
87
172
  level: board[:level] + 1,
88
- prior_effort: effort + 1.0,
173
+ prior_effort: effort + BOARD_CHANGE_PROCESSING_EFFORT,
89
174
  entry_x: x,
90
175
  entry_y: y
91
176
  })
@@ -94,7 +179,8 @@ module AACMetrics::Metrics
94
179
  else
95
180
  word = button['label']
96
181
  existing = known_buttons[word]
97
- if !existing || board[:level] < existing[:level]
182
+ if !existing || existing[:effort] < effort #board[:level] < existing[:level]
183
+ puts "LIKE #{effort}" if button['label'] == 'like'
98
184
  known_buttons[word] = {
99
185
  id: "#{button['id']}::#{board[:board]['id']}",
100
186
  label: word,
@@ -120,11 +206,45 @@ module AACMetrics::Metrics
120
206
  locale: locale,
121
207
  total_boards: total_boards,
122
208
  total_buttons: buttons.length,
209
+ reference_counts: refs,
210
+ grid: {
211
+ rows: root_rows,
212
+ columns: root_cols
213
+ },
123
214
  buttons: buttons,
124
215
  levels: clusters
125
216
  }
126
217
  end
127
218
 
219
+ SQRT2 = Math.sqrt(2)
220
+ BUTTON_SIZE_MULTIPLIER = 0.09
221
+ FIELD_SIZE_MULTIPLIER = 0.017
222
+ VISUAL_SCAN_MULTIPLIER = 0.02
223
+ BOARD_CHANGE_PROCESSING_EFFORT = 1.0
224
+ DISTANCE_MULTIPLIER = 0.5
225
+ DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN = 0.1
226
+ SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER = 0.5
227
+
228
+ def self.button_size_effort(rows, cols)
229
+ BUTTON_SIZE_MULTIPLIER * (rows + cols) / 2
230
+ end
231
+
232
+ def self.field_size_effort(button_count)
233
+ FIELD_SIZE_MULTIPLIER * button_count
234
+ end
235
+
236
+ def self.visual_scan_effort(prior_buttons)
237
+ prior_buttons * VISUAL_SCAN_MULTIPLIER
238
+ end
239
+
240
+ def self.distance_effort(x, y, entry_x, entry_y)
241
+ Math.sqrt((x - entry_x) ** 2 + (y - entry_y) ** 2) / SQRT2 * DISTANCE_MULTIPLIER
242
+ end
243
+
244
+ def self.spelling_effort(word)
245
+ 10 + (word.length * 2.5)
246
+ end
247
+
128
248
  def self.analyze_and_compare(obfset, compset)
129
249
  target = AACMetrics::Metrics.analyze(obfset, false)
130
250
  res = {}.merge(target)
@@ -142,34 +262,43 @@ module AACMetrics::Metrics
142
262
  comp_efforts[btn[:label]] = btn[:effort]
143
263
  end
144
264
 
145
- efforts = {}
265
+ sortable_efforts = {}
146
266
  target_efforts = {}
147
267
  target_words = []
268
+ # Track effort scores for each button in the set,
269
+ # used to sort and for assessing priority
270
+ # TODO: keep a list of expected effort scores for
271
+ # very frequent core words and use that when available
148
272
  res[:buttons].each{|b|
149
273
  target_words << b[:label]
150
274
  target_efforts[b[:label]] = b[:effort]
151
- efforts[b[:label]] = b[:effort]
275
+ sortable_efforts[b[:label]] = b[:effort]
152
276
  comp = compare_buttons[b[:label]]
153
277
  if comp
154
278
  b[:comp_level] = comp[:level]
155
279
  b[:comp_effort] = comp[:effort]
156
280
  end
157
281
  }
282
+ # Effort scores are the mean of thw scores from the
283
+ # two sets, or just a singular value if in only one set
158
284
  compare[:buttons].each{|b|
159
- if efforts[b[:label]]
160
- efforts[b[:label]] += b[:effort]
161
- efforts[b[:label]] /= 2
285
+ if sortable_efforts[b[:label]]
286
+ sortable_efforts[b[:label]] += b[:effort]
287
+ sortable_efforts[b[:label]] /= 2
162
288
  else
163
- efforts[b[:label]] ||= b[:effort]
289
+ sortable_efforts[b[:label]] ||= b[:effort]
164
290
  end
165
291
  }
166
292
 
167
293
  core_lists = AACMetrics::Loader.core_lists(target[:locale])
168
294
  common_words_obj = AACMetrics::Loader.common_words(target[:locale])
169
295
  synonyms = AACMetrics::Loader.synonyms(target[:locale])
170
- common_words_obj['efforts'].each{|w, e| efforts[w] ||= e }
296
+ sentences = AACMetrics::Loader.sentences(target[:locale])
297
+ fringe = AACMetrics::Loader.fringe_words(target[:locale])
298
+ common_words_obj['efforts'].each{|w, e| sortable_efforts[w] ||= e }
171
299
  common_words = common_words_obj['words']
172
300
 
301
+ # Track which words are significantly harder or easier than expected
173
302
  too_easy = []
174
303
  too_hard = []
175
304
  target[:buttons].each do |btn|
@@ -183,11 +312,11 @@ module AACMetrics::Metrics
183
312
  end
184
313
 
185
314
 
186
- missing = (compare_words - target_words).sort_by{|w| efforts[w] }
315
+ missing = (compare_words - target_words).sort_by{|w| sortable_efforts[w] }
187
316
  missing = missing.select do |word|
188
317
  !synonyms[word] || (synonyms[word] & target_words).length == 0
189
318
  end
190
- extras = (target_words - compare_words).sort_by{|w| efforts[w] }
319
+ extras = (target_words - compare_words).sort_by{|w| sortable_efforts[w] }
191
320
  extras = extras.select do |word|
192
321
  !synonyms[word] || (synonyms[word] & compare_words).length == 0
193
322
  end
@@ -235,28 +364,42 @@ module AACMetrics::Metrics
235
364
  res[:cores] = {
236
365
  :common => {name: "Common Word List", list: common_words, average_effort: common_effort, comp_effort: comp_effort}
237
366
  }
367
+ target_effort_tally = 0.0
368
+ comp_effort_tally = 0.0
369
+ # For each core list, find any missing words, and compute
370
+ # the average level of effort for all words in the set,
371
+ # using a fallback effort metric if the word isn't in the
372
+ # board set
238
373
  # puts missing.join(' ')
239
374
  core_lists.each do |list|
240
- puts list['id']
241
375
  missing = []
376
+ comp_missing = []
242
377
  list_effort = 0
243
378
  comp_effort = 0
244
379
  list['words'].each do |word|
245
380
  words = [word] + (synonyms[word] || [])
381
+ # Check if any words from the core list are missing in the set
246
382
  if (target_words & words).length == 0
247
383
  missing << word
248
384
  end
385
+ if (compare_words & words).length == 0
386
+ comp_missing << word
387
+ end
388
+
389
+ # Calculate the effort for the target and comp sets
249
390
  effort = target_efforts[word]
250
391
  if !effort
251
392
  words.each{|w| effort ||= target_efforts[w] }
252
393
  end
253
- effort ||= 2 + (word.length * 2.5)
394
+ # Fallback penalty for missing word
395
+ effort ||= spelling_effort(word)
254
396
  list_effort += effort
397
+
255
398
  effort = comp_efforts[word]
256
399
  if !effort
257
400
  words.each{|w| effort ||= comp_efforts[w] }
258
401
  end
259
- effort ||= 2 + (word.length * 2.5)
402
+ effort ||= spelling_effort(word)
260
403
  comp_effort += effort
261
404
  end
262
405
  if missing.length > 0
@@ -266,8 +409,79 @@ module AACMetrics::Metrics
266
409
  end
267
410
  list_effort = list_effort.to_f / list['words'].length.to_f
268
411
  comp_effort = comp_effort.to_f / list['words'].length.to_f
412
+ target_effort_tally += list_effort
413
+ comp_effort_tally += comp_effort
269
414
  res[:cores][list['id']] = {name: list['name'], list: list['words'], average_effort: list_effort, comp_effort: comp_effort}
270
415
  end
416
+ target_effort_tally = (target_effort_tally / core_lists.to_a.length) * 5.0
417
+
418
+ comp_effort_tally = (comp_effort_tally / core_lists.to_a.length) * 5.0
419
+
420
+ # TODO: Assemble or allow a battery of word combinations,
421
+ # and calculate the level of effort for each sequence,
422
+ # as well as an average level of effort across combinations.
423
+ res[:sentences] = []
424
+ sentences.each do |words|
425
+ puts " #{words.join(' ')}"
426
+ BOARD_CHANGE_PROCESSING_EFFORT
427
+ target_effort_score = 0.0
428
+ comp_effort_score = 0.0
429
+ words.each_with_index do |word, idx|
430
+ synonym_words = [word] + (synonyms[word] || [])
431
+ effort = target_efforts[word] || target_efforts[word.downcase]
432
+ if !effort
433
+ synonym_words.each{|w| effort ||= target_efforts[w] }
434
+ end
435
+ effort ||= spelling_effort(word)
436
+ effort += (idx == 0) ? 0.0 : BOARD_CHANGE_PROCESSING_EFFORT
437
+ ee = effort
438
+ target_effort_score += effort
439
+
440
+ effort = comp_efforts[word] || comp_efforts[word.downcase]
441
+ if !effort
442
+ synonym_words.each{|w| effort ||= comp_efforts[w] }
443
+ end
444
+ effort ||= spelling_effort(word)
445
+ effort += (idx == 0) ? 0.0 : BOARD_CHANGE_PROCESSING_EFFORT
446
+ comp_effort_score += effort
447
+ end
448
+ target_effort_score = target_effort_score / words.length
449
+ comp_effort_score = comp_effort_score / words.length
450
+ res[:sentences] << {sentence: words.join(' '), words: words, effort: target_effort_score, comp_effort: comp_effort_score}
451
+ end
452
+ target_effort_tally += res[:sentences].map{|s| s[:effort] }.sum.to_f / res[:sentences].length.to_f * 3.0
453
+ comp_effort_tally += res[:sentences].map{|s| s[:comp_effort] }.sum.to_f / res[:sentences].length.to_f * 3.0
454
+
455
+ res[:fringe_words] = []
456
+ fringe.each do |word|
457
+ target_effort_score = 0.0
458
+ comp_effort_score = 0.0
459
+ synonym_words = [word] + (synonyms[word] || [])
460
+ effort = target_efforts[word] || target_efforts[word.downcase]
461
+ if !effort
462
+ synonym_words.each{|w| effort ||= target_efforts[w] }
463
+ end
464
+ effort ||= spelling_effort(word)
465
+ target_effort_score += effort
466
+
467
+ effort = comp_efforts[word] || comp_efforts[word.downcase]
468
+ if !effort
469
+ synonym_words.each{|w| effort ||= comp_efforts[w] }
470
+ end
471
+ effort ||= spelling_effort(word)
472
+ comp_effort_score += effort
473
+ res[:fringe_words] << {word: word, effort: target_effort_score, comp_effort: comp_effort_score}
474
+ end
475
+ target_effort_tally += res[:fringe_words].map{|s| s[:effort] }.sum.to_f / res[:fringe_words].length.to_f * 2.0
476
+ comp_effort_tally += res[:fringe_words].map{|s| s[:comp_effort] }.sum.to_f / res[:fringe_words].length.to_f * 2.0
477
+
478
+ target_effort_tally += 80 # placeholder value for future added calculations
479
+ comp_effort_tally += 80
480
+
481
+
482
+
483
+ res[:target_effort_score] = target_effort_tally
484
+ res[:comp_effort_score] = comp_effort_tally
271
485
  # puts "CONSIDER MAKING EASIER"
272
486
  res[:high_effort_words] = too_hard
273
487
  # puts too_hard.join(' ')
data/lib/aac-metrics.rb CHANGED
@@ -8,7 +8,7 @@
8
8
  # Are grammar inflections available?
9
9
  # Does this support auto-home, or other motor planning supports?
10
10
  # Are other language options available?
11
- # How is thee vocabulary organized?
11
+ # How is the vocabulary organized?
12
12
  # What platforms are supported?
13
13
  # Access to keyboard w/ prediction? Numbers?
14
14
  # How easy is it to say these personalized sentences: _____
@@ -43,7 +43,7 @@
43
43
  # thing explainer phrases, lists of words
44
44
 
45
45
  module AACMetrics
46
- VERSION = "0.1"
46
+ VERSION = "0.2"
47
47
  require 'aac-metrics/loader'
48
48
  require 'aac-metrics/metrics'
49
49
  end