aac-metrics 0.1.0 → 0.1.2

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: b00b87081818fb1fe8ff225b6b7120cc8bb26ba162d84c5a8c74f2e2571f6e84
4
- data.tar.gz: 7115ff9b2081671fa60e3538e706b61deb3a8bb35bbf1b822c74cc90e4cd57eb
3
+ metadata.gz: 5cfa37f885dec40c12ac7cbb86c44ac49ceec1138866b3de2e52b84facdddb87
4
+ data.tar.gz: e29f98ec3c9aa3840a513787003a5db196dd09b81699c3a81b252439929e4dcd
5
5
  SHA512:
6
- metadata.gz: 93eeae92b189182a457ca487a75327cce7aff50912f08a7152aa3b9715335bba80940687916182cde9421b249425ecfcf2f086954cf8f51db7dbef31013849d5
7
- data.tar.gz: 99eab7e060552fd6d11b80016aa9f9f687c59d5a445f4eddb039e61cb39aa35b4dcb0e9307efff51c13dd249ff6996300213b4d1aec4f509a3150e708122eac2
6
+ metadata.gz: 3e54f361e67a70f8c68f85469ffcc0c9da6c8269928b707b2e6e4b5c353e5bc19f64de3d93d50c7c37304822924bf8ca862e35e3f6a4ed692ad2781c444403bb
7
+ data.tar.gz: c12274971262bf327f9d2e6c29286bd1147fed6e5cf3d7a022a869d9810669e796fff20bfabcf13b3e973984a480a936e33468c4d4b2b7ad8f82737bf4b1b33d
@@ -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,6 +348,14 @@ 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
 
296
360
  def self.base_words(locale)
297
361
  @@base_words ||= {}
@@ -2,31 +2,68 @@
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
-
6
5
  module AACMetrics::Metrics
7
- def self.analyze(obfset, output=true)
6
+ # TODO:
7
+ # 1. When navigating from one board to the next, grid locations
8
+ # with the same clone_id or semantic_id should result in a
9
+ # discount to overall search based more on the number of
10
+ # uncloned/unsemantic buttons than the number of total buttons
11
+ # (perhaps also factoring in the percent of board with that
12
+ # id present in the full board set)
13
+ # 2. When selecting a button with a semantic_id or clone_id,
14
+ # a discount to both search and selection should
15
+ # be applied based on the percent of boards that
16
+ # contain the same id at that grid location
17
+ # 3.5 When selecting a button with a semantic_id or clone_id,
18
+ # if the same id was present on the previous board,
19
+ # an additional discount to search and selection should be applied
20
+ def self.analyze(obfset, output=true)
8
21
  locale = nil
9
22
  buttons = []
23
+ refs = {}
10
24
  total_boards = 1
11
-
25
+
12
26
  if obfset.is_a?(Hash) && obfset['buttons']
13
27
  locale = obfset['locale'] || 'en'
28
+ refs = obfset['reference_counts']
14
29
  buttons = []
15
30
  obfset['buttons'].each do |btn|
16
31
  buttons << {
17
32
  id: btn['id'],
18
33
  label: btn['label'],
19
34
  level: btn['level'],
20
- effort: btn['effort']
35
+ effort: btn['effort'],
36
+ semantic_id: btn['semantic_id'],
37
+ clone_id: btn['clone_id']
21
38
  }
22
39
  end
23
40
  total_boards = obfset['total_boards']
24
41
  else
25
42
  visited_board_ids = {}
26
43
  to_visit = [{board: obfset[0], level: 0, entry_x: 1.0, entry_y: 1.0}]
44
+ refs = {}
45
+ obfset.each do |board|
46
+ # determine frequency within the board set
47
+ # for each semantic_id and clone_id
48
+ if board['clone_ids']
49
+ boards['clone_ids'].each do |id|
50
+ refs[id] ||= 0
51
+ refs[id] += 1
52
+ end
53
+ end
54
+ if board['semantic_ids']
55
+ boards['semantic_ids'].each do |id|
56
+ refs[id] ||= 0
57
+ refs[id] += 1
58
+ end
59
+ end
60
+ end
61
+ pcts = {}
62
+ refs.each do |id, cnt|
63
+ pcts[id] = cnt.to_f / obfset.length.to_f
64
+ end
27
65
  locale = obfset[0]['locale']
28
66
  known_buttons = {}
29
- sqrt2 = Math.sqrt(2)
30
67
  while to_visit.length > 0
31
68
  board = to_visit.shift
32
69
  visited_board_ids[board[:board]['id']] = board[:level]
@@ -35,32 +72,63 @@ module AACMetrics::Metrics
35
72
  btn_width = 1.0 / board[:board]['grid']['columns'].to_f
36
73
  board_effort = 0
37
74
  # add effort for level of complexity when new board is rendered
38
- board_effort += 0.003 * board[:board]['grid']['rows'] * board[:board]['grid']['columns']
75
+ button_size = button_size_effort(board[:board]['grid']['rows'], board[:board]['grid']['columns'])
76
+ board_effort += button_size
39
77
  # add effort for number of visible buttons
40
- board_effort += 0.007 * board[:board]['grid']['order'].flatten.length
78
+ field_size = field_size_effort(board[:board]['grid']['order'].flatten.length)
79
+ board_effort += field_size
80
+ # decrease effort here for every button on the board
81
+ # whose semantic_id or clone_id is repeated in the board set
82
+ # -0.0025 (* pct of matching boards) for semantic_id
83
+ # -0.005 (* pct of matching boards) for clone_id
84
+ board[:board]['grid']['rows'].times do |row_idx|
85
+ board[:board]['grid']['columns'].times do |col_idx|
86
+ button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
87
+ button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
88
+ if button && button['clone_id'] && pcts[button['clone_id']]
89
+ board_effort -= 0.005 * pcts[button['clone_id']]
90
+ elsif button && button['semantic_id'] && pcts[button['semantic_id']]
91
+ board_effort -= 0.0025 * pcts[button['semantic_id']]
92
+ end
93
+ end
94
+ end
95
+
41
96
  prior_buttons = 0
42
97
 
43
98
  board[:board]['grid']['rows'].times do |row_idx|
44
99
  board[:board]['grid']['columns'].times do |col_idx|
45
100
  button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
46
101
  button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
47
- prior_buttons += 0.1 if !button
102
+ # prior_buttons += 0.1 if !button
48
103
  next unless button
49
104
  x = (btn_width / 2) + (btn_width * col_idx)
50
105
  y = (btn_height / 2) + (btn_height * row_idx)
51
106
  # prior_buttons = (row_idx * board[:board]['grid']['columns']) + col_idx
52
107
  effort = 0
108
+ # TODO: additional discount on board search effort
109
+ # if this button's semantic_id or clone_id
110
+ # was also present on the prior board
111
+ # board_effort * 0.5 for semantic_id
112
+ # board_effort * 0.33 for clone_id
53
113
  effort += board_effort
54
114
  # add effort for percent distance from entry point
55
- distance = Math.sqrt((x - board[:entry_x]) ** 2 + (y - board[:entry_y]) ** 2) / sqrt2
115
+ distance = distance_effort(x, y, board[:entry_x], board[:entry_y])
116
+ # TODO: decrease effective distance if the semantic_id or clone_id:
117
+ # - are used on other boards in the set (semi)
118
+ # distance * 0.5 (* pct of matching boards) for semantic_id
119
+ # distance * 0.33 (* pct of matching boards) for clone_id
120
+ # - was also present on the prior board (total)
121
+ # distance * 0.5 for semantic_id
122
+ # distance * 0.33 for clone_id
56
123
  effort += distance
57
- if distance > 0.1 || (board[:entry_x] == 1.0 && board[:entry_y] == 1.0)
124
+ if distance > DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN || (board[:entry_x] == 1.0 && board[:entry_y] == 1.0)
58
125
  # add small effort for every prior (visible) button when visually scanning
59
- effort += prior_buttons * 0.001
126
+ visual_scan = visual_scan_effort(prior_buttons)
127
+ effort += visual_scan
60
128
  else
61
129
  # ..unless it's right by the previous button, then
62
130
  # add tiny effort for local scan
63
- effort += distance * 0.5
131
+ effort += distance * SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER
64
132
  end
65
133
  # add cumulative effort from previous sequence
66
134
  effort += board[:prior_effort] || 0
@@ -81,11 +149,12 @@ module AACMetrics::Metrics
81
149
  end
82
150
  if try_visit
83
151
  next_board = obfset.detect{|brd| brd['id'] == button['load_board']['id'] }
152
+ puts "LIKE[] #{effort}" if button['label'] == 'like'
84
153
  if next_board
85
154
  to_visit.push({
86
155
  board: next_board,
87
156
  level: board[:level] + 1,
88
- prior_effort: effort + 1.0,
157
+ prior_effort: effort + BOARD_CHANGE_PROCESSING_EFFORT,
89
158
  entry_x: x,
90
159
  entry_y: y
91
160
  })
@@ -94,7 +163,8 @@ module AACMetrics::Metrics
94
163
  else
95
164
  word = button['label']
96
165
  existing = known_buttons[word]
97
- if !existing || board[:level] < existing[:level]
166
+ if !existing || existing[:effort] < effort #board[:level] < existing[:level]
167
+ puts "LIKE #{effort}" if button['label'] == 'like'
98
168
  known_buttons[word] = {
99
169
  id: "#{button['id']}::#{board[:board]['id']}",
100
170
  label: word,
@@ -120,50 +190,94 @@ module AACMetrics::Metrics
120
190
  locale: locale,
121
191
  total_boards: total_boards,
122
192
  total_buttons: buttons.length,
193
+ reference_counts: refs,
123
194
  buttons: buttons,
124
195
  levels: clusters
125
196
  }
126
197
  end
127
198
 
199
+ SQRT2 = Math.sqrt(2)
200
+ BUTTON_SIZE_MULTIPLIER = 0.09
201
+ FIELD_SIZE_MULTIPLIER = 0.017
202
+ VISUAL_SCAN_MULTIPLIER = 0.02
203
+ BOARD_CHANGE_PROCESSING_EFFORT = 1.0
204
+ DISTANCE_MULTIPLIER = 0.5
205
+ DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN = 0.1
206
+ SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER = 0.5
207
+
208
+ def self.button_size_effort(rows, cols)
209
+ BUTTON_SIZE_MULTIPLIER * (rows + cols) / 2
210
+ end
211
+
212
+ def self.field_size_effort(button_count)
213
+ FIELD_SIZE_MULTIPLIER * button_count
214
+ end
215
+
216
+ def self.visual_scan_effort(prior_buttons)
217
+ prior_buttons * VISUAL_SCAN_MULTIPLIER
218
+ end
219
+
220
+ def self.distance_effort(x, y, entry_x, entry_y)
221
+ Math.sqrt((x - entry_x) ** 2 + (y - entry_y) ** 2) / SQRT2 * DISTANCE_MULTIPLIER
222
+ end
223
+
224
+ def self.spelling_effort(word)
225
+ 10 + (word.length * 2.5)
226
+ end
227
+
128
228
  def self.analyze_and_compare(obfset, compset)
129
229
  target = AACMetrics::Metrics.analyze(obfset, false)
130
230
  res = {}.merge(target)
131
231
 
132
232
  compare = AACMetrics::Metrics.analyze(compset, false)
233
+ res[:comp_boards] = compare[:total_boards]
234
+ res[:comp_buttons] = compare[:total_buttons]
133
235
 
134
236
  compare_words = []
135
237
  compare_buttons = {}
238
+ comp_efforts = {}
136
239
  compare[:buttons].each do |btn|
137
240
  compare_words << btn[:label]
138
241
  compare_buttons[btn[:label]] = btn
242
+ comp_efforts[btn[:label]] = btn[:effort]
139
243
  end
140
244
 
141
- efforts = {}
245
+ sortable_efforts = {}
246
+ target_efforts = {}
142
247
  target_words = []
248
+ # Track effort scores for each button in the set,
249
+ # used to sort and for assessing priority
250
+ # TODO: keep a list of expected effort scores for
251
+ # very frequent core words and use that when available
143
252
  res[:buttons].each{|b|
144
253
  target_words << b[:label]
145
- efforts[b[:label]] = b[:effort]
254
+ target_efforts[b[:label]] = b[:effort]
255
+ sortable_efforts[b[:label]] = b[:effort]
146
256
  comp = compare_buttons[b[:label]]
147
257
  if comp
148
258
  b[:comp_level] = comp[:level]
149
259
  b[:comp_effort] = comp[:effort]
150
260
  end
151
261
  }
262
+ # Effort scores are the mean of thw scores from the
263
+ # two sets, or just a singular value if in only one set
152
264
  compare[:buttons].each{|b|
153
- if efforts[b[:label]]
154
- efforts[b[:label]] += b[:effort]
155
- efforts[b[:label]] /= 2
265
+ if sortable_efforts[b[:label]]
266
+ sortable_efforts[b[:label]] += b[:effort]
267
+ sortable_efforts[b[:label]] /= 2
156
268
  else
157
- efforts[b[:label]] ||= b[:effort]
269
+ sortable_efforts[b[:label]] ||= b[:effort]
158
270
  end
159
271
  }
160
272
 
161
273
  core_lists = AACMetrics::Loader.core_lists(target[:locale])
162
274
  common_words_obj = AACMetrics::Loader.common_words(target[:locale])
163
275
  synonyms = AACMetrics::Loader.synonyms(target[:locale])
164
- common_words_obj['efforts'].each{|w, e| efforts[w] ||= e }
276
+ sentences = AACMetrics::Loader.sentences(target[:locale])
277
+ common_words_obj['efforts'].each{|w, e| sortable_efforts[w] ||= e }
165
278
  common_words = common_words_obj['words']
166
279
 
280
+ # Track which words are significantly harder or easier than expected
167
281
  too_easy = []
168
282
  too_hard = []
169
283
  target[:buttons].each do |btn|
@@ -177,11 +291,11 @@ module AACMetrics::Metrics
177
291
  end
178
292
 
179
293
 
180
- missing = (compare_words - target_words).sort_by{|w| efforts[w] }
294
+ missing = (compare_words - target_words).sort_by{|w| sortable_efforts[w] }
181
295
  missing = missing.select do |word|
182
296
  !synonyms[word] || (synonyms[word] & target_words).length == 0
183
297
  end
184
- extras = (target_words - compare_words).sort_by{|w| efforts[w] }
298
+ extras = (target_words - compare_words).sort_by{|w| sortable_efforts[w] }
185
299
  extras = extras.select do |word|
186
300
  !synonyms[word] || (synonyms[word] & compare_words).length == 0
187
301
  end
@@ -199,27 +313,131 @@ module AACMetrics::Metrics
199
313
  missing = missing.select do |word|
200
314
  !synonyms[word] || (synonyms[word] & target_words).length == 0
201
315
  end
316
+ common_effort = 0
317
+ comp_effort = 0
318
+ common_words.each do |word|
319
+ effort = target_efforts[word]
320
+ if !effort && synonyms[word]
321
+ synonyms[word].each do |syn|
322
+ effort ||= target_efforts[syn]
323
+ end
324
+ end
325
+ effort ||= 2 + (word.length * 2.5)
326
+ common_effort += effort
327
+
328
+ effort = comp_efforts[word]
329
+ if !effort && synonyms[word]
330
+ synonyms[word].each do |syn|
331
+ effort ||= comp_efforts[syn]
332
+ end
333
+ end
334
+ effort ||= 2 + (word.length * 2.5)
335
+ comp_effort += effort
336
+ end
337
+ common_effort = common_effort.to_f / common_words.length.to_f
338
+ comp_effort = comp_effort.to_f / common_words.length.to_f
202
339
  # puts "MISSING FROM COMMON (#{missing.length})"
203
340
  res[:missing] = {
204
341
  :common => {name: "Common Word List", list: missing}
205
342
  }
343
+ res[:cores] = {
344
+ :common => {name: "Common Word List", list: common_words, average_effort: common_effort, comp_effort: comp_effort}
345
+ }
346
+ target_effort_tally = 0.0
347
+ comp_effort_tally = 0.0
348
+ # For each core list, find any missing words, and compute
349
+ # the average level of effort for all words in the set,
350
+ # using a fallback effort metric if the word isn't in the
351
+ # board set
206
352
  # puts missing.join(' ')
207
353
  core_lists.each do |list|
208
- puts list['id']
209
354
  missing = []
355
+ comp_missing = []
356
+ list_effort = 0
357
+ comp_effort = 0
210
358
  list['words'].each do |word|
211
359
  words = [word] + (synonyms[word] || [])
360
+ # Check if any words from the core list are missing in the set
212
361
  if (target_words & words).length == 0
213
362
  missing << word
214
- raise "orng" if word == 'orange'
215
363
  end
364
+ if (compare_words & words).length == 0
365
+ comp_missing << word
366
+ end
367
+
368
+ # Calculate the effort for the target and comp sets
369
+ effort = target_efforts[word]
370
+ if !effort
371
+ words.each{|w| effort ||= target_efforts[w] }
372
+ end
373
+ # Fallback penalty for missing word
374
+ effort ||= spelling_effort(word)
375
+ list_effort += effort
376
+
377
+ effort = comp_efforts[word]
378
+ if !effort
379
+ words.each{|w| effort ||= comp_efforts[w] }
380
+ end
381
+ effort ||= spelling_effort(word)
382
+ comp_effort += effort
216
383
  end
217
384
  if missing.length > 0
218
385
  # puts "MISSING FROM #{list['id']} (#{missing.length}):"
219
- res[:missing][list['id']] = {name: list['name'], list: missing}
386
+ res[:missing][list['id']] = {name: list['name'], list: missing, average_effort: list_effort}
220
387
  # puts missing.join(' ')
221
388
  end
389
+ list_effort = list_effort.to_f / list['words'].length.to_f
390
+ comp_effort = comp_effort.to_f / list['words'].length.to_f
391
+ target_effort_tally += list_effort
392
+ comp_effort_tally += comp_effort
393
+ res[:cores][list['id']] = {name: list['name'], list: list['words'], average_effort: list_effort, comp_effort: comp_effort}
394
+ end
395
+ target_effort_tally = (target_effort_tally / core_lists.to_a.length) * 5.0
396
+
397
+ comp_effort_tally = (comp_effort_tally / core_lists.to_a.length) * 5.0
398
+
399
+ # TODO: Assemble or allow a battery of word combinations,
400
+ # and calculate the level of effort for each sequence,
401
+ # as well as an average level of effort across combinations.
402
+ res[:sentences] = []
403
+ sentences.each do |words|
404
+ puts " #{words.join(' ')}"
405
+ BOARD_CHANGE_PROCESSING_EFFORT
406
+ target_effort_score = 0.0
407
+ comp_effort_score = 0.0
408
+ words.each_with_index do |word, idx|
409
+ synonym_words = [word] + (synonyms[word] || [])
410
+ effort = target_efforts[word] || target_efforts[word.downcase]
411
+ if !effort
412
+ synonym_words.each{|w| effort ||= target_efforts[w] }
413
+ end
414
+ effort ||= spelling_effort(word)
415
+ effort += (idx == 0) ? 0.0 : BOARD_CHANGE_PROCESSING_EFFORT
416
+ ee = effort
417
+ target_effort_score += effort
418
+
419
+ effort = comp_efforts[word] || comp_efforts[word.downcase]
420
+ if !effort
421
+ synonym_words.each{|w| effort ||= comp_efforts[w] }
422
+ end
423
+ effort ||= spelling_effort(word)
424
+ effort += (idx == 0) ? 0.0 : BOARD_CHANGE_PROCESSING_EFFORT
425
+ comp_effort_score += effort
426
+ puts " #{word} #{ee} #{effort}"
427
+ end
428
+ target_effort_score = target_effort_score / words.length
429
+ comp_effort_score = comp_effort_score / words.length
430
+ res[:sentences] << {sentence: words.join(' '), words: words, effort: target_effort_score, comp_effort: comp_effort_score}
222
431
  end
432
+ target_effort_tally += res[:sentences].map{|s| s[:effort] }.sum.to_f / res[:sentences].length.to_f * 3.0
433
+ comp_effort_tally += res[:sentences].map{|s| s[:comp_effort] }.sum.to_f / res[:sentences].length.to_f * 3.0
434
+ target_effort_tally += 100 # placeholder value for future added calculations
435
+ comp_effort_tally += 100
436
+
437
+
438
+
439
+ res[:target_effort_score] = target_effort_tally
440
+ res[:comp_effort_score] = comp_effort_tally
223
441
  # puts "CONSIDER MAKING EASIER"
224
442
  res[:high_effort_words] = too_hard
225
443
  # 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: _____