aac-metrics 0.2.1 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|