cosing 0.1.2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,11 +12,10 @@ module Cosing
12
12
  end
13
13
 
14
14
  def add_rule(params)
15
- return unless params[:reference_number]
15
+ return unless reference = params[:reference_number]
16
16
 
17
- @rules[params[:reference_number]] = self.class::Rule.new(
18
- **params.merge(annex: self.class.name.gsub("::", " "))
19
- )
17
+ annex = self.class.name.gsub("::", " ")
18
+ @rules[reference] = self.class::Rule.new(**params.merge(annex:))
20
19
  end
21
20
 
22
21
  def lookup(reference_number)
@@ -24,15 +23,8 @@ module Cosing
24
23
  reference_number.to_s,
25
24
  fuzzy_find(reference_number.to_s)
26
25
  )
27
- rescue KeyError => e
28
- # Known missing data from original source
29
- return nil if reference_number == "19" && instance_of?(Annex::VI)
30
- return nil if reference_number == "41" && instance_of?(Annex::VI)
31
- return nil if reference_number == "31" && instance_of?(Annex::VI)
32
- return nil if reference_number == "44" && instance_of?(Annex::VI)
33
- return nil if reference_number == "268" && instance_of?(Annex::III)
34
-
35
- raise e
26
+ rescue KeyError
27
+ nil
36
28
  end
37
29
 
38
30
  private
@@ -40,8 +32,7 @@ module Cosing
40
32
  def fuzzy_find(reference_number)
41
33
  return @rules[reference_number] if @rules.key?(reference_number)
42
34
 
43
- candidates = @rules.keys.grep(/#{reference_number}[abcd]/)
44
- candidates
35
+ @rules.keys.grep(/#{reference_number}[abcd]/)
45
36
  .map { |candidate| @rules[candidate] }
46
37
  .tap do |candidates|
47
38
  if candidates.empty?
@@ -6,6 +6,24 @@ module Cosing
6
6
  class Rule < Rule
7
7
  attribute :inn, Types::String
8
8
  end
9
+
10
+ def self.load
11
+ new.tap do |annex|
12
+ Annex.parse("data/annex.II.csv") do |row|
13
+ ingredients = Cosing::Parser.transform_array!(
14
+ row,
15
+ key: :identified_ingredients,
16
+ split: ";"
17
+ )
18
+
19
+ annex.add_rule(
20
+ row.merge(
21
+ identified_ingredients: ingredients.compact
22
+ )
23
+ )
24
+ end
25
+ end
26
+ end
9
27
  end
10
28
  end
11
29
  end
@@ -10,6 +10,31 @@ module Cosing
10
10
  attribute :other_restrictions, Types::String
11
11
  attribute :product_type, Types::String
12
12
  end
13
+
14
+ def self.load
15
+ new.tap do |annex|
16
+ Annex.parse("data/annex.III.csv") do |row|
17
+ common_ingredients = Cosing::Parser.transform_array!(
18
+ row,
19
+ key: :common_ingredients,
20
+ split: ";"
21
+ )
22
+ identified_ingredients = Cosing::Parser.transform_array!(
23
+ row,
24
+ key: :identified_ingredients,
25
+ split: ";"
26
+ )
27
+
28
+ annex.add_rule(
29
+ row.merge(
30
+ common_ingredients: common_ingredients.compact,
31
+ identified_ingredients: identified_ingredients.compact,
32
+ other_restrictions: row[:other]
33
+ )
34
+ )
35
+ end
36
+ end
37
+ end
13
38
  end
14
39
  end
15
40
  end
@@ -11,6 +11,25 @@ module Cosing
11
11
  attribute :product_type, Types::String
12
12
  attribute :wording_of_conditions, Types::String
13
13
  end
14
+
15
+ def self.load
16
+ new.tap do |annex|
17
+ Annex.parse("data/annex.IV.csv") do |row|
18
+ ingredients = Cosing::Parser.transform_array!(
19
+ row,
20
+ key: :identified_ingredients,
21
+ split: ";"
22
+ )
23
+
24
+ annex.add_rule(
25
+ row.merge(
26
+ identified_ingredients: ingredients.compact,
27
+ other_restrictions: row[:other]
28
+ )
29
+ )
30
+ end
31
+ end
32
+ end
14
33
  end
15
34
  end
16
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cosing
2
4
  module Annex
3
5
  class Rule < Dry::Struct
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Cosing
2
4
  module Annex
3
5
  class SccsOpinion < Dry::Struct
@@ -10,6 +10,31 @@ module Cosing
10
10
  attribute :product_type, Types::String
11
11
  attribute :wording_of_conditions, Types::String
12
12
  end
13
+
14
+ def self.load
15
+ new.tap do |annex|
16
+ Annex.parse("data/annex.V.csv") do |row|
17
+ common_ingredients = Cosing::Parser.transform_array!(
18
+ row,
19
+ key: :common_ingredients,
20
+ split: ";"
21
+ )
22
+ identified_ingredients = Cosing::Parser.transform_array!(
23
+ row,
24
+ key: :identified_ingredients,
25
+ split: ";"
26
+ )
27
+
28
+ annex.add_rule(
29
+ row.merge(
30
+ common_ingredients: common_ingredients.compact,
31
+ identified_ingredients: identified_ingredients.compact,
32
+ other_restrictions: row[:other]
33
+ )
34
+ )
35
+ end
36
+ end
37
+ end
13
38
  end
14
39
  end
15
40
  end
@@ -11,6 +11,31 @@ module Cosing
11
11
  attribute :product_type, Types::String
12
12
  attribute :wording_of_conditions, Types::String
13
13
  end
14
+
15
+ def self.load
16
+ new.tap do |annex|
17
+ Annex.parse("data/annex.VI.csv") do |row|
18
+ common_ingredients = Cosing::Parser.transform_array!(
19
+ row,
20
+ key: :common_ingredients,
21
+ split: ";"
22
+ )
23
+ identified_ingredients = Cosing::Parser.transform_array!(
24
+ row,
25
+ key: :identified_ingredients,
26
+ split: ";"
27
+ )
28
+
29
+ annex.add_rule(
30
+ row.merge(
31
+ common_ingredients: common_ingredients.compact,
32
+ identified_ingredients: identified_ingredients.compact,
33
+ other_restrictions: row[:other]
34
+ )
35
+ )
36
+ end
37
+ end
38
+ end
14
39
  end
15
40
  end
16
41
  end
data/lib/cosing/annex.rb CHANGED
@@ -33,133 +33,25 @@ module Cosing
33
33
  yield row.merge(
34
34
  cas_numbers: cas_numbers.compact,
35
35
  ec_numbers: ec_numbers.compact,
36
- sccs_opinions: sccs_opinions.compact,
36
+ sccs_opinions: sccs_opinions.compact
37
37
  )
38
38
  end
39
39
  end
40
40
 
41
41
  def load
42
- annex_ii = Annex::II.new.tap do |annex|
43
- parse("data/annex.II.csv") do |row|
44
- identified_ingredients = transform_array!(
45
- row,
46
- key: :identified_ingredients,
47
- split: ";"
48
- )
49
-
50
- annex.add_rule(
51
- row.merge(
52
- identified_ingredients: identified_ingredients.compact
53
- )
54
- )
55
- end
56
- end
57
-
58
- annex_iii = Annex::III.new.tap do |annex|
59
- parse("data/annex.III.csv") do |row|
60
- common_ingredients = transform_array!(
61
- row,
62
- key: :common_ingredients,
63
- split: ";"
64
- )
65
- identified_ingredients = transform_array!(
66
- row,
67
- key: :identified_ingredients,
68
- split: ";"
69
- )
70
-
71
- annex.add_rule(
72
- row.merge(
73
- common_ingredients: common_ingredients.compact,
74
- identified_ingredients: identified_ingredients.compact,
75
- other_restrictions: row[:other]
76
- )
77
- )
78
- end
79
- end
80
-
81
- annex_iv = Annex::IV.new.tap do |annex|
82
- parse("data/annex.IV.csv") do |row|
83
- identified_ingredients = transform_array!(
84
- row,
85
- key: :identified_ingredients,
86
- split: ";"
87
- )
88
-
89
- annex.add_rule(
90
- row.merge(
91
- identified_ingredients: identified_ingredients.compact,
92
- other_restrictions: row[:other]
93
- )
94
- )
95
- end
96
- end
97
-
98
- annex_v = Annex::V.new.tap do |annex|
99
- parse("data/annex.V.csv") do |row|
100
- common_ingredients = transform_array!(
101
- row,
102
- key: :common_ingredients,
103
- split: ";"
104
- )
105
- identified_ingredients = transform_array!(
106
- row,
107
- key: :identified_ingredients,
108
- split: ";"
109
- )
110
-
111
- annex.add_rule(
112
- row.merge(
113
- common_ingredients: common_ingredients.compact,
114
- identified_ingredients: identified_ingredients.compact,
115
- other_restrictions: row[:other]
116
- )
117
- )
118
- end
119
- end
120
-
121
- annex_vi = Annex::VI.new.tap do |annex|
122
- parse("data/annex.VI.csv") do |row|
123
- common_ingredients = transform_array!(
124
- row,
125
- key: :common_ingredients,
126
- split: ";"
127
- )
128
- identified_ingredients = transform_array!(
129
- row,
130
- key: :identified_ingredients,
131
- split: ";"
132
- )
133
-
134
- annex.add_rule(
135
- row.merge(
136
- common_ingredients: common_ingredients.compact,
137
- identified_ingredients: identified_ingredients.compact,
138
- other_restrictions: row[:other]
139
- )
140
- )
141
- end
142
- end
143
-
144
- {
145
- ii: annex_ii,
146
- iii: annex_iii,
147
- iv: annex_iv,
148
- v: annex_v,
149
- vi: annex_vi
150
- }
151
- end
152
-
153
- def transform_array!(params, key:, split:)
154
- params
155
- .delete(key)
156
- .split(split)
157
- .map(&:strip)
158
- .reject { |n| n == "-" }
42
+ ii, iii, iv, v, vi = [
43
+ Annex::II,
44
+ Annex::III,
45
+ Annex::IV,
46
+ Annex::V,
47
+ Annex::VI
48
+ ].map(&:load)
49
+
50
+ { ii:, iii:, iv:, v:, vi: }
159
51
  end
160
52
 
161
53
  def build_sccs_opinions(row)
162
- transform_array!(
54
+ Cosing::Parser.transform_array!(
163
55
  row,
164
56
  key: :sccs_opinions,
165
57
  split: ";"
@@ -174,23 +66,23 @@ module Cosing
174
66
  end
175
67
 
176
68
  def build_cas_numbers(row)
177
- transform_array!(
69
+ Cosing::Parser.transform_array!(
178
70
  row,
179
71
  key: :cas_number,
180
72
  split: "/"
181
73
  ).map do |cas_number|
182
- match = cas_number.match(/(?<cas_number>\d{2,7}-\d{2}-\d)/)
74
+ match = cas_number.match(Patterns::CAS_NUMBER)
183
75
  match[:cas_number] if match
184
76
  end
185
77
  end
186
78
 
187
79
  def build_ec_numbers(row)
188
- transform_array!(
80
+ Cosing::Parser.transform_array!(
189
81
  row,
190
82
  key: :ec_number,
191
83
  split: "/"
192
84
  ).map do |ec_number|
193
- match = ec_number.match(/(?<ec_number>\d{3}-\d{3}-\d)/)
85
+ match = ec_number.match(Patterns::EC_NUMBER)
194
86
  match[:ec_number] if match
195
87
  end
196
88
  end
@@ -4,80 +4,86 @@ module Cosing
4
4
  class Database
5
5
  attr_reader :annexes, :ingredients
6
6
 
7
+ ANNOTATION_PATTERN = %r{([IVX]+)/([IVX]*/)?([\dabcd,]+)}
8
+
7
9
  def initialize(annexes)
8
10
  @annexes = annexes
9
11
  @ingredients = {}
10
12
  end
11
13
 
12
14
  def add_ingredient(params)
13
- restrictions = transform_array!(params, key: :restriction, split: "\n")
14
- annotation_pattern = %r{([IVX]+)/([IVX]*/)?([\dabcd,]+)}
15
+ restrictions = Parser
16
+ .transform_array!(params, key: :restriction, split: "\n")
17
+ regulations = extract_regulations(restrictions).compact
18
+ cas_numbers = extract_cas_numbers(params).compact
19
+ einecs_numbers = extract_einecs_numbers(params).compact
20
+ functions = Parser
21
+ .transform_array!(params, key: :functions, split: ",")
22
+ .compact
15
23
 
16
- regulations = restrictions
17
- .flat_map do |restriction|
18
- matches = restriction.scan(annotation_pattern)
19
- next unless matches.any?
24
+ @ingredients[params[:reference_number]] = Ingredient.new(
25
+ functions:,
26
+ restrictions:,
27
+ regulations:,
28
+ cas_numbers:,
29
+ einecs_numbers:,
30
+ **params
31
+ )
32
+ end
20
33
 
21
- hits = matches.flat_map do |match|
22
- numeral, _, reference_number = match
34
+ def save(filepath, pretty: false)
35
+ output = if pretty
36
+ JSON.pretty_generate(@ingredients.to_h)
37
+ else
38
+ JSON.dump(@ingredients.to_h)
39
+ end
23
40
 
24
- reference_number.split(",").flat_map do |number|
25
- #debugger if number == "41" && numeral == "VI"
26
- @annexes[numeral.downcase.to_sym].lookup(number)
27
- end
28
- end
41
+ File.write(filepath, output)
42
+ end
29
43
 
30
- hits.compact
31
- end
44
+ private
32
45
 
33
- cas_numbers = transform_array!(
46
+ def extract_cas_numbers(params)
47
+ Parser.transform_array!(
34
48
  params,
35
49
  key: :cas_number,
36
50
  split: "/"
37
51
  ).map do |cas_number|
38
- match = cas_number.match(/(?<cas_number>\d{2,7}-\d{2}-\d)/)
52
+ match = cas_number.match(Patterns::CAS_NUMBER)
39
53
  match[:cas_number] if match
40
54
  end
55
+ end
41
56
 
42
- functions = transform_array!(params, key: :functions, split: ",")
43
-
44
- einecs_numbers = transform_array!(
57
+ def extract_einecs_numbers(params)
58
+ Parser.transform_array!(
45
59
  params,
46
60
  key: :einecs_number,
47
61
  split: "/"
48
62
  ).map do |einecs_number|
49
- match = einecs_number.match(/(?<einecs_number>\d{3}-\d{3}-\d)/)
63
+ match = einecs_number.match(Patterns::EINECS_NUMBER)
50
64
  match[:einecs_number] if match
51
65
  end
52
-
53
- @ingredients[params[:reference_number]] = Ingredient.new(
54
- functions:,
55
- restrictions: restrictions.compact,
56
- regulations: regulations.compact,
57
- cas_numbers: cas_numbers.compact,
58
- einecs_numbers: einecs_numbers.compact,
59
- **params
60
- )
61
66
  end
62
67
 
63
- def save(filepath, pretty: false)
64
- output = if pretty
65
- JSON.pretty_generate(@ingredients.to_h)
66
- else
67
- JSON.dump(@ingredients.to_h)
68
- end
68
+ def extract_regulations(restrictions)
69
+ restrictions
70
+ .flat_map do |restriction|
71
+ matches = restriction.scan(ANNOTATION_PATTERN)
72
+ next if matches.none?
69
73
 
70
- File.write(filepath, output)
74
+ extract_hits(matches).compact
75
+ end
71
76
  end
72
77
 
73
- private
78
+ def extract_hits(matches)
79
+ matches.flat_map do |match|
80
+ numeral, _, reference_number = match
74
81
 
75
- def transform_array!(params, key:, split:)
76
- params
77
- .delete(key)
78
- .split(split)
79
- .map(&:strip)
80
- .reject { |n| n == "-" }
82
+ reference_number.split(",").flat_map do |number|
83
+ # debugger if number == "41" && numeral == "VI"
84
+ @annexes[numeral.downcase.to_sym].lookup(number)
85
+ end
86
+ end
81
87
  end
82
88
  end
83
89
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cosing
4
+ module Parser
5
+ module_function
6
+
7
+ def transform_array!(params, key:, split:)
8
+ params
9
+ .delete(key)
10
+ .split(split)
11
+ .map!(&:strip)
12
+ .reject { |n| n == "-" }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Cosing
2
+ module Patterns
3
+ CAS_NUMBER = /(?<cas_number>\d{2,7}-\d{2}-\d)/
4
+ EINECS_NUMBER = /(?<einecs_number>\d{3}-\d{3}-\d)/
5
+ EC_NUMBER = /(?<ec_number>\d{3}-\d{3}-\d)/
6
+ end
7
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cosing
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/cosing.rb CHANGED
@@ -7,6 +7,8 @@ require "dry/types"
7
7
  require "dry/struct"
8
8
  require_relative "cosing/version"
9
9
  require_relative "cosing/types"
10
+ require_relative "cosing/parser"
11
+ require_relative "cosing/patterns"
10
12
  require_relative "cosing/ingredient"
11
13
  require_relative "cosing/annex"
12
14
  require_relative "cosing/database"
@@ -30,13 +32,10 @@ module Cosing
30
32
  liberal_parsing: true,
31
33
  header_converters: :symbol
32
34
  ) do |row|
33
- row =
34
- row
35
- .to_h
36
- .transform_values(&:to_s)
37
- .transform_values(&:strip)
38
-
39
- database.add_ingredient(row)
35
+ row
36
+ .to_h
37
+ .transform_values! { |value| value.to_s.strip }
38
+ .then { |row| database.add_ingredient(row) }
40
39
  end
41
40
  end
42
41
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cosing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nolan J Tait
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-03 00:00:00.000000000 Z
11
+ date: 2024-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: csv
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: dry-struct
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -45,12 +59,8 @@ executables: []
45
59
  extensions: []
46
60
  extra_rdoc_files: []
47
61
  files:
48
- - ".rspec"
49
- - ".rubocop.yml"
50
- - CHANGELOG.md
51
62
  - LICENSE.txt
52
63
  - README.md
53
- - Rakefile
54
64
  - cosing.gemspec
55
65
  - data/annex.II.csv
56
66
  - data/annex.III.csv
@@ -70,6 +80,8 @@ files:
70
80
  - lib/cosing/annex/vi.rb
71
81
  - lib/cosing/database.rb
72
82
  - lib/cosing/ingredient.rb
83
+ - lib/cosing/parser.rb
84
+ - lib/cosing/patterns.rb
73
85
  - lib/cosing/types.rb
74
86
  - lib/cosing/version.rb
75
87
  homepage: https://github.com/inhouse-work/cosing
@@ -88,14 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
100
  requirements:
89
101
  - - ">="
90
102
  - !ruby/object:Gem::Version
91
- version: '3'
103
+ version: '3.1'
92
104
  required_rubygems_version: !ruby/object:Gem::Requirement
93
105
  requirements:
94
106
  - - ">="
95
107
  - !ruby/object:Gem::Version
96
108
  version: '0'
97
109
  requirements: []
98
- rubygems_version: 3.4.10
110
+ rubygems_version: 3.5.7
99
111
  signing_key:
100
112
  specification_version: 4
101
113
  summary: COSING database
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,6 +0,0 @@
1
- require:
2
- - rubocop-inhouse
3
-
4
- inherit_gem:
5
- rubocop-inhouse:
6
- - config/default.yml
data/CHANGELOG.md DELETED
@@ -1,13 +0,0 @@
1
- ## [Unreleased]
2
-
3
- ## [0.1.2] - 2023-12-03
4
-
5
- - Fixes missing pathname require
6
-
7
- ## [0.1.1] - 2023-12-03
8
-
9
- - Fixes gem paths
10
-
11
- ## [0.1.0] - 2023-12-03
12
-
13
- - Initial release
data/Rakefile DELETED
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- task default: :spec
9
-
10
- desc "Output the cosing database into a json format"
11
- task :export do
12
- database = Cosing.load
13
- database.save
14
- end