aac-metrics 0.0.1 → 0.0.6
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 +93 -29
- data/lib/aac-metrics/metrics.rb +185 -72
- data/sets/common_words.en.json +2826 -1494
- data/sets/l84f-e9fafa55d4.common.en.analysis +54927 -0
- data/sets/links.obz +0 -0
- data/sets/qc112-a67653dd81.en.obfset +64 -64
- data/sets/qc24-bcbee2ea83.en.obfset +37 -37
- data/sets/qc60-61bbf6171e.common.en.obfset +48 -48
- data/sets/sfy-c45a81c416.common.en.obfset +87 -87
- data/sets/wp108-c428d7f2dc.en.analysis +30427 -0
- data/sets/wp80-dad3aeda5e.common.en.analysis +29335 -0
- metadata +6 -6
- data/lib/aac-metrics/obf.rb +0 -19
- data/sets/l84f-e9fafa55d4.common.en.obfset +0 -79170
- data/sets/wp108-c428d7f2dc.en.obfset +0 -53075
- data/sets/wp80-dad3aeda5e.common.en.obfset +0 -47115
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b241e25385bbf1330a3ba38d9a3cb4935ea570a8e629b18be1775557f96c6c7c
|
4
|
+
data.tar.gz: '07127794d01330821ff34bb36acf64f6fb538a3b13ca98c61ddbb3d83fca53ff'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 262a150c97739c793df16ac9abef5b633010f5d62be8031a160996654edfa63ed3bc79c8fb42f60916212ba8d70465165c0a50b995000ef5872c3d0a9768dee7
|
7
|
+
data.tar.gz: 2e3f426ceaaf6445d31531a328d36e237bb7a33a980714afcc68c707421ebd71bfbcfb19cc37edfed60a4513aa08ca1de8f952ff7ef4330925fbe5492ed4d172
|
data/lib/aac-metrics/loader.rb
CHANGED
@@ -3,19 +3,68 @@ require 'typhoeus'
|
|
3
3
|
require 'digest'
|
4
4
|
|
5
5
|
module AACMetrics::Loader
|
6
|
-
def self.retrieve(obfset)
|
7
|
-
if
|
8
|
-
|
6
|
+
def self.retrieve(obfset, unsub=true)
|
7
|
+
if obfset.is_a?(Hash) && obfset['boards']
|
8
|
+
json = []
|
9
|
+
obfset['boards'].each do |board|
|
10
|
+
new_board = {
|
11
|
+
"id" => board['id'],
|
12
|
+
"buttons" => [],
|
13
|
+
"locale" => board['locale'] || 'en',
|
14
|
+
"grid" => board['grid'],
|
15
|
+
}
|
16
|
+
board['buttons'].each do |button|
|
17
|
+
new_button = {
|
18
|
+
"label" => ((button['vocalization'] || '').length > 0 ? button['vocalization'] : button['label']).to_s.downcase.gsub(/’/, ''),
|
19
|
+
"id" => button['id']
|
20
|
+
}
|
21
|
+
if button['load_board'] && button['load_board']['id']
|
22
|
+
new_button['load_board'] = {'id' => button['load_board']['id']}
|
23
|
+
end
|
24
|
+
new_board['buttons'].push(new_button)
|
25
|
+
end
|
26
|
+
json << new_board
|
27
|
+
end
|
28
|
+
return json
|
29
|
+
elsif obfset.match(/^http/)
|
30
|
+
res = Typhoeus.get(obfset, timeout: 10)
|
31
|
+
json = JSON.parse(res.body)
|
32
|
+
elsif !obfset.match(/\.obfset/)
|
33
|
+
fn = obfset
|
34
|
+
obfset = Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', fn + '*.obfset')))[0]
|
35
|
+
analysis = nil
|
36
|
+
if !obfset
|
37
|
+
analysis = Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', fn + '*.analysis')))[0]
|
38
|
+
end
|
39
|
+
if obfset
|
40
|
+
json = JSON.parse(File.read(obfset))
|
41
|
+
elsif analysis
|
42
|
+
json = JSON.parse(File.read(analysis))
|
43
|
+
end
|
44
|
+
else
|
45
|
+
json = JSON.parse(File.read(obfset))
|
9
46
|
end
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
47
|
+
if unsub
|
48
|
+
locale = json.is_a?(Array) ? json[0]['locale'] : json['locale']
|
49
|
+
base = self.base_words(locale)
|
50
|
+
if json.is_a?(Array)
|
51
|
+
json.each do |brd|
|
52
|
+
brd['buttons'].each do |btn|
|
53
|
+
if btn['label'].match(/^\$/)
|
54
|
+
word = base[btn['label'].sub(/^\$/, '')]
|
55
|
+
btn['label'] = word if word
|
56
|
+
end
|
57
|
+
btn['label'] = btn['label'].gsub(/’/, '')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
elsif json.is_a?(Hash)
|
61
|
+
(json['buttons'] || []).each do |button|
|
62
|
+
if button['label'].match(/^\$/)
|
63
|
+
word = base[button['label'].sub(/^\$/, '')]
|
64
|
+
button['label'] = word if word
|
65
|
+
end
|
66
|
+
button['label'] = button['label'].gsub(/’/, '')
|
17
67
|
end
|
18
|
-
btn['label'] = btn['label'].gsub(/’/, '')
|
19
68
|
end
|
20
69
|
end
|
21
70
|
json
|
@@ -35,7 +84,6 @@ module AACMetrics::Loader
|
|
35
84
|
path = paths.shift
|
36
85
|
visited_paths[path] = idx
|
37
86
|
new_json = {
|
38
|
-
"format" => "open-board-0.1",
|
39
87
|
"id" => "brd#{idx}",
|
40
88
|
"buttons" => [],
|
41
89
|
"grid" => {},
|
@@ -46,9 +94,9 @@ module AACMetrics::Loader
|
|
46
94
|
if token
|
47
95
|
path += "?access_token=#{token}"
|
48
96
|
end
|
49
|
-
req = Typhoeus.get(path)
|
97
|
+
req = Typhoeus.get(path, timeout: 10)
|
50
98
|
json = JSON.parse(req.body)
|
51
|
-
puts path
|
99
|
+
# puts path
|
52
100
|
else
|
53
101
|
if !File.exist?(path)
|
54
102
|
orig_path = path
|
@@ -57,7 +105,7 @@ module AACMetrics::Loader
|
|
57
105
|
path = File.expand_path(File.join(fn, "..", "..", orig_path))
|
58
106
|
end
|
59
107
|
end
|
60
|
-
puts "#{path}"
|
108
|
+
# puts "#{path}"
|
61
109
|
json = JSON.parse(File.read(path))
|
62
110
|
end
|
63
111
|
if json && json['grid']
|
@@ -139,20 +187,34 @@ module AACMetrics::Loader
|
|
139
187
|
end
|
140
188
|
|
141
189
|
def self.ingest(fn, token=nil)
|
142
|
-
|
143
|
-
boards =
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
f = File.open(
|
155
|
-
f.write(JSON.pretty_generate(
|
190
|
+
output = nil
|
191
|
+
boards = nil
|
192
|
+
if fn.match(/\.obfset$/)
|
193
|
+
boards = retrieve(fn, false)
|
194
|
+
output = fn
|
195
|
+
else
|
196
|
+
content = process(fn, token)
|
197
|
+
boards = content[:boards]
|
198
|
+
words = content[:words]
|
199
|
+
words_path = content[:words_path]
|
200
|
+
output_fn = Digest::MD5.hexdigest(Time.now.to_i.to_s + rand(9999).to_s)[0, 10] + ".obfset"
|
201
|
+
output = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', output_fn))
|
202
|
+
f = File.open(output, 'w')
|
203
|
+
f.write(JSON.pretty_generate(boards))
|
204
|
+
f.close
|
205
|
+
if words
|
206
|
+
new_words = {}
|
207
|
+
words.to_a.sort_by{|h, w| w }.each{|h, w| new_words[h] = w }
|
208
|
+
f = File.open(words_path, 'w')
|
209
|
+
f.write(JSON.pretty_generate(new_words))
|
210
|
+
f.close
|
211
|
+
end
|
212
|
+
end
|
213
|
+
if boards
|
214
|
+
analysis = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', fn.sub(/\.obfset$/, '.analysis')))
|
215
|
+
res = AACMetrics::Metrics.analyze(boards)
|
216
|
+
f = File.open(analysis, 'w')
|
217
|
+
f.write(JSON.pretty_generate(res))
|
156
218
|
f.close
|
157
219
|
end
|
158
220
|
output
|
@@ -171,6 +233,8 @@ module AACMetrics::Loader
|
|
171
233
|
locale = locale.split(/-|_/)[0]
|
172
234
|
common_paths = Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', "*.common.#{locale}.obfset")))
|
173
235
|
files = common_paths.map{|p| File.basename(p) }.sort
|
236
|
+
common_analysis_paths = Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', "*.common.#{locale}.analysis")))
|
237
|
+
files += common_analysis_paths.map{|p| File.basename(p) }.sort
|
174
238
|
path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', "common_words.#{locale}.json"))
|
175
239
|
res = JSON.parse(File.read(path)) rescue nil
|
176
240
|
if !res || true
|
data/lib/aac-metrics/metrics.rb
CHANGED
@@ -1,95 +1,208 @@
|
|
1
1
|
module AACMetrics::Metrics
|
2
2
|
def self.analyze(obfset, output=true)
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
3
|
+
locale = nil
|
4
|
+
buttons = []
|
5
|
+
total_boards = 1
|
6
|
+
|
7
|
+
if obfset.is_a?(Hash) && obfset['buttons']
|
8
|
+
locale = obfset['locale'] || 'en'
|
9
|
+
buttons = []
|
10
|
+
obfset['buttons'].each do |btn|
|
11
|
+
buttons << {
|
12
|
+
id: btn['id'],
|
13
|
+
label: btn['label'],
|
14
|
+
level: btn['level'],
|
15
|
+
effort: btn['effort']
|
16
|
+
}
|
17
|
+
end
|
18
|
+
total_boards = obfset['total_boards']
|
19
|
+
else
|
20
|
+
visited_board_ids = {}
|
21
|
+
to_visit = [{board: obfset[0], level: 0, entry_x: 1.0, entry_y: 1.0}]
|
22
|
+
locale = obfset[0]['locale']
|
23
|
+
known_buttons = {}
|
24
|
+
sqrt2 = Math.sqrt(2)
|
25
|
+
while to_visit.length > 0
|
26
|
+
board = to_visit.shift
|
27
|
+
visited_board_ids[board[:board]['id']] = board[:level]
|
28
|
+
puts board[:board]['id'] if output
|
29
|
+
btn_height = 1.0 / board[:board]['grid']['rows'].to_f
|
30
|
+
btn_width = 1.0 / board[:board]['grid']['columns'].to_f
|
31
|
+
board_effort = 0
|
32
|
+
# add effort for level of complexity when new board is rendered
|
33
|
+
board_effort += 0.005 * board[:board]['grid']['rows'] * board[:board]['grid']['columns']
|
34
|
+
# add effort for number of visible buttons
|
35
|
+
board_effort += 0.01 * board[:board]['grid']['order'].flatten.length
|
19
36
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
# we're not visiting it at a lower level
|
41
|
-
if visited_board_ids[button['load_board']['id']] == nil
|
42
|
-
try_visit = true
|
43
|
-
elsif visited_board_ids[button['load_board']['id']] > board[:level] + 1
|
44
|
-
try_visit = true
|
37
|
+
board[:board]['grid']['rows'].times do |row_idx|
|
38
|
+
board[:board]['grid']['columns'].times do |col_idx|
|
39
|
+
button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
|
40
|
+
button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
|
41
|
+
next unless button
|
42
|
+
x = (btn_width / 2) + (btn_width * col_idx)
|
43
|
+
y = (btn_height / 2) + (btn_height * row_idx)
|
44
|
+
prior_buttons = (row_idx * board[:board]['grid']['columns']) + col_idx
|
45
|
+
effort = 0
|
46
|
+
effort += board_effort
|
47
|
+
# add effort for percent distance from entry point
|
48
|
+
distance = Math.sqrt((x - board[:entry_x]) ** 2 + (y - board[:entry_y]) ** 2) / sqrt2
|
49
|
+
effort += distance
|
50
|
+
if distance > 0.1 || (board[:entry_x] == 1.0 && board[:entry_y] == 1.0)
|
51
|
+
# add small effort for every prior button when visually scanning
|
52
|
+
effort += prior_buttons * 0.05
|
53
|
+
else
|
54
|
+
# ..unless it's right by the previous button, then
|
55
|
+
# add tiny effort for local scan
|
56
|
+
effort += distance * 0.5
|
45
57
|
end
|
46
|
-
|
58
|
+
# add cumulative effort from previous sequence
|
59
|
+
effort += board[:prior_effort] || 0
|
60
|
+
if button['load_board']
|
47
61
|
try_visit = false
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
if
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
62
|
+
# For linked buttons, only traverse if
|
63
|
+
# the board hasn't been visited, or if
|
64
|
+
# we're not visiting it at a lower level
|
65
|
+
if visited_board_ids[button['load_board']['id']] == nil
|
66
|
+
try_visit = true
|
67
|
+
elsif visited_board_ids[button['load_board']['id']] > board[:level] + 1
|
68
|
+
try_visit = true
|
69
|
+
end
|
70
|
+
if to_visit.detect{|b| b[:board]['id'] == button['load_board']['id'] && b[:level] <= board[:level] + 1 }
|
71
|
+
try_visit = false
|
72
|
+
end
|
73
|
+
if try_visit
|
74
|
+
next_board = obfset.detect{|brd| brd['id'] == button['load_board']['id'] }
|
75
|
+
if next_board
|
76
|
+
to_visit.push({
|
77
|
+
board: next_board,
|
78
|
+
level: board[:level] + 1,
|
79
|
+
prior_effort: effort + 1.0,
|
80
|
+
entry_x: x,
|
81
|
+
entry_y: y
|
82
|
+
})
|
83
|
+
end
|
84
|
+
end
|
85
|
+
else
|
86
|
+
word = button['label']
|
87
|
+
existing = known_buttons[word]
|
88
|
+
if !existing || board[:level] < existing[:level]
|
89
|
+
known_buttons[word] = {
|
90
|
+
id: "#{button['id']}::#{board[:board]['id']}",
|
91
|
+
label: word,
|
92
|
+
level: board[:level],
|
93
|
+
effort: effort
|
94
|
+
}
|
59
95
|
end
|
60
|
-
end
|
61
|
-
else
|
62
|
-
word = button['label']
|
63
|
-
existing = known_buttons[word]
|
64
|
-
if !existing || board[:level] < existing[:level]
|
65
|
-
known_buttons[word] = {
|
66
|
-
id: "#{button['id']}::#{board[:board]['id']}",
|
67
|
-
label: word,
|
68
|
-
level: board[:level],
|
69
|
-
effort: effort
|
70
|
-
}
|
71
96
|
end
|
72
97
|
end
|
73
98
|
end
|
74
99
|
end
|
100
|
+
buttons = known_buttons.to_a.map(&:last)
|
101
|
+
total_boards = visited_board_ids.keys.length
|
75
102
|
end
|
76
|
-
buttons =
|
103
|
+
buttons = buttons.sort_by{|b| [b[:effort] || 1, b[:label] || ""] }
|
77
104
|
clusters = {}
|
78
105
|
buttons.each do |btn|
|
79
|
-
puts "#{btn[:label]}\t#{btn[:effort]}" if output
|
80
106
|
clusters[btn[:level]] ||= []
|
81
107
|
clusters[btn[:level]] << btn
|
82
108
|
end
|
83
|
-
clusters.each do |level, buttons|
|
84
|
-
puts "HITS: #{level + 1}" if output
|
85
|
-
puts buttons.map{|b| b[:label] }.join(' ') if output
|
86
|
-
end
|
87
|
-
puts "TOTAL BOARDS: #{visited_board_ids.keys.length}" if output
|
88
|
-
puts "TOTAL WORDS: #{buttons.length}" if output
|
89
109
|
{
|
90
110
|
locale: locale,
|
111
|
+
total_boards: total_boards,
|
112
|
+
total_buttons: buttons.length,
|
91
113
|
buttons: buttons,
|
92
114
|
levels: clusters
|
93
115
|
}
|
94
116
|
end
|
117
|
+
|
118
|
+
def self.analyze_and_compare(obfset, compset)
|
119
|
+
target = AACMetrics::Metrics.analyze(obfset, false)
|
120
|
+
res = {}.merge(target)
|
121
|
+
|
122
|
+
compare = AACMetrics::Metrics.analyze(compset, false)
|
123
|
+
|
124
|
+
compare_words = []
|
125
|
+
compare_buttons = {}
|
126
|
+
compare_words = compare[:buttons].each do |btn|
|
127
|
+
compare_words << btn[:label]
|
128
|
+
compare_buttons[btn[:label]] = btn
|
129
|
+
end
|
130
|
+
|
131
|
+
efforts = {}
|
132
|
+
target_words = []
|
133
|
+
res[:buttons].each{|b|
|
134
|
+
target_words << b[:label]
|
135
|
+
efforts[b[:label]] = b[:effort]
|
136
|
+
comp = compare_buttons[b[:label]]
|
137
|
+
if comp
|
138
|
+
b[:comp_level] = comp[:level]
|
139
|
+
b[:comp_effort] = comp[:effort]
|
140
|
+
end
|
141
|
+
}
|
142
|
+
compare[:buttons].each{|b|
|
143
|
+
if efforts[b[:label]]
|
144
|
+
efforts[b[:label]] += b[:effort]
|
145
|
+
efforts[b[:label]] /= 2
|
146
|
+
else
|
147
|
+
efforts[b[:label]] ||= b[:effort]
|
148
|
+
end
|
149
|
+
}
|
150
|
+
|
151
|
+
core_lists = AACMetrics::Loader.core_lists(target[:locale])
|
152
|
+
common_words_obj = AACMetrics::Loader.common_words(target[:locale])
|
153
|
+
common_words_obj['efforts'].each{|w, e| efforts[w] ||= e }
|
154
|
+
common_words = common_words_obj['words']
|
155
|
+
|
156
|
+
too_easy = []
|
157
|
+
too_hard = []
|
158
|
+
target[:buttons].each do |btn|
|
159
|
+
if btn[:effort] && common_words_obj['efforts'][btn[:label]]
|
160
|
+
if btn[:effort] < common_words_obj['efforts'][btn[:label]] - 5
|
161
|
+
too_easy << btn[:label]
|
162
|
+
elsif btn[:effort] > common_words_obj['efforts'][btn[:label]] + 3
|
163
|
+
too_hard << btn[:label]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
missing = (compare_words - target_words).sort_by{|w| efforts[w] }
|
169
|
+
extras = (target_words - compare_words).sort_by{|w| efforts[w] }
|
170
|
+
# puts "MISSING WORDS (#{missing.length}):"
|
171
|
+
res[:missing_words] = missing
|
172
|
+
# puts missing.join(' ')
|
173
|
+
# puts "EXTRA WORDS (#{extras.length}):"
|
174
|
+
res[:extra_words] = extras
|
175
|
+
# puts extras.join(' ')
|
176
|
+
overlap = (target_words & compare_words & common_words)
|
177
|
+
# puts "OVERLAPPING WORDS (#{overlap.length}):"
|
178
|
+
res[:overlapping_words] = overlap
|
179
|
+
# puts overlap.join(' ')
|
180
|
+
missing = (common_words - target_words)
|
181
|
+
# puts "MISSING FROM COMMON (#{missing.length})"
|
182
|
+
res[:missing] = {
|
183
|
+
:common => {name: "Common Word List", list: missing}
|
184
|
+
}
|
185
|
+
# puts missing.join(' ')
|
186
|
+
core_lists.each do |list|
|
187
|
+
missing = []
|
188
|
+
list['words'].each do |word|
|
189
|
+
words = word.gsub(/’/, '').downcase.split(/\|/)
|
190
|
+
if (target_words & words).length == 0
|
191
|
+
missing << words[0]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
if missing.length > 0
|
195
|
+
# puts "MISSING FROM #{list['id']} (#{missing.length}):"
|
196
|
+
res[:missing][list['id']] = {name: list['name'], list: missing}
|
197
|
+
# puts missing.join(' ')
|
198
|
+
end
|
199
|
+
end
|
200
|
+
# puts "CONSIDER MAKING EASIER"
|
201
|
+
res[:high_effort_words] = too_hard
|
202
|
+
# puts too_hard.join(' ')
|
203
|
+
# puts "CONSIDER LESS PRIORITY"
|
204
|
+
res[:low_effort_words] = too_easy
|
205
|
+
# puts too_easy.join(' ')
|
206
|
+
res
|
207
|
+
end
|
95
208
|
end
|