Skip to content

Commit

Permalink
#2009 - IIR - Update VAPRO Contact Lookup to Filter by Valid Phone Nu…
Browse files Browse the repository at this point in the history
…mbers (#2010)
  • Loading branch information
AdamKing0126 authored Sep 27, 2024
1 parent fd70716 commit 4953183
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 89 deletions.
48 changes: 24 additions & 24 deletions .talismanrc
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
fileignoreconfig:
- filename: README.md
checksum: b2cbbb8508af49abccae8b35b317f7ce09215f3508430ed31440add78f450e5a
- filename: app/celery/contact_information_tasks.py
checksum: 80d0acf88bafb1358583016b9e143f4523ef1160d6eacdc9754ca68859b90eae
- filename: app/notifications/process_notifications.py
checksum: ae4e31c6eb56d91ec80ae09d13baf4558cf461c65f08893b93fee43f036a17a7
- filename: app/service/rest.py
checksum: b42aefd1ae0e6ea76e75db4cf14d425facd0941943b17f7ba2e41f850ad1ec23
- filename: app/template/rest.py
checksum: 1e5bdac8bc694d50f8f656dec127dd036b7b1b5b6156e3282d3411956c71ba0b
- filename: lambda_functions/pinpoint_callback/pinpoint_callback_lambda.py
checksum: 7bd4900e14b1fa789bbb2568b8a8d7a400e3c8350ba32fb44cc0b5b66a2df037
- filename: lambda_functions/ses_callback/ses_callback_lambda.py
checksum: b20c36921290a9609f158784e2a3278c36190887e6054ea548004a67675fd79b
- filename: poetry.lock
checksum: 34d12acdf749363555c31add4e7e7afa9e2a27afd792bd98c85f331b87bd7112
- filename: scripts/trigger_task.py
checksum: 0e9d244dbe285de23fc84bb643407963dacf7d25a3358373f01f6272fb217778
- filename: tests/app/celery/test_process_ga4_measurement_task.py
checksum: d33a6911258922f4bd3d149c90c2ee16c021a8e59e462594e4b1cd972902d689
- filename: tests/app/conftest.py
checksum: a80aa727586db82ed1b50bdb81ddfe1379e649a9dfc1ece2c36047486b41b83d
- filename: tests/app/notifications/test_process_notifications_for_profile_v3.py
checksum: 4e15e63d349635131173ffdd7aebcd547621db08de877ef926d3a41fde72d065
- filename: README.md
checksum: b2cbbb8508af49abccae8b35b317f7ce09215f3508430ed31440add78f450e5a
- filename: app/celery/contact_information_tasks.py
checksum: 80d0acf88bafb1358583016b9e143f4523ef1160d6eacdc9754ca68859b90eae
- filename: app/notifications/process_notifications.py
checksum: ae4e31c6eb56d91ec80ae09d13baf4558cf461c65f08893b93fee43f036a17a7
- filename: app/service/rest.py
checksum: b42aefd1ae0e6ea76e75db4cf14d425facd0941943b17f7ba2e41f850ad1ec23
- filename: app/template/rest.py
checksum: 1e5bdac8bc694d50f8f656dec127dd036b7b1b5b6156e3282d3411956c71ba0b
- filename: lambda_functions/pinpoint_callback/pinpoint_callback_lambda.py
checksum: 7bd4900e14b1fa789bbb2568b8a8d7a400e3c8350ba32fb44cc0b5b66a2df037
- filename: lambda_functions/ses_callback/ses_callback_lambda.py
checksum: b20c36921290a9609f158784e2a3278c36190887e6054ea548004a67675fd79b
- filename: poetry.lock
checksum: 34d12acdf749363555c31add4e7e7afa9e2a27afd792bd98c85f331b87bd7112
- filename: scripts/trigger_task.py
checksum: 0e9d244dbe285de23fc84bb643407963dacf7d25a3358373f01f6272fb217778
- filename: tests/app/celery/test_process_ga4_measurement_task.py
checksum: d33a6911258922f4bd3d149c90c2ee16c021a8e59e462594e4b1cd972902d689
- filename: tests/app/conftest.py
checksum: a80aa727586db82ed1b50bdb81ddfe1379e649a9dfc1ece2c36047486b41b83d
- filename: tests/app/notifications/test_process_notifications_for_profile_v3.py
checksum: 4e15e63d349635131173ffdd7aebcd547621db08de877ef926d3a41fde72d065
version: "1.0"
104 changes: 86 additions & 18 deletions app/va/va_profile/va_profile_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
from dataclasses import dataclass
from enum import Enum
from http.client import responses
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional


import iso8601
import requests

from app.feature_flags import FeatureFlag, is_feature_enabled
from app.va.identifier import OIDS, IdentifierType, transform_to_fhir_format
from app.va.va_profile import NoContactInfoException, VAProfileNonRetryableException, VAProfileRetryableException
from app.va.va_profile.exceptions import (
Expand All @@ -26,6 +27,12 @@
'sms': 'Text',
}

VALID_PHONE_TYPES_FOR_SMS_DELIVERY = [
0, # "MOBILE"
2, # "VOIP"
5, # "PREPAID"
]


class CommunicationChannel(Enum):
EMAIL = ('Email', 2)
Expand Down Expand Up @@ -158,6 +165,77 @@ def get_email(self, va_profile_id: RecipientIdentifier) -> str:
self.statsd_client.incr('clients.va-profile.get-email.failure')
self._raise_no_contact_info_exception(self.EMAIL_BIO_TYPE, va_profile_id, contact_info.get(self.TX_AUDIT_ID))

def has_valid_mobile_telephone_classification(self, telephone: Telephone) -> bool:
"""
Args:
telephone (Telephone): telephone entry from ContactInformation object retrieved from Vet360 API endpoint
Returns:
bool - if AWS-classified telephone is a valid sms recipient (if nonexistent, return True)
"""
classification = telephone.get('classification', {})
classification_code = classification.get('classificationCode', None)
if classification_code is not None:
if classification_code not in VALID_PHONE_TYPES_FOR_SMS_DELIVERY:
self.logger.debug(
'V3 Profile -- Phone classification code of %s is not a valid SMS recipient (VA Profile ID: %s)',
classification_code,
telephone['vaProfileId'],
)
return False

self.logger.debug(
'V3 Profile -- Phone classification code of %s is a valid SMS recipient (VA Profile ID: %s)',
classification_code,
telephone['vaProfileId'],
)
return True

# fall back, if no phone number classification is present
self.logger.debug(
'V3 Profile -- No telephone classification present, assuming the number is a valid SMS recipient (VA Profile ID: %s)',
telephone['vaProfileId'],
)
return True

def get_mobile_telephone_from_contact_info(self, contact_info: ContactInformation) -> Optional[str]:
"""
Find the most recently created mobile phone number from a veteran's Vet360 contact information
Args:
contact_info (ContactInformation): Contact Information object retrieved from Vet360 API endpoint
Returns:
string representation of the most recently created mobile phone number, or None
"""
telephones: list[Telephone] = contact_info.get(self.PHONE_BIO_TYPE, [])

sorted_telephones = sorted(
[phone for phone in telephones if phone['phoneType'] == PhoneNumberType.MOBILE.value],
key=lambda phone: iso8601.parse_date(phone['createDate']),
reverse=True,
)

if sorted_telephones:
is_mobile = True
if is_feature_enabled(FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS):
self.logger.debug(
'V3 Profile -- VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS enabled. Checking telephone classification info.'
)
is_mobile = self.has_valid_mobile_telephone_classification(sorted_telephones[0])
else:
self.logger.debug(
'V3 Profile -- VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS is not enabled. Will not check classification info.'
)
if (
is_mobile
and sorted_telephones[0].get('countryCode')
and sorted_telephones[0].get('areaCode')
and sorted_telephones[0].get('phoneNumber')
):
self.statsd_client.incr('clients.va-profile.get-telephone.success')
return f"+{sorted_telephones[0]['countryCode']}{sorted_telephones[0]['areaCode']}{sorted_telephones[0]['phoneNumber']}"

def get_telephone_with_permission(
self,
va_profile_id: RecipientIdentifier,
Expand Down Expand Up @@ -189,24 +267,14 @@ def get_telephone_with_permission(

contact_info: ContactInformation = profile.get('contactInformation', {})

telephones: list[Telephone] = contact_info.get(self.PHONE_BIO_TYPE, [])
sorted_telephones = sorted(
[phone for phone in telephones if phone['phoneType'] == PhoneNumberType.MOBILE.value],
key=lambda phone: iso8601.parse_date(phone['createDate']),
reverse=True,
)
if sorted_telephones:
if (
sorted_telephones[0].get('countryCode')
and sorted_telephones[0].get('areaCode')
and sorted_telephones[0].get('phoneNumber')
):
self.statsd_client.incr('clients.va-profile.get-telephone.success')
telephone_result = f"+{sorted_telephones[0]['countryCode']}{sorted_telephones[0]['areaCode']}{sorted_telephones[0]['phoneNumber']}"
return VAProfileResult(telephone_result, communication_allowed, permission_message)
telephone = self.get_mobile_telephone_from_contact_info(contact_info)
if not telephone:
self.statsd_client.incr('clients.va-profile.get-telephone.failure')
self._raise_no_contact_info_exception(
self.PHONE_BIO_TYPE, va_profile_id, contact_info.get(self.TX_AUDIT_ID)
)

self.statsd_client.incr('clients.va-profile.get-telephone.failure')
self._raise_no_contact_info_exception(self.PHONE_BIO_TYPE, va_profile_id, contact_info.get(self.TX_AUDIT_ID))
return VAProfileResult(telephone, communication_allowed, permission_message)

def get_email_with_permission(
self,
Expand Down
8 changes: 7 additions & 1 deletion app/va/va_profile/va_profile_types.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TypedDict, List
from typing import TypedDict, List, Optional


class Country(TypedDict):
Expand Down Expand Up @@ -44,6 +44,11 @@ class Address(TypedDict):
geocodeDate: str


class Classification(TypedDict):
classificationCode: int
classificationName: str


class Telephone(TypedDict):
createDate: str
updateDate: str
Expand All @@ -60,6 +65,7 @@ class Telephone(TypedDict):
countryCode: str
areaCode: str
phoneNumber: str
classification: Optional[Classification]


class Email(TypedDict):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@
{
"name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP",
"value": "True"
},
{
"name": "VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS",
"value": "True"
}
],
"secrets": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@
{
"name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP",
"value": "True"
},
{
"name": "VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS",
"value": "False"
}
],
"secrets": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@
{
"name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP",
"value": "False"
},
{
"name": "VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS",
"value": "False"
}
],
"secrets": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@
{
"name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP",
"value": "True"
},
{
"name": "VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS",
"value": "False"
}
],
"secrets": [
Expand Down
18 changes: 15 additions & 3 deletions tests/app/va/va_profile/mock_response.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@
"phoneType": "MOBILE",
"countryCode": "1",
"areaCode": "582",
"phoneNumber": "3028742"
"phoneNumber": "3028742",
"classification": {
"classificationCode": 0,
"classificationName": "MOBILE"
}
},
{
"createDate": "2022-06-09T15:12:10Z",
Expand All @@ -131,7 +135,11 @@
"phoneType": "HOME",
"countryCode": "1",
"areaCode": "582",
"phoneNumber": "2015400"
"phoneNumber": "2015400",
"classification": {
"classificationCode": 1,
"classificationName": "LANDLINE"
}
},
{
"createDate": "2022-06-09T15:11:50Z",
Expand All @@ -148,7 +156,11 @@
"phoneType": "MOBILE",
"countryCode": "1",
"areaCode": "582",
"phoneNumber": "3028743"
"phoneNumber": "3028743",
"classification": {
"classificationCode": 0,
"classificationName": "MOBILE"
}
}
],
"emails": [
Expand Down
23 changes: 0 additions & 23 deletions tests/app/va/va_profile/test_va_profile_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from app.models import EMAIL_TYPE, RecipientIdentifier
from app.va.identifier import IdentifierType, transform_to_fhir_format, OIDS
from app.va.va_profile import VAProfileClient
from app.va.va_profile.exceptions import (
CommunicationItemNotFoundException,
NoContactInfoException,
Expand All @@ -19,28 +18,6 @@
MOCK_VA_PROFILE_URL = 'http://mock.vaprofile.va.gov'


@pytest.fixture(scope='function')
def mock_va_profile_client(mocker, notify_api):
with notify_api.app_context():
mock_logger = mocker.Mock()
mock_ssl_key_path = 'some_key.pem'
mock_ssl_cert_path = 'some_cert.pem'
mock_statsd_client = mocker.Mock()
mock_va_profile_token = mocker.Mock()

client = VAProfileClient()
client.init_app(
logger=mock_logger,
va_profile_url=MOCK_VA_PROFILE_URL,
ssl_cert_path=mock_ssl_cert_path,
ssl_key_path=mock_ssl_key_path,
va_profile_token=mock_va_profile_token,
statsd_client=mock_statsd_client,
)

return client


@pytest.fixture(scope='function')
def mock_response():
with open('tests/app/va/va_profile/mock_response.json', 'r') as f:
Expand Down
Loading

0 comments on commit 4953183

Please sign in to comment.