Skip to content

Commit

Permalink
Make section names constants. (#1956)
Browse files Browse the repository at this point in the history
Metaclass wrangling to set up a class that has read-only class-level attributes containing the all_caps section names, each of which has the corresponding snake_case as its value.

Co-authored-by: James Person <[email protected]>
  • Loading branch information
tadhg-ohiggins and jperson1 authored Aug 30, 2023
1 parent 077a8f3 commit 0d0c126
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 35 deletions.
43 changes: 27 additions & 16 deletions backend/audit/cross_validation/naming.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions backend/audit/cross_validation/sac_validation_shape.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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": {
Expand Down
34 changes: 17 additions & 17 deletions backend/audit/cross_validation/submission_progress_check.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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"]}

Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 0d0c126

Please sign in to comment.