inci_score 4.5.2 → 4.6.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: 9b62e565ea7191b030ebcdf3b278130065ecb325743bf4ca618e0c8e7d7559a8
4
- data.tar.gz: 0fcbf56e6edb8a314c202200e89d8f53428b009472440d7a1e92815be90b9df9
3
+ metadata.gz: 948a01654e32fadaaa2cce158a59e84fc3f904a403d89fabfe18b3529600d0a8
4
+ data.tar.gz: 664c64f7dfb9003fcd1fa795d39c59157f1742d1e42653e504e9be2be47c196e
5
5
  SHA512:
6
- metadata.gz: 7f4040838f05e804090a2482d3b6ea03c1c4575e5133a0989af9f733c373f6c1c507426196fca5eba539c4fbe5c3322a62e623a1630c8ade6dfe2f056a616938
7
- data.tar.gz: c14b303bae2a60c0f16b1df28e95e77cf2b32caaa33086d8852edf8486dfccbaf29bb361a7d8c2150eb32a569890c84a812369f13bdd38f61df979a76f18abd2
6
+ metadata.gz: 7429c7077f89e8054782460772db0ed3f95f28a6ce686b2c060c3eceea0fa23e1169072727fca021f9c53a7e8afb55a28f87d7f1e7309292a48511a2fbcf895e
7
+ data.tar.gz: ca66118c3e79de310d0031fb2dfa17e91c62e772138798072c4c3eff0566e6e91e51a170c3d1f727511af3470455f1eefe8c064bbfda961cf7086b6c0f19c9dd
data/README.md CHANGED
@@ -58,18 +58,21 @@ require "inci_score"
58
58
 
59
59
  inci = InciScore::Computer.new(src: 'aqua, dimethicone').call
60
60
  inci.score # 56.25
61
+ inci.precision # 100.0
61
62
  ```
62
63
 
63
64
  As you see the results are wrapped by an *InciScore::Response* object, this is useful when dealing with the CLI and HTTP interfaces (read below).
64
65
 
65
66
  #### Unrecognized components
66
- The API treats unrecognized components as a common case by just marking the object as non valid and raise a warning in case more than 30% of the ingredients are not found.
67
+ The API treats unrecognized components as a common case by just marking the object as non valid.
67
68
  In such case the score is computed anyway by considering only recognized components.
68
- Is still possible to query the object for its state:
69
+ You can check the `precision` value, which is zero for unrecognized components, and changes based on the applied recognizer rule (100% when exact matching).
69
70
 
70
71
  ```ruby
71
72
  inci = InciScore::Computer.new(src: 'ingredients:aqua,noent1,noent2')
72
73
  inci.valid? # false
74
+ inci.score # 100.0
75
+ inci.precision # 33.33
73
76
  inci.unrecognized # ["noent1", "noent2"]
74
77
  ```
75
78
 
@@ -82,7 +85,7 @@ inci_score --src="ingredients: aqua, dimethicone, pej-10, noent"
82
85
  TOTAL SCORE:
83
86
  53.22
84
87
  PRECISION:
85
- 75.0
88
+ 71.54
86
89
  COMPONENTS:
87
90
  aqua (0), dimethicone (4), peg-10 (3)
88
91
  UNRECOGNIZED:
data/config/catalog.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  aqua: 0
3
3
  water: 0
4
+ aroma: 2
4
5
  parfum: 3
5
6
  fragrance: 3
6
7
  phosphatidylcholine: 1
@@ -1580,6 +1581,7 @@ fagus sylvatica: 0
1580
1581
  farnesol: 2
1581
1582
  farnesyl acetate: 0
1582
1583
  fast green fcf: 4
1584
+ fava tonka: 1
1583
1585
  fermented vegetable extract: 0
1584
1586
  ferric chloride: 0
1585
1587
  ferula assa foetida: 0
@@ -2816,7 +2818,7 @@ pantothenic acid polypeptide: 0
2816
2818
  papaver orientale: 0
2817
2819
  papaver rhoeas: 0
2818
2820
  paraffin: 3
2819
- paraffinum liquidum: 3
2821
+ paraffinum liquidum (mineral oil): 3
2820
2822
  parietaria officinalis: 0
2821
2823
  passiflora incarnata: 0
2822
2824
  passiflora quadrangularis: 0
@@ -3370,6 +3372,7 @@ pentasodium aminotrimethylene phosphonate: 3
3370
3372
  pentasodium pentetate: 3
3371
3373
  pentasodium triphosphate: 2
3372
3374
  pentetic acid: 3
3375
+ pentylene glycol: 2
3373
3376
  perfluorodecalin: 3
3374
3377
  perfluoropolymethylisopropyl ether: 3
3375
3378
  perfluorotetralin: 3
@@ -4832,6 +4835,8 @@ thymus extract: 4
4832
4835
  thymus hydrolysate: 4
4833
4836
  thymus serpyllum: 0
4834
4837
  thymus vulgaris: 0
4838
+ thymus vulgaris leaf oil: 0
4839
+ thymus vulgaris leaf water: 0
4835
4840
  tilia americana: 0
4836
4841
  tilia cordata: 0
4837
4842
  tilia vulgaris: 0
@@ -15,7 +15,7 @@ module InciScore
15
15
 
16
16
  def call
17
17
  parser.parse!(args)
18
- return io.puts(%q{Specify inci list as: --src='aqua, parfum, etc'}) unless src
18
+ return io.puts(%q{Specify INCI list as: --src='aqua, parfum, etc'}) unless src
19
19
  computer = Computer.new(src: src)
20
20
  io.puts computer.call
21
21
  end
@@ -5,10 +5,11 @@ module InciScore
5
5
  TOLERANCE = 30.0
6
6
  DECIMALS = 2
7
7
 
8
- attr_reader :src, :rules, :ingredients, :components, :unrecognized
8
+ attr_reader :src, :rules, :ingredients, :components, :unrecognized, :precisions
9
9
 
10
10
  def initialize(src:, rules: Normalizer::DEFAULT_RULES)
11
11
  @unrecognized = []
12
+ @precisions = []
12
13
  @src = src
13
14
  @rules = rules
14
15
  @ingredients = Normalizer.new(src: src, rules: rules).call
@@ -28,7 +29,7 @@ module InciScore
28
29
  end
29
30
 
30
31
  def precision
31
- (100 - ((unrecognized.size / Float(ingredients.size)) * 100)).round(DECIMALS)
32
+ (precisions.sum / ingredients.size).round(DECIMALS)
32
33
  end
33
34
 
34
35
  def valid?
@@ -39,7 +40,9 @@ module InciScore
39
40
 
40
41
  def fetch_components
41
42
  ingredients.map do |ingredient|
42
- Recognizer.new(ingredient).call.tap do |component|
43
+ recognizer = Recognizer.new(ingredient)
44
+ recognizer.call.tap do |component|
45
+ precisions << recognizer.precision
43
46
  unrecognized << ingredient unless component
44
47
  end
45
48
  end.compact
@@ -5,7 +5,6 @@ require 'yaml'
5
5
  module InciScore
6
6
  module Config
7
7
  CATALOG = YAML::load_file(File::expand_path('../../../config/catalog.yml', __FILE__)).freeze
8
- CIR = File.readlines(File::expand_path('../../../config/cir', __FILE__)).freeze
9
8
  HAZARDS = YAML::load_file(File::expand_path('../../../config/hazards.yml', __FILE__)).freeze
10
9
  end
11
10
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module InciScore
4
4
  class Ingredient
5
- SLASH = '/'
6
5
  SLASH_RULE = /(?<!ate)\//.freeze
7
6
  PARENTHESIS = %w[( ) [ ]].freeze
8
7
  PARENTHESIS_RULE = /(\(.+\)|\[.+\])/.freeze
@@ -11,24 +10,23 @@ module InciScore
11
10
 
12
11
  def initialize(raw)
13
12
  @raw = raw.to_s
14
- @values = fetch_values
13
+ @values = fetch_values.uniq
15
14
  freeze
16
15
  end
17
16
 
18
- def to_s
19
- values.join(SLASH)
20
- end
21
-
22
17
  private
23
18
 
24
19
  def fetch_values
25
- if parenthesis?
26
- parenthesis = PARENTHESIS.join
27
- parenthesis_values = raw.match(PARENTHESIS_RULE).captures.map { |c| c.delete(parenthesis) }
28
- deparenthesized = raw.sub(PARENTHESIS_RULE, '').sub(/\s{2,}/, ' ').strip
29
- [deparenthesized].concat(parenthesis_values)
30
- else
31
- raw.split(SLASH_RULE).map(&:strip)
20
+ [raw].tap do |vals|
21
+ if parenthesis?
22
+ parenthesis = PARENTHESIS.join
23
+ parenthesis_values = raw.match(PARENTHESIS_RULE).captures.map { |c| c.delete(parenthesis) }
24
+ deparenthesized = raw.sub(PARENTHESIS_RULE, '').sub(/\s{2,}/, ' ').strip
25
+ vals << deparenthesized
26
+ vals.concat(parenthesis_values)
27
+ else
28
+ vals.concat(raw.split(SLASH_RULE).map(&:strip))
29
+ end
32
30
  end
33
31
  end
34
32
 
@@ -3,19 +3,31 @@
3
3
  module InciScore
4
4
  class Recognizer
5
5
  DEFAULT_RULES = [Rules::Key, Rules::Levenshtein, Rules::Hazard, Rules::Prefix, Rules::Tokens].freeze
6
+ PRECISION_BASE = 4
6
7
 
7
8
  attr_reader :ingredient, :rules, :applied
9
+ attr_accessor :found
8
10
 
9
11
  def initialize(ingredient, rules = DEFAULT_RULES)
10
12
  @ingredient = Ingredient.new(ingredient)
11
13
  @rules = rules
12
14
  @applied = []
13
- freeze
15
+ @found = false
14
16
  end
15
17
 
16
18
  def call
17
19
  return if ingredient.to_s.empty?
18
- find_component
20
+ find_component.tap do |c|
21
+ self.found = true if c
22
+ end
23
+ end
24
+
25
+ def precision
26
+ return 0.0 unless found
27
+ rule = applied.last
28
+ index = rules.index(rule) + PRECISION_BASE
29
+ ratio = Math.log(index, PRECISION_BASE)
30
+ (100 / ratio).round(2)
19
31
  end
20
32
 
21
33
  private
@@ -28,24 +28,31 @@ module InciScore
28
28
  extend self
29
29
 
30
30
  Result = Struct.new(:name, :distance, :score) do
31
- def tolerable?(size)
32
- distance < TOLERANCE && distance <= (size-1)
31
+ def tolerable?(size, tolerance)
32
+ distance < tolerance && distance <= (size-1)
33
33
  end
34
34
  end
35
35
 
36
36
  def call(src)
37
37
  return if src.empty?
38
38
  size = src.size
39
+ t = tolerance(size)
39
40
  farthest = Result.new(nil, size)
40
41
  initial = src[0]
41
42
  result = Config::CATALOG.reduce(farthest) do |nearest, (name, score)|
42
43
  next nearest unless name.start_with?(initial)
43
- next nearest if name.size > (size + TOLERANCE)
44
+ next nearest if name.size > (size + t)
44
45
  d = src.distance(name)
45
46
  nearest = Result.new(name, d, score) if d < nearest.distance
46
47
  nearest
47
48
  end
48
- Component.new(result.name, result.score) if result.tolerable?(size)
49
+ Component.new(result.name, result.score) if result.tolerable?(size, t)
50
+ end
51
+
52
+ private
53
+
54
+ def tolerance(size)
55
+ Math.log(size, 1.8).round
49
56
  end
50
57
  end
51
58
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module InciScore
4
- VERSION = '4.5.2'
4
+ VERSION = '4.6.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inci_score
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.5.2
4
+ version: 4.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - costajob
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-05 00:00:00.000000000 Z
11
+ date: 2023-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -106,7 +106,6 @@ files:
106
106
  - README.md
107
107
  - bin/inci_score
108
108
  - config/catalog.yml
109
- - config/cir
110
109
  - config/hazards.yml
111
110
  - ext/levenshtein.c
112
111
  - lib/inci_score.rb