Skip to content

Commit

Permalink
YAUF (#2446)
Browse files Browse the repository at this point in the history
* YAUF.

* Add test for oddly-shaped results.
  • Loading branch information
tadhg-ohiggins authored Oct 11, 2023
1 parent 32a9257 commit 94d1f48
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 27 deletions.
106 changes: 79 additions & 27 deletions backend/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,40 +72,92 @@ def validate_user_provided_organization_type(self, value):


class UEISerializer(serializers.Serializer):
"""
Does a UEI request against the SAM.gov API and returns a flattened shape
containing only the fields we're interested in.
The below operations are nested and mixed among functions, rather than done
serially, but the approximate order of operations is:
+ Assemble the parameters to pass to the API.
Mostly in api.uei.get_uei_info_from_sam_gov.
+ Make the API request.
api.uei.call_sam_api
+ Check for high-level errors.
api.uei.get_uei_info_from_sam_gov
+ Extract the JSON for the individual record out of the response and check
for some other errors.
api.uei.parse_sam_uei_json
+ For a specific class of error, retry the API call with different
parameters.
api.uei.get_uei_info_from_sam_gov
api.uei.call_sam_api
api.uei.parse_sam_uei_json
+ If we don't have errors by that point, flatten the data.
api.serializsers.UEISerializer.validate_auditee_uei
+ If we don't encounter errors at that point, return the flattened data.
api.serializsers.UEISerializer.validate_auditee_uei
"""

auditee_uei = serializers.CharField()

def validate_auditee_uei(self, value):
"""
Flattens the UEI response info and returns this shape:
{
"auditee_uei": …,
"auditee_name": …,
"auditee_fiscal_year_end_date": …,
"auditee_address_line_1": …,
"auditee_city": …,
"auditee_state": …,
"auditee_zip": …,
}
Will provide default error-message-like values (such as “No address in SAM.gov)
if the keys are missing, but if the SAM.gov fields are present but empty, we
return the empty strings.
"""
sam_response = get_uei_info_from_sam_gov(value)
if sam_response.get("errors"):
raise serializers.ValidationError(sam_response.get("errors"))
return json.dumps(
{
"auditee_uei": value,
"auditee_name": sam_response.get("response")
.get("entityRegistration")
.get("legalBusinessName"),
"auditee_fiscal_year_end_date": sam_response.get("response")
.get("coreData")
.get("entityInformation")
.get("fiscalYearEndCloseDate"),
"auditee_address_line_1": sam_response.get("response")
.get("coreData")
.get("mailingAddress")
.get("addressLine1"),
"auditee_city": sam_response.get("response")
.get("coreData")
.get("mailingAddress")
.get("city"),
"auditee_state": sam_response.get("response")
.get("coreData")
.get("mailingAddress")
.get("stateOrProvinceCode"),
"auditee_zip": sam_response.get("response")
.get("coreData")
.get("mailingAddress")
.get("zipCode"),

entity_registration = sam_response.get("response")["entityRegistration"]
core = sam_response.get("response")["coreData"]

basic_data = {
"auditee_uei": value,
"auditee_name": entity_registration.get("legalBusinessName"),
}
addr_key = "mailingAddress" if "mailingAddress" in core else "physicalAddress"

mailing_data = {
"auditee_address_line_1": "No address in SAM.gov.",
"auditee_city": "No address in SAM.gov.",
"auditee_state": "No address in SAM.gov.",
"auditee_zip": "No address in SAM.gov.",
}

if addr_key in core:
mailing_data = {
"auditee_address_line_1": core.get(addr_key).get("addressLine1"),
"auditee_city": core.get(addr_key).get("city"),
"auditee_state": core.get(addr_key).get("stateOrProvinceCode"),
"auditee_zip": core.get(addr_key).get("zipCode"),
}
)

# 2023-10-10: Entities with a samRegistered value of No may be missing
# some fields from coreData entirely.
entity_information = core.get("entityInformation", {})
extra_data = {
"auditee_fiscal_year_end_date": entity_information.get(
"fiscalYearEndCloseDate", "No fiscal year end date in SAM.gov."
),
}
return json.dumps(basic_data | mailing_data | extra_data)


class AuditeeInfoSerializer(serializers.Serializer):
Expand Down
54 changes: 54 additions & 0 deletions backend/api/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,60 @@ def test_invalid_uei_payload(self):
# Invalid
self.assertFalse(UEISerializer(data=invalid).is_valid())

def test_quirky_uei_payload(self):
"""
It turns out that some entries can be missing fields that we thought would
always be present.
"""
quirky = {
"totalRecords": 1,
"entityData": [
{
"entityRegistration": {
"samRegistered": "No",
"ueiSAM": "ZQGGHJH74DW7",
"cageCode": None,
"legalBusinessName": "Some organization",
"registrationStatus": "ID Assigned",
"evsSource": "X&Y",
"ueiStatus": "Active",
"ueiExpirationDate": None,
"ueiCreationDate": "2023-04-01",
"publicDisplayFlag": "Y",
"dnbOpenData": None,
},
"coreData": {
"physicalAddress": {
"addressLine1": "SOME RD",
"addressLine2": None,
"city": "SOME CITY",
"stateOrProvinceCode": "AL",
"zipCode": "36659",
"zipCodePlus4": "3903",
"countryCode": "USA",
}
},
}
],
"links": {
"selfLink": "https://api.sam.gov/entity-information/v3/entities?api_key=REPLACE_WITH_API_KEY&ueiSAM=ZQGGHJH74DW7&samRegistered=No&page=0&size=10"
},
}
empty = {"totalRecords": 0, "entityData": []}
data = {"auditee_uei": "ZQGGHJH74DW7"}
with patch("api.uei.SESSION.get") as mock_get:
mock_get.return_value.status_code = 200
# First time, it should return zero results and retry:
mock_get.return_value.json.return_value = empty
self.assertFalse(UEISerializer(data=data).is_valid())
self.assertEqual(mock_get.call_count, 2)
# Second time, it should return the samRegistered No result and only
# call the get method once:
mock_get.return_value.json.return_value = quirky
self.assertTrue(UEISerializer(data=data).is_valid())
self.assertEqual(mock_get.call_count, 3)


class AuditeeInfoStepTests(SimpleTestCase):
def test_valid_auditee_info_with_uei(self):
Expand Down

0 comments on commit 94d1f48

Please sign in to comment.