inci_score 4.1.4 → 4.2.1
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 +4 -4
- data/config/catalog.yml +1 -0
- data/lib/inci_score/cli.rb +3 -4
- data/lib/inci_score/computer.rb +3 -5
- data/lib/inci_score/recognizer.rb +5 -6
- data/lib/inci_score/recognizer_rules.rb +12 -10
- data/lib/inci_score/response.rb +2 -5
- data/lib/inci_score/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 07a79ba247cd43e62ef0988548df410598bf87cb653fd1540f8c6e419a31d03a
|
|
4
|
+
data.tar.gz: d0ea4f382824592d33d5bc6ec8bc5d3f88c6a704d24f093259a90da77bf3c6c9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5b92528b5d0ef275afce0db6c5d8039eb48daa2e08c098cd5d768dc61be10c2bfa72c24795dea1f28ebc54ef3287beb60449a4a41930e19283072a7c3948cc6f
|
|
7
|
+
data.tar.gz: e414756b761775502d54ab4e62e20385148a1a706fed3057ad5a74b47be7ee8274cddcde235d357851158e3e5b28f9961e7873dd675125291b1c226b6f2dc71f
|
data/config/catalog.yml
CHANGED
data/lib/inci_score/cli.rb
CHANGED
|
@@ -4,20 +4,19 @@ require 'optparse'
|
|
|
4
4
|
|
|
5
5
|
module InciScore
|
|
6
6
|
class CLI
|
|
7
|
-
attr_reader :args, :io
|
|
7
|
+
attr_reader :args, :io
|
|
8
8
|
attr_accessor :src
|
|
9
9
|
|
|
10
|
-
def initialize(args:, io: STDOUT
|
|
10
|
+
def initialize(args:, io: STDOUT)
|
|
11
11
|
@args = args
|
|
12
12
|
@io = io
|
|
13
|
-
@catalog = catalog
|
|
14
13
|
@src = nil
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def call
|
|
18
17
|
parser.parse!(args)
|
|
19
18
|
return io.puts(%q{Specify inci list as: --src='aqua, parfum, etc'}) unless src
|
|
20
|
-
computer = Computer.new(src: src
|
|
19
|
+
computer = Computer.new(src: src)
|
|
21
20
|
io.puts computer.call
|
|
22
21
|
end
|
|
23
22
|
|
data/lib/inci_score/computer.rb
CHANGED
|
@@ -5,12 +5,11 @@ module InciScore
|
|
|
5
5
|
TOLERANCE = 30.0
|
|
6
6
|
DECIMALS = 2
|
|
7
7
|
|
|
8
|
-
attr_reader :src, :
|
|
8
|
+
attr_reader :src, :rules, :ingredients, :components, :unrecognized
|
|
9
9
|
|
|
10
|
-
def initialize(src:,
|
|
10
|
+
def initialize(src:, rules: Normalizer::DEFAULT_RULES)
|
|
11
11
|
@unrecognized = []
|
|
12
12
|
@src = src
|
|
13
|
-
@catalog = catalog
|
|
14
13
|
@rules = rules
|
|
15
14
|
@ingredients = Normalizer.new(src: src, rules: rules).call
|
|
16
15
|
@components = fetch_components
|
|
@@ -21,7 +20,6 @@ module InciScore
|
|
|
21
20
|
Response.new(components: components,
|
|
22
21
|
unrecognized: unrecognized,
|
|
23
22
|
score: score,
|
|
24
|
-
valid: valid?,
|
|
25
23
|
precision: precision)
|
|
26
24
|
end
|
|
27
25
|
|
|
@@ -41,7 +39,7 @@ module InciScore
|
|
|
41
39
|
|
|
42
40
|
def fetch_components
|
|
43
41
|
ingredients.map do |ingredient|
|
|
44
|
-
Recognizer.new(ingredient
|
|
42
|
+
Recognizer.new(ingredient).call.tap do |component|
|
|
45
43
|
unrecognized << ingredient unless component
|
|
46
44
|
end
|
|
47
45
|
end.compact
|
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module InciScore
|
|
4
4
|
class Recognizer
|
|
5
|
-
DEFAULT_RULES = [Rules::Key, Rules::Levenshtein, Rules::
|
|
5
|
+
DEFAULT_RULES = [Rules::Key, Rules::Levenshtein, Rules::Hazard, Rules::Prefix, Rules::Tokens].freeze
|
|
6
6
|
|
|
7
7
|
Component = Struct.new(:name, :hazard)
|
|
8
8
|
|
|
9
|
-
attr_reader :ingredient, :
|
|
9
|
+
attr_reader :ingredient, :rules, :applied
|
|
10
10
|
|
|
11
|
-
def initialize(ingredient,
|
|
11
|
+
def initialize(ingredient, rules = DEFAULT_RULES, wrapper = Ingredient)
|
|
12
12
|
@ingredient = wrapper.new(ingredient)
|
|
13
|
-
@catalog = catalog
|
|
14
13
|
@rules = rules
|
|
15
14
|
@applied = []
|
|
16
15
|
freeze
|
|
@@ -20,7 +19,7 @@ module InciScore
|
|
|
20
19
|
return if ingredient.to_s.empty?
|
|
21
20
|
component = find_component
|
|
22
21
|
return unless component
|
|
23
|
-
Component.new(component,
|
|
22
|
+
Component.new(component, Config::CATALOG[component])
|
|
24
23
|
end
|
|
25
24
|
|
|
26
25
|
private
|
|
@@ -35,7 +34,7 @@ module InciScore
|
|
|
35
34
|
|
|
36
35
|
def apply(rule)
|
|
37
36
|
ingredient.values.map do |value|
|
|
38
|
-
rule.call(value
|
|
37
|
+
rule.call(value)
|
|
39
38
|
end.find(&:itself)
|
|
40
39
|
end
|
|
41
40
|
end
|
|
@@ -7,9 +7,9 @@ module InciScore
|
|
|
7
7
|
module Rules
|
|
8
8
|
TOLERANCE = 3
|
|
9
9
|
|
|
10
|
-
Key = ->(src
|
|
10
|
+
Key = ->(src) { src if Config::CATALOG.has_key?(src) }
|
|
11
11
|
|
|
12
|
-
Hazard = ->(src
|
|
12
|
+
Hazard = ->(src) { 'generic-hazard' if Config::HAZARDS.any? { |h| src.include?(h) } }
|
|
13
13
|
|
|
14
14
|
module Levenshtein
|
|
15
15
|
extend self
|
|
@@ -20,12 +20,12 @@ module InciScore
|
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def call(src
|
|
23
|
+
def call(src)
|
|
24
24
|
return if src.empty?
|
|
25
25
|
size = src.size
|
|
26
26
|
farthest = Result.new(nil, size)
|
|
27
27
|
initial = src[0]
|
|
28
|
-
result =
|
|
28
|
+
result = Config::CATALOG.reduce(farthest) do |nearest, (component, _)|
|
|
29
29
|
next nearest unless component.start_with?(initial)
|
|
30
30
|
next nearest if component.size > (size + TOLERANCE)
|
|
31
31
|
d = src.distance(component)
|
|
@@ -36,15 +36,15 @@ module InciScore
|
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
module
|
|
39
|
+
module Prefix
|
|
40
40
|
extend self
|
|
41
41
|
|
|
42
42
|
MIN_MEANINGFUL = 7
|
|
43
43
|
|
|
44
|
-
def call(src
|
|
44
|
+
def call(src)
|
|
45
45
|
return if src.size < TOLERANCE
|
|
46
46
|
digits = src[0, MIN_MEANINGFUL]
|
|
47
|
-
|
|
47
|
+
Config::CATALOG.detect { |component, _| component.start_with?(digits) }.to_a.first
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
@@ -53,9 +53,10 @@ module InciScore
|
|
|
53
53
|
|
|
54
54
|
UNMATCHABLE = %w[extract oil sodium acid sulfate].freeze
|
|
55
55
|
|
|
56
|
-
def call(src
|
|
56
|
+
def call(src)
|
|
57
|
+
return if src.size <= TOLERANCE
|
|
57
58
|
tokens(src).each do |token|
|
|
58
|
-
|
|
59
|
+
Config::CATALOG.each do |component, _|
|
|
59
60
|
return component if component.include?(token)
|
|
60
61
|
end
|
|
61
62
|
end
|
|
@@ -65,7 +66,8 @@ module InciScore
|
|
|
65
66
|
private
|
|
66
67
|
|
|
67
68
|
def tokens(src)
|
|
68
|
-
|
|
69
|
+
words = src.split(' ').map { |w| w.split('-') }.flatten
|
|
70
|
+
(words - UNMATCHABLE).reject { |t| t.size < TOLERANCE }.sort! { |a, b| b.size <=> a.size }
|
|
69
71
|
end
|
|
70
72
|
end
|
|
71
73
|
end
|
data/lib/inci_score/response.rb
CHANGED
|
@@ -4,19 +4,18 @@ require 'oj'
|
|
|
4
4
|
|
|
5
5
|
module InciScore
|
|
6
6
|
class Response
|
|
7
|
-
attr_reader :components, :score, :unrecognized, :
|
|
7
|
+
attr_reader :components, :score, :unrecognized, :precision
|
|
8
8
|
|
|
9
9
|
def initialize(options = {})
|
|
10
10
|
@components = options.fetch(:components) { [] }
|
|
11
11
|
@score = options.fetch(:score) { 0.0 }
|
|
12
12
|
@unrecognized = options.fetch(:unrecognized) { [] }
|
|
13
|
-
@valid = options.fetch(:valid) { false }
|
|
14
13
|
@precision = options.fetch(:precision) { 0 }
|
|
15
14
|
freeze
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def to_json
|
|
19
|
-
data = { components: components.map(&:to_h), unrecognized: unrecognized, score: score,
|
|
18
|
+
data = { components: components.map(&:to_h), unrecognized: unrecognized, score: score, precision: precision }.freeze
|
|
20
19
|
Oj.dump(data, mode: :compat)
|
|
21
20
|
end
|
|
22
21
|
|
|
@@ -24,8 +23,6 @@ module InciScore
|
|
|
24
23
|
%Q{
|
|
25
24
|
TOTAL SCORE:
|
|
26
25
|
\t#{score}
|
|
27
|
-
VALID STATE:
|
|
28
|
-
\t#{valid}
|
|
29
26
|
PRECISION:
|
|
30
27
|
\t#{precision}
|
|
31
28
|
COMPONENTS:
|
data/lib/inci_score/version.rb
CHANGED