diff --git a/pyvet/benefits/claims/api.py b/pyvet/benefits/claims/api.py index be1e5a4..5a53485 100644 --- a/pyvet/benefits/claims/api.py +++ b/pyvet/benefits/claims/api.py @@ -2,6 +2,8 @@ Benefits Intake API: https://developer.va.gov/explore/benefits/docs/claims?version=v1 Note: V1 is for external users, V2 is for internal users. """ + +import base64 import logging import requests @@ -13,6 +15,16 @@ get_bearer_token, ) from pyvet.creds import API_URL +from pyvet.file_submission_schemas import ( + Address, + Claimant, + Phone, + POAForm, + ServiceOrganization, + Signature, + Signatures, + Veteran, +) from pyvet.json_alias import Json BENEFITS_INTAKE_URL = API_URL + "claims/v1/" @@ -233,30 +245,56 @@ def submit_526( # pass -# def submit_intent_to_file( -# is_representative: bool = False, -# ssn: str = "", -# first_name: str = "", -# last_name: str = "", -# birth_date: str = "", -# ) -> Json: -# """Submit an intent to file for disability compensation, burial, or pension claims. -# is_representative : bool -# If the consumer is a representative on behalf of a veteran. -# ssn : str -# The veteran's SSN. -# first_name : str -# The veteran's first name. -# last_name : str -# The veteran's last name. -# birth_date : str -# The veteran's birth date in iso8601 format. -# Returns -# ------- -# r : json -# Response in json format. -# """ -# pass +def submit_intent_to_file( + is_representative: bool = False, + ssn: str = "", + first_name: str = "", + last_name: str = "", + birth_date: str = "", +) -> Json: + """Submit an intent to file for disability compensation or pension claims (burial not allowed). + is_representative : bool + If the consumer is a representative on behalf of a veteran. + ssn : str + The veteran's SSN. + first_name : str + The veteran's first name. + last_name : str + The veteran's last name. + birth_date : str + The veteran's birth date in iso8601 format. + Returns + ------- + r : json + Response in json format. + """ + submission_url = BENEFITS_INTAKE_URL + "forms/0966" + authorization = session.headers.get("Authorization") + if authorization is None: + logging.error("No token set.") + session.headers[ + "Authorization" + ] = f"""Bearer {get_bearer_token(scope=CLAIM_SCOPE)}""" + if session.headers.get("Authorization") is None: + logging.error("Fetcing token failed.") + return None + + if is_representative: + session.headers["X-VA-SSN"] = ssn + session.headers["X-VA-First-Name"] = first_name + session.headers["X-VA-Last-Name"] = last_name + session.headers["X-VA-Birth-Date"] = birth_date + + try: + r = session.post( + submission_url, + headers=session.headers, + data={"type": "form/0966", "attributes": {"type": "compensation"}}, + ) + r.raise_for_status() + return r.json() + except requests.exceptions.RequestException as e: + logging.error(e) def get_last_active_intent_to_file( @@ -308,32 +346,105 @@ def get_last_active_intent_to_file( logging.error(e) -# def submit_poa( -# is_representative: bool = False, -# ssn: str = "", -# first_name: str = "", -# last_name: str = "", -# birth_date: str = "", -# ) -> Json: -# """Submit a Power of Attorney form 2122. -# Parameters -# ---------- -# is_representative : bool -# If the consumer is a representative on behalf of a veteran. -# ssn : str -# The veteran's SSN. -# first_name : str -# The veteran's first name. -# last_name : str -# The veteran's last name. -# birth_date : str -# The veteran's birth date in iso8601 format. -# Returns -# ------- -# r : json -# Response in json format. -# """ -# pass +def submit_poa( + is_representative: bool = False, + ssn: str = "", + first_name: str = "", + last_name: str = "", + birth_date: str = "", +) -> Json: + """Submit a Power of Attorney form 2122. + Parameters + ---------- + is_representative : bool + If the consumer is a representative on behalf of a veteran. + ssn : str + The veteran's SSN. + first_name : str + The veteran's first name. + last_name : str + The veteran's last name. + birth_date : str + The veteran's birth date in iso8601 format. + Returns + ------- + r : json + Response in json format. + """ + submission_url = BENEFITS_INTAKE_URL + "forms/2122" + authorization = session.headers.get("Authorization") + if authorization is None: + logging.error("No token set.") + session.headers[ + "Authorization" + ] = f"""Bearer {get_bearer_token(scope=CLAIM_SCOPE)}""" + if session.headers.get("Authorization") is None: + logging.error("Fetching token failed.") + return None + + if is_representative: + session.headers["X-VA-SSN"] = ssn + session.headers["X-VA-First-Name"] = first_name + session.headers["X-VA-Last-Name"] = last_name + session.headers["X-VA-Birth-Date"] = birth_date + + poa_form: POAForm = POAForm( + veteran=Veteran( + address=Address( + numberAndStreet="123 Main St", + city="Anytown", + country="USA", + zipFirstFive="12345", + ), + phone=Phone( + areaCode="123", + phoneNumber="4567890", + ), + emailAddress="example@example.com", + ), + claimant=Claimant( + firstName="John", + lastName="Doe", + address=Address( + numberAndStreet="123 Main St", + city="Anytown", + country="USA", + zipFirstFive="12345", + ), + phone=Phone( + areaCode="123", + phoneNumber="4567890", + ), + email="example@example.com", + relationship="self", + ), + serviceOrganization=ServiceOrganization( + poaCode="1", + ), + recordConsent=True, + signatures=Signatures( + veteran=Signature( + title=base64.b64encode(b"John Doe"), + ), + representative=Signature( + title=base64.b64encode(b"Jane Doe"), + ), + ), + ) + + try: + r = session.post( + submission_url, + headers=session.headers, + data={ + "type": "form/21-22", + "attributes": poa_form, + }, + ) + r.raise_for_status() + return r.json() + except requests.exceptions.RequestException as e: + logging.error(e) # def upload_signed_poa( diff --git a/pyvet/client.py b/pyvet/client.py index a2859ee..841acea 100644 --- a/pyvet/client.py +++ b/pyvet/client.py @@ -3,7 +3,6 @@ """ import logging -# the below is an internal version, with some modifications. import oidc_client as oidc import requests from requests.adapters import HTTPAdapter @@ -26,8 +25,6 @@ def get_bearer_token(scope: str = DEFAULT_SCOPE) -> str | None: """Get a bearer token from the VA OIDC server. Parameters ---------- - va_api : str - The VA API to request a token for. scope : str A scope to request from the VA OIDC server (different per VA API). Returns diff --git a/pyvet/file_submission_schemas.py b/pyvet/file_submission_schemas.py new file mode 100644 index 0000000..a84dc15 --- /dev/null +++ b/pyvet/file_submission_schemas.py @@ -0,0 +1,99 @@ +"""Dataclasses for Intent to File API""" +# pylint: skip-file +import base64 +from dataclasses import dataclass +from datetime import datetime +from typing import Annotated + +BytesBase64 = Annotated[bytes, bytes] + + +@dataclass +class Address: + """Address dataclass for Intent to File API""" + + numberAndStreet: str + city: str + country: str + zipFirstFive: str + aptUnitNumber: int | None = None + state: str | None = None + zipLastFour: str | None = None + additionalProperties: bool | None = None + + +@dataclass +class Phone: + """Phone dataclass for Intent to File API""" + + areaCode: str + phoneNumber: str + countryCode: str | None = None + phoneNumberExtstring: str | None = None + + +@dataclass +class Veteran: + """Veteran dataclass for Intent to File API""" + + address: Address + phone: Phone + emailAddress: str + serviceBranch: str | None = None + serviceBranchOtherstring: str | None = None + + +@dataclass +class Claimant: + """Claimant dataclass for Intent to File API""" + + firstName: str + lastName: str + address: Address + phone: Phone + email: str + relationship: str + middleInitial: str | None = None + + +@dataclass +class ServiceOrganization: + """Service Organization dataclass for Intent to File API""" + + poaCode: str + description: str | None = None + organizationNamestring: str | None = None + firstName: str | None = None + lastName: str | None = None + jobTitle: str | None = None + address: Address | None = None + email: str | None = None + + +@dataclass +class Signature: + """Signature dataclass for Intent to File API""" + + # Base64 encoded png image of the signature. + title: BytesBase64 + + +@dataclass +class Signatures: + """Signatures dataclass for Intent to File API""" + + veteran: Signature + representative: Signature + + +@dataclass +class POAForm: + """POA Form dataclass for Intent to File API""" + + veteran: Veteran + claimant: Claimant + serviceOrganization: ServiceOrganization + recordConsent: bool + signatures: Signatures + consentLimits: str | None = None + consentAddressChangeboolean: bool | None = None diff --git a/tests/benefits/test_benefits_claims.py b/tests/benefits/test_benefits_claims.py index 303a87a..bb147ce 100644 --- a/tests/benefits/test_benefits_claims.py +++ b/tests/benefits/test_benefits_claims.py @@ -15,6 +15,7 @@ from tests.data.mock_benefits_claims import ( MOCK_CLAIM, MOCK_CLAIMS, + MOCK_INTENT_TO_FILE, MOCK_INTENT_TO_FILE_LAST_ACTIVE, MOCK_POA_STATUS_ID, MOCK_POA_LAST_ACTIVE, @@ -100,13 +101,48 @@ def test_get_claim(self, mock_get, mock_auth, mock_token): }, ) + # @patch( + # "pyvet.benefits.claims.api.get_bearer_token", + # return_value="somerandomtoken", + # ) + # @patch.object(Session().headers, "get", return_value=None) + # @patch.object( + # Session, "post", headers=dict(apiKey=creds.API_KEY_HEADER.get("apiKey")) + # ) + # def test_submit_intent_to_file(self, mock_post, mock_auth, mock_token): + # mock_post.return_value.status_code = 200 + # mock_post.return_value.json.return_value = MOCK_INTENT_TO_FILE + # assert mock_auth.return_value == None + # intent = submit_intent_to_file( + # is_representative=False, + # ssn="796130115", + # first_name="Tamara", + # last_name="Ellis", + # birth_date="1967-06-19", + # ) + # print(intent) + # assert mock_post.headers == mock_headers + # mock_token.assert_called_once() + # self.assertDictEqual( + # intent, + # MOCK_INTENT_TO_FILE, + # ) + # mock_post.assert_called_once_with( + # self.benefits_claims_url + "forms/0966", + # headers=dict( + # apiKey=creds.API_KEY_HEADER.get("apiKey"), + # Authorization="Bearer somerandomtoken", + # ), + # data={"type": "form/0966", "attributes": {"type": "compensation"}}, + # ) + @patch( "pyvet.benefits.claims.api.get_bearer_token", return_value="somerandomtoken", ) @patch.object(Session().headers, "get", return_value=None) @patch.object( - Session, "get", headers={"apiKey": creds.API_KEY_HEADER.get("apiKey")} + Session, "get", headers=dict(apiKey=creds.API_KEY_HEADER.get("apiKey")) ) def test_get_last_active_intent_to_file(self, mock_get, mock_auth, mock_token): mock_get.return_value.status_code = 200 diff --git a/tests/data/mock_benefits_claims.py b/tests/data/mock_benefits_claims.py index 0677678..08acc36 100644 --- a/tests/data/mock_benefits_claims.py +++ b/tests/data/mock_benefits_claims.py @@ -121,3 +121,16 @@ }, } } + +MOCK_INTENT_TO_FILE = { + "data": { + "id": "184058", + "type": "intent_to_file", + "attributes": { + "creation_date": "2020-08-10T08:31:26.000-05:00", + "expiration_date": "2021-08-10T08:31:18.000-05:00", + "type": "compensation", + "status": "duplicate", + }, + } +}