aac-metrics 0.1.1 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/aac-metrics/loader.rb +91 -11
- data/lib/aac-metrics/metrics.rb +241 -27
- data/lib/aac-metrics.rb +2 -2
- data/sets/common_words.en.json +1508 -2838
- data/sets/fringe.en.json +2048 -0
- data/sets/l84f-e9fafa55d4.common.en.obfset +79170 -0
- data/sets/sentences.en.json +31 -0
- data/sets/synonyms.en.json +14 -2
- data/sets/wp108-c428d7f2dc.en.obfset +53075 -0
- data/sets/wp80-dad3aeda5e.common.en.obfset +47115 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57db00976963f424efaab981a2b5066280b2de8dd8d3b241301ba3880fe1ce5c
|
4
|
+
data.tar.gz: fc386c30321ad3f94f03b00bfde72c19ea20dfebf400f6cc8322c841c5b5950b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b9778b4e5a1dce3c1571bdd3038025b7ca6acec1dc9359cc4a04c5183dfd6640fd11fc06651d3059a5561f20d5f22974e6fa35c72d6b6273ee326b38c09612b
|
7
|
+
data.tar.gz: a2762f3a27e48b3445495d0ff16e46da23d80bd4d8e4a08f347df957fd3b595bc93f7697c769b92ba61bd633d57ec07b8df83c0bc2a1d374b763e48be9506724
|
data/lib/aac-metrics/loader.rb
CHANGED
@@ -78,6 +78,7 @@ module AACMetrics::Loader
|
|
78
78
|
words_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', "base_words"))
|
79
79
|
words = nil
|
80
80
|
do_ingest = true
|
81
|
+
relations_hash = {}
|
81
82
|
|
82
83
|
while paths.length > 0
|
83
84
|
path = paths.shift
|
@@ -130,17 +131,7 @@ module AACMetrics::Loader
|
|
130
131
|
"id" => "btn#{btn_idx}",
|
131
132
|
"label" => (btn['vocalization'] || '').length > 0 ? btn['vocalization'] : btn['label']
|
132
133
|
}
|
133
|
-
|
134
|
-
str = new_btn['label'].downcase.sub(/^\s+/, '').sub(/\s+$/, '')
|
135
|
-
if str.scan(/\s+/).length < 2
|
136
|
-
word_hash = Digest::MD5.hexdigest(str)[0, 10]
|
137
|
-
raise "collision!" if words[word_hash] && words[word_hash] != str
|
138
|
-
if add_words || words[word_hash]
|
139
|
-
words[word_hash] = str
|
140
|
-
new_btn['label'] = "$#{word_hash}"
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
134
|
+
# record load_board reference
|
144
135
|
btn_idx += 1
|
145
136
|
if btn['load_board']
|
146
137
|
if btn['load_board']['path']
|
@@ -167,6 +158,36 @@ module AACMetrics::Loader
|
|
167
158
|
# treat action buttons for metrics
|
168
159
|
new_btn = nil
|
169
160
|
end
|
161
|
+
# temporarily save semantic_id and possible clone_id for later use
|
162
|
+
# 1. Buttons in the same location with the same
|
163
|
+
# semantic_id should be marked in the obfset as having
|
164
|
+
# the same semantic_id
|
165
|
+
# 2. Buttons in the same location with the same label & voc
|
166
|
+
# and same load_board setting
|
167
|
+
# should be marked in the obfset as having the same clone_id
|
168
|
+
ref = "#{new_json['grid']['rows']}x#{new_json['grid']['columns']}-#{row_ids}.#{col_id}"
|
169
|
+
if btn['semantic_id']
|
170
|
+
relations_hash["s#{ref}-#{btn['semantic_id']}"] ||= []
|
171
|
+
relations_hash["s#{ref}-#{btn['semantic_id']}"] << [new_json['id'], new_btn['id']]
|
172
|
+
end
|
173
|
+
if new_btn['label']
|
174
|
+
# TODO: currently doesn't enforce same-location on links, just whether it's a linked button or not
|
175
|
+
pre = new_btn['load_board'] ? 'cl' : 'c'
|
176
|
+
relations_hash["#{pre}#{ref}-#{new_btn['label']}"] ||= []
|
177
|
+
relations_hash["#{pre}#{ref}-#{new_btn['label']}"] ||= [new_json['id'], new_btn['id']]
|
178
|
+
end
|
179
|
+
if do_ingest && new_btn['label']
|
180
|
+
str = new_btn['label'].downcase.sub(/^\s+/, '').sub(/\s+$/, '')
|
181
|
+
if str.scan(/\s+/).length < 2
|
182
|
+
word_hash = Digest::MD5.hexdigest(str)[0, 10]
|
183
|
+
raise "collision!" if words[word_hash] && words[word_hash] != str
|
184
|
+
if add_words || words[word_hash]
|
185
|
+
words[word_hash] = str
|
186
|
+
new_btn['label'] = "$#{word_hash}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
170
191
|
end
|
171
192
|
new_row.push(new_btn ? new_btn['id'] : nil)
|
172
193
|
new_json['buttons'].push(new_btn) if new_btn
|
@@ -176,6 +197,29 @@ module AACMetrics::Loader
|
|
176
197
|
boards << new_json
|
177
198
|
end
|
178
199
|
end
|
200
|
+
# any semantic_id or clone_id repeats must be recorded
|
201
|
+
relations_hash.each do |id, btns|
|
202
|
+
if btns && btns.length > 0
|
203
|
+
btns.each do |brd_id, btn_id|
|
204
|
+
brd = boards.detect{|b| b['id'] == brd_id }
|
205
|
+
if brd && brd['buttons']
|
206
|
+
btn = brd['buttons'].detect{|b| b['id'] == btn_id }
|
207
|
+
if btn
|
208
|
+
if id.match(/^s/)
|
209
|
+
btn['semantic_id'] = id
|
210
|
+
brd['semantic_ids'] ||= []
|
211
|
+
brd['semantic_ids'] << id
|
212
|
+
elsif id.match(/^c/)
|
213
|
+
btn['clone_id'] = id
|
214
|
+
brd['clone_ids'] ||= []
|
215
|
+
brd['clone_ids'] << id
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
#
|
221
|
+
end
|
222
|
+
end
|
179
223
|
boards.each do |brd|
|
180
224
|
brd['buttons'].each do |btn|
|
181
225
|
if btn['load_board'] && btn['load_board']['tmp_path']
|
@@ -184,9 +228,21 @@ module AACMetrics::Loader
|
|
184
228
|
end
|
185
229
|
end
|
186
230
|
end
|
231
|
+
# TODO: record whether the board set is expected to have auto-home
|
187
232
|
{boards: boards, words: words, words_path: words_path}
|
188
233
|
end
|
189
234
|
|
235
|
+
# TODO: Qualitative assessments of common vocabularies,
|
236
|
+
# gather perspectives on what makes a "good" vocabulary
|
237
|
+
# and collect reviews from field experts, also free
|
238
|
+
# response sections.
|
239
|
+
# Some criteria:
|
240
|
+
# - works well for age group X, Y, Z
|
241
|
+
# - works well for a beginning communicator
|
242
|
+
# - allows long-term growth as-is
|
243
|
+
# - comprehensive core
|
244
|
+
# -
|
245
|
+
|
190
246
|
def self.ingest(fn, token=nil)
|
191
247
|
output = nil
|
192
248
|
boards = nil
|
@@ -292,7 +348,31 @@ module AACMetrics::Loader
|
|
292
348
|
@@synonyms[locale] = res
|
293
349
|
end
|
294
350
|
|
351
|
+
def self.sentences(locale)
|
352
|
+
@@sentences ||= {}
|
353
|
+
return @@sentences[locale] if @@sentences[locale]
|
354
|
+
locale = locale.split(/-|_/)[0]
|
355
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', "sentences.#{locale}.json"))
|
356
|
+
res = JSON.parse(File.read(path))
|
357
|
+
@@sentences[locale] = res
|
358
|
+
end
|
295
359
|
|
360
|
+
def self.fringe_words(locale)
|
361
|
+
@@fringe_words ||= {}
|
362
|
+
return @@fringe_words[locale] if @@fringe_words[locale]
|
363
|
+
locale = locale.split(/-|_/)[0]
|
364
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', "fringe.#{locale}.json"))
|
365
|
+
all_words = []
|
366
|
+
list = JSON.parse(File.read(path))
|
367
|
+
list.each do |set|
|
368
|
+
set['categories'].each do |cat|
|
369
|
+
all_words += cat['words']
|
370
|
+
end
|
371
|
+
end
|
372
|
+
all_words.uniq!
|
373
|
+
@@synonyms[locale] = all_words
|
374
|
+
end
|
375
|
+
|
296
376
|
def self.base_words(locale)
|
297
377
|
@@base_words ||= {}
|
298
378
|
return @@base_words[locale] if @@base_words[locale]
|
data/lib/aac-metrics/metrics.rb
CHANGED
@@ -2,31 +2,84 @@
|
|
2
2
|
# Scores for average effort level for word sets (spelling if that's th only way)
|
3
3
|
# Effort scores for sentence corpus
|
4
4
|
# Effort algorithms for scanning/eyes
|
5
|
-
|
5
|
+
# TODO: manual way to flag button as conceptually
|
6
|
+
# related to the same-locaed button on the
|
7
|
+
# prior board, allowing for a discounted penalty
|
6
8
|
module AACMetrics::Metrics
|
7
|
-
|
9
|
+
# TODO:
|
10
|
+
# 1. When navigating from one board to the next, grid locations
|
11
|
+
# with the same clone_id or semantic_id should result in a
|
12
|
+
# discount to overall search based more on the number of
|
13
|
+
# uncloned/unsemantic buttons than the number of total buttons
|
14
|
+
# (perhaps also factoring in the percent of board with that
|
15
|
+
# id present in the full board set)
|
16
|
+
# 2. When selecting a button with a semantic_id or clone_id,
|
17
|
+
# a discount to both search and selection should
|
18
|
+
# be applied based on the percent of boards that
|
19
|
+
# contain the same id at that grid location
|
20
|
+
# 3.5 When selecting a button with a semantic_id or clone_id,
|
21
|
+
# if the same id was present on the previous board,
|
22
|
+
# an additional discount to search and selection should be applied
|
23
|
+
def self.analyze(obfset, output=true)
|
8
24
|
locale = nil
|
9
25
|
buttons = []
|
10
|
-
|
11
|
-
|
26
|
+
refs = {}
|
27
|
+
grid = {}
|
28
|
+
|
12
29
|
if obfset.is_a?(Hash) && obfset['buttons']
|
13
30
|
locale = obfset['locale'] || 'en'
|
31
|
+
refs = obfset['reference_counts']
|
32
|
+
grid = obfset['grid']
|
14
33
|
buttons = []
|
15
34
|
obfset['buttons'].each do |btn|
|
16
35
|
buttons << {
|
17
36
|
id: btn['id'],
|
18
37
|
label: btn['label'],
|
19
38
|
level: btn['level'],
|
20
|
-
effort: btn['effort']
|
39
|
+
effort: btn['effort'],
|
40
|
+
semantic_id: btn['semantic_id'],
|
41
|
+
clone_id: btn['clone_id']
|
21
42
|
}
|
22
43
|
end
|
23
44
|
total_boards = obfset['total_boards']
|
24
45
|
else
|
25
46
|
visited_board_ids = {}
|
26
47
|
to_visit = [{board: obfset[0], level: 0, entry_x: 1.0, entry_y: 1.0}]
|
48
|
+
refs = {}
|
49
|
+
rows_tally = 0.0
|
50
|
+
cols_tally = 0.0
|
51
|
+
root_rows = nil
|
52
|
+
root_cols = nil
|
53
|
+
obfset.each do |board|
|
54
|
+
root_rows ||= board['grid']['rows']
|
55
|
+
root_cols ||= board['grid']['columns']
|
56
|
+
rows_tally += board['grid']['rows']
|
57
|
+
cols_tally += board['grid']['columns']
|
58
|
+
# determine frequency within the board set
|
59
|
+
# for each semantic_id and clone_id
|
60
|
+
if board['clone_ids']
|
61
|
+
boards['clone_ids'].each do |id|
|
62
|
+
refs[id] ||= 0
|
63
|
+
refs[id] += 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
if board['semantic_ids']
|
67
|
+
boards['semantic_ids'].each do |id|
|
68
|
+
refs[id] ||= 0
|
69
|
+
refs[id] += 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
if (rows_tally / obfset.length.to_f - root_rows).abs > 3 || (cols_tally / obfset.length.to_f - root_cols).abs > 3
|
74
|
+
root_rows = (rows_tally / obfset.length.to_f).floor
|
75
|
+
root_cols = (cols_tally / obfset.length.to_f).floor
|
76
|
+
end
|
77
|
+
pcts = {}
|
78
|
+
refs.each do |id, cnt|
|
79
|
+
pcts[id] = cnt.to_f / obfset.length.to_f
|
80
|
+
end
|
27
81
|
locale = obfset[0]['locale']
|
28
82
|
known_buttons = {}
|
29
|
-
sqrt2 = Math.sqrt(2)
|
30
83
|
while to_visit.length > 0
|
31
84
|
board = to_visit.shift
|
32
85
|
visited_board_ids[board[:board]['id']] = board[:level]
|
@@ -35,32 +88,63 @@ module AACMetrics::Metrics
|
|
35
88
|
btn_width = 1.0 / board[:board]['grid']['columns'].to_f
|
36
89
|
board_effort = 0
|
37
90
|
# add effort for level of complexity when new board is rendered
|
38
|
-
|
91
|
+
button_size = button_size_effort(board[:board]['grid']['rows'], board[:board]['grid']['columns'])
|
92
|
+
board_effort += button_size
|
39
93
|
# add effort for number of visible buttons
|
40
|
-
|
94
|
+
field_size = field_size_effort(board[:board]['grid']['order'].flatten.length)
|
95
|
+
board_effort += field_size
|
96
|
+
# decrease effort here for every button on the board
|
97
|
+
# whose semantic_id or clone_id is repeated in the board set
|
98
|
+
# -0.0025 (* pct of matching boards) for semantic_id
|
99
|
+
# -0.005 (* pct of matching boards) for clone_id
|
100
|
+
board[:board]['grid']['rows'].times do |row_idx|
|
101
|
+
board[:board]['grid']['columns'].times do |col_idx|
|
102
|
+
button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
|
103
|
+
button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
|
104
|
+
if button && button['clone_id'] && pcts[button['clone_id']]
|
105
|
+
board_effort -= 0.005 * pcts[button['clone_id']]
|
106
|
+
elsif button && button['semantic_id'] && pcts[button['semantic_id']]
|
107
|
+
board_effort -= 0.0025 * pcts[button['semantic_id']]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
41
112
|
prior_buttons = 0
|
42
113
|
|
43
114
|
board[:board]['grid']['rows'].times do |row_idx|
|
44
115
|
board[:board]['grid']['columns'].times do |col_idx|
|
45
116
|
button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
|
46
117
|
button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
|
47
|
-
prior_buttons += 0.1 if !button
|
118
|
+
# prior_buttons += 0.1 if !button
|
48
119
|
next unless button
|
49
120
|
x = (btn_width / 2) + (btn_width * col_idx)
|
50
121
|
y = (btn_height / 2) + (btn_height * row_idx)
|
51
122
|
# prior_buttons = (row_idx * board[:board]['grid']['columns']) + col_idx
|
52
123
|
effort = 0
|
124
|
+
# TODO: additional discount on board search effort
|
125
|
+
# if this button's semantic_id or clone_id
|
126
|
+
# was also present on the prior board
|
127
|
+
# board_effort * 0.5 for semantic_id
|
128
|
+
# board_effort * 0.33 for clone_id
|
53
129
|
effort += board_effort
|
54
130
|
# add effort for percent distance from entry point
|
55
|
-
distance =
|
131
|
+
distance = distance_effort(x, y, board[:entry_x], board[:entry_y])
|
132
|
+
# TODO: decrease effective distance if the semantic_id or clone_id:
|
133
|
+
# - are used on other boards in the set (semi)
|
134
|
+
# distance * 0.5 (* pct of matching boards) for semantic_id
|
135
|
+
# distance * 0.33 (* pct of matching boards) for clone_id
|
136
|
+
# - was also present on the prior board (total)
|
137
|
+
# distance * 0.5 for semantic_id
|
138
|
+
# distance * 0.33 for clone_id
|
56
139
|
effort += distance
|
57
|
-
if distance >
|
140
|
+
if distance > DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN || (board[:entry_x] == 1.0 && board[:entry_y] == 1.0)
|
58
141
|
# add small effort for every prior (visible) button when visually scanning
|
59
|
-
|
142
|
+
visual_scan = visual_scan_effort(prior_buttons)
|
143
|
+
effort += visual_scan
|
60
144
|
else
|
61
145
|
# ..unless it's right by the previous button, then
|
62
146
|
# add tiny effort for local scan
|
63
|
-
effort += distance *
|
147
|
+
effort += distance * SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER
|
64
148
|
end
|
65
149
|
# add cumulative effort from previous sequence
|
66
150
|
effort += board[:prior_effort] || 0
|
@@ -81,11 +165,12 @@ module AACMetrics::Metrics
|
|
81
165
|
end
|
82
166
|
if try_visit
|
83
167
|
next_board = obfset.detect{|brd| brd['id'] == button['load_board']['id'] }
|
168
|
+
puts "LIKE[] #{effort}" if button['label'] == 'like'
|
84
169
|
if next_board
|
85
170
|
to_visit.push({
|
86
171
|
board: next_board,
|
87
172
|
level: board[:level] + 1,
|
88
|
-
prior_effort: effort +
|
173
|
+
prior_effort: effort + BOARD_CHANGE_PROCESSING_EFFORT,
|
89
174
|
entry_x: x,
|
90
175
|
entry_y: y
|
91
176
|
})
|
@@ -94,7 +179,8 @@ module AACMetrics::Metrics
|
|
94
179
|
else
|
95
180
|
word = button['label']
|
96
181
|
existing = known_buttons[word]
|
97
|
-
if !existing || board[:level] < existing[:level]
|
182
|
+
if !existing || existing[:effort] < effort #board[:level] < existing[:level]
|
183
|
+
puts "LIKE #{effort}" if button['label'] == 'like'
|
98
184
|
known_buttons[word] = {
|
99
185
|
id: "#{button['id']}::#{board[:board]['id']}",
|
100
186
|
label: word,
|
@@ -120,11 +206,45 @@ module AACMetrics::Metrics
|
|
120
206
|
locale: locale,
|
121
207
|
total_boards: total_boards,
|
122
208
|
total_buttons: buttons.length,
|
209
|
+
reference_counts: refs,
|
210
|
+
grid: {
|
211
|
+
rows: root_rows,
|
212
|
+
columns: root_cols
|
213
|
+
},
|
123
214
|
buttons: buttons,
|
124
215
|
levels: clusters
|
125
216
|
}
|
126
217
|
end
|
127
218
|
|
219
|
+
SQRT2 = Math.sqrt(2)
|
220
|
+
BUTTON_SIZE_MULTIPLIER = 0.09
|
221
|
+
FIELD_SIZE_MULTIPLIER = 0.017
|
222
|
+
VISUAL_SCAN_MULTIPLIER = 0.02
|
223
|
+
BOARD_CHANGE_PROCESSING_EFFORT = 1.0
|
224
|
+
DISTANCE_MULTIPLIER = 0.5
|
225
|
+
DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN = 0.1
|
226
|
+
SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER = 0.5
|
227
|
+
|
228
|
+
def self.button_size_effort(rows, cols)
|
229
|
+
BUTTON_SIZE_MULTIPLIER * (rows + cols) / 2
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.field_size_effort(button_count)
|
233
|
+
FIELD_SIZE_MULTIPLIER * button_count
|
234
|
+
end
|
235
|
+
|
236
|
+
def self.visual_scan_effort(prior_buttons)
|
237
|
+
prior_buttons * VISUAL_SCAN_MULTIPLIER
|
238
|
+
end
|
239
|
+
|
240
|
+
def self.distance_effort(x, y, entry_x, entry_y)
|
241
|
+
Math.sqrt((x - entry_x) ** 2 + (y - entry_y) ** 2) / SQRT2 * DISTANCE_MULTIPLIER
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.spelling_effort(word)
|
245
|
+
10 + (word.length * 2.5)
|
246
|
+
end
|
247
|
+
|
128
248
|
def self.analyze_and_compare(obfset, compset)
|
129
249
|
target = AACMetrics::Metrics.analyze(obfset, false)
|
130
250
|
res = {}.merge(target)
|
@@ -142,34 +262,43 @@ module AACMetrics::Metrics
|
|
142
262
|
comp_efforts[btn[:label]] = btn[:effort]
|
143
263
|
end
|
144
264
|
|
145
|
-
|
265
|
+
sortable_efforts = {}
|
146
266
|
target_efforts = {}
|
147
267
|
target_words = []
|
268
|
+
# Track effort scores for each button in the set,
|
269
|
+
# used to sort and for assessing priority
|
270
|
+
# TODO: keep a list of expected effort scores for
|
271
|
+
# very frequent core words and use that when available
|
148
272
|
res[:buttons].each{|b|
|
149
273
|
target_words << b[:label]
|
150
274
|
target_efforts[b[:label]] = b[:effort]
|
151
|
-
|
275
|
+
sortable_efforts[b[:label]] = b[:effort]
|
152
276
|
comp = compare_buttons[b[:label]]
|
153
277
|
if comp
|
154
278
|
b[:comp_level] = comp[:level]
|
155
279
|
b[:comp_effort] = comp[:effort]
|
156
280
|
end
|
157
281
|
}
|
282
|
+
# Effort scores are the mean of thw scores from the
|
283
|
+
# two sets, or just a singular value if in only one set
|
158
284
|
compare[:buttons].each{|b|
|
159
|
-
if
|
160
|
-
|
161
|
-
|
285
|
+
if sortable_efforts[b[:label]]
|
286
|
+
sortable_efforts[b[:label]] += b[:effort]
|
287
|
+
sortable_efforts[b[:label]] /= 2
|
162
288
|
else
|
163
|
-
|
289
|
+
sortable_efforts[b[:label]] ||= b[:effort]
|
164
290
|
end
|
165
291
|
}
|
166
292
|
|
167
293
|
core_lists = AACMetrics::Loader.core_lists(target[:locale])
|
168
294
|
common_words_obj = AACMetrics::Loader.common_words(target[:locale])
|
169
295
|
synonyms = AACMetrics::Loader.synonyms(target[:locale])
|
170
|
-
|
296
|
+
sentences = AACMetrics::Loader.sentences(target[:locale])
|
297
|
+
fringe = AACMetrics::Loader.fringe_words(target[:locale])
|
298
|
+
common_words_obj['efforts'].each{|w, e| sortable_efforts[w] ||= e }
|
171
299
|
common_words = common_words_obj['words']
|
172
300
|
|
301
|
+
# Track which words are significantly harder or easier than expected
|
173
302
|
too_easy = []
|
174
303
|
too_hard = []
|
175
304
|
target[:buttons].each do |btn|
|
@@ -183,11 +312,11 @@ module AACMetrics::Metrics
|
|
183
312
|
end
|
184
313
|
|
185
314
|
|
186
|
-
missing = (compare_words - target_words).sort_by{|w|
|
315
|
+
missing = (compare_words - target_words).sort_by{|w| sortable_efforts[w] }
|
187
316
|
missing = missing.select do |word|
|
188
317
|
!synonyms[word] || (synonyms[word] & target_words).length == 0
|
189
318
|
end
|
190
|
-
extras = (target_words - compare_words).sort_by{|w|
|
319
|
+
extras = (target_words - compare_words).sort_by{|w| sortable_efforts[w] }
|
191
320
|
extras = extras.select do |word|
|
192
321
|
!synonyms[word] || (synonyms[word] & compare_words).length == 0
|
193
322
|
end
|
@@ -235,28 +364,42 @@ module AACMetrics::Metrics
|
|
235
364
|
res[:cores] = {
|
236
365
|
:common => {name: "Common Word List", list: common_words, average_effort: common_effort, comp_effort: comp_effort}
|
237
366
|
}
|
367
|
+
target_effort_tally = 0.0
|
368
|
+
comp_effort_tally = 0.0
|
369
|
+
# For each core list, find any missing words, and compute
|
370
|
+
# the average level of effort for all words in the set,
|
371
|
+
# using a fallback effort metric if the word isn't in the
|
372
|
+
# board set
|
238
373
|
# puts missing.join(' ')
|
239
374
|
core_lists.each do |list|
|
240
|
-
puts list['id']
|
241
375
|
missing = []
|
376
|
+
comp_missing = []
|
242
377
|
list_effort = 0
|
243
378
|
comp_effort = 0
|
244
379
|
list['words'].each do |word|
|
245
380
|
words = [word] + (synonyms[word] || [])
|
381
|
+
# Check if any words from the core list are missing in the set
|
246
382
|
if (target_words & words).length == 0
|
247
383
|
missing << word
|
248
384
|
end
|
385
|
+
if (compare_words & words).length == 0
|
386
|
+
comp_missing << word
|
387
|
+
end
|
388
|
+
|
389
|
+
# Calculate the effort for the target and comp sets
|
249
390
|
effort = target_efforts[word]
|
250
391
|
if !effort
|
251
392
|
words.each{|w| effort ||= target_efforts[w] }
|
252
393
|
end
|
253
|
-
|
394
|
+
# Fallback penalty for missing word
|
395
|
+
effort ||= spelling_effort(word)
|
254
396
|
list_effort += effort
|
397
|
+
|
255
398
|
effort = comp_efforts[word]
|
256
399
|
if !effort
|
257
400
|
words.each{|w| effort ||= comp_efforts[w] }
|
258
401
|
end
|
259
|
-
effort ||=
|
402
|
+
effort ||= spelling_effort(word)
|
260
403
|
comp_effort += effort
|
261
404
|
end
|
262
405
|
if missing.length > 0
|
@@ -266,8 +409,79 @@ module AACMetrics::Metrics
|
|
266
409
|
end
|
267
410
|
list_effort = list_effort.to_f / list['words'].length.to_f
|
268
411
|
comp_effort = comp_effort.to_f / list['words'].length.to_f
|
412
|
+
target_effort_tally += list_effort
|
413
|
+
comp_effort_tally += comp_effort
|
269
414
|
res[:cores][list['id']] = {name: list['name'], list: list['words'], average_effort: list_effort, comp_effort: comp_effort}
|
270
415
|
end
|
416
|
+
target_effort_tally = (target_effort_tally / core_lists.to_a.length) * 5.0
|
417
|
+
|
418
|
+
comp_effort_tally = (comp_effort_tally / core_lists.to_a.length) * 5.0
|
419
|
+
|
420
|
+
# TODO: Assemble or allow a battery of word combinations,
|
421
|
+
# and calculate the level of effort for each sequence,
|
422
|
+
# as well as an average level of effort across combinations.
|
423
|
+
res[:sentences] = []
|
424
|
+
sentences.each do |words|
|
425
|
+
puts " #{words.join(' ')}"
|
426
|
+
BOARD_CHANGE_PROCESSING_EFFORT
|
427
|
+
target_effort_score = 0.0
|
428
|
+
comp_effort_score = 0.0
|
429
|
+
words.each_with_index do |word, idx|
|
430
|
+
synonym_words = [word] + (synonyms[word] || [])
|
431
|
+
effort = target_efforts[word] || target_efforts[word.downcase]
|
432
|
+
if !effort
|
433
|
+
synonym_words.each{|w| effort ||= target_efforts[w] }
|
434
|
+
end
|
435
|
+
effort ||= spelling_effort(word)
|
436
|
+
effort += (idx == 0) ? 0.0 : BOARD_CHANGE_PROCESSING_EFFORT
|
437
|
+
ee = effort
|
438
|
+
target_effort_score += effort
|
439
|
+
|
440
|
+
effort = comp_efforts[word] || comp_efforts[word.downcase]
|
441
|
+
if !effort
|
442
|
+
synonym_words.each{|w| effort ||= comp_efforts[w] }
|
443
|
+
end
|
444
|
+
effort ||= spelling_effort(word)
|
445
|
+
effort += (idx == 0) ? 0.0 : BOARD_CHANGE_PROCESSING_EFFORT
|
446
|
+
comp_effort_score += effort
|
447
|
+
end
|
448
|
+
target_effort_score = target_effort_score / words.length
|
449
|
+
comp_effort_score = comp_effort_score / words.length
|
450
|
+
res[:sentences] << {sentence: words.join(' '), words: words, effort: target_effort_score, comp_effort: comp_effort_score}
|
451
|
+
end
|
452
|
+
target_effort_tally += res[:sentences].map{|s| s[:effort] }.sum.to_f / res[:sentences].length.to_f * 3.0
|
453
|
+
comp_effort_tally += res[:sentences].map{|s| s[:comp_effort] }.sum.to_f / res[:sentences].length.to_f * 3.0
|
454
|
+
|
455
|
+
res[:fringe_words] = []
|
456
|
+
fringe.each do |word|
|
457
|
+
target_effort_score = 0.0
|
458
|
+
comp_effort_score = 0.0
|
459
|
+
synonym_words = [word] + (synonyms[word] || [])
|
460
|
+
effort = target_efforts[word] || target_efforts[word.downcase]
|
461
|
+
if !effort
|
462
|
+
synonym_words.each{|w| effort ||= target_efforts[w] }
|
463
|
+
end
|
464
|
+
effort ||= spelling_effort(word)
|
465
|
+
target_effort_score += effort
|
466
|
+
|
467
|
+
effort = comp_efforts[word] || comp_efforts[word.downcase]
|
468
|
+
if !effort
|
469
|
+
synonym_words.each{|w| effort ||= comp_efforts[w] }
|
470
|
+
end
|
471
|
+
effort ||= spelling_effort(word)
|
472
|
+
comp_effort_score += effort
|
473
|
+
res[:fringe_words] << {word: word, effort: target_effort_score, comp_effort: comp_effort_score}
|
474
|
+
end
|
475
|
+
target_effort_tally += res[:fringe_words].map{|s| s[:effort] }.sum.to_f / res[:fringe_words].length.to_f * 2.0
|
476
|
+
comp_effort_tally += res[:fringe_words].map{|s| s[:comp_effort] }.sum.to_f / res[:fringe_words].length.to_f * 2.0
|
477
|
+
|
478
|
+
target_effort_tally += 80 # placeholder value for future added calculations
|
479
|
+
comp_effort_tally += 80
|
480
|
+
|
481
|
+
|
482
|
+
|
483
|
+
res[:target_effort_score] = target_effort_tally
|
484
|
+
res[:comp_effort_score] = comp_effort_tally
|
271
485
|
# puts "CONSIDER MAKING EASIER"
|
272
486
|
res[:high_effort_words] = too_hard
|
273
487
|
# puts too_hard.join(' ')
|
data/lib/aac-metrics.rb
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
# Are grammar inflections available?
|
9
9
|
# Does this support auto-home, or other motor planning supports?
|
10
10
|
# Are other language options available?
|
11
|
-
# How is
|
11
|
+
# How is the vocabulary organized?
|
12
12
|
# What platforms are supported?
|
13
13
|
# Access to keyboard w/ prediction? Numbers?
|
14
14
|
# How easy is it to say these personalized sentences: _____
|
@@ -43,7 +43,7 @@
|
|
43
43
|
# thing explainer phrases, lists of words
|
44
44
|
|
45
45
|
module AACMetrics
|
46
|
-
VERSION = "0.
|
46
|
+
VERSION = "0.2"
|
47
47
|
require 'aac-metrics/loader'
|
48
48
|
require 'aac-metrics/metrics'
|
49
49
|
end
|