atlas_engine 0.1.2 → 0.2.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +497 -33
  3. data/app/countries/atlas_engine/be/country_profile.yml +2 -0
  4. data/app/countries/atlas_engine/be/validation_transcriber/address_parser.rb +84 -0
  5. data/app/countries/atlas_engine/bm/address_importer/corrections/open_address/city_alias_corrector.rb +12 -11
  6. data/app/countries/atlas_engine/bm/synonyms.yml +6 -0
  7. data/app/countries/atlas_engine/ch/country_profile.yml +2 -0
  8. data/app/countries/atlas_engine/ch/locales/de/country_profile.yml +0 -1
  9. data/app/countries/atlas_engine/ch/locales/fr/country_profile.yml +3 -0
  10. data/app/countries/atlas_engine/ch/locales/fr/validation_transcriber/address_parser.rb +29 -0
  11. data/app/countries/atlas_engine/cz/address_validation/es/query_builder.rb +43 -0
  12. data/app/countries/atlas_engine/cz/country_profile.yml +2 -1
  13. data/app/countries/atlas_engine/cz/validation_transcriber/address_parser.rb +26 -0
  14. data/app/countries/atlas_engine/it/address_importer/open_address/mapper.rb +1 -1
  15. data/app/countries/atlas_engine/it/country_profile.yml +1 -0
  16. data/app/countries/atlas_engine/sa/country_profile.yml +4 -1
  17. data/app/countries/atlas_engine/us/country_profile.yml +0 -2
  18. data/app/lib/atlas_engine/validation_transcriber/address_parsings.rb +1 -1
  19. data/app/lib/atlas_engine/validation_transcriber/formatter.rb +2 -2
  20. data/app/models/atlas_engine/address_validation/datastore_base.rb +3 -0
  21. data/app/models/atlas_engine/address_validation/es/datastore.rb +11 -6
  22. data/app/models/atlas_engine/address_validation/es/query_builder.rb +40 -29
  23. data/app/models/atlas_engine/address_validation/es/validators/full_address.rb +1 -1
  24. data/app/models/atlas_engine/address_validation/log_emitter.rb +1 -0
  25. data/app/models/atlas_engine/address_validation/normalizer.rb +0 -9
  26. data/app/models/atlas_engine/address_validation/validators/full_address/address_comparison.rb +7 -23
  27. data/app/models/atlas_engine/address_validation/validators/full_address/candidate_result.rb +42 -16
  28. data/app/models/atlas_engine/address_validation/validators/full_address/comparison_helper.rb +109 -109
  29. data/app/models/atlas_engine/address_validation/validators/full_address/{components_to_validate.rb → relevant_components.rb} +26 -18
  30. data/app/models/atlas_engine/country_profile_validation_subset.rb +5 -0
  31. data/app/tasks/maintenance/atlas_engine/elasticsearch_index_create_task.rb +1 -1
  32. data/db/data/country_profiles/default.yml +0 -2
  33. data/lib/atlas_engine/version.rb +1 -1
  34. metadata +11 -5
@@ -6,128 +6,128 @@ module AtlasEngine
6
6
  module Validators
7
7
  module FullAddress
8
8
  class ComparisonHelper
9
- class << self
10
- extend T::Sig
11
-
12
- sig do
13
- params(
14
- datastore: DatastoreBase,
15
- candidate: Candidate,
16
- ).returns(T.nilable(Token::Sequence::Comparison))
17
- end
18
- def street_comparison(datastore:, candidate:)
19
- street_sequences = datastore.fetch_street_sequences
20
- candidate_sequences = T.must(candidate.component(:street)).sequences
21
-
22
- street_sequences.map do |street_sequence|
23
- best_comparison(
24
- street_sequence,
25
- candidate_sequences,
26
- )
27
- end.min
28
- end
9
+ extend T::Sig
29
10
 
30
- sig do
31
- params(
32
- datastore: DatastoreBase,
33
- candidate: Candidate,
34
- ).returns(T.nilable(Token::Sequence::Comparison))
35
- end
36
- def city_comparison(datastore:, candidate:)
37
- best_comparison(
38
- datastore.fetch_city_sequence,
39
- T.must(candidate.component(:city)).sequences,
40
- )
41
- end
11
+ sig { params(address: AbstractAddress, candidate: Candidate, datastore: DatastoreBase).void }
12
+ def initialize(address:, candidate:, datastore:)
13
+ @address = address
14
+ @datastore = datastore
15
+ @candidate = candidate
16
+ end
42
17
 
43
- sig do
44
- params(
45
- address: AbstractAddress,
46
- candidate: Candidate,
47
- ).returns(T.nilable(Token::Sequence::Comparison))
48
- end
49
- def province_code_comparison(address:, candidate:)
50
- normalized_session_province_code = ValidationTranscriber::ProvinceCodeNormalizer.normalize(
51
- country_code: address.country_code,
52
- province_code: address.province_code,
53
- )
54
- normalized_candidate_province_code = ValidationTranscriber::ProvinceCodeNormalizer.normalize(
55
- country_code: T.must(candidate.component(:country_code)).value,
56
- province_code: T.must(candidate.component(:province_code)).value,
57
- )
18
+ sig { returns(T.nilable(Token::Sequence::Comparison)) }
19
+ def street_comparison
20
+ return @street_comparison if defined?(@street_comparison)
58
21
 
59
- best_comparison(
60
- Token::Sequence.from_string(normalized_session_province_code),
61
- [Token::Sequence.from_string(normalized_candidate_province_code)],
62
- )
63
- end
22
+ street_sequences = datastore.fetch_street_sequences
23
+ candidate_sequences = T.must(candidate.component(:street)).sequences
64
24
 
65
- sig do
66
- params(
67
- address: AbstractAddress,
68
- candidate: Candidate,
69
- ).returns(T.nilable(Token::Sequence::Comparison))
70
- end
71
- def zip_comparison(address:, candidate:)
72
- candidate.component(:zip)&.value = PostalCodeMatcher.new(
73
- T.must(address.country_code),
74
- T.must(address.zip),
75
- candidate.component(:zip)&.value,
76
- ).truncate
77
-
78
- normalized_zip = ValidationTranscriber::ZipNormalizer.normalize(
79
- country_code: address.country_code, zip: address.zip,
80
- )
81
- zip_sequence = Token::Sequence.from_string(normalized_zip)
25
+ @street_comparison = street_sequences.map do |street_sequence|
82
26
  best_comparison(
83
- zip_sequence,
84
- T.must(candidate.component(:zip)).sequences,
27
+ street_sequence,
28
+ candidate_sequences,
85
29
  )
86
- end
30
+ end.min
31
+ end
87
32
 
88
- sig do
89
- params(
90
- datastore: DatastoreBase,
91
- candidate: Candidate,
92
- ).returns(NumberComparison)
93
- end
94
- def building_comparison(datastore:, candidate:)
95
- NumberComparison.new(
96
- numbers: datastore.parsings.potential_building_numbers,
97
- candidate_ranges: building_ranges_from_candidate(candidate),
98
- )
99
- end
33
+ sig { returns(T.nilable(Token::Sequence::Comparison)) }
34
+ def city_comparison
35
+ return @city_comparison if defined?(@city_comparison)
100
36
 
101
- private
37
+ @city_comparison = best_comparison(
38
+ datastore.fetch_city_sequence,
39
+ T.must(candidate.component(:city)).sequences,
40
+ )
41
+ end
102
42
 
103
- sig do
104
- params(
105
- sequence: Token::Sequence,
106
- component_sequences: T::Array[Token::Sequence],
107
- ).returns(T.nilable(Token::Sequence::Comparison))
108
- end
109
- def best_comparison(sequence, component_sequences)
110
- component_sequences.map do |component_sequence|
111
- Token::Sequence::Comparator.new(
112
- left_sequence: sequence,
113
- right_sequence: component_sequence,
114
- ).compare
115
- end.min_by.with_index do |comparison, index|
116
- # ruby's `min` and `sort` methods are not stable
117
- # so we need to prefer the leftmost comparison when two comparisons are equivalent
118
- [comparison, index]
119
- end
120
- end
43
+ sig { returns(T.nilable(Token::Sequence::Comparison)) }
44
+ def province_code_comparison
45
+ return @province_code_comparison if defined?(@province_code_comparison)
46
+
47
+ normalized_session_province_code = ValidationTranscriber::ProvinceCodeNormalizer.normalize(
48
+ country_code: address.country_code,
49
+ province_code: address.province_code,
50
+ )
51
+ normalized_candidate_province_code = ValidationTranscriber::ProvinceCodeNormalizer.normalize(
52
+ country_code: T.must(candidate.component(:country_code)).value,
53
+ province_code: T.must(candidate.component(:province_code)).value,
54
+ )
55
+
56
+ @province_code_comparison = best_comparison(
57
+ Token::Sequence.from_string(normalized_session_province_code),
58
+ [Token::Sequence.from_string(normalized_candidate_province_code)],
59
+ )
60
+ end
61
+
62
+ sig { returns(T.nilable(Token::Sequence::Comparison)) }
63
+ def zip_comparison
64
+ return @zip_comparison if defined?(@zip_comparison)
121
65
 
122
- sig { params(candidate: Candidate).returns(T::Array[AddressNumberRange]) }
123
- def building_ranges_from_candidate(candidate)
124
- building_and_unit_ranges = candidate.component(:building_and_unit_ranges)&.value
125
- return [] if building_and_unit_ranges.blank?
66
+ candidate.component(:zip)&.value = PostalCodeMatcher.new(
67
+ T.must(address.country_code),
68
+ T.must(address.zip),
69
+ candidate.component(:zip)&.value,
70
+ ).truncate
126
71
 
127
- building_ranges = JSON.parse(building_and_unit_ranges).keys
128
- building_ranges.map { |building_range| AddressNumberRange.new(range_string: building_range) }
72
+ normalized_zip = ValidationTranscriber::ZipNormalizer.normalize(
73
+ country_code: address.country_code, zip: address.zip,
74
+ )
75
+ zip_sequence = Token::Sequence.from_string(normalized_zip)
76
+ @zip_comparison = best_comparison(
77
+ zip_sequence,
78
+ T.must(candidate.component(:zip)).sequences,
79
+ )
80
+ end
81
+
82
+ sig { returns(NumberComparison) }
83
+ def building_comparison
84
+ @building_comparison ||= NumberComparison.new(
85
+ numbers: datastore.parsings.potential_building_numbers,
86
+ candidate_ranges: building_ranges_from_candidate(candidate),
87
+ )
88
+ end
89
+
90
+ private
91
+
92
+ sig { returns(AbstractAddress) }
93
+ attr_reader :address
94
+
95
+ sig { returns(DatastoreBase) }
96
+ attr_reader :datastore
97
+
98
+ sig { returns(Candidate) }
99
+ attr_reader :candidate
100
+
101
+ sig do
102
+ params(
103
+ sequence: Token::Sequence,
104
+ component_sequences: T::Array[Token::Sequence],
105
+ ).returns(T.nilable(Token::Sequence::Comparison))
106
+ end
107
+ def best_comparison(
108
+ sequence,
109
+ component_sequences
110
+ )
111
+ component_sequences.map do |component_sequence|
112
+ Token::Sequence::Comparator.new(
113
+ left_sequence: sequence,
114
+ right_sequence: component_sequence,
115
+ ).compare
116
+ end.min_by.with_index do |comparison, index|
117
+ # ruby's `min` and `sort` methods are not stable
118
+ # so we need to prefer the leftmost comparison when two comparisons are equivalent
119
+ [comparison, index]
129
120
  end
130
121
  end
122
+
123
+ sig { params(candidate: Candidate).returns(T::Array[AddressNumberRange]) }
124
+ def building_ranges_from_candidate(candidate)
125
+ building_and_unit_ranges = candidate.component(:building_and_unit_ranges)&.value
126
+ return [] if building_and_unit_ranges.blank?
127
+
128
+ building_ranges = JSON.parse(building_and_unit_ranges).keys
129
+ building_ranges.map { |building_range| AddressNumberRange.new(range_string: building_range) }
130
+ end
131
131
  end
132
132
  end
133
133
  end
@@ -5,7 +5,7 @@ module AtlasEngine
5
5
  module AddressValidation
6
6
  module Validators
7
7
  module FullAddress
8
- class ComponentsToValidate
8
+ class RelevantComponents
9
9
  extend T::Sig
10
10
 
11
11
  attr_reader :session, :candidate, :street_comparison
@@ -31,29 +31,35 @@ module AtlasEngine
31
31
  end
32
32
 
33
33
  sig { returns(T::Array[Symbol]) }
34
- def run
34
+ def components_to_validate
35
35
  supported_components = ALL_SUPPORTED_COMPONENTS.dup - unsupported_components_for_country
36
36
  supported_components.delete(:street) if exclude_street_validation?
37
37
  supported_components
38
38
  end
39
39
 
40
+ sig { returns(T::Array[Symbol]) }
41
+ def components_to_compare
42
+ ALL_SUPPORTED_COMPONENTS.dup - unsupported_components_for_country
43
+ end
44
+
40
45
  private
41
46
 
42
47
  sig { returns(T::Boolean) }
43
48
  def exclude_street_validation?
44
- return true unless session.matching_strategy == AddressValidation::MatchingStrategies::EsStreet
49
+ return @exclude_street_validation if defined?(@exclude_street_validation)
45
50
 
46
- if street_comparison.blank?
51
+ @exclude_street_validation = if session.matching_strategy !=
52
+ AddressValidation::MatchingStrategies::EsStreet
53
+ true
54
+ elsif street_comparison.blank?
47
55
  emit_excluded_validation("street", "not_found")
48
- return true
49
- end
50
-
51
- if exclusions("street").any? { |exclusion| exclusion.apply?(session, candidate) }
56
+ true
57
+ elsif exclusions("street").any? { |exclusion| exclusion.apply?(session, candidate) }
52
58
  emit_excluded_validation("street", "excluded")
53
- return true
59
+ true
60
+ else
61
+ false
54
62
  end
55
-
56
- false
57
63
  end
58
64
 
59
65
  sig { params(component: String).returns(T::Array[T.class_of(Exclusions::ExclusionBase)]) }
@@ -73,13 +79,15 @@ module AtlasEngine
73
79
 
74
80
  sig { returns(T::Array[Symbol]) }
75
81
  def unsupported_components_for_country
76
- unsupported_components = []
77
- country = Worldwide.region(code: session.address.country_code)
78
- unsupported_components << :province_code if country.province_optional?
79
- unsupported_components << :province_code if country.hide_provinces_from_addresses
80
- unsupported_components << :city unless country.city_required?
81
- unsupported_components << :zip unless country.zip_required? && !country.zip_autofill_enabled
82
- unsupported_components.uniq
82
+ @unsupported_components_for_country ||= begin
83
+ unsupported_components = []
84
+ country = Worldwide.region(code: session.address.country_code)
85
+ unsupported_components << :province_code if country.province_optional?
86
+ unsupported_components << :province_code if country.hide_provinces_from_addresses
87
+ unsupported_components << :city unless country.city_required?
88
+ unsupported_components << :zip unless country.zip_required? && !country.zip_autofill_enabled
89
+ unsupported_components.uniq
90
+ end
83
91
  end
84
92
  end
85
93
  end
@@ -44,5 +44,10 @@ module AtlasEngine
44
44
  def address_parser
45
45
  attributes.dig("address_parser").constantize
46
46
  end
47
+
48
+ sig { returns(T::Array[String]) }
49
+ def normalized_components
50
+ attributes.dig("normalized_components") || []
51
+ end
47
52
  end
48
53
  end
@@ -36,7 +36,7 @@ module Maintenance
36
36
  record_count = ::AtlasEngine::PostAddress.where(address_conditions).size
37
37
  raise "No records to process for country code: #{country_code}" if record_count.zero?
38
38
 
39
- repository.create_next_index(ensure_clean: true)
39
+ repository.create_next_index(ensure_clean: true, raise_errors: true)
40
40
 
41
41
  ::AtlasEngine::PostAddress.where(address_conditions).in_batches(of: batch_size)
42
42
  end
@@ -11,8 +11,6 @@ validation:
11
11
  has_provinces: true
12
12
  index_locales: []
13
13
  default_matching_strategy: local
14
- city_fields:
15
- - city_aliases
16
14
  normalized_components: []
17
15
  exclusions: {}
18
16
  partial_postal_code_range_for_length: {}
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module AtlasEngine
5
- VERSION = "0.1.2"
5
+ VERSION = "0.2.0"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atlas_engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-18 00:00:00.000000000 Z
11
+ date: 2024-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: annex_29
@@ -212,14 +212,14 @@ dependencies:
212
212
  requirements:
213
213
  - - ">="
214
214
  - !ruby/object:Gem::Version
215
- version: '0'
215
+ version: 0.6.4
216
216
  type: :runtime
217
217
  prerelease: false
218
218
  version_requirements: !ruby/object:Gem::Requirement
219
219
  requirements:
220
220
  - - ">="
221
221
  - !ruby/object:Gem::Version
222
- version: '0'
222
+ version: 0.6.4
223
223
  description: The Atlas Engine is a rails engine that provides a GraphQL API for address
224
224
  validation.
225
225
  email: developers@shopify.com
@@ -250,9 +250,11 @@ files:
250
250
  - app/countries/atlas_engine/au/synonyms.yml
251
251
  - app/countries/atlas_engine/au/validation_transcriber/address_parser.rb
252
252
  - app/countries/atlas_engine/be/country_profile.yml
253
+ - app/countries/atlas_engine/be/validation_transcriber/address_parser.rb
253
254
  - app/countries/atlas_engine/bm/address_importer/corrections/open_address/city_alias_corrector.rb
254
255
  - app/countries/atlas_engine/bm/address_importer/open_address/mapper.rb
255
256
  - app/countries/atlas_engine/bm/country_profile.yml
257
+ - app/countries/atlas_engine/bm/synonyms.yml
256
258
  - app/countries/atlas_engine/br/country_profile.yml
257
259
  - app/countries/atlas_engine/ca/country_profile.yml
258
260
  - app/countries/atlas_engine/ca/synonyms.yml
@@ -263,8 +265,12 @@ files:
263
265
  - app/countries/atlas_engine/ch/locales/de/country_profile.yml
264
266
  - app/countries/atlas_engine/ch/locales/de/index_configuration.yml
265
267
  - app/countries/atlas_engine/ch/locales/de/synonyms.yml
268
+ - app/countries/atlas_engine/ch/locales/fr/country_profile.yml
266
269
  - app/countries/atlas_engine/ch/locales/fr/synonyms.yml
270
+ - app/countries/atlas_engine/ch/locales/fr/validation_transcriber/address_parser.rb
271
+ - app/countries/atlas_engine/cz/address_validation/es/query_builder.rb
267
272
  - app/countries/atlas_engine/cz/country_profile.yml
273
+ - app/countries/atlas_engine/cz/validation_transcriber/address_parser.rb
268
274
  - app/countries/atlas_engine/de/country_profile.yml
269
275
  - app/countries/atlas_engine/de/index_configuration.yml
270
276
  - app/countries/atlas_engine/de/synonyms.yml
@@ -437,7 +443,6 @@ files:
437
443
  - app/models/atlas_engine/address_validation/validators/full_address/candidate_result.rb
438
444
  - app/models/atlas_engine/address_validation/validators/full_address/candidate_result_base.rb
439
445
  - app/models/atlas_engine/address_validation/validators/full_address/comparison_helper.rb
440
- - app/models/atlas_engine/address_validation/validators/full_address/components_to_validate.rb
441
446
  - app/models/atlas_engine/address_validation/validators/full_address/concern_builder.rb
442
447
  - app/models/atlas_engine/address_validation/validators/full_address/exclusions/exclusion_base.rb
443
448
  - app/models/atlas_engine/address_validation/validators/full_address/invalid_zip_concern_builder.rb
@@ -446,6 +451,7 @@ files:
446
451
  - app/models/atlas_engine/address_validation/validators/full_address/no_candidate_result.rb
447
452
  - app/models/atlas_engine/address_validation/validators/full_address/number_comparison.rb
448
453
  - app/models/atlas_engine/address_validation/validators/full_address/postal_code_matcher.rb
454
+ - app/models/atlas_engine/address_validation/validators/full_address/relevant_components.rb
449
455
  - app/models/atlas_engine/address_validation/validators/full_address/result_updater.rb
450
456
  - app/models/atlas_engine/address_validation/validators/full_address/suggestion_builder.rb
451
457
  - app/models/atlas_engine/address_validation/validators/full_address/unknown_address_concern.rb