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

jefftmarks/APPEALS-34933 #19963

Merged
merged 11 commits into from
Nov 22, 2023
Merged
16 changes: 9 additions & 7 deletions app/services/external_api/va_dot_gov_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,10 @@ def validate_address(address)
# }
# }
# ```
def validate_zip_code(zip_code)
response = send_va_dot_gov_request(zip_code_validation_request(zip_code))
def validate_zip_code(address)
response = send_va_dot_gov_request(zip_code_validation_request(address))

ExternalApi::VADotGovService::AddressValidationResponse.new(response)
ExternalApi::VADotGovService::ZipCodeValidationResponse.new(response)
end

# Gets full list of facility IDs available from the VA.gov API
Expand Down Expand Up @@ -453,17 +453,19 @@ def address_validation_request(address)
# This will return "AddressCouldNotBeFound" and "lowConfidenceScore" messages. However, given a valid zip code, the
# response body will include valid coordinates for latitude and longitude.
#
# Note: Hard code placeholder string for addressLine1 to avoid "InvalidRequestStreetAddress" error
# Note 1: Hard code placeholder string for addressLine1 to avoid "InvalidRequestStreetAddress" error
# Note 2: Include country name to ensure foreign addresses are properly handled
#
# @param zip_code [String] The veteran's five-digit zip code
# @param address [Address] The veteran's address
#
# @return [Hash] The payload to send to the VA.gov API
def zip_code_validation_request(zip_code)
def zip_code_validation_request(address)
{
body: {
requestAddress: {
addressLine1: "address",
zipCode5: zip_code
zipCode5: address.zip,
requestCountry: { countryName: address.country }
}
},
headers: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ def data

private

# The coordinates_invalid? check prevents the creation of a HearingAdminActionVerifyAddressTask when
# the response contains valid geographic coordiantes sufficient to complete geomatching
def message_error
messages&.find { |message| message.error.present? && coordinates_invalid? }&.error
messages&.find { |message| message.error.present? }&.error
end

def messages
Expand Down Expand Up @@ -48,12 +46,4 @@ def formatted_valid_address
zip_code: address.zip
}
end

# When using only an appellant's zip code to validate an address, an invalid zip code will return
# float values of 0.0 for both latitude and longitude
def coordinates_invalid?
return true if body[:geocode].nil?

[body[:geocode][:latitude], body[:geocode][:longitude]] == [0.0, 0.0]
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

# Inherits most of its behavior from AddressValidationResponse, but redefines a successful response
# as one where a zip code returns valid geographic coordinates (regardless of whether a specific
# addres could be found.

class ExternalApi::VADotGovService::ZipCodeValidationResponse < ExternalApi::VADotGovService::AddressValidationResponse
def error
message_error || response_error || foreign_address_error
end

private

# The coordinates_invalid? check prevents the creation of a HearingAdminActionVerifyAddressTask when
# the response contains valid geographic coordiantes sufficient to complete geomatching
def message_error
messages&.find { |message| message.error.present? && coordinates_invalid? }&.error
end

# When using only an appellant's zip code to validate an address, an invalid zip code will return
# float values of 0.0 for both latitude and longitude
def coordinates_invalid?
return true if body[:geocode].nil?

[body[:geocode][:latitude], body[:geocode][:longitude]] == [0.0, 0.0]
end

def foreign_address_error
if coordinates_invalid? && address_type == "International"
Caseflow::Error::VaDotGovForeignVeteranError.new(
code: 500,
message: "Appellant address is not in US territories."
)
end
end

def address_type
body.dig(:addressMetaData, :addressType)
end
end
44 changes: 21 additions & 23 deletions app/services/va_dot_gov_address_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ class VaDotGovAddressValidator
# Address was in the Philippines, and assigned to RO58.
philippines_exception: :defaulted_to_philippines_RO58,

# Foreign addresses need to be handled by an admin.
created_foreign_veteran_admin_action: :created_foreign_veteran_admin_action,
# Address was foreign (not Philippines), and assigned to RO11.
foreign_veteran_exception: :defaulted_to_pittsburgh_RO11,

# An admin needs to manually handle addresses that can't be verified.
created_verify_address_admin_action: :created_verify_address_admin_action
Expand Down Expand Up @@ -59,26 +59,7 @@ def valid_address
end

def state_code
map_country_code_to_state
end

def closest_regional_office
@closest_regional_office ||= begin
return unless closest_ro_response.success?
# Note: In `ro_facility_ids_to_geomatch`, the San Antonio facility ID and Elpaso facility Id is passed
# as a valid RO for any veteran living in Texas.
return "RO62" if closest_regional_office_facility_id_is_san_antonio?
return "RO49" if closest_regional_office_facility_id_is_el_paso?
return "RO58" if appellant_lives_in_phillipines?
return "RO11" if !appellant_lives_in_usa?

RegionalOffice
.cities
.detect do |ro|
ro.facility_id == closest_ro_facility_id
end
.key
end
map_state_code_to_state_with_ro
end

def available_hearing_locations
Expand Down Expand Up @@ -143,6 +124,23 @@ def ro_facility_ids_to_geomatch

private

def closest_regional_office
@closest_regional_office ||= begin
return unless closest_ro_response.success?
# Note: In `ro_facility_ids_to_geomatch`, the San Antonio facility ID and Elpaso facility Id is passed
# as a valid RO for any veteran living in Texas.
return "RO62" if closest_regional_office_facility_id_is_san_antonio?
return "RO49" if closest_regional_office_facility_id_is_el_paso?

RegionalOffice
.cities
.detect do |ro|
ro.facility_id == closest_ro_facility_id
end
.key
end
end

def update_closest_regional_office
appeal.update(closest_regional_office: closest_regional_office_with_exceptions)
end
Expand Down Expand Up @@ -174,7 +172,7 @@ def create_available_hearing_location(facility:)
end

def valid_address_response
@valid_address_response ||= VADotGovService.validate_zip_code(address.zip)
@valid_address_response ||= VADotGovService.validate_zip_code(address)
end

def available_hearing_locations_response
Expand Down
11 changes: 4 additions & 7 deletions app/services/va_dot_gov_address_validator/error_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,17 @@ def initialize(appeal:, appellant_address:)
def handle(error)
if check_for_philippines_and_maybe_update
:philippines_exception
elsif foreign_veteran_errors.any? { |klass| error.instance_of?(klass) }
appeal.va_dot_gov_address_validator.assign_ro_and_update_ahls("RO11")

:foreign_veteran_exception
elsif verify_address_errors.any? { |klass| error.instance_of?(klass) }
create_admin_action_for_schedule_hearing_task(
instructions: "The appellant's address in VBMS does not exist, is incomplete, or is ambiguous.",
admin_action_type: HearingAdminActionVerifyAddressTask
)

:created_verify_address_admin_action
elsif foreign_veteran_errors.any? { |klass| error.instance_of?(klass) }
create_admin_action_for_schedule_hearing_task(
instructions: "The appellant's address in VBMS is outside of US territories.",
admin_action_type: HearingAdminActionForeignVeteranCaseTask
)

:created_foreign_veteran_admin_action
else
# :nocov:
raise error # rubocop:disable Style/SignalException
Expand Down
27 changes: 7 additions & 20 deletions app/services/va_dot_gov_address_validator/validations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@ module VaDotGovAddressValidator::Validations
private

# :nocov:
def map_country_code_to_state
case valid_address[:country_code]
def map_state_code_to_state_with_ro
case valid_address[:state_code]
# Guam, American Samoa, Marshall Islands, Micronesia, Northern Mariana Islands, Palau
when "GQ", "AQ", "RM", "FM", "CQ", "PS"
when "GU", "AS", "MH", "FM", "MP", "PW"
"HI"
# Philippine Islands
when "PH", "RP", "PI"
"PI"
# Puerto Rico, Vieques, U.S. Virgin Islands
when "VI", "VQ", "PR"
# U.S. Virgin Islands
when "VI"
"PR"
when "US", "USA"
valid_address.dig(:state_code)
else
valid_address[:state_code]
end
end
# :nocov:
Expand Down Expand Up @@ -82,14 +79,4 @@ def appeal_is_legacy_and_veteran_requested_central_office?
def veteran_lives_in_texas?
state_code == "TX"
end

def appellant_lives_in_usa?
return true if address.country.nil?

%w[USA US].include? address.country unless address.country.nil?
end

def appellant_lives_in_phillipines?
%w[PH RP PI].include? address.country
end
end
2 changes: 1 addition & 1 deletion lib/caseflow/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class VaDotGovMissingFacilityError < VaDotGovAPIError; end
class VaDotGovInvalidInputError < VaDotGovAPIError; end
class VaDotGovMultipleAddressError < VaDotGovAPIError; end
class VaDotGovNullAddressError < StandardError; end
class VaDotGovForeignVeteranError < StandardError; end
class VaDotGovForeignVeteranError < SerializableError; end

class FetchHearingLocationsJobError < SerializableError; end

Expand Down
2 changes: 1 addition & 1 deletion lib/fakes/va_dot_gov_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def self.send_va_dot_gov_request(endpoint:, query: {}, **args)
request_address_keys = args[:body][:requestAddress].keys

# If request was built by self.zip_code_validations_request
if request_address_keys == [:addressLine1, :zipCode5]
if request_address_keys.sort == [:addressLine1, :requestCountry, :zipCode5]
HTTPI::Response.new 200, {}, fake_zip_code_data.to_json
# If request was built by self.address_validations_request
else
Expand Down
28 changes: 14 additions & 14 deletions spec/services/external_api/va_dot_gov_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
stub_const("VADotGovService", Fakes::VADotGovService)
end

let(:address) do
Address.new(
address_line_1: "fake address",
address_line_2: "fake address",
address_line_3: "fake address",
city: "City",
state: "State",
zip: "11111",
country: "USA"
)
end

describe "#validate_address" do
it "returns validated address" do
result = VADotGovService.validate_address(
Address.new(
address_line_1: "fake address",
address_line_2: "fake address",
address_line_3: "fake address",
city: "City",
state: "State",
zip: "Zip",
country: "US"
)
)
result = VADotGovService.validate_address(address)

body = JSON.parse(result.response.body)
message_keys = body["messages"].pluck("key")
Expand All @@ -29,10 +31,8 @@
end

describe "#validate_zip_code" do
let(:zip) { "11385" }

it "returns invalid full address with valid geographic coordinates" do
result = VADotGovService.validate_zip_code(zip)
result = VADotGovService.validate_zip_code(address)

body = JSON.parse(result.response.body)
message_keys = body["messages"].pluck("key")
Expand Down
23 changes: 19 additions & 4 deletions spec/services/geomatch_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@
end

let(:appeal) { create(:legacy_appeal, vacols_case: vacols_case) }
let(:non_us_address) { Address.new(country: "MX", country_name: "Mexico", city: "Mexico City") }
let(:philippines_address) { Address.new(country: "PI", country_name: "Philippines", city: "Manila") }

let(:mock_response) { HTTPI::Response.new(200, {}, {}.to_json) }
let(:valid_address_response) { ExternalApi::VADotGovService::ZipCodeValidationResponse.new(mock_response) }
let(:response_body) { valid_address_response.body }

it "geomatches for the travel board appeal" do
subject
Expand All @@ -91,7 +93,13 @@
end

context "foreign appeal" do
before { appeal.instance_variable_set(:@address, non_us_address) }
before do
allow_any_instance_of(VaDotGovAddressValidator).to receive(:valid_address_response)
.and_return(valid_address_response)
allow(valid_address_response).to receive(:coordinates_invalid?).and_return(true)
allow(response_body).to receive(:dig).with(:addressMetaData, :addressType).and_return("International")
end

it "geomatches for a foreign appeal" do
subject

Expand All @@ -103,7 +111,14 @@
end

context "phillipines appeal" do
before { appeal.instance_variable_set(:@address, philippines_address) }
before do
allow_any_instance_of(VaDotGovAddressValidator).to receive(:valid_address_response)
.and_return(valid_address_response)
allow(valid_address_response).to receive(:coordinates_invalid?).and_return(true)
allow(response_body).to receive(:dig).with(:addressMetaData, :addressType).and_return("International")
allow_any_instance_of(Address).to receive(:country).and_return("Philippines")
end

it "geomatches for a phillipines appeal" do
subject

Expand Down
Loading
Loading