aac-metrics 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|