aac-metrics 0.2.1 → 0.2.2

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: bddb8e430a21868a5b26312fb5924f36b914446dc212d849330462768fd9b233
4
- data.tar.gz: c5ddd0e2b835678f0dabf8743099b29d462612d94a4aa4dd261e1cb7337eaa02
3
+ metadata.gz: 52fb3c41b9427605e9a78c258c5f4e5d7303c98f8988b52aba750597cb164900
4
+ data.tar.gz: 60ccc8a6c1dd2c25d29dd159134c7953e133a1f7a0bb9d710696807f853e8139
5
5
  SHA512:
6
- metadata.gz: 876e09d6be2b2e2b7579c11bb06367296ee7fbba627db0f8d421b4e60d2b7042d9a41433f9b1af10c78a6fc8ac84077170224e059a5a5ba30db6b217cab56936
7
- data.tar.gz: 5072598db0226690da77985e4e1ed760b24f0d0edc2bc99a5961e59d7f8e8bfa549073f4d0b223559ba48d7d2348556041d8b513944e5427c9c158db3b467f6c
6
+ metadata.gz: ac227f5770623d9df36635c92f71aa7a122404fa40f006dce31432be0bd17b6199c0c16ec6a4f5c777f19255106bf1c7f8811bfe4972996b9d12980a75b2911a
7
+ data.tar.gz: fd2ef145c944fea960a30282b6f3960f313d5f6f44d0db13f3a4581909744e127455010371d5b4138e546132a2e8d4cfb26ec291793f9ff83b970b54d47aeed9
data/README.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  A tool for analysing and comparing grid-based AAC board sets
4
4
 
5
+ To compare two datasets
6
+ `ruby setup.rb qc24 qc112`
7
+
8
+ To compare two datasets and export the minimal obfset for the first
9
+ `ruby setup.rb qc24 qc112 export`
10
+ `ruby setup.rb qc24 qc112 render` # updates interactive preview in sets/preview.html
11
+
12
+ To generate an obfset from an external data source
13
+ `ruby lib/ingester.rb path/to/manifest-from-unzipped-obz.json`
14
+ `ruby lib/ingester.rb path/to/compiled-set.obfset`
15
+ `ruby lib/ingester.rb path/to/root-file.obf`
16
+
17
+
18
+ NOTE: if you add an obfset to sets, it will be accessible
19
+ simply by its prefix. If you add ".common" to the filename
20
+ similar to the existing files, it will be added to the
21
+ corpus of "common" word sets.
22
+
5
23
  ## License
6
24
 
7
25
  Licensed under the MIT License.
@@ -20,6 +20,7 @@ module AACMetrics::Loader
20
20
  }
21
21
  if button['load_board'] && button['load_board']['id']
22
22
  new_button['load_board'] = {'id' => button['load_board']['id']}
23
+ new_button['load_board']['temporary_home'] = button['load_board']['temporary_home'] if button['load_board']['temporary_home'] == true || button['load_board']['temporary_home'] == 'prior'
23
24
  new_button['load_board']['add_to_sentence'] = true if button['load_board']['add_to_sentence']
24
25
  end
25
26
  new_board['buttons'].push(new_button)
@@ -73,11 +74,11 @@ module AACMetrics::Loader
73
74
  if json.is_a?(Array)
74
75
  json.each do |brd|
75
76
  brd['buttons'].each do |btn|
76
- if btn['label'].match(/^\$/)
77
+ if (btn['label'] || '').match(/^\$/)
77
78
  word = base[btn['label'].sub(/^\$/, '')]
78
79
  btn['label'] = word if word
79
80
  end
80
- btn['label'] = btn['label'].gsub(/’/, '')
81
+ btn['label'] = (btn['label'] || '').gsub(/’/, '')
81
82
  end
82
83
  end
83
84
  elsif json.is_a?(Hash)
@@ -159,6 +160,12 @@ module AACMetrics::Loader
159
160
  # record load_board reference
160
161
  btn_idx += 1
161
162
  if btn['load_board']
163
+ if !btn['load_board']['path'] && btn['load_board']['id']
164
+ bpath = File.join(File.dirname(path), btn['load_board']['id'] + '.obf')
165
+ if File.exists?(bpath)
166
+ btn['load_board']['path'] = bpath
167
+ end
168
+ end
162
169
  if btn['load_board']['path']
163
170
  if visited_paths[btn['load_board']['path']]
164
171
  new_btn['load_board'] = {'id' => "brd#{visited_paths[btn['load_board']['path']]}"}
@@ -176,8 +183,10 @@ module AACMetrics::Loader
176
183
  new_btn['load_board'] = {'tmp_path' => btn['load_board']['data_url']}
177
184
  end
178
185
  else
186
+ puts path
179
187
  puts "Link found with no access #{btn['load_board'].to_json}"
180
188
  end
189
+ new_btn['load_board']['temporary_home'] = true if new_btn['load_board'] && btn['load_board']['temporary_home']
181
190
  new_btn['load_board']['add_to_sentence'] = true if new_btn['load_board'] && btn['load_board']['add_to_sentence']
182
191
  elsif btn['action']
183
192
  # TODO: track keyboard actions and don't
@@ -30,14 +30,16 @@ module AACMetrics::Metrics
30
30
  # as the link used to get there if they share an id
31
31
  def self.analyze(obfset, output=true, include_obfset=false)
32
32
  locale = nil
33
- buttons = []
33
+ buttons = nil
34
34
  set_refs = {}
35
35
  grid = {}
36
+ alt_scores = {}
36
37
 
37
38
  if obfset.is_a?(Hash) && obfset['buttons']
38
39
  locale = obfset['locale'] || 'en'
39
40
  set_refs = obfset['reference_counts']
40
41
  grid = obfset['grid']
42
+ alt_scores = obfset['alternates']
41
43
  buttons = []
42
44
  obfset['buttons'].each do |btn|
43
45
  buttons << {
@@ -51,6 +53,7 @@ module AACMetrics::Metrics
51
53
  end
52
54
  total_boards = obfset['total_boards']
53
55
  else
56
+ start_boards = [obfset[0]]
54
57
  visited_board_ids = {}
55
58
  to_visit = [{board: obfset[0], level: 0, entry_x: 1.0, entry_y: 1.0}]
56
59
  set_refs = {}
@@ -87,6 +90,17 @@ module AACMetrics::Metrics
87
90
  set_refs[id] += 1
88
91
  end
89
92
  end
93
+ board['buttons'].each do |link_btn|
94
+ if link_btn['load_board'] && link_btn['load_board']['id'] && link_btn['load_board']['temporary_home']
95
+ # TODO: buttons can have multiple efforts, depending
96
+ # on if they are navigable from a temporary_home
97
+ if link_btn['load_board']['temporary_home'] == 'prior'
98
+ start_boards << board
99
+ elsif link_btn['load_board']['temporary_home'] == true
100
+ start_boards << obfset.detect{|b| b['id'] == link_btn['load_board']['id']}
101
+ end
102
+ end
103
+ end
90
104
  end
91
105
  # If the average grid size is much different than the root
92
106
  # grid size, only then use the average as the size for this board set
@@ -99,7 +113,52 @@ module AACMetrics::Metrics
99
113
  loc = id.split(/-/)[1]
100
114
  set_pcts[id] = cnt.to_f / (cell_refs[loc] || obfset.length).to_f
101
115
  end
102
- locale = obfset[0]['locale']
116
+
117
+ total_boards = nil
118
+ locale = nil
119
+ clusters = nil
120
+ # TODO: this list used to be reversed, but I don't know why.
121
+ # What we want is for these analyses to be run for the root
122
+ # board, don't we?
123
+ # puts JSON.pretty_generate(obfset[0])
124
+ start_boards.uniq.each do |brd|
125
+ analysis = analyze_for(obfset, brd, set_pcts, output)
126
+ buttons ||= analysis[:buttons]
127
+ if brd != obfset[0]
128
+ alt_scores[brd['id']] = {
129
+ buttons: analysis[:buttons],
130
+ levels: analysis[:levels]
131
+ }
132
+ end
133
+ total_boards ||= analysis[:total_boards]
134
+ clusters ||= analysis[:levels]
135
+ locale ||= analysis[:locale]
136
+ end
137
+ end
138
+ res = {
139
+ analysis_version: AACMetrics::VERSION,
140
+ locale: locale,
141
+ total_boards: total_boards,
142
+ total_buttons: buttons.length,
143
+ reference_counts: set_refs,
144
+ grid: {
145
+ rows: root_rows,
146
+ columns: root_cols
147
+ },
148
+ buttons: buttons,
149
+ levels: clusters,
150
+ alternates: alt_scores
151
+ }
152
+ if include_obfset
153
+ res[:obfset] = obfset
154
+ end
155
+ res
156
+ end
157
+
158
+ def self.analyze_for(obfset, brd, set_pcts, output)
159
+ visited_board_ids = {}
160
+ to_visit = [{board: brd, level: 0, entry_x: 1.0, entry_y: 1.0}]
161
+ locale = brd['locale'] || 'en'
103
162
  known_buttons = {}
104
163
  while to_visit.length > 0
105
164
  board = to_visit.shift
@@ -173,7 +232,7 @@ module AACMetrics::Metrics
173
232
  button_id = (board[:board]['grid']['order'][row_idx] || [])[col_idx]
174
233
  button = board[:board]['buttons'].detect{|b| b['id'] == button_id }
175
234
  # prior_buttons += 0.1 if !button
176
- next unless button
235
+ next unless button && (button['label'] || button['vocalization'] || '').length > 0
177
236
  x = (btn_width / 2) + (btn_width * col_idx)
178
237
  y = (btn_height / 2) + (btn_height * row_idx)
179
238
  # prior_buttons = (row_idx * board[:board]['grid']['columns']) + col_idx
@@ -271,10 +330,14 @@ module AACMetrics::Metrics
271
330
  next_board = obfset.detect{|brd| brd['id'] == button['load_board']['id'] }
272
331
  change_effort = BOARD_CHANGE_PROCESSING_EFFORT
273
332
  if next_board
333
+ temp_home_id = board[:temporary_home_id]
334
+ temp_home_id = board[:board]['id'] if button['load_board']['temporary_home'] == 'prior'
335
+ temp_home_id = button['load_board']['id'] if button['load_board']['temporary_home'] == true
274
336
  to_visit.push({
275
337
  board: next_board,
276
338
  level: board[:level] + 1,
277
339
  prior_effort: effort + change_effort,
340
+ temporary_home_id: temp_home_id,
278
341
  entry_x: x,
279
342
  entry_y: y,
280
343
  entry_clone_id: button['clone_id'],
@@ -286,29 +349,34 @@ module AACMetrics::Metrics
286
349
  if !button['load_board'] || button['load_board']['add_to_sentence']
287
350
  word = button['label']
288
351
  existing = known_buttons[word]
289
- if !existing || effort < existing[:effort] #board[:level] < existing[:level]
290
- if board_pcts[button['clone_id']]
291
- effort -= [BOARD_CHANGE_PROCESSING_EFFORT, BOARD_CHANGE_PROCESSING_EFFORT * 0.3 / board_pcts[button['clone_id']]].min
292
- elsif board_pcts[button['semantic_id']]
293
- effort -= [BOARD_CHANGE_PROCESSING_EFFORT, BOARD_CHANGE_PROCESSING_EFFORT * 0.5 / board_pcts[button['semantic_id']]].min
294
- end
295
-
296
- known_buttons[word] = {
352
+ if board_pcts[button['clone_id']]
353
+ effort -= [BOARD_CHANGE_PROCESSING_EFFORT, BOARD_CHANGE_PROCESSING_EFFORT * 0.3 / board_pcts[button['clone_id']]].min
354
+ elsif board_pcts[button['semantic_id']]
355
+ effort -= [BOARD_CHANGE_PROCESSING_EFFORT, BOARD_CHANGE_PROCESSING_EFFORT * 0.5 / board_pcts[button['semantic_id']]].min
356
+ end
357
+ if !existing || effort < existing[:effort]
358
+ ww = {
297
359
  id: "#{button['id']}::#{board[:board]['id']}",
298
360
  label: word,
299
361
  level: board[:level],
300
362
  effort: effort,
301
363
  }
364
+ # If a board set has any temporary_home links,
365
+ # then that can possibly affect the effort
366
+ # score for sentences
367
+ if board[:temporary_home_id]
368
+ ww[:temporary_home_id] = board[:temporary_home_id]
369
+ end
370
+ known_buttons[word] = ww
302
371
  end
303
372
  end
304
373
  button['effort'] = effort
305
-
306
374
  end
307
375
  end
308
- end
376
+ end # end to_visit list
309
377
  buttons = known_buttons.to_a.map(&:last)
310
378
  total_boards = visited_board_ids.keys.length
311
- end
379
+
312
380
  buttons = buttons.sort_by{|b| [b[:effort] || 1, b[:label] || ""] }
313
381
  clusters = {}
314
382
  buttons.each do |btn|
@@ -320,25 +388,38 @@ module AACMetrics::Metrics
320
388
  locale: locale,
321
389
  total_boards: total_boards,
322
390
  total_buttons: buttons.length,
323
- reference_counts: set_refs,
324
- grid: {
325
- rows: root_rows,
326
- columns: root_cols
327
- },
328
391
  buttons: buttons,
329
392
  levels: clusters
330
393
  }
331
- if include_obfset
332
- res[:obfset] = obfset
333
- end
334
394
  res
335
395
  end
336
396
 
397
+ class ExtraFloat < Numeric
398
+ include Math
399
+ def initialize(float=0.0)
400
+ @float = Float(float)
401
+ end
402
+
403
+ def to_f
404
+ @float.to_f
405
+ end
406
+
407
+ def method_missing(message, *args, &block)
408
+ if block_given?
409
+ @float.public_send(message, *args, &block)
410
+ else
411
+ @float.public_send(message, *args)
412
+ end
413
+ end
414
+ end
415
+
337
416
  SQRT2 = Math.sqrt(2)
338
417
  BUTTON_SIZE_MULTIPLIER = 0.09
339
418
  FIELD_SIZE_MULTIPLIER = 0.005
340
419
  VISUAL_SCAN_MULTIPLIER = 0.015
341
420
  BOARD_CHANGE_PROCESSING_EFFORT = 1.0
421
+ BOARD_HOME_EFFORT = 1.0
422
+ COMBINED_WORDS_REMEMBERING_EFFORT = 1.0
342
423
  DISTANCE_MULTIPLIER = 0.4
343
424
  DISTANCE_THRESHOLD_TO_SKIP_VISUAL_SCAN = 0.1
344
425
  SKIPPED_VISUAL_SCAN_DISTANCE_MULTIPLIER = 0.5
@@ -386,9 +467,21 @@ module AACMetrics::Metrics
386
467
  compare[:buttons].each do |btn|
387
468
  compare_words << btn[:label]
388
469
  compare_buttons[btn[:label]] = btn
389
- comp_efforts[btn[:label]] = btn[:effort]
470
+ comp_efforts[btn[:label]] = ExtraFloat.new(btn[:effort])
471
+ comp_efforts[btn[:label]].instance_variable_set('@temp_home_id', btn[:temporary_home_id])
390
472
  comp_levels[btn[:label]] = btn[:level]
391
473
  end
474
+ compare[:alternates].each do |id, alt|
475
+ efforts = {}
476
+ levels = {}
477
+ alt[:buttons].each do |btn|
478
+ efforts[btn[:label]] = ExtraFloat.new(btn[:effort])
479
+ efforts[btn[:label]].instance_variable_set('@temp_home_id', btn[:temporary_home_id])
480
+ levels[btn[:label]] = btn[:level]
481
+ end
482
+ comp_efforts["H:#{id}"] = efforts
483
+ comp_levels["H:#{id}"] = levels
484
+ end
392
485
 
393
486
  sortable_efforts = {}
394
487
  target_efforts = {}
@@ -400,7 +493,9 @@ module AACMetrics::Metrics
400
493
  # very frequent core words and use that when available
401
494
  res[:buttons].each{|b|
402
495
  target_words << b[:label]
403
- target_efforts[b[:label]] = b[:effort]
496
+ target_efforts[b[:label]] = ExtraFloat.new(b[:effort])
497
+ target_efforts[b[:label]].instance_variable_set('@temp_home_id', b[:temporary_home_id])
498
+
404
499
  target_levels[b[:label]] = b[:level]
405
500
  sortable_efforts[b[:label]] = b[:effort]
406
501
  comp = compare_buttons[b[:label]]
@@ -409,6 +504,18 @@ module AACMetrics::Metrics
409
504
  b[:comp_effort] = comp[:effort]
410
505
  end
411
506
  }
507
+ res[:alternates].each do |id, alt|
508
+ efforts = {}
509
+ levels = {}
510
+ alt[:buttons].each do |btn|
511
+ efforts[btn[:label]] = ExtraFloat.new(btn[:effort])
512
+ efforts[btn[:label]].instance_variable_set('@temp_home_id', btn[:temporary_home_id])
513
+ levels[btn[:label]] = btn[:level]
514
+ end
515
+ target_efforts["H:#{id}"] = efforts
516
+ target_levels["H:#{id}"] = levels
517
+ end
518
+ res.delete(:alternates)
412
519
  # Effort scores are the mean of thw scores from the
413
520
  # two sets, or just a singular value if in only one set
414
521
  compare[:buttons].each{|b|
@@ -441,7 +548,6 @@ module AACMetrics::Metrics
441
548
  end
442
549
  end
443
550
  end
444
-
445
551
 
446
552
  missing = (compare_words - target_words).sort_by{|w| sortable_efforts[w] }
447
553
  missing = missing.select do |word|
@@ -519,20 +625,11 @@ module AACMetrics::Metrics
519
625
  end
520
626
 
521
627
  # Calculate the effort for the target and comp sets
522
- effort = target_efforts[word]
523
- if !effort
524
- words.each{|w| effort ||= target_efforts[w] }
525
- end
526
- # Fallback penalty for missing word
527
- effort ||= spelling_effort(word)
628
+ effort, level = best_match(word, target_efforts, nil, synonyms)
528
629
  reffort = effort
529
630
  list_effort += effort
530
631
 
531
- effort = comp_efforts[word]
532
- if !effort
533
- words.each{|w| effort ||= comp_efforts[w] }
534
- end
535
- effort ||= spelling_effort(word)
632
+ effort, level = best_match(word, comp_efforts, nil, synonyms)
536
633
  comp_effort += effort
537
634
  # puts "#{word} - #{reffort.round(1)} - #{effort.round(1)}"
538
635
  end
@@ -552,50 +649,18 @@ module AACMetrics::Metrics
552
649
  res[:care_components][:comp_core] = (comp_effort_tally / core_lists.to_a.length) * 5.0
553
650
  comp_effort_tally = res[:care_components][:comp_core]
554
651
 
555
- # TODO: Assemble or allow a battery of word combinations,
652
+ # Assemble or allow a battery of word combinations,
556
653
  # and calculate the level of effort for each sequence,
557
654
  # as well as an average level of effort across combinations.
655
+ # TODO: sets with temporary_home settings will have custom
656
+ # effort scores for subsequent words in the sentence
558
657
  res[:sentences] = []
559
658
  sentences.each do |words|
560
- target_effort_score = 0.0
561
- comp_effort_score = 0.0
562
- words.each_with_index do |word, idx|
563
- synonym_words = [word] + (synonyms[word] || [])
564
- effort = target_efforts[word] || target_efforts[word.downcase]
565
- level = target_levels[word] || target_levels[word.downcase]
566
- if !effort
567
- synonym_words.each do |w|
568
- if !effort && target_efforts[w]
569
- effort = target_efforts[w]
570
- level = target_levels[w]
571
- end
572
- end
573
- end
574
- effort ||= spelling_effort(word)
575
- if level && level > 0 && idx > 0
576
- effort += BOARD_CHANGE_PROCESSING_EFFORT
577
- end
578
- ee = effort
579
- target_effort_score += effort
580
-
581
- effort = comp_efforts[word] || comp_efforts[word.downcase]
582
- level = comp_levels[word] || comp_levels[word.downcase]
583
- if !effort
584
- synonym_words.each do |w|
585
- if !effort && comp_efforts[w]
586
- effort = comp_efforts[w]
587
- level = comp_levels[w]
588
- end
589
- end
590
- end
591
- effort ||= spelling_effort(word)
592
- if level && level > 0 && idx > 0
593
- effort += BOARD_CHANGE_PROCESSING_EFFORT
594
- end
595
- comp_effort_score += effort
596
- end
597
- target_effort_score = target_effort_score / words.length
598
- comp_effort_score = comp_effort_score / words.length
659
+ sequence = best_combo(words, target_efforts, target_levels, synonyms)
660
+ target_effort_score = sequence.map{|w, e| e }.sum.to_f / words.length.to_f
661
+ sequence = best_combo(words, comp_efforts, comp_levels, synonyms)
662
+ comp_effort_score = sequence.map{|w, e| e }.sum.to_f / words.length.to_f
663
+
599
664
  res[:sentences] << {sentence: words.join(' '), words: words, effort: target_effort_score, comp_effort: comp_effort_score}
600
665
  end
601
666
  res[:care_components][:sentences] = res[:sentences].map{|s| s[:effort] }.sum.to_f / res[:sentences].length.to_f * 3.0
@@ -607,20 +672,11 @@ module AACMetrics::Metrics
607
672
  fringe.each do |word|
608
673
  target_effort_score = 0.0
609
674
  comp_effort_score = 0.0
610
- synonym_words = [word] + (synonyms[word] || [])
611
- effort = target_efforts[word] || target_efforts[word.downcase]
612
- if !effort
613
- synonym_words.each{|w| effort ||= target_efforts[w] }
614
- end
615
- # puts "!#{word}" unless effort
616
- effort ||= spelling_effort(word)
675
+
676
+ effort, level = best_match(word, target_efforts, nil, synonyms)
617
677
  target_effort_score += effort
618
678
 
619
- effort = comp_efforts[word] || comp_efforts[word.downcase]
620
- if !effort
621
- synonym_words.each{|w| effort ||= comp_efforts[w] }
622
- end
623
- effort ||= spelling_effort(word)
679
+ effort, level = best_match(word, comp_efforts, nil, synonyms)
624
680
  comp_effort_score += effort
625
681
  res[:fringe_words] << {word: word, effort: target_effort_score, comp_effort: comp_effort_score}
626
682
  end
@@ -633,19 +689,10 @@ module AACMetrics::Metrics
633
689
  common_fringe.each do |word|
634
690
  target_effort_score = 0.0
635
691
  comp_effort_score = 0.0
636
- synonym_words = [word] + (synonyms[word] || [])
637
- effort = target_efforts[word] || target_efforts[word.downcase]
638
- if !effort
639
- synonym_words.each{|w| effort ||= target_efforts[w] }
640
- end
641
- effort ||= spelling_effort(word)
692
+ effort, level = best_match(word, target_efforts, nil, synonyms)
642
693
  target_effort_score += effort
643
694
 
644
- effort = comp_efforts[word] || comp_efforts[word.downcase]
645
- if !effort
646
- synonym_words.each{|w| effort ||= comp_efforts[w] }
647
- end
648
- effort ||= spelling_effort(word)
695
+ effort, level = best_match(word, comp_efforts, nil, synonyms)
649
696
  comp_effort_score += effort
650
697
  res[:common_fringe_words] << {word: word, effort: target_effort_score, comp_effort: comp_effort_score}
651
698
  end
@@ -657,13 +704,6 @@ module AACMetrics::Metrics
657
704
  target_effort_tally += 70 # placeholder value for future added calculations
658
705
  comp_effort_tally += 70
659
706
 
660
- # puts target_effort_tally
661
- # puts comp_effort_tally
662
- # puts (target_effort_tally - comp_effort_tally).abs
663
- # puts JSON.pretty_generate(res[:care_components])
664
- # raise 'arf'
665
-
666
-
667
707
  res[:target_effort_score] = [0.0, 350.0 - target_effort_tally].max
668
708
  res[:comp_effort_score] = [0.0, 350.0 - comp_effort_tally].max
669
709
  # puts "CONSIDER MAKING EASIER"
@@ -674,4 +714,94 @@ module AACMetrics::Metrics
674
714
  # puts too_easy.join(' ')
675
715
  res
676
716
  end
717
+
718
+ # Find the effort for a word, its synonyms, or its spelling.
719
+ # Always returns a non-nil effort score
720
+ def self.best_match(word, target_efforts, target_levels, synonyms)
721
+ synonym_words = [word] + (synonyms[word] || [])
722
+ effort = target_efforts[word] || target_efforts[word.downcase]
723
+ target_levels ||= {}
724
+ level = target_levels[word] || target_levels[word.downcase]
725
+ if !effort
726
+ synonym_words.each do |w|
727
+ if !effort && target_efforts[w]
728
+ effort = target_efforts[w]
729
+ level = target_levels[w]
730
+ end
731
+ end
732
+ end
733
+ # Fallback penalty for missing word
734
+ fallback_effort = spelling_effort(word)
735
+ effort = fallback_effort if !effort || fallback_effort < effort
736
+
737
+ [effort, level || 0]
738
+ end
739
+
740
+ def self.best_combo(words, efforts, levels, synonyms)
741
+ options = [{next_idx: 0, list: []}]
742
+ words.length.times do |idx|
743
+ options.each do |option|
744
+ home_id = option[:temporary_home_id]
745
+ if option[:next_idx] == idx
746
+ combos = forward_combos(words, idx, efforts, levels)
747
+ if home_id
748
+ # Effort of hitting home button, and processing change, plus usual
749
+ combos.each{|c| c[:effort] += BOARD_HOME_EFFORT + BOARD_CHANGE_PROCESSING_EFFORT}
750
+ more_combos = forward_combos(words, idx, efforts["H:#{home_id}"] || {}, levels["H:#{home_id}"] || {})
751
+ more_combos.each{|c| c[:temporary_home_id] ||= home_id }
752
+ combos += more_combos
753
+ end
754
+ combos.each do |combo|
755
+ if idx > 0 && combo[:level] && combo[:level] > 0
756
+ combo[:effort] += BOARD_CHANGE_PROCESSING_EFFORT
757
+ end
758
+ options << {
759
+ next_idx: idx + combo[:size],
760
+ list: option[:list] + [[combo[:partial], combo[:effort]]],
761
+ temporary_home_id: combo[:temporary_home_id]
762
+ }
763
+ end
764
+ effort, level = best_match(words[idx], efforts, levels, synonyms)
765
+ option[:temporary_home_id] = effort.instance_variable_get('@temp_home_id')
766
+ effort += BOARD_CHANGE_PROCESSING_EFFORT if idx > 0 && level && level > 0
767
+ if home_id
768
+ effort += BOARD_HOME_EFFORT + BOARD_CHANGE_PROCESSING_EFFORT
769
+ other_effort, other_level = best_match(words[idx], efforts["H:#{home_id}"] || {}, levels["H:#{home_id}"] || {}, synonyms)
770
+ new_home_id = other_effort.instance_variable_get('@temp_home_id') || home_id
771
+ other_effort += BOARD_CHANGE_PROCESSING_EFFORT if idx > 0 && other_level && other_level > 0
772
+ other_list = option[:list] + [[words[idx], other_effort]]
773
+ options << {next_idx: idx + 1, list: other_list, temporary_home_id: new_home_id}
774
+ end
775
+ option[:list] << [words[idx], effort]
776
+ option[:next_idx] = idx + 1
777
+ end
778
+ end
779
+ end
780
+ options.sort_by{|o| o[:list].map{|w, e| e}.sum }.reverse[0][:list]
781
+ end
782
+
783
+ # Checks if any buttons will work for multiple words in a sentence
784
+ def self.forward_combos(words, idx, target_efforts, target_levels)
785
+ words_left = words.length - idx
786
+ combos = []
787
+ skip = 0
788
+ temp_home_id = nil
789
+ if words_left > 1
790
+ (words_left - 1).times do |minus|
791
+ partial = words[idx, words_left - minus].join(' ')
792
+ if target_efforts[partial] || target_efforts[partial.downcase]
793
+ effort = (target_efforts[partial] || target_efforts[partial.downcase]) + COMBINED_WORDS_REMEMBERING_EFFORT
794
+ level = target_levels[partial] || target_levels[partial.downcase]
795
+ combos << {
796
+ partial: partial,
797
+ effort: effort,
798
+ temporary_home_id: effort.instance_variable_get('@temp_home_id'),
799
+ level: level,
800
+ size: words_left - minus
801
+ }
802
+ end
803
+ end
804
+ end
805
+ combos
806
+ end
677
807
  end
data/lib/ingester.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # Imports an obf dataset for future access.
2
+
1
3
  lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
4
  $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
3
5