uk-hs_code 0.1.3 → 0.3.0
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/.rubocop.yml +21 -4
- data/Gemfile.lock +18 -1
- data/README.md +62 -22
- data/lib/uk/hs_code/api_request.rb +0 -1
- data/lib/uk/hs_code/client.rb +53 -0
- data/lib/uk/hs_code/configuration.rb +36 -0
- data/lib/uk/hs_code/entities/commodity.rb +59 -0
- data/lib/uk/hs_code/entities/exact_match.rb +51 -0
- data/lib/uk/hs_code/entities/search_reference.rb +44 -0
- data/lib/uk/hs_code/entities/search_result.rb +20 -0
- data/lib/uk/hs_code/parsers/search_response_parser.rb +110 -0
- data/lib/uk/hs_code/record.rb +74 -1
- data/lib/uk/hs_code/services/search_references_service.rb +49 -0
- data/lib/uk/hs_code/services/search_service.rb +39 -0
- data/lib/uk/hs_code/version.rb +1 -1
- data/lib/uk/hs_code.rb +26 -8
- data/uk-hs_code.gemspec +39 -0
- metadata +58 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a4dd60a00dee0539c09bcef47261cf9433b3dcc00f81cb9a71eb8f22899cf096
|
|
4
|
+
data.tar.gz: 857040a2ba439ceda069276cd50606a371650e4198a1d5a8d660cd2b1de16737
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fba1933687255a01899e19f066b5372d0e1b764713372fa91e5bb9fe572cecc8b8afebc2c87681af00d50a100b1cc4de3eec5034f7a4b5f0b8d4d217e3bb9883
|
|
7
|
+
data.tar.gz: 88b271cee51462b6471a45a2899982508a5e243814ef658bf78abddfb341041fe18f2e509f9a666dd0d81c39c9c36773fe7c75f7913ef97e7dec6e5b2a368729
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
-
TargetRubyVersion:
|
|
2
|
+
TargetRubyVersion: 3.0
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
3
5
|
|
|
4
6
|
Style/StringLiterals:
|
|
5
7
|
Enabled: true
|
|
@@ -12,9 +14,6 @@ Style/StringLiteralsInInterpolation:
|
|
|
12
14
|
Style/Documentation:
|
|
13
15
|
Enabled: false
|
|
14
16
|
|
|
15
|
-
Layout/LineLength:
|
|
16
|
-
Max: 120
|
|
17
|
-
|
|
18
17
|
Layout/LineLength:
|
|
19
18
|
Enabled: false
|
|
20
19
|
|
|
@@ -23,3 +22,21 @@ Metrics/AbcSize:
|
|
|
23
22
|
|
|
24
23
|
Metrics/BlockLength:
|
|
25
24
|
Enabled: false
|
|
25
|
+
|
|
26
|
+
Metrics/MethodLength:
|
|
27
|
+
Enabled: false
|
|
28
|
+
|
|
29
|
+
Metrics/CyclomaticComplexity:
|
|
30
|
+
Enabled: false
|
|
31
|
+
|
|
32
|
+
Metrics/PerceivedComplexity:
|
|
33
|
+
Enabled: false
|
|
34
|
+
|
|
35
|
+
Metrics/ParameterLists:
|
|
36
|
+
Enabled: false
|
|
37
|
+
|
|
38
|
+
Gemspec/DevelopmentDependencies:
|
|
39
|
+
Enabled: false
|
|
40
|
+
|
|
41
|
+
Gemspec/RequireMFA:
|
|
42
|
+
Enabled: false
|
data/Gemfile.lock
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
uk-hs_code (0.
|
|
4
|
+
uk-hs_code (0.3.0)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
|
+
addressable (2.9.0)
|
|
10
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
9
11
|
ast (2.4.2)
|
|
12
|
+
base64 (0.3.0)
|
|
13
|
+
bigdecimal (4.1.3)
|
|
10
14
|
coderay (1.1.3)
|
|
15
|
+
crack (1.0.1)
|
|
16
|
+
bigdecimal
|
|
17
|
+
rexml
|
|
11
18
|
diff-lcs (1.5.0)
|
|
19
|
+
hashdiff (1.2.1)
|
|
12
20
|
json (2.6.3)
|
|
13
21
|
method_source (1.0.0)
|
|
14
22
|
parallel (1.22.1)
|
|
@@ -17,6 +25,8 @@ GEM
|
|
|
17
25
|
pry (0.14.2)
|
|
18
26
|
coderay (~> 1.1)
|
|
19
27
|
method_source (~> 1.0)
|
|
28
|
+
public_suffix (7.0.5)
|
|
29
|
+
racc (1.8.1)
|
|
20
30
|
rainbow (3.1.1)
|
|
21
31
|
rake (13.0.6)
|
|
22
32
|
regexp_parser (2.7.0)
|
|
@@ -48,17 +58,24 @@ GEM
|
|
|
48
58
|
parser (>= 3.1.1.0)
|
|
49
59
|
ruby-progressbar (1.11.0)
|
|
50
60
|
unicode-display_width (2.4.2)
|
|
61
|
+
webmock (3.34.0)
|
|
62
|
+
addressable (>= 2.8.0)
|
|
63
|
+
crack (>= 0.3.2)
|
|
64
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
51
65
|
|
|
52
66
|
PLATFORMS
|
|
53
67
|
arm64-darwin-23
|
|
54
68
|
x86_64-linux
|
|
55
69
|
|
|
56
70
|
DEPENDENCIES
|
|
71
|
+
base64
|
|
57
72
|
pry
|
|
73
|
+
racc (~> 1.8)
|
|
58
74
|
rake (~> 13.0)
|
|
59
75
|
rspec (~> 3.0)
|
|
60
76
|
rubocop
|
|
61
77
|
uk-hs_code!
|
|
78
|
+
webmock (~> 3.0)
|
|
62
79
|
|
|
63
80
|
BUNDLED WITH
|
|
64
81
|
2.4.5
|
data/README.md
CHANGED
|
@@ -1,52 +1,92 @@
|
|
|
1
1
|
# Uk::HsCode
|
|
2
2
|
|
|
3
|
-
uk-hs_code enables
|
|
3
|
+
`uk-hs_code` enables communication with the UK Trade Tariff Public API v2 (https://www.trade-tariff.service.gov.uk/api/v2).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
The repository is here: https://github.com/enigmatt-pl/uk-hs_code.git
|
|
8
|
-
|
|
9
7
|
Add to your Gemfile:
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'uk-hs_code'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
## Configuration
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
Configure the gem during application startup (e.g., `config/initializers/uk_hs_code.rb`):
|
|
16
22
|
|
|
23
|
+
```ruby
|
|
24
|
+
Uk::HsCode.configure do |config|
|
|
25
|
+
# Target UK tariff (:uk, default) or Northern Ireland tariff (:xi)
|
|
26
|
+
config.service = :uk
|
|
27
|
+
|
|
28
|
+
# Optional: Network timeouts in seconds (default: 10)
|
|
29
|
+
config.timeout = 15
|
|
30
|
+
end
|
|
31
|
+
```
|
|
17
32
|
|
|
18
33
|
## Usage
|
|
19
34
|
|
|
35
|
+
### Searching by Commodity Description
|
|
36
|
+
|
|
20
37
|
```ruby
|
|
21
|
-
#
|
|
38
|
+
# Free-text commodity search
|
|
39
|
+
result = Uk::HsCode.search('cotton t-shirt')
|
|
22
40
|
|
|
23
|
-
|
|
41
|
+
# Result acts as an Enumerable Array of records
|
|
42
|
+
result.first.code
|
|
43
|
+
# => "6109100010"
|
|
24
44
|
|
|
25
|
-
|
|
45
|
+
result.first.title
|
|
46
|
+
# => "T-shirts"
|
|
26
47
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
#<Uk::HsCode::Record id="11205", type="search_reference", attributes=#<Uk::HsCode::Record title="tablecloths, knitted or crocheted", referenced_id="6302", referenced_class="Heading", goods_nomenclature_item_id="6302000000", productline_suffix="80", goods_nomenclature_sid=43645>>,
|
|
30
|
-
#<Uk::HsCode::Record id="7289", type="search_reference", attributes=#<Uk::HsCode::Record title="tablecloths, of paper", referenced_id="4818300000-80", referenced_class="Commodity", goods_nomenclature_item_id="4818300000", productline_suffix="80", goods_nomenclature_sid=40686>>,
|
|
31
|
-
#<Uk::HsCode::Record id="11206", type="search_reference", attributes=#<Uk::HsCode::Record title="table cloths, other than knitted or crocheted", referenced_id="6302510000-10", referenced_class="Subheading", goods_nomenclature_item_id="6302510000", productline_suffix="10", goods_nomenclature_sid=43679>>
|
|
48
|
+
result.first.declarable
|
|
49
|
+
# => true
|
|
32
50
|
|
|
33
|
-
#
|
|
51
|
+
# Structured access to categorized match groups
|
|
52
|
+
result.commodities.each do |commodity|
|
|
53
|
+
puts "#{commodity.code}: #{commodity.description} (declarable: #{commodity.declarable})"
|
|
54
|
+
end
|
|
34
55
|
|
|
35
|
-
|
|
56
|
+
result.references.each do |reference|
|
|
57
|
+
puts "#{reference.code}: #{reference.title}"
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Searching by Exact Commodity Code
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
result = Uk::HsCode.search('6109100010')
|
|
65
|
+
|
|
66
|
+
result.first.code
|
|
67
|
+
# => "6109100010"
|
|
36
68
|
|
|
37
|
-
|
|
69
|
+
result.first.type
|
|
70
|
+
# => "exact"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Search References (Alphabetical Index Terms)
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
records = Uk::HsCode.search_references('table')
|
|
38
77
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
#<Uk::HsCode::Record code="5402000000", title="carpet yarn, of man-made filaments">]
|
|
78
|
+
records.first.code
|
|
79
|
+
# => "9504000000"
|
|
42
80
|
|
|
81
|
+
records.first.title
|
|
82
|
+
# => "table, billiard and bagatelle"
|
|
43
83
|
```
|
|
44
84
|
|
|
45
85
|
## Development
|
|
46
86
|
|
|
47
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake
|
|
87
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake` to run tests and RuboCop. You can also run `bin/console` for an interactive prompt.
|
|
48
88
|
|
|
49
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release
|
|
89
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`.
|
|
50
90
|
|
|
51
91
|
## Contributing
|
|
52
92
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Uk
|
|
8
|
+
module HsCode
|
|
9
|
+
class Client
|
|
10
|
+
def initialize(configuration: Uk::HsCode.configuration, base_url: nil)
|
|
11
|
+
@configuration = configuration
|
|
12
|
+
@base_url = base_url || @configuration.base_url
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get(path, params = {})
|
|
16
|
+
uri = build_uri(path, params)
|
|
17
|
+
request = Net::HTTP::Get.new(uri)
|
|
18
|
+
apply_headers(request)
|
|
19
|
+
|
|
20
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
21
|
+
http.use_ssl = true if uri.scheme == "https"
|
|
22
|
+
http.open_timeout = @configuration.timeout
|
|
23
|
+
http.read_timeout = @configuration.timeout
|
|
24
|
+
|
|
25
|
+
response = http.request(request)
|
|
26
|
+
handle_response(response)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def apply_headers(request)
|
|
32
|
+
request["User-Agent"] = @configuration.user_agent
|
|
33
|
+
request["Accept"] = "application/json"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_uri(path, params)
|
|
37
|
+
full_url = "#{@base_url}#{path}"
|
|
38
|
+
uri = URI.parse(full_url)
|
|
39
|
+
if params && !params.empty?
|
|
40
|
+
query_str = URI.encode_www_form(params)
|
|
41
|
+
uri.query = uri.query ? "#{uri.query}&#{query_str}" : query_str
|
|
42
|
+
end
|
|
43
|
+
uri
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def handle_response(response)
|
|
47
|
+
raise Uk::HsCode::Error, "Request failed with status: #{response.code} - #{response.message}" unless response.is_a?(Net::HTTPSuccess)
|
|
48
|
+
|
|
49
|
+
JSON.parse(response.body)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uk
|
|
4
|
+
module HsCode
|
|
5
|
+
class Configuration
|
|
6
|
+
SERVICE_URLS = {
|
|
7
|
+
uk: "https://www.trade-tariff.service.gov.uk/api/v2",
|
|
8
|
+
xi: "https://www.trade-tariff.service.gov.uk/xi/api/v2"
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
DEFAULT_TIMEOUT = 10
|
|
12
|
+
|
|
13
|
+
attr_accessor :timeout, :user_agent
|
|
14
|
+
attr_writer :base_url
|
|
15
|
+
attr_reader :service
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
@service = :uk
|
|
19
|
+
@base_url = nil
|
|
20
|
+
@timeout = DEFAULT_TIMEOUT
|
|
21
|
+
@user_agent = "UK HS Code gem/#{Uk::HsCode::VERSION}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def service=(val)
|
|
25
|
+
symbolized = val.to_s.downcase.to_sym
|
|
26
|
+
raise ArgumentError, "Unsupported service: #{val}. Use :uk or :xi." unless SERVICE_URLS.key?(symbolized)
|
|
27
|
+
|
|
28
|
+
@service = symbolized
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def base_url
|
|
32
|
+
@base_url || SERVICE_URLS.fetch(@service)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uk
|
|
4
|
+
module HsCode
|
|
5
|
+
module Entities
|
|
6
|
+
class Commodity
|
|
7
|
+
attr_reader :code, :description, :score, :declarable, :producline_suffix,
|
|
8
|
+
:number_indents, :chapter, :heading, :section, :ancestor_descriptions
|
|
9
|
+
|
|
10
|
+
def initialize(code:, description:, score: nil, declarable: true, producline_suffix: "80",
|
|
11
|
+
number_indents: 0, chapter: nil, heading: nil, section: nil, ancestor_descriptions: [])
|
|
12
|
+
@code = code
|
|
13
|
+
@description = description
|
|
14
|
+
@score = score
|
|
15
|
+
@declarable = declarable
|
|
16
|
+
@producline_suffix = producline_suffix
|
|
17
|
+
@number_indents = number_indents
|
|
18
|
+
@chapter = chapter
|
|
19
|
+
@heading = heading
|
|
20
|
+
@section = section
|
|
21
|
+
@ancestor_descriptions = ancestor_descriptions || []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def title
|
|
25
|
+
@description
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def goods_nomenclature_item_id
|
|
29
|
+
@code
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def type
|
|
33
|
+
"commodity"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_h
|
|
37
|
+
{
|
|
38
|
+
code: @code,
|
|
39
|
+
description: @description,
|
|
40
|
+
title: title,
|
|
41
|
+
score: @score,
|
|
42
|
+
declarable: @declarable,
|
|
43
|
+
producline_suffix: @producline_suffix,
|
|
44
|
+
number_indents: @number_indents,
|
|
45
|
+
chapter: @chapter,
|
|
46
|
+
heading: @heading,
|
|
47
|
+
section: @section,
|
|
48
|
+
ancestor_descriptions: @ancestor_descriptions,
|
|
49
|
+
type: type
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def ==(other)
|
|
54
|
+
other.is_a?(Commodity) && code == other.code && description == other.description
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uk
|
|
4
|
+
module HsCode
|
|
5
|
+
module Entities
|
|
6
|
+
class ExactMatch
|
|
7
|
+
attr_reader :code, :endpoint, :description
|
|
8
|
+
|
|
9
|
+
def initialize(code:, endpoint: "commodities", description: nil)
|
|
10
|
+
@code = code
|
|
11
|
+
@endpoint = endpoint
|
|
12
|
+
@description = description || "Exact match for #{code}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def title
|
|
16
|
+
@description
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def goods_nomenclature_item_id
|
|
20
|
+
@code
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def declarable
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def score
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def type
|
|
32
|
+
"exact"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_h
|
|
36
|
+
{
|
|
37
|
+
code: @code,
|
|
38
|
+
endpoint: @endpoint,
|
|
39
|
+
title: title,
|
|
40
|
+
description: @description,
|
|
41
|
+
type: type
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ==(other)
|
|
46
|
+
other.is_a?(ExactMatch) && code == other.code && endpoint == other.endpoint
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uk
|
|
4
|
+
module HsCode
|
|
5
|
+
module Entities
|
|
6
|
+
class SearchReference
|
|
7
|
+
attr_reader :title, :code, :reference_class, :description, :score, :declarable
|
|
8
|
+
|
|
9
|
+
def initialize(title:, code:, reference_class: "Commodity", description: nil, score: nil, declarable: true)
|
|
10
|
+
@title = title
|
|
11
|
+
@code = code
|
|
12
|
+
@reference_class = reference_class
|
|
13
|
+
@description = description || title
|
|
14
|
+
@score = score
|
|
15
|
+
@declarable = declarable
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def goods_nomenclature_item_id
|
|
19
|
+
@code
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def type
|
|
23
|
+
"reference"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_h
|
|
27
|
+
{
|
|
28
|
+
title: @title,
|
|
29
|
+
code: @code,
|
|
30
|
+
reference_class: @reference_class,
|
|
31
|
+
description: @description,
|
|
32
|
+
score: @score,
|
|
33
|
+
declarable: @declarable,
|
|
34
|
+
type: type
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ==(other)
|
|
39
|
+
other.is_a?(SearchReference) && code == other.code && title == other.title
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uk
|
|
4
|
+
module HsCode
|
|
5
|
+
module Entities
|
|
6
|
+
class SearchResult < Array
|
|
7
|
+
attr_reader :query, :type, :commodities, :references, :exact_match
|
|
8
|
+
|
|
9
|
+
def initialize(records = [], query: nil, type: "null_match", commodities: [], references: [], exact_match: nil)
|
|
10
|
+
@query = query
|
|
11
|
+
@type = type
|
|
12
|
+
@commodities = commodities || []
|
|
13
|
+
@references = references || []
|
|
14
|
+
@exact_match = exact_match
|
|
15
|
+
super(records)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uk
|
|
4
|
+
module HsCode
|
|
5
|
+
module Parsers
|
|
6
|
+
class SearchResponseParser
|
|
7
|
+
def self.call(response_hash, query: nil)
|
|
8
|
+
new(response_hash, query: query).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(response_hash, query: nil)
|
|
12
|
+
@response_hash = response_hash || {}
|
|
13
|
+
@query = query
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
data = @response_hash["data"] || @response_hash[:data]
|
|
18
|
+
return Entities::SearchResult.new([], query: @query) unless data
|
|
19
|
+
|
|
20
|
+
type = data["type"] || data[:type]
|
|
21
|
+
attributes = data["attributes"] || data[:attributes] || {}
|
|
22
|
+
|
|
23
|
+
case type
|
|
24
|
+
when "exact_search"
|
|
25
|
+
parse_exact_search(attributes)
|
|
26
|
+
when "fuzzy_search"
|
|
27
|
+
parse_fuzzy_search(attributes)
|
|
28
|
+
else
|
|
29
|
+
Entities::SearchResult.new([], query: @query, type: type.to_s)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def parse_exact_search(attributes)
|
|
36
|
+
entry = attributes["entry"] || attributes[:entry] || {}
|
|
37
|
+
code = entry["id"] || entry[:id]
|
|
38
|
+
endpoint = entry["endpoint"] || entry[:endpoint] || "commodities"
|
|
39
|
+
return Entities::SearchResult.new([], query: @query, type: "exact_match") unless code
|
|
40
|
+
|
|
41
|
+
exact = Entities::ExactMatch.new(code: code, endpoint: endpoint)
|
|
42
|
+
Entities::SearchResult.new([exact], query: @query, type: "exact_match", exact_match: exact)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parse_fuzzy_search(attributes)
|
|
46
|
+
commodities = extract_commodities(attributes)
|
|
47
|
+
references = extract_references(attributes)
|
|
48
|
+
records = (commodities + references).uniq(&:code)
|
|
49
|
+
|
|
50
|
+
Entities::SearchResult.new(
|
|
51
|
+
records,
|
|
52
|
+
query: @query,
|
|
53
|
+
type: "fuzzy_match",
|
|
54
|
+
commodities: commodities,
|
|
55
|
+
references: references
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def extract_commodities(attributes)
|
|
60
|
+
commodities = []
|
|
61
|
+
gn_commodities = attributes.dig("goods_nomenclature_match", "commodities") || []
|
|
62
|
+
|
|
63
|
+
gn_commodities.each do |hit|
|
|
64
|
+
source = hit["_source"] || {}
|
|
65
|
+
code = source["goods_nomenclature_item_id"]
|
|
66
|
+
next if code.nil?
|
|
67
|
+
|
|
68
|
+
commodities << Entities::Commodity.new(
|
|
69
|
+
code: code,
|
|
70
|
+
description: source["description"],
|
|
71
|
+
score: hit["_score"],
|
|
72
|
+
declarable: source["declarable"],
|
|
73
|
+
producline_suffix: source["producline_suffix"],
|
|
74
|
+
number_indents: source["number_indents"],
|
|
75
|
+
chapter: source["chapter"],
|
|
76
|
+
heading: source["heading"],
|
|
77
|
+
section: source["section"],
|
|
78
|
+
ancestor_descriptions: source["ancestor_descriptions"]
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
commodities
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def extract_references(attributes)
|
|
86
|
+
references = []
|
|
87
|
+
ref_commodities = attributes.dig("reference_match", "commodities") || []
|
|
88
|
+
|
|
89
|
+
ref_commodities.each do |hit|
|
|
90
|
+
source = hit["_source"] || {}
|
|
91
|
+
ref_data = source["reference"] || {}
|
|
92
|
+
code = ref_data["goods_nomenclature_item_id"]
|
|
93
|
+
next if code.nil?
|
|
94
|
+
|
|
95
|
+
references << Entities::SearchReference.new(
|
|
96
|
+
code: code,
|
|
97
|
+
title: source["title"],
|
|
98
|
+
description: ref_data["description"] || source["title"],
|
|
99
|
+
score: hit["_score"],
|
|
100
|
+
declarable: ref_data["declarable"],
|
|
101
|
+
reference_class: source["reference_class"] || "Commodity"
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
references
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/uk/hs_code/record.rb
CHANGED
|
@@ -2,6 +2,79 @@
|
|
|
2
2
|
|
|
3
3
|
module Uk
|
|
4
4
|
module HsCode
|
|
5
|
-
class Record
|
|
5
|
+
class Record
|
|
6
|
+
attr_reader :score, :declarable, :type
|
|
7
|
+
|
|
8
|
+
def initialize(code: nil, title: nil, description: nil, score: nil, declarable: nil, type: nil, **extra)
|
|
9
|
+
@extra = extra || {}
|
|
10
|
+
@code = code || @extra[:goods_nomenclature_item_id] || @extra["goods_nomenclature_item_id"]
|
|
11
|
+
@title = title || description || @extra[:negated_title] || @extra["negated_title"]
|
|
12
|
+
@description = description || title
|
|
13
|
+
@score = score
|
|
14
|
+
@declarable = declarable
|
|
15
|
+
@type = type
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def code
|
|
19
|
+
@code || self["code"] || self["goods_nomenclature_item_id"]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def title
|
|
23
|
+
@title || self["title"] || self["description"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def description
|
|
27
|
+
@description || self["description"] || self["title"]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def goods_nomenclature_item_id
|
|
31
|
+
code
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def attributes
|
|
35
|
+
self["attributes"] || self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def [](key)
|
|
39
|
+
instance_variable_get("@#{key}") || @extra[key.to_s] || @extra[key.to_sym]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def []=(key, val)
|
|
43
|
+
instance_variable_set("@#{key}", val)
|
|
44
|
+
@extra[key.to_s] = val
|
|
45
|
+
@extra[key.to_sym] = val
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def method_missing(method_name, *args, &block)
|
|
49
|
+
str_key = method_name.to_s
|
|
50
|
+
if str_key.end_with?("=")
|
|
51
|
+
self[str_key.chop] = args.first
|
|
52
|
+
elsif @extra.key?(str_key) || @extra.key?(method_name)
|
|
53
|
+
self[method_name]
|
|
54
|
+
else
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
60
|
+
str_key = method_name.to_s.chomp("=")
|
|
61
|
+
@extra.key?(str_key) || @extra.key?(str_key.to_sym) || super
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_h
|
|
65
|
+
{
|
|
66
|
+
code: code,
|
|
67
|
+
title: title,
|
|
68
|
+
description: description,
|
|
69
|
+
score: score,
|
|
70
|
+
declarable: declarable,
|
|
71
|
+
type: type
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ==(other)
|
|
76
|
+
other.is_a?(Record) && code == other.code && title == other.title
|
|
77
|
+
end
|
|
78
|
+
end
|
|
6
79
|
end
|
|
7
80
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uk
|
|
4
|
+
module HsCode
|
|
5
|
+
module Services
|
|
6
|
+
class SearchReferencesService
|
|
7
|
+
def self.call(...)
|
|
8
|
+
new(...).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(params = {}, client: Client.new, **keyword_params)
|
|
12
|
+
merged_params = keyword_params.empty? ? params : keyword_params
|
|
13
|
+
@params = merged_params.is_a?(Hash) ? merged_params : { query: merged_params }
|
|
14
|
+
@client = client
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
validate_prerequisites!
|
|
19
|
+
execute_workflow
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def validate_prerequisites!
|
|
25
|
+
raise ArgumentError, "Missing required query" if query.empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def execute_workflow
|
|
29
|
+
raw_response = @client.get("/search_references.json", { "query[letter]" => query })
|
|
30
|
+
data = raw_response["data"] || []
|
|
31
|
+
data.map do |item|
|
|
32
|
+
attrs = item["attributes"] || {}
|
|
33
|
+
Record.new(
|
|
34
|
+
code: attrs["goods_nomenclature_item_id"],
|
|
35
|
+
title: attrs["title"],
|
|
36
|
+
id: item["id"],
|
|
37
|
+
goods_nomenclature_sid: attrs["goods_nomenclature_sid"],
|
|
38
|
+
productline_suffix: attrs["productline_suffix"]
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def query
|
|
44
|
+
(@params[:query] || @params["query"] || @params[:letter] || @params["letter"]).to_s.strip
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uk
|
|
4
|
+
module HsCode
|
|
5
|
+
module Services
|
|
6
|
+
class SearchService
|
|
7
|
+
def self.call(...)
|
|
8
|
+
new(...).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(params = {}, client: Client.new, **keyword_params)
|
|
12
|
+
merged_params = keyword_params.empty? ? params : keyword_params
|
|
13
|
+
@params = merged_params.is_a?(Hash) ? merged_params : { query: merged_params }
|
|
14
|
+
@client = client
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
validate_prerequisites!
|
|
19
|
+
execute_workflow
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def validate_prerequisites!
|
|
25
|
+
raise ArgumentError, "Missing required query" if query.empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def execute_workflow
|
|
29
|
+
raw_response = @client.get("/search", { q: query })
|
|
30
|
+
Parsers::SearchResponseParser.call(raw_response, query: query)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def query
|
|
34
|
+
(@params[:query] || @params["query"] || @params[:q] || @params["q"]).to_s.strip
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/uk/hs_code/version.rb
CHANGED
data/lib/uk/hs_code.rb
CHANGED
|
@@ -1,29 +1,47 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "hs_code/version"
|
|
4
|
-
require_relative "hs_code/
|
|
4
|
+
require_relative "hs_code/configuration"
|
|
5
|
+
require_relative "hs_code/entities/commodity"
|
|
6
|
+
require_relative "hs_code/entities/search_reference"
|
|
7
|
+
require_relative "hs_code/entities/exact_match"
|
|
8
|
+
require_relative "hs_code/entities/search_result"
|
|
5
9
|
require_relative "hs_code/record"
|
|
10
|
+
require_relative "hs_code/client"
|
|
11
|
+
require_relative "hs_code/parsers/search_response_parser"
|
|
12
|
+
require_relative "hs_code/services/search_service"
|
|
13
|
+
require_relative "hs_code/services/search_references_service"
|
|
14
|
+
require_relative "hs_code/api_request"
|
|
6
15
|
|
|
7
16
|
module Uk
|
|
8
17
|
module HsCode
|
|
9
|
-
API_ENDPOINT = "https://www.trade-tariff.service.gov.uk/api"
|
|
18
|
+
API_ENDPOINT = "https://www.trade-tariff.service.gov.uk/api/v2"
|
|
10
19
|
|
|
11
20
|
class Error < StandardError; end
|
|
12
21
|
|
|
13
22
|
class << self
|
|
23
|
+
def configuration
|
|
24
|
+
@configuration ||= Configuration.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def configure
|
|
28
|
+
yield(configuration) if block_given?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def reset_configuration!
|
|
32
|
+
@configuration = Configuration.new
|
|
33
|
+
end
|
|
34
|
+
|
|
14
35
|
def search(query)
|
|
15
|
-
|
|
36
|
+
Services::SearchService.call(query: query)
|
|
16
37
|
end
|
|
17
38
|
|
|
18
39
|
def search_hs_codes(query)
|
|
19
|
-
|
|
20
|
-
result.map { |a| Record.new(code: a.attributes.goods_nomenclature_item_id, title: a.attributes.title) }
|
|
40
|
+
search(query)
|
|
21
41
|
end
|
|
22
42
|
|
|
23
|
-
private
|
|
24
|
-
|
|
25
43
|
def search_references(query)
|
|
26
|
-
|
|
44
|
+
Services::SearchReferencesService.call(query: query)
|
|
27
45
|
end
|
|
28
46
|
end
|
|
29
47
|
end
|
data/uk-hs_code.gemspec
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/uk/hs_code/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "uk-hs_code"
|
|
7
|
+
spec.version = Uk::HsCode::VERSION
|
|
8
|
+
spec.authors = ["Mateusz Michalski"]
|
|
9
|
+
spec.email = ["enigmatt.priv@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Enables communication with Trade Tariff Public API V2."
|
|
12
|
+
spec.description = "Trade Tariff Public API V2 communicator."
|
|
13
|
+
spec.homepage = "https://github.com/enigmatt-pl/uk-hs_code.git"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
16
|
+
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["changelog_uri"] = spec.homepage
|
|
20
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
spec.bindir = "exe"
|
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
30
|
+
spec.require_paths = ["lib"]
|
|
31
|
+
|
|
32
|
+
spec.add_development_dependency "base64"
|
|
33
|
+
spec.add_development_dependency "pry"
|
|
34
|
+
spec.add_development_dependency "racc", "~> 1.8"
|
|
35
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
37
|
+
spec.add_development_dependency "rubocop"
|
|
38
|
+
spec.add_development_dependency "webmock", "~> 3.0"
|
|
39
|
+
end
|
metadata
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: uk-hs_code
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mateusz Michalski
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
13
|
+
name: base64
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
@@ -25,7 +24,7 @@ dependencies:
|
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
25
|
version: '0'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
27
|
+
name: pry
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
30
|
- - ">="
|
|
@@ -38,6 +37,20 @@ dependencies:
|
|
|
38
37
|
- - ">="
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
39
|
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: racc
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.8'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.8'
|
|
41
54
|
- !ruby/object:Gem::Dependency
|
|
42
55
|
name: rake
|
|
43
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -66,6 +79,34 @@ dependencies:
|
|
|
66
79
|
- - "~>"
|
|
67
80
|
- !ruby/object:Gem::Version
|
|
68
81
|
version: '3.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rubocop
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: webmock
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '3.0'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '3.0'
|
|
69
110
|
description: Trade Tariff Public API V2 communicator.
|
|
70
111
|
email:
|
|
71
112
|
- enigmatt.priv@gmail.com
|
|
@@ -83,9 +124,19 @@ files:
|
|
|
83
124
|
- Rakefile
|
|
84
125
|
- lib/uk/hs_code.rb
|
|
85
126
|
- lib/uk/hs_code/api_request.rb
|
|
127
|
+
- lib/uk/hs_code/client.rb
|
|
128
|
+
- lib/uk/hs_code/configuration.rb
|
|
129
|
+
- lib/uk/hs_code/entities/commodity.rb
|
|
130
|
+
- lib/uk/hs_code/entities/exact_match.rb
|
|
131
|
+
- lib/uk/hs_code/entities/search_reference.rb
|
|
132
|
+
- lib/uk/hs_code/entities/search_result.rb
|
|
133
|
+
- lib/uk/hs_code/parsers/search_response_parser.rb
|
|
86
134
|
- lib/uk/hs_code/record.rb
|
|
135
|
+
- lib/uk/hs_code/services/search_references_service.rb
|
|
136
|
+
- lib/uk/hs_code/services/search_service.rb
|
|
87
137
|
- lib/uk/hs_code/version.rb
|
|
88
138
|
- sig/uk/hs_code.rbs
|
|
139
|
+
- uk-hs_code.gemspec
|
|
89
140
|
homepage: https://github.com/enigmatt-pl/uk-hs_code.git
|
|
90
141
|
licenses:
|
|
91
142
|
- MIT
|
|
@@ -93,7 +144,6 @@ metadata:
|
|
|
93
144
|
homepage_uri: https://github.com/enigmatt-pl/uk-hs_code.git
|
|
94
145
|
source_code_uri: https://github.com/enigmatt-pl/uk-hs_code.git
|
|
95
146
|
changelog_uri: https://github.com/enigmatt-pl/uk-hs_code.git
|
|
96
|
-
post_install_message:
|
|
97
147
|
rdoc_options: []
|
|
98
148
|
require_paths:
|
|
99
149
|
- lib
|
|
@@ -101,15 +151,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
101
151
|
requirements:
|
|
102
152
|
- - ">="
|
|
103
153
|
- !ruby/object:Gem::Version
|
|
104
|
-
version:
|
|
154
|
+
version: 3.0.0
|
|
105
155
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
156
|
requirements:
|
|
107
157
|
- - ">="
|
|
108
158
|
- !ruby/object:Gem::Version
|
|
109
159
|
version: '0'
|
|
110
160
|
requirements: []
|
|
111
|
-
rubygems_version: 3.
|
|
112
|
-
signing_key:
|
|
161
|
+
rubygems_version: 3.6.7
|
|
113
162
|
specification_version: 4
|
|
114
163
|
summary: Enables communication with Trade Tariff Public API V2.
|
|
115
164
|
test_files: []
|