diff --git a/backend/audit/cross_validation/naming.py b/backend/audit/cross_validation/naming.py index ed8885078b..de31a73966 100644 --- a/backend/audit/cross_validation/naming.py +++ b/backend/audit/cross_validation/naming.py @@ -1,21 +1,7 @@ +from types import new_class from typing import NamedTuple -# There's no way around it, we need a canonical source of the different versions of each -# name. -""" - Starting point: - - "AdditionalUEIs": "additional_ueis", - "AdditionalUEIs": "additional_ueis", - "AuditInformation": "audit_information", - "CorrectiveActionPlan": "corrective_action_plan", - "FederalAwards": "federal_awards", - "FindingsText": "findings_text", - "FindingsUniformGuidance": "findings_uniform_guidance", - "GeneralInformation": "general_information", - "NotesToSefa": "notes_to_sefa", - "TribalDataConsent": "tribal_data_consent", -""" +# We need a canonical source of the different versions of each name. class SectionBabelFish(NamedTuple): @@ -157,6 +143,31 @@ class SectionBabelFish(NamedTuple): } +# The following sets up NameConstantMetaclass as a metaclass that has property methods +# for all of its attributes, then creates NC with NameConstantMetaclass as its +# metaclass. The resulting class has each of the all_caps names from SECTION_NAMES as a +# class-level attribute that is also read-only. +# Thus e.g. NC.GENERAL_INFORMATION will return "general_information" and be read-only. + + +def _readonly(value): + """ + Given a value, return property method for that value, in order to set up the + metaclass below. + """ + return property(lambda self: value) + + +nameproperties = { + guide.all_caps: _readonly(guide.snake_case) for guide in SECTION_NAMES.values() +} + + +NameConstantsMetaclass = type("NCM", (type,), nameproperties) +NC = new_class("NC", (object,), {"metaclass": NameConstantsMetaclass}) + + +# Helper funtions: def find_section_by_name(name): """ Find the answers, first trying snake_case and then all the other versions. diff --git a/backend/audit/cross_validation/sac_validation_shape.py b/backend/audit/cross_validation/sac_validation_shape.py index 58ebe36804..0edbc19aff 100644 --- a/backend/audit/cross_validation/sac_validation_shape.py +++ b/backend/audit/cross_validation/sac_validation_shape.py @@ -1,10 +1,11 @@ from audit.cross_validation.naming import ( + NC, SECTION_NAMES, camel_to_snake, snake_to_camel, ) -at_root_sections = ("audit_information", "general_information") +at_root_sections = (NC.AUDIT_INFORMATION, NC.GENERAL_INFORMATION) # type: ignore def get_shaped_section(sac, section_name): @@ -34,7 +35,7 @@ def sac_validation_shape(sac): that's appropriate for passing to the validation functions. For example, if the Audit Information and Notes to SEFA sections have content, - this function wil return something like: + this function will return something like: { "sf_sac_sections": { diff --git a/backend/audit/cross_validation/submission_progress_check.py b/backend/audit/cross_validation/submission_progress_check.py index 2f7a161687..07165d6f65 100644 --- a/backend/audit/cross_validation/submission_progress_check.py +++ b/backend/audit/cross_validation/submission_progress_check.py @@ -1,4 +1,4 @@ -from audit.cross_validation.naming import find_section_by_name +from audit.cross_validation.naming import NC, find_section_by_name def submission_progress_check(sac, sar=None, crossval=True): @@ -36,10 +36,10 @@ def submission_progress_check(sac, sar=None, crossval=True): } """ # TODO: remove these once tribal data consent are implemented: - del sac["sf_sac_sections"]["tribal_data_consent"] + del sac["sf_sac_sections"][NC.TRIBAL_DATA_CONSENT] # Add the status of the SAR into the list of sections: - sac["sf_sac_sections"]["single_audit_report"] = bool(sar) + sac["sf_sac_sections"][NC.SINGLE_AUDIT_REPORT] = bool(sar) result = {k: None for k in sac["sf_sac_sections"]} @@ -87,22 +87,22 @@ def get_num_findings(award): "section_name": key, } awards = {} - if sections["federal_awards"]: - awards = sections.get("federal_awards", {}).get("federal_awards", []) - general_info = sections.get("general_information", {}) or {} + if sections[NC.FEDERAL_AWARDS]: + awards = sections.get(NC.FEDERAL_AWARDS, {}).get(NC.FEDERAL_AWARDS, []) + general_info = sections.get(NC.GENERAL_INFORMATION, {}) or {} num_findings = sum(get_num_findings(award) for award in awards) conditions = { - "general_information": True, - "audit_information": True, - "federal_awards": True, - "notes_to_sefa": True, - "findings_uniform_guidance": num_findings > 0, - "findings_text": num_findings > 0, - "corrective_action_plan": num_findings > 0, - "additional_ueis": bool(general_info.get("multiple_ueis_covered")), - "additional_eins": bool(general_info.get("multiple_eins_covered")), - "secondary_auditors": bool(general_info.get("secondary_auditors_exist")), - "single_audit_report": True, + NC.GENERAL_INFORMATION: True, + NC.AUDIT_INFORMATION: True, + NC.FEDERAL_AWARDS: True, + NC.NOTES_TO_SEFA: True, + NC.FINDINGS_UNIFORM_GUIDANCE: num_findings > 0, + NC.FINDINGS_TEXT: num_findings > 0, + NC.CORRECTIVE_ACTION_PLAN: num_findings > 0, + NC.ADDITIONAL_UEIS: bool(general_info.get("multiple_ueis_covered")), + NC.ADDITIONAL_EINS: bool(general_info.get("multiple_eins_covered")), + NC.SECONDARY_AUDITORS: bool(general_info.get("secondary_auditors_exist")), + NC.SINGLE_AUDIT_REPORT: True, } # If it's not required, it's inactive: