From 14afcc4511344515e3734d3210cd0cdb2556d291 Mon Sep 17 00:00:00 2001 From: "Hassan D. M. Sambo" Date: Thu, 17 Aug 2023 19:47:22 -0400 Subject: [PATCH] #1848 Added cross validation for award reference uniqueness --- backend/audit/cross_validation/__init__.py | 2 + .../check_award_reference_uniqueness.py | 33 +++++++++++++ backend/audit/cross_validation/errors.py | 7 +++ .../test_check_award_reference_uniqueness.py | 46 +++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 backend/audit/cross_validation/check_award_reference_uniqueness.py create mode 100644 backend/audit/cross_validation/test_check_award_reference_uniqueness.py diff --git a/backend/audit/cross_validation/__init__.py b/backend/audit/cross_validation/__init__.py index 4a61bf46f9..e8561ef20d 100644 --- a/backend/audit/cross_validation/__init__.py +++ b/backend/audit/cross_validation/__init__.py @@ -55,6 +55,7 @@ ] """ +from .check_award_reference_uniqueness import check_award_reference_uniqueness from .number_of_findings import number_of_findings from .additional_ueis import additional_ueis from .auditee_ueis_match import auditee_ueis_match @@ -69,6 +70,7 @@ auditee_ueis_match, additional_ueis, award_ref_and_references_uniqueness, + check_award_reference_uniqueness, number_of_findings, submission_progress_check, tribal_data_sharing_consent, diff --git a/backend/audit/cross_validation/check_award_reference_uniqueness.py b/backend/audit/cross_validation/check_award_reference_uniqueness.py new file mode 100644 index 0000000000..833edc3796 --- /dev/null +++ b/backend/audit/cross_validation/check_award_reference_uniqueness.py @@ -0,0 +1,33 @@ +from .errors import ( + err_duplicate_award_reference, +) +from collections import Counter + + +def check_award_reference_uniqueness(sac_dict, *_args, **_kwargs): + """ + Check that all award references in the Federal Award workbook are distinct. + """ + + all_sections = sac_dict.get("sf_sac_sections", {}) + federal_awards_section = all_sections.get("federal_awards") or {} + federal_awards = federal_awards_section.get("federal_awards", []) + award_refs = [] + errors = [] + + for award in federal_awards: + award_reference = award.get("award_reference", None) + if award_reference: + award_refs.append(award_reference) + + duplicated_award_refs = _find_duplicates(award_refs) + + for dup_award_ref in duplicated_award_refs: + errors.append({"error": err_duplicate_award_reference(dup_award_ref)}) + + return errors + + +def _find_duplicates(lst): + count = Counter(lst) + return [item for item, count in count.items() if count > 1] diff --git a/backend/audit/cross_validation/errors.py b/backend/audit/cross_validation/errors.py index 0e2fc8499c..6a7c0ccc14 100644 --- a/backend/audit/cross_validation/errors.py +++ b/backend/audit/cross_validation/errors.py @@ -45,3 +45,10 @@ def err_number_of_findings_inconsistent(total_expected, total_counted, award_ref f"You reported {total_expected} findings for award {award_ref} in the {SECTION_NAMES.FEDERAL_AWARDS} workbook, " f"but declared {total_counted} findings for the same award in the {SECTION_NAMES.FEDERAL_AWARDS_AUDIT_FINDINGS} workbook." ) + + +def err_duplicate_award_reference(award_ref): + return ( + f"The award reference {award_ref} shows up more than once. " + f"This should not be possible. Please contact customer support." + ) diff --git a/backend/audit/cross_validation/test_check_award_reference_uniqueness.py b/backend/audit/cross_validation/test_check_award_reference_uniqueness.py new file mode 100644 index 0000000000..65aa872a35 --- /dev/null +++ b/backend/audit/cross_validation/test_check_award_reference_uniqueness.py @@ -0,0 +1,46 @@ +from django.test import TestCase +from audit.models import SingleAuditChecklist +from .check_award_reference_uniqueness import check_award_reference_uniqueness +from .sac_validation_shape import sac_validation_shape +from .errors import err_duplicate_award_reference +from model_bakery import baker + + +class CheckAwardReferenceUniquenessTests(TestCase): + def setUp(self): + """Set up the common variables for the test cases.""" + self.award1 = {"award_reference": "AWARD-0001"} + self.award2 = {"award_reference": "AWARD-2219"} + self.award3 = {"award_reference": "AWARD-2910"} + + def _make_federal_awards(self, award_refs) -> dict: + return { + "FederalAwards": { + "federal_awards": award_refs, + } + } + + def _make_sac(self, award_refs) -> SingleAuditChecklist: + sac = baker.make(SingleAuditChecklist) + sac.federal_awards = self._make_federal_awards(award_refs) + return sac + + def test_unique_award_references_raise_no_errors(self): + """When all award references are unique, no errors should be raised.""" + sac = self._make_sac([self.award1, self.award2, self.award3]) + errors = check_award_reference_uniqueness(sac_validation_shape(sac)) + self.assertEqual(errors, []) + + def test_duplicate_award_references_raise_errors(self): + """When there are duplicate award references, the appropriate errors should be raised.""" + sac = self._make_sac( + [self.award1, self.award1, self.award3, self.award3, self.award3] + ) + errors = errors = check_award_reference_uniqueness(sac_validation_shape(sac)) + self.assertEqual(len(errors), 2) + + errors = check_award_reference_uniqueness(sac_validation_shape(sac)) + expected_error1 = err_duplicate_award_reference(self.award1["award_reference"]) + expected_error2 = err_duplicate_award_reference(self.award3["award_reference"]) + self.assertIn({"error": expected_error1}, errors) + self.assertIn({"error": expected_error2}, errors)