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

feature/APPEALS-21478: Update Geo-matching to use zip code instead of street address #19756

Merged
merged 33 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c96fcec
APPEALS-31482 Update gov service to include zip validation request
jefftmarks Oct 12, 2023
17b6282
APPEALS-31482 Update error handling for when address not found but co…
jefftmarks Oct 12, 2023
c6824e3
APPEALS-31482 Update validator to call validate zip code
jefftmarks Oct 12, 2023
4a21ec3
APPEALS-31482 Keep in hardcoded zip code in case of api failure
jefftmarks Oct 12, 2023
839e3da
APPEALS-31482 Update validator to call validate_zip
jefftmarks Oct 13, 2023
6fda88c
APPEALS-31842 Refactor manual zip code validation
jefftmarks Oct 13, 2023
c3c03bf
APPEALS-31482 Update fake service
jefftmarks Oct 16, 2023
edaeb8f
APPEALS-31482 Update va_dot_gov_service_spec
jefftmarks Oct 16, 2023
3030220
APPEALS-31482 Update fake to use same Florida zip to prevent failing …
jefftmarks Oct 16, 2023
8c4653a
APPEALS-31482 Update geomatch spec to pass zip code conditional
jefftmarks Oct 16, 2023
ca3b839
APPEALS-31482 Update tasks_controller_spec to pass zip condition
jefftmarks Oct 16, 2023
c026da2
APPEALS-31482 Change method name in validator spec
jefftmarks Oct 16, 2023
40f7597
APPEALS-31482 Add invalid zip code test to spec file
jefftmarks Oct 16, 2023
e9f9608
APPEALS-31482 Fix linting
jefftmarks Oct 17, 2023
5c4585c
APPEALS-31482 Add comments
jefftmarks Oct 17, 2023
56f99fc
APPEALS-31482 Remove commented out code
jefftmarks Oct 17, 2023
2f0b1cf
Merge branch 'feature/APPEALS-21478' of https://github.com/department…
jefftmarks Oct 18, 2023
9e4d121
APPEALS-31482 Remove nil check
jefftmarks Oct 18, 2023
c12dbee
Merge pull request #19712 from department-of-veterans-affairs/jefftma…
msteele96 Oct 19, 2023
bcb8551
Min/APPEALS-31484 (#19713)
minhazur9 Oct 24, 2023
b1ef3d2
Merge branch 'master' into feature/APPEALS-21478
msteele96 Oct 26, 2023
0391cf8
Merge branch 'master' into feature/APPEALS-21478
msteele96 Oct 31, 2023
220260f
Merge branch 'master' into feature/APPEALS-21478
msteele96 Oct 31, 2023
775bfd2
Merge branch 'master' into feature/APPEALS-21478
msteele96 Nov 7, 2023
7b69c68
Merge branch 'master' into feature/APPEALS-21478
msteele96 Nov 20, 2023
abd6624
Merge branch 'master' into feature/APPEALS-21478
msteele96 Nov 20, 2023
0668811
jefftmarks/APPEALS-34933 (#19963)
jefftmarks Nov 22, 2023
e285616
Linting fix
msteele96 Nov 27, 2023
c4f43d8
Merge branch 'master' into feature/APPEALS-21478
msteele96 Nov 30, 2023
3715020
Merge branch 'master' into feature/APPEALS-21478
msteele96 Dec 7, 2023
82bc33b
Merge branch 'master' into feature/APPEALS-21478
msteele96 Dec 11, 2023
5f7a5e1
jefftmarks/APPEALS-36158 (#20132)
jefftmarks Dec 12, 2023
483f3d7
Merge branch 'master' into feature/APPEALS-21478
msteele96 Dec 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/jobs/fetch_hearing_locations_for_veterans_job.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

class FetchHearingLocationsForVeteransJob < ApplicationJob
class FetchHearingLocationsForVeteransJob < CaseflowJob
queue_with_priority :low_priority
application_attr :hearing_schedule

Expand Down
4 changes: 3 additions & 1 deletion app/services/bgs_address_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ def cache_key
def fetch_bgs_record
Rails.cache.fetch(cache_key, expires_in: 24.hours) do
bgs.find_address_by_participant_id(participant_id)
rescue Savon::Error
rescue Savon::Error => error
Raven.capture_exception(error)
Rails.logger.warn("Failed to fetch address from BGS for participant id: #{participant_id}: #{error}")
# If there is no address for this participant id then we get an error.
# catch it and return an empty array
nil
Expand Down
87 changes: 87 additions & 0 deletions app/services/external_api/va_dot_gov_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,67 @@ def validate_address(address)
ExternalApi::VADotGovService::AddressValidationResponse.new(response)
end

# Verifies a veteran's zip code and returns its associated geographic coordinates in latitude and longitude.
#
# @param zip_code [String] A veteran's five-digit zip code
#
# @return [ExternalApi::VADotGovService::AddressValidationResponse]
# A wrapper around the VA.gov API response that includes the geocode (latitude and longitude) associated
# with the veteran's zip code.
#
# Note: The response will include an "AddressCouldNotBeFound" and "lowConfidenceScore" messages
#
# API Documentation: https://developer.va.gov/explore/verification/docs/address_validation
#
# Expected JSON Response from API:
#
# ```
# {
# "messages": [
# {
# "code": "ADDRVAL112",
# "key": "AddressCouldNotBeFound",
# "text": "The Address could not be found",
# "severity": "WARN"
# },
# {
# "code": "ADDR306",
# "key": "lowConfidenceScore",
# "text": "VaProfile Validation Failed: Confidence Score less than 80",
# "severity": "WARN"
# }
# ],
# "address": {
# "addressLine1": "Address",
# "zipCode5": "string",
# "stateProvince": {},
# "country": {
# "name": "United States",
# "code": "USA",
# "fipsCode": "US",
# "iso2Code": "US",
# "iso3Code": "USA"
# }
# },
# "geocode": {
# "calcDate": "2023-10-12T20:27:04Z",
# "latitude": 40.7029,
# "longitude": -73.8868
# },
# "addressMetaData": {
# "confidenceScore": 0.0,
# "addressType": "Domestic",
# "deliveryPointValidation": "MISSING_ZIP",
# "validationKey": 359084376
# }
# }
# ```
def validate_zip_code(address)
response = send_va_dot_gov_request(zip_code_validation_request(address))

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

# Gets full list of facility IDs available from the VA.gov API
#
# @param ids [Array<String, Symbol>] facility ids to check
Expand Down Expand Up @@ -388,6 +449,32 @@ def address_validation_request(address)
}
end

# Builds a request for the VA.gov veteran address validation endpoint using only the veteran's five-digit zip code.
# 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 1: Hard code placeholder string for addressLine1 to avoid "InvalidRequestStreetAddress" error
# Note 2: Include country name to ensure foreign addresses are properly handled
#
# @param address [Address] The veteran's address
#
# @return [Hash] The payload to send to the VA.gov API
def zip_code_validation_request(address)
{
body: {
requestAddress: {
addressLine1: "address",
zipCode5: address.zip,
requestCountry: { countryName: address.country }
}
},
headers: {
"Content-Type": "application/json", Accept: "application/json"
},
endpoint: ADDRESS_VALIDATION_ENDPOINT, method: :post
}
end

def track_pages(pages)
DataDogService.emit_gauge(
metric_group: "service",
Expand Down
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
60 changes: 28 additions & 32 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 All @@ -47,38 +47,19 @@ def update_closest_ro_and_ahls
update_closest_regional_office
destroy_existing_available_hearing_locations!
create_available_hearing_locations

{ status: :matched_available_hearing_locations }
end

def valid_address
@valid_address ||= if valid_address_response.success?
valid_address_response.data
else
validate_zip_code
manually_validate_zip_code
end
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?

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_address(address)
@valid_address_response ||= VADotGovService.validate_zip_code(address)
end

def available_hearing_locations_response
Expand Down Expand Up @@ -220,15 +218,13 @@ def closest_ro_facility_id
closest_ro_response.data.first&.dig(:facility_id)
end

def validate_zip_code
if address.zip_code_not_validatable?
nil
else
lat_lng = ZipCodeToLatLngMapper::MAPPING[address.zip[0..4]]
def manually_validate_zip_code
return if address.zip_code_not_validatable?

return nil if lat_lng.nil?
lat_lng = ZipCodeToLatLngMapper::MAPPING[address.zip[0..4]]

{ lat: lat_lng[0], long: lat_lng[1], country_code: address.country, state_code: address.state }
end
return if lat_lng.nil?

{ lat: lat_lng[0], long: lat_lng[1], country_code: address.country, state_code: address.state }
end
end
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
17 changes: 7 additions & 10 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
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
Loading
Loading