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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9fbfbfcd5c426a844f692b0cfb1151bd800dc0fe9d679b838a5308479c087b58
4
- data.tar.gz: b056c6c7339b7c8da6d1cba1f3d05e0c27da02aa49958cd186242e570bde7639
3
+ metadata.gz: b241e25385bbf1330a3ba38d9a3cb4935ea570a8e629b18be1775557f96c6c7c
4
+ data.tar.gz: '07127794d01330821ff34bb36acf64f6fb538a3b13ca98c61ddbb3d83fca53ff'
5
5
  SHA512:
6
- metadata.gz: ee6cb311b8c50c78cda543094f71840e35a119b23c82f42e131f5a2927b393edde3874a823bb2fdc1dc0f6db3d30d480eb65407da50cb0634536668676194257
7
- data.tar.gz: 2d9bf2b511df96879609260448b1de4712146d117853147a9dc591f848b677f59526c0439f7d8a619d108264f0be21412de3761c34f3231eeafde9795efcbf41
6
+ metadata.gz: 262a150c97739c793df16ac9abef5b633010f5d62be8031a160996654edfa63ed3bc79c8fb42f60916212ba8d70465165c0a50b995000ef5872c3d0a9768dee7
7
+ data.tar.gz: 2e3f426ceaaf6445d31531a328d36e237bb7a33a980714afcc68c707421ebd71bfbcfb19cc37edfed60a4513aa08ca1de8f952ff7ef4330925fbe5492ed4d172
@@ -3,19 +3,68 @@ require 'typhoeus'
3
3
  require 'digest'
4
4
 
5
5
  module AACMetrics::Loader
6
- def self.retrieve(obfset)
7
- if !obfset.match(/\.obfset/)
8
- obfset = Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', obfset + '*.obfset')))[0]
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
- json = JSON.parse(File.read(obfset))
11
- base = self.base_words(json[0]['locale'])
12
- json.each do |brd|
13
- brd['buttons'].each do |btn|
14
- if btn['label'].match(/^\$/)
15
- word = base[btn['label'].sub(/^\$/, '')]
16
- btn['label'] = word if word
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
- content = process(fn, token)
143
- boards = content[:boards]
144
- words = content[:words]
145
- words_path = content[:words_path]
146
- output_fn = Digest::MD5.hexdigest(Time.now.to_i.to_s + rand(9999).to_s)[0, 10] + ".obfset"
147
- output = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'sets', output_fn))
148
- f = File.open(output, 'w')
149
- f.write(JSON.pretty_generate(boards))
150
- f.close
151
- if words
152
- new_words = {}
153
- words.to_a.sort_by{|h, w| w }.each{|h, w| new_words[h] = w }
154
- f = File.open(words_path, 'w')
155
- f.write(JSON.pretty_generate(new_words))
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
@@ -1,95 +1,208 @@
1
1
  module AACMetrics::Metrics
2
2
  def self.analyze(obfset, output=true)
3
- visited_board_ids = {}
4
- to_visit = [{board: obfset[0], level: 0, entry_x: 1.0, entry_y: 1.0}]
5
- locale = obfset[0]['locale']
6
- known_buttons = {}
7
- sqrt2 = Math.sqrt(2)
8
- while to_visit.length > 0
9
- board = to_visit.shift
10
- visited_board_ids[board[:board]['id']] = board[:level]
11
- puts board[:board]['id'] if output
12
- btn_height = 1.0 / board[:board]['grid']['rows'].to_f
13
- btn_width = 1.0 / board[:board]['grid']['columns'].to_f
14
- board_effort = 0
15
- # add effort for level of complexity when new board is rendered
16
- board_effort += 0.005 * board[:board]['grid']['rows'] * board[:board]['grid']['columns']
17
- # add effort for number of visible buttons
18
- board_effort += 0.01 * board[:board]['grid']['order'].flatten.length
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
- board[:board]['grid']['rows'].times do |row_idx|
21
- board[:board]['grid']['columns'].times do |col_idx|
22
- button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
23
- button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
24
- next unless button
25
- x = (btn_width / 2) + (btn_width * col_idx)
26
- y = (btn_height / 2) + (btn_height * row_idx)
27
- prior_buttons = (row_idx * board[:board]['grid']['columns']) + col_idx
28
- effort = 0
29
- effort += board_effort
30
- # add small effort for every prior button when visually scanning
31
- effort += prior_buttons * 0.05
32
- # add effort for percent distance from entry point
33
- effort += Math.sqrt((x - board[:entry_x]) ** 2 + (y - board[:entry_y]) ** 2) / sqrt2
34
- # add cumulative effort from previous sequence
35
- effort += board[:prior_effort] || 0
36
- if button['load_board']
37
- try_visit = false
38
- # For linked buttons, only traverse if
39
- # the board hasn't been visited, or if
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
- if to_visit.detect{|b| b[:board]['id'] == button['load_board']['id'] && b[:level] <= board[:level] + 1 }
58
+ # add cumulative effort from previous sequence
59
+ effort += board[:prior_effort] || 0
60
+ if button['load_board']
47
61
  try_visit = false
48
- end
49
- if try_visit
50
- next_board = obfset.detect{|brd| brd['id'] == button['load_board']['id'] }
51
- if next_board
52
- to_visit.push({
53
- board: next_board,
54
- level: board[:level] + 1,
55
- prior_effort: effort + 1.0,
56
- entry_x: x,
57
- entry_y: y
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 = known_buttons.to_a.map(&:last).sort_by{|b| b[:effort] }
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