atlas_engine 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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