aac-metrics 0.2.1 → 0.2.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 +4 -4
- data/README.md +18 -0
- data/lib/aac-metrics/loader.rb +11 -2
- data/lib/aac-metrics/metrics.rb +236 -106
- data/lib/ingester.rb +2 -0
- data/sets/ac99-3db3a9ef48.obfset +99885 -0
- data/sets/base_words.en.json +1738 -0
- data/sets/cbc-a58fb7ec74.obfset +5124 -0
- data/sets/cbu-2ae9aef211.obfset +372 -0
- data/sets/preview.html +327 -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-555e3cd10a.obfset +19893 -0
- data/sets/vc12-9db81b58c4.obfset +57148 -0
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52fb3c41b9427605e9a78c258c5f4e5d7303c98f8988b52aba750597cb164900
|
4
|
+
data.tar.gz: 60ccc8a6c1dd2c25d29dd159134c7953e133a1f7a0bb9d710696807f853e8139
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac227f5770623d9df36635c92f71aa7a122404fa40f006dce31432be0bd17b6199c0c16ec6a4f5c777f19255106bf1c7f8811bfe4972996b9d12980a75b2911a
|
7
|
+
data.tar.gz: fd2ef145c944fea960a30282b6f3960f313d5f6f44d0db13f3a4581909744e127455010371d5b4138e546132a2e8d4cfb26ec291793f9ff83b970b54d47aeed9
|
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,8 +183,10 @@ 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']
|
181
190
|
new_btn['load_board']['add_to_sentence'] = true if new_btn['load_board'] && btn['load_board']['add_to_sentence']
|
182
191
|
elsif btn['action']
|
183
192
|
# TODO: track keyboard actions and don't
|
data/lib/aac-metrics/metrics.rb
CHANGED
@@ -30,14 +30,16 @@ module AACMetrics::Metrics
|
|
30
30
|
# as the link used to get there if they share an id
|
31
31
|
def self.analyze(obfset, output=true, include_obfset=false)
|
32
32
|
locale = nil
|
33
|
-
buttons =
|
33
|
+
buttons = nil
|
34
34
|
set_refs = {}
|
35
35
|
grid = {}
|
36
|
+
alt_scores = {}
|
36
37
|
|
37
38
|
if obfset.is_a?(Hash) && obfset['buttons']
|
38
39
|
locale = obfset['locale'] || 'en'
|
39
40
|
set_refs = obfset['reference_counts']
|
40
41
|
grid = obfset['grid']
|
42
|
+
alt_scores = obfset['alternates']
|
41
43
|
buttons = []
|
42
44
|
obfset['buttons'].each do |btn|
|
43
45
|
buttons << {
|
@@ -51,6 +53,7 @@ module AACMetrics::Metrics
|
|
51
53
|
end
|
52
54
|
total_boards = obfset['total_boards']
|
53
55
|
else
|
56
|
+
start_boards = [obfset[0]]
|
54
57
|
visited_board_ids = {}
|
55
58
|
to_visit = [{board: obfset[0], level: 0, entry_x: 1.0, entry_y: 1.0}]
|
56
59
|
set_refs = {}
|
@@ -87,6 +90,17 @@ module AACMetrics::Metrics
|
|
87
90
|
set_refs[id] += 1
|
88
91
|
end
|
89
92
|
end
|
93
|
+
board['buttons'].each do |link_btn|
|
94
|
+
if link_btn['load_board'] && link_btn['load_board']['id'] && link_btn['load_board']['temporary_home']
|
95
|
+
# TODO: buttons can have multiple efforts, depending
|
96
|
+
# on if they are navigable from a temporary_home
|
97
|
+
if link_btn['load_board']['temporary_home'] == 'prior'
|
98
|
+
start_boards << board
|
99
|
+
elsif link_btn['load_board']['temporary_home'] == true
|
100
|
+
start_boards << obfset.detect{|b| b['id'] == link_btn['load_board']['id']}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
90
104
|
end
|
91
105
|
# If the average grid size is much different than the root
|
92
106
|
# grid size, only then use the average as the size for this board set
|
@@ -99,7 +113,52 @@ module AACMetrics::Metrics
|
|
99
113
|
loc = id.split(/-/)[1]
|
100
114
|
set_pcts[id] = cnt.to_f / (cell_refs[loc] || obfset.length).to_f
|
101
115
|
end
|
102
|
-
|
116
|
+
|
117
|
+
total_boards = nil
|
118
|
+
locale = nil
|
119
|
+
clusters = nil
|
120
|
+
# TODO: this list used to be reversed, but I don't know why.
|
121
|
+
# What we want is for these analyses to be run for the root
|
122
|
+
# board, don't we?
|
123
|
+
# puts JSON.pretty_generate(obfset[0])
|
124
|
+
start_boards.uniq.each do |brd|
|
125
|
+
analysis = analyze_for(obfset, brd, set_pcts, output)
|
126
|
+
buttons ||= analysis[:buttons]
|
127
|
+
if brd != obfset[0]
|
128
|
+
alt_scores[brd['id']] = {
|
129
|
+
buttons: analysis[:buttons],
|
130
|
+
levels: analysis[:levels]
|
131
|
+
}
|
132
|
+
end
|
133
|
+
total_boards ||= analysis[:total_boards]
|
134
|
+
clusters ||= analysis[:levels]
|
135
|
+
locale ||= analysis[:locale]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
res = {
|
139
|
+
analysis_version: AACMetrics::VERSION,
|
140
|
+
locale: locale,
|
141
|
+
total_boards: total_boards,
|
142
|
+
total_buttons: buttons.length,
|
143
|
+
reference_counts: set_refs,
|
144
|
+
grid: {
|
145
|
+
rows: root_rows,
|
146
|
+
columns: root_cols
|
147
|
+
},
|
148
|
+
buttons: buttons,
|
149
|
+
levels: clusters,
|
150
|
+
alternates: alt_scores
|
151
|
+
}
|
152
|
+
if include_obfset
|
153
|
+
res[:obfset] = obfset
|
154
|
+
end
|
155
|
+
res
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.analyze_for(obfset, brd, set_pcts, output)
|
159
|
+
visited_board_ids = {}
|
160
|
+
to_visit = [{board: brd, level: 0, entry_x: 1.0, entry_y: 1.0}]
|
161
|
+
locale = brd['locale'] || 'en'
|
103
162
|
known_buttons = {}
|
104
163
|
while to_visit.length > 0
|
105
164
|
board = to_visit.shift
|
@@ -173,7 +232,7 @@ module AACMetrics::Metrics
|
|
173
232
|
button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
|
174
233
|
button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
|
175
234
|
# prior_buttons += 0.1 if !button
|
176
|
-
next unless button
|
235
|
+
next unless button && (button['label'] || button['vocalization'] || '').length > 0
|
177
236
|
x = (btn_width / 2) + (btn_width * col_idx)
|
178
237
|
y = (btn_height / 2) + (btn_height * row_idx)
|
179
238
|
# prior_buttons = (row_idx * board[:board]['grid']['columns']) + col_idx
|
@@ -271,10 +330,14 @@ module AACMetrics::Metrics
|
|
271
330
|
next_board = obfset.detect{|brd| brd['id'] == button['load_board']['id'] }
|
272
331
|
change_effort = BOARD_CHANGE_PROCESSING_EFFORT
|
273
332
|
if next_board
|
333
|
+
temp_home_id = board[:temporary_home_id]
|
334
|
+
temp_home_id = board[:board]['id'] if button['load_board']['temporary_home'] == 'prior'
|
335
|
+
temp_home_id = button['load_board']['id'] if button['load_board']['temporary_home'] == true
|
274
336
|
to_visit.push({
|
275
337
|
board: next_board,
|
276
338
|
level: board[:level] + 1,
|
277
339
|
prior_effort: effort + change_effort,
|
340
|
+
temporary_home_id: temp_home_id,
|
278
341
|
entry_x: x,
|
279
342
|
entry_y: y,
|
280
343
|
entry_clone_id: button['clone_id'],
|
@@ -286,29 +349,34 @@ module AACMetrics::Metrics
|
|
286
349
|
if !button['load_board'] || button['load_board']['add_to_sentence']
|
287
350
|
word = button['label']
|
288
351
|
existing = known_buttons[word]
|
289
|
-
if
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
known_buttons[word] = {
|
352
|
+
if board_pcts[button['clone_id']]
|
353
|
+
effort -= [BOARD_CHANGE_PROCESSING_EFFORT, BOARD_CHANGE_PROCESSING_EFFORT * 0.3 / board_pcts[button['clone_id']]].min
|
354
|
+
elsif board_pcts[button['semantic_id']]
|
355
|
+
effort -= [BOARD_CHANGE_PROCESSING_EFFORT, BOARD_CHANGE_PROCESSING_EFFORT * 0.5 / board_pcts[button['semantic_id']]].min
|
356
|
+
end
|
357
|
+
if !existing || effort < existing[:effort]
|
358
|
+
ww = {
|
297
359
|
id: "#{button['id']}::#{board[:board]['id']}",
|
298
360
|
label: word,
|
299
361
|
level: board[:level],
|
300
362
|
effort: effort,
|
301
363
|
}
|
364
|
+
# If a board set has any temporary_home links,
|
365
|
+
# then that can possibly affect the effort
|
366
|
+
# score for sentences
|
367
|
+
if board[:temporary_home_id]
|
368
|
+
ww[:temporary_home_id] = board[:temporary_home_id]
|
369
|
+
end
|
370
|
+
known_buttons[word] = ww
|
302
371
|
end
|
303
372
|
end
|
304
373
|
button['effort'] = effort
|
305
|
-
|
306
374
|
end
|
307
375
|
end
|
308
|
-
end
|
376
|
+
end # end to_visit list
|
309
377
|
buttons = known_buttons.to_a.map(&:last)
|
310
378
|
total_boards = visited_board_ids.keys.length
|
311
|
-
|
379
|
+
|
312
380
|
buttons = buttons.sort_by{|b| [b[:effort] || 1, b[:label] || ""] }
|
313
381
|
clusters = {}
|
314
382
|
buttons.each do |btn|
|
@@ -320,25 +388,38 @@ module AACMetrics::Metrics
|
|
320
388
|
locale: locale,
|
321
389
|
total_boards: total_boards,
|
322
390
|
total_buttons: buttons.length,
|
323
|
-
reference_counts: set_refs,
|
324
|
-
grid: {
|
325
|
-
rows: root_rows,
|
326
|
-
columns: root_cols
|
327
|
-
},
|
328
391
|
buttons: buttons,
|
329
392
|
levels: clusters
|
330
393
|
}
|
331
|
-
if include_obfset
|
332
|
-
res[:obfset] = obfset
|
333
|
-
end
|
334
394
|
res
|
335
395
|
end
|
336
396
|
|
397
|
+
class ExtraFloat < Numeric
|
398
|
+
include Math
|
399
|
+
def initialize(float=0.0)
|
400
|
+
@float = Float(float)
|
401
|
+
end
|
402
|
+
|
403
|
+
def to_f
|
404
|
+
@float.to_f
|
405
|
+
end
|
406
|
+
|
407
|
+
def method_missing(message, *args, &block)
|
408
|
+
if block_given?
|
409
|
+
@float.public_send(message, *args, &block)
|
410
|
+
else
|
411
|
+
@float.public_send(message, *args)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
337
416
|
SQRT2 = Math.sqrt(2)
|
338
417
|
BUTTON_SIZE_MULTIPLIER = 0.09
|
339
418
|
FIELD_SIZE_MULTIPLIER = 0.005
|
340
419
|
VISUAL_SCAN_MULTIPLIER = 0.015
|
341
420
|
BOARD_CHANGE_PROCESSING_EFFORT = 1.0
|
421
|
+
BOARD_HOME_EFFORT = 1.0
|
422
|
+
COMBINED_WORDS_REMEMBERING_EFFORT = 1.0
|
342
423
|
DISTANCE_MULTIPLIER = 0.4
|
343
424
|
DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN = 0.1
|
344
425
|
SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER = 0.5
|
@@ -386,9 +467,21 @@ module AACMetrics::Metrics
|
|
386
467
|
compare[:buttons].each do |btn|
|
387
468
|
compare_words << btn[:label]
|
388
469
|
compare_buttons[btn[:label]] = btn
|
389
|
-
comp_efforts[btn[:label]] = btn[:effort]
|
470
|
+
comp_efforts[btn[:label]] = ExtraFloat.new(btn[:effort])
|
471
|
+
comp_efforts[btn[:label]].instance_variable_set('@temp_home_id', btn[:temporary_home_id])
|
390
472
|
comp_levels[btn[:label]] = btn[:level]
|
391
473
|
end
|
474
|
+
compare[:alternates].each do |id, alt|
|
475
|
+
efforts = {}
|
476
|
+
levels = {}
|
477
|
+
alt[:buttons].each do |btn|
|
478
|
+
efforts[btn[:label]] = ExtraFloat.new(btn[:effort])
|
479
|
+
efforts[btn[:label]].instance_variable_set('@temp_home_id', btn[:temporary_home_id])
|
480
|
+
levels[btn[:label]] = btn[:level]
|
481
|
+
end
|
482
|
+
comp_efforts["H:#{id}"] = efforts
|
483
|
+
comp_levels["H:#{id}"] = levels
|
484
|
+
end
|
392
485
|
|
393
486
|
sortable_efforts = {}
|
394
487
|
target_efforts = {}
|
@@ -400,7 +493,9 @@ module AACMetrics::Metrics
|
|
400
493
|
# very frequent core words and use that when available
|
401
494
|
res[:buttons].each{|b|
|
402
495
|
target_words << b[:label]
|
403
|
-
target_efforts[b[:label]] = b[:effort]
|
496
|
+
target_efforts[b[:label]] = ExtraFloat.new(b[:effort])
|
497
|
+
target_efforts[b[:label]].instance_variable_set('@temp_home_id', b[:temporary_home_id])
|
498
|
+
|
404
499
|
target_levels[b[:label]] = b[:level]
|
405
500
|
sortable_efforts[b[:label]] = b[:effort]
|
406
501
|
comp = compare_buttons[b[:label]]
|
@@ -409,6 +504,18 @@ module AACMetrics::Metrics
|
|
409
504
|
b[:comp_effort] = comp[:effort]
|
410
505
|
end
|
411
506
|
}
|
507
|
+
res[:alternates].each do |id, alt|
|
508
|
+
efforts = {}
|
509
|
+
levels = {}
|
510
|
+
alt[:buttons].each do |btn|
|
511
|
+
efforts[btn[:label]] = ExtraFloat.new(btn[:effort])
|
512
|
+
efforts[btn[:label]].instance_variable_set('@temp_home_id', btn[:temporary_home_id])
|
513
|
+
levels[btn[:label]] = btn[:level]
|
514
|
+
end
|
515
|
+
target_efforts["H:#{id}"] = efforts
|
516
|
+
target_levels["H:#{id}"] = levels
|
517
|
+
end
|
518
|
+
res.delete(:alternates)
|
412
519
|
# Effort scores are the mean of thw scores from the
|
413
520
|
# two sets, or just a singular value if in only one set
|
414
521
|
compare[:buttons].each{|b|
|
@@ -441,7 +548,6 @@ module AACMetrics::Metrics
|
|
441
548
|
end
|
442
549
|
end
|
443
550
|
end
|
444
|
-
|
445
551
|
|
446
552
|
missing = (compare_words - target_words).sort_by{|w| sortable_efforts[w] }
|
447
553
|
missing = missing.select do |word|
|
@@ -519,20 +625,11 @@ module AACMetrics::Metrics
|
|
519
625
|
end
|
520
626
|
|
521
627
|
# 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)
|
628
|
+
effort, level = best_match(word, target_efforts, nil, synonyms)
|
528
629
|
reffort = effort
|
529
630
|
list_effort += effort
|
530
631
|
|
531
|
-
effort = comp_efforts
|
532
|
-
if !effort
|
533
|
-
words.each{|w| effort ||= comp_efforts[w] }
|
534
|
-
end
|
535
|
-
effort ||= spelling_effort(word)
|
632
|
+
effort, level = best_match(word, comp_efforts, nil, synonyms)
|
536
633
|
comp_effort += effort
|
537
634
|
# puts "#{word} - #{reffort.round(1)} - #{effort.round(1)}"
|
538
635
|
end
|
@@ -552,50 +649,18 @@ module AACMetrics::Metrics
|
|
552
649
|
res[:care_components][:comp_core] = (comp_effort_tally / core_lists.to_a.length) * 5.0
|
553
650
|
comp_effort_tally = res[:care_components][:comp_core]
|
554
651
|
|
555
|
-
#
|
652
|
+
# Assemble or allow a battery of word combinations,
|
556
653
|
# and calculate the level of effort for each sequence,
|
557
654
|
# as well as an average level of effort across combinations.
|
655
|
+
# TODO: sets with temporary_home settings will have custom
|
656
|
+
# effort scores for subsequent words in the sentence
|
558
657
|
res[:sentences] = []
|
559
658
|
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
|
659
|
+
sequence = best_combo(words, target_efforts, target_levels, synonyms)
|
660
|
+
target_effort_score = sequence.map{|w, e| e }.sum.to_f / words.length.to_f
|
661
|
+
sequence = best_combo(words, comp_efforts, comp_levels, synonyms)
|
662
|
+
comp_effort_score = sequence.map{|w, e| e }.sum.to_f / words.length.to_f
|
663
|
+
|
599
664
|
res[:sentences] << {sentence: words.join(' '), words: words, effort: target_effort_score, comp_effort: comp_effort_score}
|
600
665
|
end
|
601
666
|
res[:care_components][:sentences] = res[:sentences].map{|s| s[:effort] }.sum.to_f / res[:sentences].length.to_f * 3.0
|
@@ -607,20 +672,11 @@ module AACMetrics::Metrics
|
|
607
672
|
fringe.each do |word|
|
608
673
|
target_effort_score = 0.0
|
609
674
|
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)
|
675
|
+
|
676
|
+
effort, level = best_match(word, target_efforts, nil, synonyms)
|
617
677
|
target_effort_score += effort
|
618
678
|
|
619
|
-
effort =
|
620
|
-
if !effort
|
621
|
-
synonym_words.each{|w| effort ||= comp_efforts[w] }
|
622
|
-
end
|
623
|
-
effort ||= spelling_effort(word)
|
679
|
+
effort, level = best_match(word, comp_efforts, nil, synonyms)
|
624
680
|
comp_effort_score += effort
|
625
681
|
res[:fringe_words] << {word: word, effort: target_effort_score, comp_effort: comp_effort_score}
|
626
682
|
end
|
@@ -633,19 +689,10 @@ module AACMetrics::Metrics
|
|
633
689
|
common_fringe.each do |word|
|
634
690
|
target_effort_score = 0.0
|
635
691
|
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)
|
692
|
+
effort, level = best_match(word, target_efforts, nil, synonyms)
|
642
693
|
target_effort_score += effort
|
643
694
|
|
644
|
-
effort =
|
645
|
-
if !effort
|
646
|
-
synonym_words.each{|w| effort ||= comp_efforts[w] }
|
647
|
-
end
|
648
|
-
effort ||= spelling_effort(word)
|
695
|
+
effort, level = best_match(word, comp_efforts, nil, synonyms)
|
649
696
|
comp_effort_score += effort
|
650
697
|
res[:common_fringe_words] << {word: word, effort: target_effort_score, comp_effort: comp_effort_score}
|
651
698
|
end
|
@@ -657,13 +704,6 @@ module AACMetrics::Metrics
|
|
657
704
|
target_effort_tally += 70 # placeholder value for future added calculations
|
658
705
|
comp_effort_tally += 70
|
659
706
|
|
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
707
|
res[:target_effort_score] = [0.0, 350.0 - target_effort_tally].max
|
668
708
|
res[:comp_effort_score] = [0.0, 350.0 - comp_effort_tally].max
|
669
709
|
# puts "CONSIDER MAKING EASIER"
|
@@ -674,4 +714,94 @@ module AACMetrics::Metrics
|
|
674
714
|
# puts too_easy.join(' ')
|
675
715
|
res
|
676
716
|
end
|
717
|
+
|
718
|
+
# Find the effort for a word, its synonyms, or its spelling.
|
719
|
+
# Always returns a non-nil effort score
|
720
|
+
def self.best_match(word, target_efforts, target_levels, synonyms)
|
721
|
+
synonym_words = [word] + (synonyms[word] || [])
|
722
|
+
effort = target_efforts[word] || target_efforts[word.downcase]
|
723
|
+
target_levels ||= {}
|
724
|
+
level = target_levels[word] || target_levels[word.downcase]
|
725
|
+
if !effort
|
726
|
+
synonym_words.each do |w|
|
727
|
+
if !effort && target_efforts[w]
|
728
|
+
effort = target_efforts[w]
|
729
|
+
level = target_levels[w]
|
730
|
+
end
|
731
|
+
end
|
732
|
+
end
|
733
|
+
# Fallback penalty for missing word
|
734
|
+
fallback_effort = spelling_effort(word)
|
735
|
+
effort = fallback_effort if !effort || fallback_effort < effort
|
736
|
+
|
737
|
+
[effort, level || 0]
|
738
|
+
end
|
739
|
+
|
740
|
+
def self.best_combo(words, efforts, levels, synonyms)
|
741
|
+
options = [{next_idx: 0, list: []}]
|
742
|
+
words.length.times do |idx|
|
743
|
+
options.each do |option|
|
744
|
+
home_id = option[:temporary_home_id]
|
745
|
+
if option[:next_idx] == idx
|
746
|
+
combos = forward_combos(words, idx, efforts, levels)
|
747
|
+
if home_id
|
748
|
+
# Effort of hitting home button, and processing change, plus usual
|
749
|
+
combos.each{|c| c[:effort] += BOARD_HOME_EFFORT + BOARD_CHANGE_PROCESSING_EFFORT}
|
750
|
+
more_combos = forward_combos(words, idx, efforts["H:#{home_id}"] || {}, levels["H:#{home_id}"] || {})
|
751
|
+
more_combos.each{|c| c[:temporary_home_id] ||= home_id }
|
752
|
+
combos += more_combos
|
753
|
+
end
|
754
|
+
combos.each do |combo|
|
755
|
+
if idx > 0 && combo[:level] && combo[:level] > 0
|
756
|
+
combo[:effort] += BOARD_CHANGE_PROCESSING_EFFORT
|
757
|
+
end
|
758
|
+
options << {
|
759
|
+
next_idx: idx + combo[:size],
|
760
|
+
list: option[:list] + [[combo[:partial], combo[:effort]]],
|
761
|
+
temporary_home_id: combo[:temporary_home_id]
|
762
|
+
}
|
763
|
+
end
|
764
|
+
effort, level = best_match(words[idx], efforts, levels, synonyms)
|
765
|
+
option[:temporary_home_id] = effort.instance_variable_get('@temp_home_id')
|
766
|
+
effort += BOARD_CHANGE_PROCESSING_EFFORT if idx > 0 && level && level > 0
|
767
|
+
if home_id
|
768
|
+
effort += BOARD_HOME_EFFORT + BOARD_CHANGE_PROCESSING_EFFORT
|
769
|
+
other_effort, other_level = best_match(words[idx], efforts["H:#{home_id}"] || {}, levels["H:#{home_id}"] || {}, synonyms)
|
770
|
+
new_home_id = other_effort.instance_variable_get('@temp_home_id') || home_id
|
771
|
+
other_effort += BOARD_CHANGE_PROCESSING_EFFORT if idx > 0 && other_level && other_level > 0
|
772
|
+
other_list = option[:list] + [[words[idx], other_effort]]
|
773
|
+
options << {next_idx: idx + 1, list: other_list, temporary_home_id: new_home_id}
|
774
|
+
end
|
775
|
+
option[:list] << [words[idx], effort]
|
776
|
+
option[:next_idx] = idx + 1
|
777
|
+
end
|
778
|
+
end
|
779
|
+
end
|
780
|
+
options.sort_by{|o| o[:list].map{|w, e| e}.sum }.reverse[0][:list]
|
781
|
+
end
|
782
|
+
|
783
|
+
# Checks if any buttons will work for multiple words in a sentence
|
784
|
+
def self.forward_combos(words, idx, target_efforts, target_levels)
|
785
|
+
words_left = words.length - idx
|
786
|
+
combos = []
|
787
|
+
skip = 0
|
788
|
+
temp_home_id = nil
|
789
|
+
if words_left > 1
|
790
|
+
(words_left - 1).times do |minus|
|
791
|
+
partial = words[idx, words_left - minus].join(' ')
|
792
|
+
if target_efforts[partial] || target_efforts[partial.downcase]
|
793
|
+
effort = (target_efforts[partial] || target_efforts[partial.downcase]) + COMBINED_WORDS_REMEMBERING_EFFORT
|
794
|
+
level = target_levels[partial] || target_levels[partial.downcase]
|
795
|
+
combos << {
|
796
|
+
partial: partial,
|
797
|
+
effort: effort,
|
798
|
+
temporary_home_id: effort.instance_variable_get('@temp_home_id'),
|
799
|
+
level: level,
|
800
|
+
size: words_left - minus
|
801
|
+
}
|
802
|
+
end
|
803
|
+
end
|
804
|
+
end
|
805
|
+
combos
|
806
|
+
end
|
677
807
|
end
|