diff --git a/backend/audit/fixtures/workbooks/should_fail/federal-awards-audit-findings/has_bad_award_references.xlsx b/backend/audit/fixtures/workbooks/should_fail/federal-awards-audit-findings/has_bad_award_references.xlsx
new file mode 100644
index 0000000000..d0cb9b1138
Binary files /dev/null and b/backend/audit/fixtures/workbooks/should_fail/federal-awards-audit-findings/has_bad_award_references.xlsx differ
diff --git a/backend/audit/fixtures/workbooks/should_pass/award_references/has-lowercase-award-reference.xlsx b/backend/audit/fixtures/workbooks/should_pass/award_references/has-lowercase-award-reference.xlsx
new file mode 100644
index 0000000000..75d94ba831
Binary files /dev/null and b/backend/audit/fixtures/workbooks/should_pass/award_references/has-lowercase-award-reference.xlsx differ
diff --git a/backend/audit/intakelib/checks/check_finding_award_references_pattern.py b/backend/audit/intakelib/checks/check_finding_award_references_pattern.py
new file mode 100644
index 0000000000..e265211253
--- /dev/null
+++ b/backend/audit/intakelib/checks/check_finding_award_references_pattern.py
@@ -0,0 +1,38 @@
+import logging
+from django.core.exceptions import ValidationError
+import re
+from audit.intakelib.intermediate_representation import (
+ get_range_by_name,
+)
+from audit.intakelib.common import (
+ get_message,
+ build_cell_error_tuple,
+)
+
+logger = logging.getLogger(__name__)
+
+# A version of these regexes also exists in Base.libsonnet
+AWARD_REFERENCES_REGEX = r"^AWARD-(?!0000)[0-9]{4}$"
+
+
+# DESCRIPTION
+# Award references should be of format AWARD-####
+# TESTED BY
+# has_bad_award_references.xlsx
+def award_references_pattern(ir):
+ award_references = get_range_by_name(ir, "award_reference")
+ errors = []
+ for index, award_reference in enumerate(award_references["values"]):
+ if not re.match(AWARD_REFERENCES_REGEX, str(award_reference)):
+ errors.append(
+ build_cell_error_tuple(
+ ir,
+ award_references,
+ index,
+ get_message("check_award_references_invalid"),
+ )
+ )
+
+ if len(errors) > 0:
+ logger.info("Raising a validation error.")
+ raise ValidationError(errors)
diff --git a/backend/audit/intakelib/checks/runners.py b/backend/audit/intakelib/checks/runners.py
index 3199df4064..4ef792a63a 100644
--- a/backend/audit/intakelib/checks/runners.py
+++ b/backend/audit/intakelib/checks/runners.py
@@ -1,5 +1,7 @@
from django.core.exceptions import ValidationError
import logging
+
+from .check_finding_award_references_pattern import award_references_pattern
from .check_cluster_names import check_cluster_names
from audit.fixtures.excel import FORM_SECTIONS
from .check_gsa_migration_keyword import check_for_gsa_migration_keyword
@@ -93,6 +95,7 @@
has_all_the_named_ranges(FORM_SECTIONS.FINDINGS_UNIFORM_GUIDANCE),
has_all_required_fields(FORM_SECTIONS.FINDINGS_UNIFORM_GUIDANCE),
has_invalid_yorn_field(FORM_SECTIONS.FINDINGS_UNIFORM_GUIDANCE),
+ award_references_pattern,
prior_references_pattern,
no_repeat_findings,
findings_grid_validation,
diff --git a/backend/audit/intakelib/common/error_messages.py b/backend/audit/intakelib/common/error_messages.py
index fc4c416a06..f2bd641dd7 100644
--- a/backend/audit/intakelib/common/error_messages.py
+++ b/backend/audit/intakelib/common/error_messages.py
@@ -65,6 +65,7 @@
"check_missing_aln_three_digit_extension": "Missing ALN (CFDA) three digit extension",
"check_aln_three_digit_extension_invalid": "The three digit extension should follow one of these formats: ###, RD#, or U##, where # represents a number",
"check_prior_references_invalid": "Prior references must be N/A or a comma-separated list of values in the format 20##-###, for example, 2019-001, 2019-002",
+ "check_award_references_invalid": "Award references must be of the form AWARD-####, for example, AWARD-0001",
"check_additional_award_identification_present": "Missing additional award identification",
"check_federal_program_total": "Federal program total is {}, but should be {}",
"check_cluster_total": "This cluster total is {}, but should be {}",
diff --git a/backend/audit/intakelib/transforms/runners.py b/backend/audit/intakelib/transforms/runners.py
index 32441ffb25..e68d333a96 100644
--- a/backend/audit/intakelib/transforms/runners.py
+++ b/backend/audit/intakelib/transforms/runners.py
@@ -41,6 +41,7 @@
from .xform_add_transform_for_cfda_key import generate_cfda_keys
from .xform_uniform_cluster_names import regenerate_uniform_cluster_names
from .xform_reformat_prior_references import reformat_prior_references
+from .xform_reformat_award_references import reformat_award_reference
logger = logging.getLogger(__name__)
@@ -73,7 +74,7 @@ def run_all_audit_findings_text_transforms(ir):
def run_all_audit_findings_transforms(ir):
- return run_all_transforms(ir, general_transforms)
+ return run_all_transforms(ir, audit_findings_transforms)
def run_all_corrective_action_plan_transforms(ir):
@@ -108,5 +109,6 @@ def run_all_secondary_auditors_transforms(ir):
]
audit_findings_transforms = general_transforms + [
+ reformat_award_reference,
reformat_prior_references,
]
diff --git a/backend/audit/intakelib/transforms/xform_reformat_award_references.py b/backend/audit/intakelib/transforms/xform_reformat_award_references.py
new file mode 100644
index 0000000000..b9b7b4a683
--- /dev/null
+++ b/backend/audit/intakelib/transforms/xform_reformat_award_references.py
@@ -0,0 +1,15 @@
+import logging
+from audit.intakelib.intermediate_representation import (
+ get_range_by_name,
+ replace_range_by_name,
+)
+
+logger = logging.getLogger(__name__)
+
+
+# Tested by has_lowercase_award_reference.py
+def reformat_award_reference(ir):
+ references = get_range_by_name(ir, "award_reference")
+ new_values = list(map(lambda v: v.upper() if v else v, references["values"]))
+ new_ir = replace_range_by_name(ir, "award_reference", new_values)
+ return new_ir