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.
- checksums.yaml +4 -4
- data/README.md +497 -33
- data/app/countries/atlas_engine/be/country_profile.yml +2 -0
- data/app/countries/atlas_engine/be/validation_transcriber/address_parser.rb +84 -0
- data/app/countries/atlas_engine/bm/address_importer/corrections/open_address/city_alias_corrector.rb +12 -11
- data/app/countries/atlas_engine/bm/synonyms.yml +6 -0
- data/app/countries/atlas_engine/ch/country_profile.yml +2 -0
- data/app/countries/atlas_engine/ch/locales/de/country_profile.yml +0 -1
- data/app/countries/atlas_engine/ch/locales/fr/country_profile.yml +3 -0
- data/app/countries/atlas_engine/ch/locales/fr/validation_transcriber/address_parser.rb +29 -0
- data/app/countries/atlas_engine/cz/address_validation/es/query_builder.rb +43 -0
- data/app/countries/atlas_engine/cz/country_profile.yml +2 -1
- data/app/countries/atlas_engine/cz/validation_transcriber/address_parser.rb +26 -0
- data/app/countries/atlas_engine/it/address_importer/open_address/mapper.rb +1 -1
- data/app/countries/atlas_engine/it/country_profile.yml +1 -0
- data/app/countries/atlas_engine/sa/country_profile.yml +4 -1
- data/app/countries/atlas_engine/us/country_profile.yml +0 -2
- data/app/lib/atlas_engine/validation_transcriber/address_parsings.rb +1 -1
- data/app/lib/atlas_engine/validation_transcriber/formatter.rb +2 -2
- data/app/models/atlas_engine/address_validation/datastore_base.rb +3 -0
- data/app/models/atlas_engine/address_validation/es/datastore.rb +11 -6
- data/app/models/atlas_engine/address_validation/es/query_builder.rb +40 -29
- data/app/models/atlas_engine/address_validation/es/validators/full_address.rb +1 -1
- data/app/models/atlas_engine/address_validation/log_emitter.rb +1 -0
- data/app/models/atlas_engine/address_validation/normalizer.rb +0 -9
- data/app/models/atlas_engine/address_validation/validators/full_address/address_comparison.rb +7 -23
- data/app/models/atlas_engine/address_validation/validators/full_address/candidate_result.rb +42 -16
- data/app/models/atlas_engine/address_validation/validators/full_address/comparison_helper.rb +109 -109
- data/app/models/atlas_engine/address_validation/validators/full_address/{components_to_validate.rb → relevant_components.rb} +26 -18
- data/app/models/atlas_engine/country_profile_validation_subset.rb +5 -0
- data/app/tasks/maintenance/atlas_engine/elasticsearch_index_create_task.rb +1 -1
- data/db/data/country_profiles/default.yml +0 -2
- data/lib/atlas_engine/version.rb +1 -1
- metadata +11 -5
data/app/models/atlas_engine/address_validation/validators/full_address/comparison_helper.rb
CHANGED
@@ -6,128 +6,128 @@ module AtlasEngine
|
|
6
6
|
module Validators
|
7
7
|
module FullAddress
|
8
8
|
class ComparisonHelper
|
9
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
60
|
-
|
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
|
-
|
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
|
-
|
84
|
-
|
27
|
+
street_sequence,
|
28
|
+
candidate_sequences,
|
85
29
|
)
|
86
|
-
end
|
30
|
+
end.min
|
31
|
+
end
|
87
32
|
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
37
|
+
@city_comparison = best_comparison(
|
38
|
+
datastore.fetch_city_sequence,
|
39
|
+
T.must(candidate.component(:city)).sequences,
|
40
|
+
)
|
41
|
+
end
|
102
42
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
128
|
-
|
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
|
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
|
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
|
49
|
+
return @exclude_street_validation if defined?(@exclude_street_validation)
|
45
50
|
|
46
|
-
if
|
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
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
@@ -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
|
data/lib/atlas_engine/version.rb
CHANGED
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.
|
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-
|
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:
|
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:
|
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
|