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