Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split Comparison Helper #93

Merged
merged 1 commit into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- Address TestCase#fixture_path deprecation [#94](https://github.com/Shopify/atlas_engine/pull/94)
- Address parser test cleanup [#92](https://github.com/Shopify/atlas_engine/pull/92)
- Split field comparison logic into their respective classes [#93](https://github.com/Shopify/atlas_engine/pull/93)
- Improved validation suggestions for Luxembourg [#82](https://github.com/Shopify/atlas_engine/pull/82)
- Store a duplicate of Result in ConcernRecord [#85](https://github.com/Shopify/atlas_engine/pull/85)
- Introduce parser and city exclusion for South Korea [#79](https://github.com/Shopify/atlas_engine/pull/79)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ def apply?(session, candidate, address_comparison)
address_comparison.building_comparison.nil? ||
address_comparison.building_comparison.candidate_ranges.empty?

!address_comparison.street_comparison.match? || !address_comparison.building_comparison.match?
!T.must(address_comparison.street_comparison).match? ||
!T.must(address_comparison.building_comparison).match?
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@ class AddressComparison
extend T::Sig
include Comparable

attr_reader :comparison_helper

delegate :street_comparison,
:city_comparison,
:province_code_comparison,
:zip_comparison,
:building_comparison,
to: :comparison_helper

sig { params(address: AbstractAddress, candidate: Candidate, datastore: DatastoreBase).void }
def initialize(address:, candidate:, datastore:)
@comparison_helper = ComparisonHelper.new(address:, candidate:, datastore:)
@street_comparison = StreetComparison.new(address: address, candidate: candidate, datastore: datastore)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@city_comparison = CityComparison.new(address: address, candidate: candidate, datastore: datastore)
@province_code_comparison = ProvinceCodeComparison.new(
address: address,
candidate: candidate,
datastore: datastore,
)
@zip_comparison = ZipComparison.new(address: address, candidate: candidate, datastore: datastore)
@building_comparison = BuildingComparison.new(address: address, candidate: candidate, datastore: datastore)
end

sig { params(other: AddressComparison).returns(Integer) }
Expand All @@ -45,6 +44,31 @@ def potential_match?
street_comparison.nil? || T.must(street_comparison).potential_match?
end

sig { returns(T.nilable(Token::Sequence::Comparison)) }
def zip_comparison
@zip_comparison.compare
end

sig { returns(T.nilable(Token::Sequence::Comparison)) }
def street_comparison
@street_comparison.compare
end

sig { returns(T.nilable(Token::Sequence::Comparison)) }
def city_comparison
@city_comparison.compare
end

sig { returns(T.nilable(Token::Sequence::Comparison)) }
def province_code_comparison
@province_code_comparison.compare
end

sig { returns(NumberComparison) }
def building_comparison
@building_comparison.compare
end

protected

sig do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# typed: true
# frozen_string_literal: true

module AtlasEngine
module AddressValidation
module Validators
module FullAddress
class BuildingComparison < FieldComparisonBase
extend T::Sig

sig { override.returns(T.nilable(NumberComparison)) }
def compare
@building_comparison ||= NumberComparison.new(
numbers: datastore.parsings.potential_building_numbers,
candidate_ranges: building_ranges_from_candidate(candidate),
)
end

private

sig { params(candidate: Candidate).returns(T::Array[AddressNumberRange]) }
def building_ranges_from_candidate(candidate)
building_and_unit_ranges = candidate.component(:building_and_unit_ranges)&.value
return [] if building_and_unit_ranges.blank?

building_ranges = JSON.parse(building_and_unit_ranges).keys
building_ranges.map { |building_range| AddressNumberRange.new(range_string: building_range) }
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# typed: true
# frozen_string_literal: true

module AtlasEngine
module AddressValidation
module Validators
module FullAddress
class CityComparison < FieldComparisonBase
extend T::Sig

sig { override.returns(T.nilable(Token::Sequence::Comparison)) }
def compare
return @city_comparison if defined?(@city_comparison)

@city_comparison = best_comparison(
datastore.fetch_city_sequence,
T.must(candidate.component(:city)).sequences,
field_policy(:city),
)
end
end
end
end
end
end

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# typed: true
# frozen_string_literal: true

module AtlasEngine
module AddressValidation
module Validators
module FullAddress
class FieldComparisonBase
extend T::Sig
extend T::Helpers

abstract!

sig { params(address: AbstractAddress, candidate: Candidate, datastore: DatastoreBase).void }
def initialize(address:, candidate:, datastore:)
@address = address
@datastore = datastore
@candidate = candidate
end

sig { abstract.returns(T.any(T.nilable(Token::Sequence::Comparison), T.nilable(NumberComparison))) }
def compare; end

private

sig { returns(AbstractAddress) }
attr_reader :address

sig { returns(DatastoreBase) }
attr_reader :datastore

sig { returns(Candidate) }
attr_reader :candidate

sig do
params(
sequence: Token::Sequence,
component_sequences: T::Array[Token::Sequence],
comparison_policy: Token::Sequence::ComparisonPolicy,
).returns(T.nilable(Token::Sequence::Comparison))
end
def best_comparison(
sequence,
component_sequences,
comparison_policy = Token::Sequence::ComparisonPolicy::DEFAULT_POLICY
)
component_sequences.map do |component_sequence|
Token::Sequence::Comparator.new(
left_sequence: sequence,
right_sequence: component_sequence,
comparison_policy:,
).compare
end.min_by.with_index do |comparison, index|
# ruby's `min` and `sort` methods are not stable
# so we need to prefer the leftmost comparison when two comparisons are equivalent
[comparison, index]
end
end

sig { params(field: Symbol).returns(Token::Sequence::ComparisonPolicy) }
def field_policy(field)
datastore.country_profile.validation.comparison_policy(field)
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# typed: true
# frozen_string_literal: true

module AtlasEngine
module AddressValidation
module Validators
module FullAddress
class ProvinceCodeComparison < FieldComparisonBase
extend T::Sig

sig { override.returns(T.nilable(Token::Sequence::Comparison)) }
def compare
return @province_code_comparison if defined?(@province_code_comparison)

normalized_session_province_code = ValidationTranscriber::ProvinceCodeNormalizer.normalize(
country_code: address.country_code,
province_code: address.province_code,
)
normalized_candidate_province_code = ValidationTranscriber::ProvinceCodeNormalizer.normalize(
country_code: T.must(candidate.component(:country_code)).value,
province_code: T.must(candidate.component(:province_code)).value,
)

@province_code_comparison = best_comparison(
Token::Sequence.from_string(normalized_session_province_code),
[Token::Sequence.from_string(normalized_candidate_province_code)],
field_policy(:province_code),
)
end
end
end
end
end
end