aac-metrics 0.0.1 → 0.0.6

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 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