aac-metrics 0.2.1 → 0.2.2
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 +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
|