aac-metrics 0.2.1 → 0.2.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 +4 -4
- data/README.md +18 -0
- data/lib/aac-metrics/loader.rb +14 -2
- data/lib/aac-metrics/metrics.rb +239 -112
- data/lib/ingester.rb +2 -0
- data/sets/ac99-3db3a9ef48.obfset +99885 -0
- data/sets/base_words.en.json +2146 -0
- data/sets/bl99-f7fa334245.obfset +31059 -0
- data/sets/cbc-a58fb7ec74.obfset +5124 -0
- data/sets/cbu-5da852dd65.obfset +1321 -0
- data/sets/cw20-2a2a04587d.obfset +12532 -0
- data/sets/cw40-b73146776d.obfset +36249 -0
- data/sets/cw6-9239957af6.obfset +2111 -0
- data/sets/cw60-a365d1821c.obfset +65562 -0
- data/sets/preview.html +413 -0
- data/sets/sc12-3309ddfa88.obfset +16078 -0
- data/sets/sc30-c14f4bdc24.obfset +105622 -0
- data/sets/sc50-40100cbd63.obfset +135833 -0
- data/sets/sentences.en.json +3 -2
- data/sets/seq15-ce2e1c7a45.obfset +19965 -0
- data/sets/st63-f270ee4c31.obfset +17432 -0
- data/sets/vc12-9db81b58c4.obfset +57148 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d8e0a136e07e00454338b3ddafc3d3374a0b38cb19f915110aef990dfea3f20
|
4
|
+
data.tar.gz: dcc8109cc6c77156e12205e7f518059696aa271d622b085ea0399877dd488542
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0de1a12d33d59f0440bab00fb60e426ced9c9cb95c2311931ac0a67a10032e1752b8803b91ef5fb5e80e1f0aa610575ae56917ef42ad7e15cbcb0ac04d4be31b
|
7
|
+
data.tar.gz: f372e88fcac486795074d2f425da2d9ff1651b7ad5e6af66b6df46b3e573c927ba30106465bdace6a350c3d6704b425f65bd4b1fb7b60a0ce4142fd2ba9eb83b
|
data/README.md
CHANGED
@@ -2,6 +2,24 @@
|
|
2
2
|
|
3
3
|
A tool for analysing and comparing grid-based AAC board sets
|
4
4
|
|
5
|
+
To compare two datasets
|
6
|
+
`ruby setup.rb qc24 qc112`
|
7
|
+
|
8
|
+
To compare two datasets and export the minimal obfset for the first
|
9
|
+
`ruby setup.rb qc24 qc112 export`
|
10
|
+
`ruby setup.rb qc24 qc112 render` # updates interactive preview in sets/preview.html
|
11
|
+
|
12
|
+
To generate an obfset from an external data source
|
13
|
+
`ruby lib/ingester.rb path/to/manifest-from-unzipped-obz.json`
|
14
|
+
`ruby lib/ingester.rb path/to/compiled-set.obfset`
|
15
|
+
`ruby lib/ingester.rb path/to/root-file.obf`
|
16
|
+
|
17
|
+
|
18
|
+
NOTE: if you add an obfset to sets, it will be accessible
|
19
|
+
simply by its prefix. If you add ".common" to the filename
|
20
|
+
similar to the existing files, it will be added to the
|
21
|
+
corpus of "common" word sets.
|
22
|
+
|
5
23
|
## License
|
6
24
|
|
7
25
|
Licensed under the MIT License.
|
data/lib/aac-metrics/loader.rb
CHANGED
@@ -20,6 +20,7 @@ module AACMetrics::Loader
|
|
20
20
|
}
|
21
21
|
if button['load_board'] && button['load_board']['id']
|
22
22
|
new_button['load_board'] = {'id' => button['load_board']['id']}
|
23
|
+
new_button['load_board']['temporary_home'] = button['load_board']['temporary_home'] if button['load_board']['temporary_home'] == true || button['load_board']['temporary_home'] == 'prior'
|
23
24
|
new_button['load_board']['add_to_sentence'] = true if button['load_board']['add_to_sentence']
|
24
25
|
end
|
25
26
|
new_board['buttons'].push(new_button)
|
@@ -73,11 +74,11 @@ module AACMetrics::Loader
|
|
73
74
|
if json.is_a?(Array)
|
74
75
|
json.each do |brd|
|
75
76
|
brd['buttons'].each do |btn|
|
76
|
-
if btn['label'].match(/^\$/)
|
77
|
+
if (btn['label'] || '').match(/^\$/)
|
77
78
|
word = base[btn['label'].sub(/^\$/, '')]
|
78
79
|
btn['label'] = word if word
|
79
80
|
end
|
80
|
-
btn['label'] = btn['label'].gsub(/’/, '')
|
81
|
+
btn['label'] = (btn['label'] || '').gsub(/’/, '')
|
81
82
|
end
|
82
83
|
end
|
83
84
|
elsif json.is_a?(Hash)
|
@@ -159,6 +160,12 @@ module AACMetrics::Loader
|
|
159
160
|
# record load_board reference
|
160
161
|
btn_idx += 1
|
161
162
|
if btn['load_board']
|
163
|
+
if !btn['load_board']['path'] && btn['load_board']['id']
|
164
|
+
bpath = File.join(File.dirname(path), btn['load_board']['id'] + '.obf')
|
165
|
+
if File.exists?(bpath)
|
166
|
+
btn['load_board']['path'] = bpath
|
167
|
+
end
|
168
|
+
end
|
162
169
|
if btn['load_board']['path']
|
163
170
|
if visited_paths[btn['load_board']['path']]
|
164
171
|
new_btn['load_board'] = {'id' => "brd#{visited_paths[btn['load_board']['path']]}"}
|
@@ -176,9 +183,14 @@ module AACMetrics::Loader
|
|
176
183
|
new_btn['load_board'] = {'tmp_path' => btn['load_board']['data_url']}
|
177
184
|
end
|
178
185
|
else
|
186
|
+
puts path
|
179
187
|
puts "Link found with no access #{btn['load_board'].to_json}"
|
180
188
|
end
|
189
|
+
new_btn['load_board']['temporary_home'] = true if new_btn['load_board'] && btn['load_board']['temporary_home']
|
190
|
+
new_btn['load_board']['temporary_home'] = true if new_btn['load_board'] && btn['ext_coughdrop_home_lock']
|
181
191
|
new_btn['load_board']['add_to_sentence'] = true if new_btn['load_board'] && btn['load_board']['add_to_sentence']
|
192
|
+
new_btn['load_board']['add_to_sentence'] = true if new_btn['load_board'] && btn['ext_coughdrop_add_to_vocalization']
|
193
|
+
new_btn['load_board']['add_to_sentence'] = true if new_btn['load_board'] && btn['ext_coughdrop_add_vocalization']
|
182
194
|
elsif btn['action']
|
183
195
|
# TODO: track keyboard actions and don't
|
184
196
|
# treat action buttons for metrics
|
data/lib/aac-metrics/metrics.rb
CHANGED
@@ -1,14 +1,8 @@
|
|
1
1
|
# TODO:
|
2
2
|
# Qualitative evaluation criteria:
|
3
|
-
# - this set looks easy to learn for communicators
|
4
|
-
# - this set looks easy to learn for supporters
|
5
|
-
# - this vocabulary organization of this set makes sense
|
6
3
|
# - this set provides clear locations for user-specific words to be added
|
7
4
|
# - this set supports the use of grammatical forms (tenses and other inflections)
|
8
5
|
# - this set provides predefined simplification for beginning communicators
|
9
|
-
# - this set allows for long-term vocabulary growth over time
|
10
|
-
# - this vocabulary looks like it will work well for young users
|
11
|
-
# - this vocabulary looks like it will work well for adult users
|
12
6
|
|
13
7
|
# Effort algorithms for scanning/eyes
|
14
8
|
module AACMetrics::Metrics
|
@@ -30,14 +24,16 @@ module AACMetrics::Metrics
|
|
30
24
|
# as the link used to get there if they share an id
|
31
25
|
def self.analyze(obfset, output=true, include_obfset=false)
|
32
26
|
locale = nil
|
33
|
-
buttons =
|
27
|
+
buttons = nil
|
34
28
|
set_refs = {}
|
35
29
|
grid = {}
|
30
|
+
alt_scores = {}
|
36
31
|
|
37
32
|
if obfset.is_a?(Hash) && obfset['buttons']
|
38
33
|
locale = obfset['locale'] || 'en'
|
39
34
|
set_refs = obfset['reference_counts']
|
40
35
|
grid = obfset['grid']
|
36
|
+
alt_scores = obfset['alternates']
|
41
37
|
buttons = []
|
42
38
|
obfset['buttons'].each do |btn|
|
43
39
|
buttons << {
|
@@ -51,6 +47,7 @@ module AACMetrics::Metrics
|
|
51
47
|
end
|
52
48
|
total_boards = obfset['total_boards']
|
53
49
|
else
|
50
|
+
start_boards = [obfset[0]]
|
54
51
|
visited_board_ids = {}
|
55
52
|
to_visit = [{board: obfset[0], level: 0, entry_x: 1.0, entry_y: 1.0}]
|
56
53
|
set_refs = {}
|
@@ -87,6 +84,17 @@ module AACMetrics::Metrics
|
|
87
84
|
set_refs[id] += 1
|
88
85
|
end
|
89
86
|
end
|
87
|
+
board['buttons'].each do |link_btn|
|
88
|
+
if link_btn['load_board'] && link_btn['load_board']['id'] && link_btn['load_board']['temporary_home']
|
89
|
+
# TODO: buttons can have multiple efforts, depending
|
90
|
+
# on if they are navigable from a temporary_home
|
91
|
+
if link_btn['load_board']['temporary_home'] == 'prior'
|
92
|
+
start_boards << board
|
93
|
+
elsif link_btn['load_board']['temporary_home'] == true
|
94
|
+
start_boards << obfset.detect{|b| b['id'] == link_btn['load_board']['id']}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
90
98
|
end
|
91
99
|
# If the average grid size is much different than the root
|
92
100
|
# grid size, only then use the average as the size for this board set
|
@@ -99,7 +107,53 @@ module AACMetrics::Metrics
|
|
99
107
|
loc = id.split(/-/)[1]
|
100
108
|
set_pcts[id] = cnt.to_f / (cell_refs[loc] || obfset.length).to_f
|
101
109
|
end
|
102
|
-
|
110
|
+
|
111
|
+
total_boards = nil
|
112
|
+
locale = nil
|
113
|
+
clusters = nil
|
114
|
+
# TODO: this list used to be reversed, but I don't know why.
|
115
|
+
# What we want is for these analyses to be run for the root
|
116
|
+
# board, don't we?
|
117
|
+
# puts JSON.pretty_generate(obfset[0])
|
118
|
+
start_boards.uniq.each do |brd|
|
119
|
+
analysis = analyze_for(obfset, brd, set_pcts, output)
|
120
|
+
buttons ||= analysis[:buttons]
|
121
|
+
if brd != obfset[0]
|
122
|
+
alt_scores[brd['id']] = {
|
123
|
+
buttons: analysis[:buttons],
|
124
|
+
levels: analysis[:levels]
|
125
|
+
}
|
126
|
+
end
|
127
|
+
total_boards ||= analysis[:total_boards]
|
128
|
+
clusters ||= analysis[:levels]
|
129
|
+
locale ||= analysis[:locale]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
res = {
|
133
|
+
analysis_version: AACMetrics::VERSION,
|
134
|
+
locale: locale,
|
135
|
+
total_boards: total_boards,
|
136
|
+
total_buttons: buttons.map{|b| b[:count] || 1}.sum,
|
137
|
+
total_words: buttons.map{|b| b[:label] }.uniq.length,
|
138
|
+
reference_counts: set_refs,
|
139
|
+
grid: {
|
140
|
+
rows: root_rows,
|
141
|
+
columns: root_cols
|
142
|
+
},
|
143
|
+
buttons: buttons,
|
144
|
+
levels: clusters,
|
145
|
+
alternates: alt_scores
|
146
|
+
}
|
147
|
+
if include_obfset
|
148
|
+
res[:obfset] = obfset
|
149
|
+
end
|
150
|
+
res
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.analyze_for(obfset, brd, set_pcts, output)
|
154
|
+
visited_board_ids = {}
|
155
|
+
to_visit = [{board: brd, level: 0, entry_x: 1.0, entry_y: 1.0}]
|
156
|
+
locale = brd['locale'] || 'en'
|
103
157
|
known_buttons = {}
|
104
158
|
while to_visit.length > 0
|
105
159
|
board = to_visit.shift
|
@@ -173,7 +227,7 @@ module AACMetrics::Metrics
|
|
173
227
|
button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
|
174
228
|
button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
|
175
229
|
# prior_buttons += 0.1 if !button
|
176
|
-
next unless button
|
230
|
+
next unless button && (button['label'] || button['vocalization'] || '').length > 0
|
177
231
|
x = (btn_width / 2) + (btn_width * col_idx)
|
178
232
|
y = (btn_height / 2) + (btn_height * row_idx)
|
179
233
|
# prior_buttons = (row_idx * board[:board]['grid']['columns']) + col_idx
|
@@ -271,10 +325,14 @@ module AACMetrics::Metrics
|
|
271
325
|
next_board = obfset.detect{|brd| brd['id'] == button['load_board']['id'] }
|
272
326
|
change_effort = BOARD_CHANGE_PROCESSING_EFFORT
|
273
327
|
if next_board
|
328
|
+
temp_home_id = board[:temporary_home_id]
|
329
|
+
temp_home_id = board[:board]['id'] if button['load_board']['temporary_home'] == 'prior'
|
330
|
+
temp_home_id = button['load_board']['id'] if button['load_board']['temporary_home'] == true
|
274
331
|
to_visit.push({
|
275
332
|
board: next_board,
|
276
333
|
level: board[:level] + 1,
|
277
334
|
prior_effort: effort + change_effort,
|
335
|
+
temporary_home_id: temp_home_id,
|
278
336
|
entry_x: x,
|
279
337
|
entry_y: y,
|
280
338
|
entry_clone_id: button['clone_id'],
|
@@ -286,29 +344,35 @@ module AACMetrics::Metrics
|
|
286
344
|
if !button['load_board'] || button['load_board']['add_to_sentence']
|
287
345
|
word = button['label']
|
288
346
|
existing = known_buttons[word]
|
289
|
-
if
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
known_buttons[word] = {
|
347
|
+
if board_pcts[button['clone_id']]
|
348
|
+
effort -= [BOARD_CHANGE_PROCESSING_EFFORT, BOARD_CHANGE_PROCESSING_EFFORT * 0.3 / board_pcts[button['clone_id']]].min
|
349
|
+
elsif board_pcts[button['semantic_id']]
|
350
|
+
effort -= [BOARD_CHANGE_PROCESSING_EFFORT, BOARD_CHANGE_PROCESSING_EFFORT * 0.5 / board_pcts[button['semantic_id']]].min
|
351
|
+
end
|
352
|
+
if !existing || effort < existing[:effort]
|
353
|
+
ww = {
|
297
354
|
id: "#{button['id']}::#{board[:board]['id']}",
|
298
355
|
label: word,
|
299
356
|
level: board[:level],
|
300
357
|
effort: effort,
|
358
|
+
count: ((existing || {})[:count] || 0) + 1
|
301
359
|
}
|
360
|
+
# If a board set has any temporary_home links,
|
361
|
+
# then that can possibly affect the effort
|
362
|
+
# score for sentences
|
363
|
+
if board[:temporary_home_id]
|
364
|
+
ww[:temporary_home_id] = board[:temporary_home_id]
|
365
|
+
end
|
366
|
+
known_buttons[word] = ww
|
302
367
|
end
|
303
368
|
end
|
304
369
|
button['effort'] = effort
|
305
|
-
|
306
370
|
end
|
307
371
|
end
|
308
|
-
end
|
372
|
+
end # end to_visit list
|
309
373
|
buttons = known_buttons.to_a.map(&:last)
|
310
374
|
total_boards = visited_board_ids.keys.length
|
311
|
-
|
375
|
+
|
312
376
|
buttons = buttons.sort_by{|b| [b[:effort] || 1, b[:label] || ""] }
|
313
377
|
clusters = {}
|
314
378
|
buttons.each do |btn|
|
@@ -320,25 +384,38 @@ module AACMetrics::Metrics
|
|
320
384
|
locale: locale,
|
321
385
|
total_boards: total_boards,
|
322
386
|
total_buttons: buttons.length,
|
323
|
-
reference_counts: set_refs,
|
324
|
-
grid: {
|
325
|
-
rows: root_rows,
|
326
|
-
columns: root_cols
|
327
|
-
},
|
328
387
|
buttons: buttons,
|
329
388
|
levels: clusters
|
330
389
|
}
|
331
|
-
if include_obfset
|
332
|
-
res[:obfset] = obfset
|
333
|
-
end
|
334
390
|
res
|
335
391
|
end
|
336
392
|
|
393
|
+
class ExtraFloat < Numeric
|
394
|
+
include Math
|
395
|
+
def initialize(float=0.0)
|
396
|
+
@float = Float(float)
|
397
|
+
end
|
398
|
+
|
399
|
+
def to_f
|
400
|
+
@float.to_f
|
401
|
+
end
|
402
|
+
|
403
|
+
def method_missing(message, *args, &block)
|
404
|
+
if block_given?
|
405
|
+
@float.public_send(message, *args, &block)
|
406
|
+
else
|
407
|
+
@float.public_send(message, *args)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
337
412
|
SQRT2 = Math.sqrt(2)
|
338
413
|
BUTTON_SIZE_MULTIPLIER = 0.09
|
339
414
|
FIELD_SIZE_MULTIPLIER = 0.005
|
340
415
|
VISUAL_SCAN_MULTIPLIER = 0.015
|
341
416
|
BOARD_CHANGE_PROCESSING_EFFORT = 1.0
|
417
|
+
BOARD_HOME_EFFORT = 1.0
|
418
|
+
COMBINED_WORDS_REMEMBERING_EFFORT = 1.0
|
342
419
|
DISTANCE_MULTIPLIER = 0.4
|
343
420
|
DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN = 0.1
|
344
421
|
SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER = 0.5
|
@@ -377,6 +454,7 @@ module AACMetrics::Metrics
|
|
377
454
|
compare = AACMetrics::Metrics.analyze(compset, false)
|
378
455
|
res[:comp_boards] = compare[:total_boards]
|
379
456
|
res[:comp_buttons] = compare[:total_buttons]
|
457
|
+
res[:comp_words] = compare[:total_words]
|
380
458
|
res[:comp_grid] = compare[:grid]
|
381
459
|
|
382
460
|
compare_words = []
|
@@ -386,9 +464,21 @@ module AACMetrics::Metrics
|
|
386
464
|
compare[:buttons].each do |btn|
|
387
465
|
compare_words << btn[:label]
|
388
466
|
compare_buttons[btn[:label]] = btn
|
389
|
-
comp_efforts[btn[:label]] = btn[:effort]
|
467
|
+
comp_efforts[btn[:label]] = ExtraFloat.new(btn[:effort])
|
468
|
+
comp_efforts[btn[:label]].instance_variable_set('@temp_home_id', btn[:temporary_home_id])
|
390
469
|
comp_levels[btn[:label]] = btn[:level]
|
391
470
|
end
|
471
|
+
compare[:alternates].each do |id, alt|
|
472
|
+
efforts = {}
|
473
|
+
levels = {}
|
474
|
+
alt[:buttons].each do |btn|
|
475
|
+
efforts[btn[:label]] = ExtraFloat.new(btn[:effort])
|
476
|
+
efforts[btn[:label]].instance_variable_set('@temp_home_id', btn[:temporary_home_id])
|
477
|
+
levels[btn[:label]] = btn[:level]
|
478
|
+
end
|
479
|
+
comp_efforts["H:#{id}"] = efforts
|
480
|
+
comp_levels["H:#{id}"] = levels
|
481
|
+
end
|
392
482
|
|
393
483
|
sortable_efforts = {}
|
394
484
|
target_efforts = {}
|
@@ -400,7 +490,9 @@ module AACMetrics::Metrics
|
|
400
490
|
# very frequent core words and use that when available
|
401
491
|
res[:buttons].each{|b|
|
402
492
|
target_words << b[:label]
|
403
|
-
target_efforts[b[:label]] = b[:effort]
|
493
|
+
target_efforts[b[:label]] = ExtraFloat.new(b[:effort])
|
494
|
+
target_efforts[b[:label]].instance_variable_set('@temp_home_id', b[:temporary_home_id])
|
495
|
+
|
404
496
|
target_levels[b[:label]] = b[:level]
|
405
497
|
sortable_efforts[b[:label]] = b[:effort]
|
406
498
|
comp = compare_buttons[b[:label]]
|
@@ -409,6 +501,18 @@ module AACMetrics::Metrics
|
|
409
501
|
b[:comp_effort] = comp[:effort]
|
410
502
|
end
|
411
503
|
}
|
504
|
+
res[:alternates].each do |id, alt|
|
505
|
+
efforts = {}
|
506
|
+
levels = {}
|
507
|
+
alt[:buttons].each do |btn|
|
508
|
+
efforts[btn[:label]] = ExtraFloat.new(btn[:effort])
|
509
|
+
efforts[btn[:label]].instance_variable_set('@temp_home_id', btn[:temporary_home_id])
|
510
|
+
levels[btn[:label]] = btn[:level]
|
511
|
+
end
|
512
|
+
target_efforts["H:#{id}"] = efforts
|
513
|
+
target_levels["H:#{id}"] = levels
|
514
|
+
end
|
515
|
+
res.delete(:alternates)
|
412
516
|
# Effort scores are the mean of thw scores from the
|
413
517
|
# two sets, or just a singular value if in only one set
|
414
518
|
compare[:buttons].each{|b|
|
@@ -441,7 +545,6 @@ module AACMetrics::Metrics
|
|
441
545
|
end
|
442
546
|
end
|
443
547
|
end
|
444
|
-
|
445
548
|
|
446
549
|
missing = (compare_words - target_words).sort_by{|w| sortable_efforts[w] }
|
447
550
|
missing = missing.select do |word|
|
@@ -519,20 +622,11 @@ module AACMetrics::Metrics
|
|
519
622
|
end
|
520
623
|
|
521
624
|
# Calculate the effort for the target and comp sets
|
522
|
-
effort = target_efforts
|
523
|
-
if !effort
|
524
|
-
words.each{|w| effort ||= target_efforts[w] }
|
525
|
-
end
|
526
|
-
# Fallback penalty for missing word
|
527
|
-
effort ||= spelling_effort(word)
|
625
|
+
effort, level = best_match(word, target_efforts, nil, synonyms)
|
528
626
|
reffort = effort
|
529
627
|
list_effort += effort
|
530
628
|
|
531
|
-
effort = comp_efforts
|
532
|
-
if !effort
|
533
|
-
words.each{|w| effort ||= comp_efforts[w] }
|
534
|
-
end
|
535
|
-
effort ||= spelling_effort(word)
|
629
|
+
effort, level = best_match(word, comp_efforts, nil, synonyms)
|
536
630
|
comp_effort += effort
|
537
631
|
# puts "#{word} - #{reffort.round(1)} - #{effort.round(1)}"
|
538
632
|
end
|
@@ -552,50 +646,18 @@ module AACMetrics::Metrics
|
|
552
646
|
res[:care_components][:comp_core] = (comp_effort_tally / core_lists.to_a.length) * 5.0
|
553
647
|
comp_effort_tally = res[:care_components][:comp_core]
|
554
648
|
|
555
|
-
#
|
649
|
+
# Assemble or allow a battery of word combinations,
|
556
650
|
# and calculate the level of effort for each sequence,
|
557
651
|
# as well as an average level of effort across combinations.
|
652
|
+
# TODO: sets with temporary_home settings will have custom
|
653
|
+
# effort scores for subsequent words in the sentence
|
558
654
|
res[:sentences] = []
|
559
655
|
sentences.each do |words|
|
560
|
-
|
561
|
-
|
562
|
-
words
|
563
|
-
|
564
|
-
|
565
|
-
level = target_levels[word] || target_levels[word.downcase]
|
566
|
-
if !effort
|
567
|
-
synonym_words.each do |w|
|
568
|
-
if !effort && target_efforts[w]
|
569
|
-
effort = target_efforts[w]
|
570
|
-
level = target_levels[w]
|
571
|
-
end
|
572
|
-
end
|
573
|
-
end
|
574
|
-
effort ||= spelling_effort(word)
|
575
|
-
if level && level > 0 && idx > 0
|
576
|
-
effort += BOARD_CHANGE_PROCESSING_EFFORT
|
577
|
-
end
|
578
|
-
ee = effort
|
579
|
-
target_effort_score += effort
|
580
|
-
|
581
|
-
effort = comp_efforts[word] || comp_efforts[word.downcase]
|
582
|
-
level = comp_levels[word] || comp_levels[word.downcase]
|
583
|
-
if !effort
|
584
|
-
synonym_words.each do |w|
|
585
|
-
if !effort && comp_efforts[w]
|
586
|
-
effort = comp_efforts[w]
|
587
|
-
level = comp_levels[w]
|
588
|
-
end
|
589
|
-
end
|
590
|
-
end
|
591
|
-
effort ||= spelling_effort(word)
|
592
|
-
if level && level > 0 && idx > 0
|
593
|
-
effort += BOARD_CHANGE_PROCESSING_EFFORT
|
594
|
-
end
|
595
|
-
comp_effort_score += effort
|
596
|
-
end
|
597
|
-
target_effort_score = target_effort_score / words.length
|
598
|
-
comp_effort_score = comp_effort_score / words.length
|
656
|
+
sequence = best_combo(words, target_efforts, target_levels, synonyms)
|
657
|
+
target_effort_score = sequence.map{|w, e| e }.sum.to_f / words.length.to_f
|
658
|
+
sequence = best_combo(words, comp_efforts, comp_levels, synonyms)
|
659
|
+
comp_effort_score = sequence.map{|w, e| e }.sum.to_f / words.length.to_f
|
660
|
+
|
599
661
|
res[:sentences] << {sentence: words.join(' '), words: words, effort: target_effort_score, comp_effort: comp_effort_score}
|
600
662
|
end
|
601
663
|
res[:care_components][:sentences] = res[:sentences].map{|s| s[:effort] }.sum.to_f / res[:sentences].length.to_f * 3.0
|
@@ -607,20 +669,11 @@ module AACMetrics::Metrics
|
|
607
669
|
fringe.each do |word|
|
608
670
|
target_effort_score = 0.0
|
609
671
|
comp_effort_score = 0.0
|
610
|
-
|
611
|
-
effort =
|
612
|
-
if !effort
|
613
|
-
synonym_words.each{|w| effort ||= target_efforts[w] }
|
614
|
-
end
|
615
|
-
# puts "!#{word}" unless effort
|
616
|
-
effort ||= spelling_effort(word)
|
672
|
+
|
673
|
+
effort, level = best_match(word, target_efforts, nil, synonyms)
|
617
674
|
target_effort_score += effort
|
618
675
|
|
619
|
-
effort =
|
620
|
-
if !effort
|
621
|
-
synonym_words.each{|w| effort ||= comp_efforts[w] }
|
622
|
-
end
|
623
|
-
effort ||= spelling_effort(word)
|
676
|
+
effort, level = best_match(word, comp_efforts, nil, synonyms)
|
624
677
|
comp_effort_score += effort
|
625
678
|
res[:fringe_words] << {word: word, effort: target_effort_score, comp_effort: comp_effort_score}
|
626
679
|
end
|
@@ -633,19 +686,10 @@ module AACMetrics::Metrics
|
|
633
686
|
common_fringe.each do |word|
|
634
687
|
target_effort_score = 0.0
|
635
688
|
comp_effort_score = 0.0
|
636
|
-
|
637
|
-
effort = target_efforts[word] || target_efforts[word.downcase]
|
638
|
-
if !effort
|
639
|
-
synonym_words.each{|w| effort ||= target_efforts[w] }
|
640
|
-
end
|
641
|
-
effort ||= spelling_effort(word)
|
689
|
+
effort, level = best_match(word, target_efforts, nil, synonyms)
|
642
690
|
target_effort_score += effort
|
643
691
|
|
644
|
-
effort =
|
645
|
-
if !effort
|
646
|
-
synonym_words.each{|w| effort ||= comp_efforts[w] }
|
647
|
-
end
|
648
|
-
effort ||= spelling_effort(word)
|
692
|
+
effort, level = best_match(word, comp_efforts, nil, synonyms)
|
649
693
|
comp_effort_score += effort
|
650
694
|
res[:common_fringe_words] << {word: word, effort: target_effort_score, comp_effort: comp_effort_score}
|
651
695
|
end
|
@@ -657,13 +701,6 @@ module AACMetrics::Metrics
|
|
657
701
|
target_effort_tally += 70 # placeholder value for future added calculations
|
658
702
|
comp_effort_tally += 70
|
659
703
|
|
660
|
-
# puts target_effort_tally
|
661
|
-
# puts comp_effort_tally
|
662
|
-
# puts (target_effort_tally - comp_effort_tally).abs
|
663
|
-
# puts JSON.pretty_generate(res[:care_components])
|
664
|
-
# raise 'arf'
|
665
|
-
|
666
|
-
|
667
704
|
res[:target_effort_score] = [0.0, 350.0 - target_effort_tally].max
|
668
705
|
res[:comp_effort_score] = [0.0, 350.0 - comp_effort_tally].max
|
669
706
|
# puts "CONSIDER MAKING EASIER"
|
@@ -674,4 +711,94 @@ module AACMetrics::Metrics
|
|
674
711
|
# puts too_easy.join(' ')
|
675
712
|
res
|
676
713
|
end
|
714
|
+
|
715
|
+
# Find the effort for a word, its synonyms, or its spelling.
|
716
|
+
# Always returns a non-nil effort score
|
717
|
+
def self.best_match(word, target_efforts, target_levels, synonyms)
|
718
|
+
synonym_words = [word] + (synonyms[word] || [])
|
719
|
+
effort = target_efforts[word] || target_efforts[word.downcase]
|
720
|
+
target_levels ||= {}
|
721
|
+
level = target_levels[word] || target_levels[word.downcase]
|
722
|
+
if !effort
|
723
|
+
synonym_words.each do |w|
|
724
|
+
if !effort && target_efforts[w]
|
725
|
+
effort = target_efforts[w]
|
726
|
+
level = target_levels[w]
|
727
|
+
end
|
728
|
+
end
|
729
|
+
end
|
730
|
+
# Fallback penalty for missing word
|
731
|
+
fallback_effort = spelling_effort(word)
|
732
|
+
effort = fallback_effort if !effort || fallback_effort < effort
|
733
|
+
|
734
|
+
[effort, level || 0]
|
735
|
+
end
|
736
|
+
|
737
|
+
def self.best_combo(words, efforts, levels, synonyms)
|
738
|
+
options = [{next_idx: 0, list: []}]
|
739
|
+
words.length.times do |idx|
|
740
|
+
options.each do |option|
|
741
|
+
home_id = option[:temporary_home_id]
|
742
|
+
if option[:next_idx] == idx
|
743
|
+
combos = forward_combos(words, idx, efforts, levels)
|
744
|
+
if home_id
|
745
|
+
# Effort of hitting home button, and processing change, plus usual
|
746
|
+
combos.each{|c| c[:effort] += BOARD_HOME_EFFORT + BOARD_CHANGE_PROCESSING_EFFORT}
|
747
|
+
more_combos = forward_combos(words, idx, efforts["H:#{home_id}"] || {}, levels["H:#{home_id}"] || {})
|
748
|
+
more_combos.each{|c| c[:temporary_home_id] ||= home_id }
|
749
|
+
combos += more_combos
|
750
|
+
end
|
751
|
+
combos.each do |combo|
|
752
|
+
if idx > 0 && combo[:level] && combo[:level] > 0
|
753
|
+
combo[:effort] += BOARD_CHANGE_PROCESSING_EFFORT
|
754
|
+
end
|
755
|
+
options << {
|
756
|
+
next_idx: idx + combo[:size],
|
757
|
+
list: option[:list] + [[combo[:partial], combo[:effort]]],
|
758
|
+
temporary_home_id: combo[:temporary_home_id]
|
759
|
+
}
|
760
|
+
end
|
761
|
+
effort, level = best_match(words[idx], efforts, levels, synonyms)
|
762
|
+
option[:temporary_home_id] = effort.instance_variable_get('@temp_home_id')
|
763
|
+
effort += BOARD_CHANGE_PROCESSING_EFFORT if idx > 0 && level && level > 0
|
764
|
+
if home_id
|
765
|
+
effort += BOARD_HOME_EFFORT + BOARD_CHANGE_PROCESSING_EFFORT
|
766
|
+
other_effort, other_level = best_match(words[idx], efforts["H:#{home_id}"] || {}, levels["H:#{home_id}"] || {}, synonyms)
|
767
|
+
new_home_id = other_effort.instance_variable_get('@temp_home_id') || home_id
|
768
|
+
other_effort += BOARD_CHANGE_PROCESSING_EFFORT if idx > 0 && other_level && other_level > 0
|
769
|
+
other_list = option[:list] + [[words[idx], other_effort]]
|
770
|
+
options << {next_idx: idx + 1, list: other_list, temporary_home_id: new_home_id}
|
771
|
+
end
|
772
|
+
option[:list] << [words[idx], effort]
|
773
|
+
option[:next_idx] = idx + 1
|
774
|
+
end
|
775
|
+
end
|
776
|
+
end
|
777
|
+
options.sort_by{|o| o[:list].map{|w, e| e}.sum }.reverse[0][:list]
|
778
|
+
end
|
779
|
+
|
780
|
+
# Checks if any buttons will work for multiple words in a sentence
|
781
|
+
def self.forward_combos(words, idx, target_efforts, target_levels)
|
782
|
+
words_left = words.length - idx
|
783
|
+
combos = []
|
784
|
+
skip = 0
|
785
|
+
temp_home_id = nil
|
786
|
+
if words_left > 1
|
787
|
+
(words_left - 1).times do |minus|
|
788
|
+
partial = words[idx, words_left - minus].join(' ')
|
789
|
+
if target_efforts[partial] || target_efforts[partial.downcase]
|
790
|
+
effort = (target_efforts[partial] || target_efforts[partial.downcase]) + COMBINED_WORDS_REMEMBERING_EFFORT
|
791
|
+
level = target_levels[partial] || target_levels[partial.downcase]
|
792
|
+
combos << {
|
793
|
+
partial: partial,
|
794
|
+
effort: effort,
|
795
|
+
temporary_home_id: effort.instance_variable_get('@temp_home_id'),
|
796
|
+
level: level,
|
797
|
+
size: words_left - minus
|
798
|
+
}
|
799
|
+
end
|
800
|
+
end
|
801
|
+
end
|
802
|
+
combos
|
803
|
+
end
|
677
804
|
end
|