eco-helpers 2.0.18 → 2.0.24

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -1
  3. data/eco-helpers.gemspec +4 -1
  4. data/lib/eco/api/common/base_loader.rb +9 -5
  5. data/lib/eco/api/common/loaders/parser.rb +1 -0
  6. data/lib/eco/api/common/people/default_parsers.rb +1 -0
  7. data/lib/eco/api/common/people/default_parsers/xls_parser.rb +53 -0
  8. data/lib/eco/api/common/people/entries.rb +1 -0
  9. data/lib/eco/api/common/people/entry_factory.rb +88 -23
  10. data/lib/eco/api/common/people/person_entry.rb +1 -0
  11. data/lib/eco/api/common/people/person_parser.rb +1 -1
  12. data/lib/eco/api/common/session.rb +1 -0
  13. data/lib/eco/api/common/session/base_session.rb +2 -0
  14. data/lib/eco/api/common/session/helpers.rb +30 -0
  15. data/lib/eco/api/common/session/helpers/prompt_user.rb +34 -0
  16. data/lib/eco/api/common/version_patches/ecoportal_api/external_person.rb +1 -1
  17. data/lib/eco/api/common/version_patches/ecoportal_api/internal_person.rb +7 -4
  18. data/lib/eco/api/common/version_patches/exception.rb +5 -2
  19. data/lib/eco/api/microcases/with_each.rb +67 -6
  20. data/lib/eco/api/microcases/with_each_present.rb +4 -2
  21. data/lib/eco/api/microcases/with_each_starter.rb +4 -2
  22. data/lib/eco/api/organization.rb +1 -1
  23. data/lib/eco/api/organization/people.rb +94 -25
  24. data/lib/eco/api/organization/people_similarity.rb +272 -0
  25. data/lib/eco/api/organization/person_schemas.rb +5 -1
  26. data/lib/eco/api/organization/policy_groups.rb +5 -1
  27. data/lib/eco/api/organization/tag_tree.rb +33 -0
  28. data/lib/eco/api/session.rb +19 -8
  29. data/lib/eco/api/session/batch.rb +7 -5
  30. data/lib/eco/api/session/batch/job.rb +34 -9
  31. data/lib/eco/api/usecases.rb +2 -2
  32. data/lib/eco/api/usecases/base_case.rb +2 -2
  33. data/lib/eco/api/usecases/base_io.rb +17 -4
  34. data/lib/eco/api/usecases/default_cases.rb +1 -0
  35. data/lib/eco/api/usecases/default_cases/analyse_people_case.rb +179 -32
  36. data/lib/eco/api/usecases/default_cases/clean_unknown_tags_case.rb +37 -0
  37. data/lib/eco/api/usecases/default_cases/to_csv_case.rb +81 -36
  38. data/lib/eco/api/usecases/default_cases/to_csv_detailed_case.rb +3 -4
  39. data/lib/eco/api/usecases/ooze_samples/ooze_update_case.rb +3 -2
  40. data/lib/eco/cli/config/default/input.rb +61 -8
  41. data/lib/eco/cli/config/default/options.rb +47 -2
  42. data/lib/eco/cli/config/default/people.rb +18 -24
  43. data/lib/eco/cli/config/default/usecases.rb +33 -2
  44. data/lib/eco/cli/config/default/workflow.rb +12 -7
  45. data/lib/eco/cli/scripting/args_helpers.rb +2 -2
  46. data/lib/eco/csv.rb +4 -2
  47. data/lib/eco/csv/table.rb +121 -21
  48. data/lib/eco/data/fuzzy_match.rb +109 -27
  49. data/lib/eco/data/fuzzy_match/chars_position_score.rb +3 -2
  50. data/lib/eco/data/fuzzy_match/ngrams_score.rb +19 -10
  51. data/lib/eco/data/fuzzy_match/pairing.rb +12 -19
  52. data/lib/eco/data/fuzzy_match/result.rb +22 -2
  53. data/lib/eco/data/fuzzy_match/results.rb +30 -6
  54. data/lib/eco/data/fuzzy_match/score.rb +12 -7
  55. data/lib/eco/data/fuzzy_match/string_helpers.rb +14 -1
  56. data/lib/eco/version.rb +1 -1
  57. metadata +67 -3
  58. data/lib/eco/api/organization/people_analytics.rb +0 -60
@@ -12,8 +12,9 @@ module Eco
12
12
  def chars_position_score(str1, str2, max_distance: 3, normalized: false)
13
13
  str1, str2 = normalize_string([str1, str2]) unless normalized
14
14
  len1 = str1 && str1.length; len2 = str2 && str2.length
15
- Score.new(0, len1 || 0).tap do |score|
16
- next if !str1 || !str2
15
+ Score.new(0, 0).tap do |score|
16
+ next if !str2 || !str1 || str2.empty? || str1.empty?
17
+ score.total = len1
17
18
  next score.increase(score.total) if str1 == str2
18
19
  next if len1 < 2
19
20
  pos = 0
@@ -16,13 +16,19 @@ module Eco
16
16
 
17
17
  Score.new(0, 0).tap do |score|
18
18
  next if !str2 || !str1
19
- next score.increase(score.total) if str1 == str2
20
- next if str1.length < 2 || str1.length < 2
19
+ next score.increase_total(len1) if str2.empty? || str1.empty?
20
+ if str1 == str2
21
+ score.total = len1
22
+ score.increase(score.total)
23
+ end
24
+ if str1.length < 2 || str1.length < 2
25
+ score.increase_total(len1)
26
+ end
21
27
 
22
- paired_words(str1, str2, normalized: true) do |needle, item|
28
+ pairs = paired_words(str1, str2, normalized: true) do |needle, item|
23
29
  ngrams_score(needle, item, range: range, normalized: true)
24
- end.each do |sub_str1, (item, iscore)|
25
- #puts "pairs '#{sub_str1}' --> '#{item}' (score: #{iscore.ratio})"
30
+ end.each do |sub_str1, data|
31
+ item, iscore = data
26
32
  score.merge!(iscore)
27
33
  end
28
34
  end
@@ -39,14 +45,17 @@ module Eco
39
45
 
40
46
  Score.new(0, len1 || 0).tap do |score|
41
47
  next if !str2 || !str1
48
+ next if str2.empty? || str1.empty?
49
+ score.total = len1
42
50
  next score.increase(score.total) if str1 == str2
43
51
  next if str1.length < 2 || str2.length < 2
44
52
 
45
- grams = word_ngrams(str2, range, normalized: true)
46
- next unless grams.length > 0
53
+ grams = word_ngrams(str2, range, normalized: true)
54
+ grams_count = grams.length
55
+ next unless grams_count > 0
47
56
 
48
57
  if range.is_a?(Integer)
49
- item_weight = score.total.to_f / grams.length
58
+ item_weight = score.total.to_f / grams_count
50
59
  matches = grams.select {|res| str1.include?(gram)}.length
51
60
  score.increase(matches * item_weight)
52
61
  else
@@ -57,9 +66,9 @@ module Eco
57
66
 
58
67
  groups.each do |len, grams|
59
68
  len_max_score = score.total * group_weight
60
- item_weight = len_max_score / grams.length
69
+ item_weight = len_max_score / grams_count
61
70
  matches = grams.select {|gram| str1.include?(gram)}.length
62
- #pp "#{len} match: #{matches} (over #{grams.length}) || max_score: #{len_max_score} (over #{score.total})"
71
+ #pp "(#{len}) match: #{matches} (of #{grams.length} of total #{grams_count}) || max_score: #{len_max_score} (over #{score.total})"
63
72
  score.increase(matches * item_weight)
64
73
  end
65
74
  end
@@ -15,20 +15,12 @@ module Eco
15
15
  # @yieldreturn [Eco::Data::FuzzyMatch::Score] the `Score` object with the results of comparing `str1` and `str2`
16
16
  # @param str1 [String] the string of reference.
17
17
  # @param str2 [String] one of the haystack items.
18
- # @param format [Symbol] determines the `values` of the returned `Hash`::
19
- # 1. `:pair` for just pair
20
- # 2. `:score` for just score
21
- # 2. `[:pair, :score]` for `Array`
22
18
  # @normalized [Boolean] to avoid double ups in normalizing.
23
- # @return [Hash] where `keys` are the **words** of `str1` and their `values`:
24
- # 1. if `format` is `:pair` => the `str2` words with highest match.
25
- # 2. if `format` is `:score` => the `Score` words with highest match.
26
- # 3. if `format` is `[:pair, :score]` => both in an `Array`.
27
- def paired_words(str1, str2, format: [:pair, :score], normalized: false)
19
+ # @return [Hash] where `keys` are the **words** of `str1` and their `values` a pair array of `pair` and `Score`
20
+ def paired_words(str1, str2, normalized: false)
28
21
  str1, str2 = normalize_string([str1, str2]) unless normalized
29
- return {} if !str2 || !str1
30
- return score.increase(score.total) if str1 == str2
31
- return {str1 => nil} if str1.length < 2 || str1.length < 2
22
+ return {nil => [nil, Score.new(0, 0)]} if !str2 || !str1
23
+ return {str1 => [nil, Score.new(0, 0)]} if str1.length < 2 || str1.length < 2
32
24
 
33
25
  needles = get_words(str1, normalized: true)
34
26
  haystack = get_words(str2, normalized: true)
@@ -59,6 +51,9 @@ module Eco
59
51
  result[:score].ratio
60
52
  end.reverse
61
53
  if result = sorted.shift
54
+ unless result[:score].is_a?(Eco::Data::FuzzyMatch::Score)
55
+ raise "Parining ('#{str1}' vs '#{str2}') -> Something got sour with needle '#{result[:needle]}' and item #{item}"
56
+ end
62
57
  paired[result[:needle]] = {
63
58
  pair: item,
64
59
  score: result[:score]
@@ -74,6 +69,9 @@ module Eco
74
69
  pending_items.include?(result[:pair]) && result[:score].ratio > 0.05
75
70
  end
76
71
  if result = results.shift
72
+ unless result[:score].is_a?(Eco::Data::FuzzyMatch::Score)
73
+ raise "Parining ('#{str1}' vs '#{str2}') -> Something got sour with needle '#{needle}' and item #{result[:pair]}"
74
+ end
77
75
  paired[needle] = result
78
76
  pending_items.delete(result[:pair])
79
77
  end
@@ -86,13 +84,8 @@ module Eco
86
84
  score: Score.new(0, needle.length)
87
85
  }
88
86
  end
89
- paired.transform_values do |result|
90
- case format
91
- when Array
92
- result.values_at(*format)
93
- else
94
- restult[format]
95
- end
87
+ paired.each_with_object({}) do |(needle, data), out|
88
+ out[needle] = data.values_at(:pair, :score)
96
89
  end
97
90
  end
98
91
 
@@ -1,9 +1,11 @@
1
1
  module Eco
2
2
  module Data
3
3
  module FuzzyMatch
4
- class Result < Struct.new(:match, :value, :dice, :levenshtein, :jaro_winkler, :ngrams, :words_ngrams, :chars_position)
4
+ class Result < Struct.new(:match, :value, :needle_value, :dice, :levenshtein, :jaro_winkler, :ngrams, :words_ngrams, :chars_position)
5
5
  ALL_METHODS = [:dice, :levenshtein, :jaro_winkler, :ngrams, :words_ngrams, :chars_position]
6
6
 
7
+ attr_accessor :pivot
8
+
7
9
  def dice; super&.round(3); end
8
10
  def levenshtein; super&.round(3); end
9
11
  def jaro_winkler; super&.round(3); end
@@ -11,12 +13,24 @@ module Eco
11
13
  def words_ngrams; super&.round(3); end
12
14
  def chars_position; super&.round(3); end
13
15
 
16
+ #Shortcuts
17
+ def lev; levenshtein; end
18
+ def jaro; jaro_winkler; end
19
+ def wngrams; words_ngrams; end
20
+ def pos; chars_position; end
21
+
22
+ def average
23
+ values = [dice, levenshtein, jaro_winkler, ngrams, words_ngrams, chars_position]
24
+ (values.inject(0.0, :+) / values.length).round(3)
25
+ end
26
+
14
27
  # TODO: print in the order of `order`
15
28
  def print
16
29
  msg = "(Dice: #{dice}) (Lev Dst: #{levenshtein}) "
17
30
  msg << "(Jaro: #{jaro_winkler}) "
18
31
  msg << "(Ngram: #{ngrams}) (WNgrams: #{words_ngrams}) "
19
32
  msg << "(C Pos: #{chars_position}) "
33
+ msg << "(Avg: #{average}) "
20
34
  msg << "'#{value}'"
21
35
  end
22
36
 
@@ -37,7 +51,7 @@ module Eco
37
51
 
38
52
  def order=(values)
39
53
  @order = [values].flatten.compact.tap do |o|
40
- o = [:words_ngrams, :dice] if o.empty?
54
+ o << [:words_ngrams, :dice] if o.empty?
41
55
  end
42
56
  end
43
57
 
@@ -49,6 +63,12 @@ module Eco
49
63
  compare(result)
50
64
  end
51
65
 
66
+ def values_at(*keys)
67
+ keys.map do |key|
68
+ self.send(key) if self.respond_to?(key)
69
+ end
70
+ end
71
+
52
72
  private
53
73
 
54
74
  def compare(other, order: self.order)
@@ -2,24 +2,48 @@ module Eco
2
2
  module Data
3
3
  module FuzzyMatch
4
4
  class Results < Struct.new(:needle, :value, :raw_results)
5
+ include Enumerable
6
+
7
+ attr_accessor :threshold
8
+
9
+ def empty?
10
+ count < 1
11
+ end
12
+
13
+ def each(&block)
14
+ return to_enum(:each) unless block
15
+ raw_results.each(&block)
16
+ end
17
+
18
+ # Merges the results of both Results object
19
+ def merge(res)
20
+ unless self.needle == res.needle
21
+ raise "To merge 2 Results, needle should be the same ('#{value}'). Given '#{res.value}'"
22
+ end
23
+ self.class.new(needle, value, raw_results.concat(res.raw_results))
24
+ end
5
25
 
6
26
  def results_with_false_positives
7
- relevant_results(methods: :jaro_winkler, threshold: 0.5)
27
+ relevant_results(order: :jaro_winkler, threshold: 0.5)
8
28
  end
9
29
 
10
- def relevant_results(methods: order, threshold: 0.5)
30
+ def relevant_results(**options)
31
+ options = {order: order, threshold: threshold || 0.5}.merge(options)
11
32
  raw_results.select do |result|
12
- result.all_threshold?(methods, threshold)
33
+ result.all_threshold?(options[:order], options[:threshold])
13
34
  end.yield_self do |filtered|
14
35
  self.class.new(needle, value, filtered).tap do |results|
15
- results.order = methods
36
+ results.order = options[:order]
16
37
  end
17
38
  end
18
39
  end
19
40
 
41
+ # @param values[Array<Symbol>] the algorithms' results it should be ordered by
42
+ # * Possible values: `:dice`, `:levenshtein`, `:jaro_winkler`, `:ngrams`, `:words_ngrams`, `:chars_position`, `:average`
20
43
  def order=(values)
21
- @order = [values].flatten.compact
22
- raw_results.each {|r| r.order = @order}
44
+ @order = [values].flatten.compact.tap do |o|
45
+ raw_results.each {|r| r.order = o}
46
+ end
23
47
  end
24
48
 
25
49
  def order
@@ -4,7 +4,10 @@ module Eco
4
4
  class Score < Struct.new(:score, :total)
5
5
 
6
6
  def ratio(decimals = 6)
7
- ((score || 0).to_f / (total || 1)).round(decimals)
7
+ tot = self.total; sc = self.score
8
+ tot = tot && tot > 0 ? tot : 1
9
+ sc = sc && sc > 0 ? sc : 0
10
+ (sc.to_f / tot).round(decimals)
8
11
  end
9
12
 
10
13
  def percent(decimals = 3)
@@ -13,6 +16,8 @@ module Eco
13
16
 
14
17
  def increase(value = 1)
15
18
  self.score += value
19
+ raise "Score #{self.score} (increase: #{value}) can't be greater than total #{self.total}" if self.score > self.total
20
+ self.score
16
21
  end
17
22
 
18
23
  def increase_total(value)
@@ -26,14 +31,14 @@ module Eco
26
31
  end
27
32
 
28
33
  # Merges 2 Score instance objects
29
- def merge(value)
30
- Score.new(*values_at(:score, :total)).merge!(value)
34
+ def merge(scr)
35
+ Score.new(*values_at(:score, :total)).merge!(scr)
31
36
  end
32
37
 
33
- def merge!(value)
34
- raise "Expecting Score object. Given: #{value.class}" unless value.is_a?(Score)
35
- increase(value.score)
36
- increase_total(value.total)
38
+ def merge!(scr)
39
+ raise "Expecting Score object. Given: #{scr.class}" unless scr.is_a?(Score)
40
+ increase_total(scr.total)
41
+ increase(scr.score)
37
42
  self
38
43
  end
39
44
 
@@ -17,7 +17,7 @@ module Eco
17
17
  def get_words(str, normalized: false)
18
18
  return [] unless str
19
19
  str = normalize_string(str) unless normalized
20
- str.scan(/[a-zA-Z'-]+/)
20
+ str.scan(/[a-zA-Z'-]+/).compact
21
21
  end
22
22
 
23
23
  # Keeps the start order of the `words` and consecutive `words` together/consecutive.
@@ -63,6 +63,19 @@ module Eco
63
63
  str.tr(' ', '')
64
64
  end
65
65
 
66
+ # Deletes the words of `str1` and `str2` that match
67
+ # @return [Array<String>] pair of words.
68
+ def remove_matching_words(str1, str2, normalized: false)
69
+ unless normalized
70
+ str1 = normalize_string(str1)
71
+ str2 = normalize_string(str2)
72
+ end
73
+ return [str1, str2] if !str1 || !str2 || str1.empty? || str2.empty?
74
+ ws1 = get_words(str1)
75
+ ws2 = get_words(str2)
76
+ [(ws1 - ws2).join(" "), (ws2 - ws1).join(" ")]
77
+ end
78
+
66
79
  end
67
80
  end
68
81
  end
data/lib/eco/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eco
2
- VERSION = "2.0.18"
2
+ VERSION = "2.0.24"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eco-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.18
4
+ version: 2.0.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oscar Segura
@@ -230,6 +230,26 @@ dependencies:
230
230
  - - "<"
231
231
  - !ruby/object:Gem::Version
232
232
  version: '3.1'
233
+ - !ruby/object:Gem::Dependency
234
+ name: hashdiff
235
+ requirement: !ruby/object:Gem::Requirement
236
+ requirements:
237
+ - - ">="
238
+ - !ruby/object:Gem::Version
239
+ version: 1.0.1
240
+ - - "<"
241
+ - !ruby/object:Gem::Version
242
+ version: '1.1'
243
+ type: :runtime
244
+ prerelease: false
245
+ version_requirements: !ruby/object:Gem::Requirement
246
+ requirements:
247
+ - - ">="
248
+ - !ruby/object:Gem::Version
249
+ version: 1.0.1
250
+ - - "<"
251
+ - !ruby/object:Gem::Version
252
+ version: '1.1'
233
253
  - !ruby/object:Gem::Dependency
234
254
  name: fuzzy_match
235
255
  requirement: !ruby/object:Gem::Requirement
@@ -290,6 +310,46 @@ dependencies:
290
310
  - - "<"
291
311
  - !ruby/object:Gem::Version
292
312
  version: '1.6'
313
+ - !ruby/object:Gem::Dependency
314
+ name: roo
315
+ requirement: !ruby/object:Gem::Requirement
316
+ requirements:
317
+ - - ">="
318
+ - !ruby/object:Gem::Version
319
+ version: 2.8.3
320
+ - - "<"
321
+ - !ruby/object:Gem::Version
322
+ version: '2.9'
323
+ type: :runtime
324
+ prerelease: false
325
+ version_requirements: !ruby/object:Gem::Requirement
326
+ requirements:
327
+ - - ">="
328
+ - !ruby/object:Gem::Version
329
+ version: 2.8.3
330
+ - - "<"
331
+ - !ruby/object:Gem::Version
332
+ version: '2.9'
333
+ - !ruby/object:Gem::Dependency
334
+ name: roo-xls
335
+ requirement: !ruby/object:Gem::Requirement
336
+ requirements:
337
+ - - ">="
338
+ - !ruby/object:Gem::Version
339
+ version: 1.2.0
340
+ - - "<"
341
+ - !ruby/object:Gem::Version
342
+ version: '1.3'
343
+ type: :runtime
344
+ prerelease: false
345
+ version_requirements: !ruby/object:Gem::Requirement
346
+ requirements:
347
+ - - ">="
348
+ - !ruby/object:Gem::Version
349
+ version: 1.2.0
350
+ - - "<"
351
+ - !ruby/object:Gem::Version
352
+ version: '1.3'
293
353
  description:
294
354
  email:
295
355
  - oscar@ecoportal.co.nz
@@ -332,6 +392,7 @@ files:
332
392
  - lib/eco/api/common/people/default_parsers/policy_groups_parser.rb
333
393
  - lib/eco/api/common/people/default_parsers/select_parser.rb
334
394
  - lib/eco/api/common/people/default_parsers/send_invites_parser.rb
395
+ - lib/eco/api/common/people/default_parsers/xls_parser.rb
335
396
  - lib/eco/api/common/people/entries.rb
336
397
  - lib/eco/api/common/people/entry_factory.rb
337
398
  - lib/eco/api/common/people/person_attribute_parser.rb
@@ -345,6 +406,8 @@ files:
345
406
  - lib/eco/api/common/session/base_session.rb
346
407
  - lib/eco/api/common/session/environment.rb
347
408
  - lib/eco/api/common/session/file_manager.rb
409
+ - lib/eco/api/common/session/helpers.rb
410
+ - lib/eco/api/common/session/helpers/prompt_user.rb
348
411
  - lib/eco/api/common/session/logger.rb
349
412
  - lib/eco/api/common/session/logger/cache.rb
350
413
  - lib/eco/api/common/session/logger/log.rb
@@ -397,7 +460,7 @@ files:
397
460
  - lib/eco/api/organization.rb
398
461
  - lib/eco/api/organization/login_providers.rb
399
462
  - lib/eco/api/organization/people.rb
400
- - lib/eco/api/organization/people_analytics.rb
463
+ - lib/eco/api/organization/people_similarity.rb
401
464
  - lib/eco/api/organization/person_schemas.rb
402
465
  - lib/eco/api/organization/policy_groups.rb
403
466
  - lib/eco/api/organization/preferences.rb
@@ -441,6 +504,7 @@ files:
441
504
  - lib/eco/api/usecases/default_cases/analyse_people_case.rb
442
505
  - lib/eco/api/usecases/default_cases/append_usergroups_case.rb
443
506
  - lib/eco/api/usecases/default_cases/change_email_case.rb
507
+ - lib/eco/api/usecases/default_cases/clean_unknown_tags_case.rb
444
508
  - lib/eco/api/usecases/default_cases/codes_to_tags_case.rb
445
509
  - lib/eco/api/usecases/default_cases/create_case.rb
446
510
  - lib/eco/api/usecases/default_cases/create_details_case.rb
@@ -545,7 +609,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
545
609
  requirements:
546
610
  - - ">="
547
611
  - !ruby/object:Gem::Version
548
- version: 2.4.4
612
+ version: 2.5.0
549
613
  required_rubygems_version: !ruby/object:Gem::Requirement
550
614
  requirements:
551
615
  - - ">="
@@ -1,60 +0,0 @@
1
- module Eco
2
- module API
3
- module Organization
4
- class PeopleAnalytics < Eco::API::Organization::People
5
- include Eco::Data::FuzzyMatch
6
-
7
- # @!group Helpers
8
-
9
- # @!endgroup
10
-
11
- # @!group Searchers
12
-
13
- # It gathers those that have the same `email`
14
- # @return [Hash] where `keys` are `email`s and `values` an `Array<Person>`
15
- def repeated_emails
16
- init_caches
17
- @by_email.select do |email, people|
18
- people.count > 1
19
- end
20
- end
21
-
22
- # @!endgroup
23
-
24
- # @!group Analysers
25
-
26
- # TODO: Sort results by `results.first.methods`
27
- def similarity(**options)
28
- each_with_object({}) do |person, results|
29
- results[person.id] = find_all_with_score(person, **options)
30
- end
31
- end
32
-
33
-
34
- def print_analysis(threshold)
35
- similarity.each do |id, results|
36
- msg = results.results.select do |result|
37
- result.threshold?(threshold)
38
- end.map do |result|
39
- result.print
40
- end.join("\n ")
41
-
42
- puts "'#{self[id].identify}':\n " + msg
43
- end
44
- end
45
- # @!endgroup
46
-
47
- protected
48
-
49
- def on_change
50
- remove_instance_variable(@fuzzy_match)
51
- super
52
- end
53
-
54
- private
55
-
56
-
57
- end
58
- end
59
- end
60
- end