From 42136d69eee0f663ca8d36130f6f831db07f3ab4 Mon Sep 17 00:00:00 2001 From: Aldrian Harjati Date: Thu, 17 Aug 2023 10:50:31 -0400 Subject: [PATCH 1/4] add anonymous validate and upload endpoint --- .devcontainer/devcontainer.json | 1 - .devcontainer/requirements.txt | 5 +- config.py | 8 +- src/__init__.py | 5 + src/api/__init__.py | 9 + src/api/main.py | 44 + src/api/routers/__init__.py | 3 + src/api/routers/test_data.txt | 252 + src/api/routers/validate.py | 44 + src/tests/__init__.py | 10 +- src/tests/test_check_functions.py | 55 +- src/tests/test_schema_functions.py | 298 ++ src/util/__init__.py | 9 + src/{validator => util}/global_data.py | 31 +- src/validator/__init__.py | 8 +- src/validator/create_schemas.py | 154 +- src/validator/main.py | 46 +- src/validator/phase_validations.py | 6209 +++++++++++----------- src/validator/schema.py | 6614 ++++++++++++------------ src/validator/schema_template.py | 2 + tools/__init__.py | 7 + tools/process_census.py | 20 +- tools/process_naics.py | 10 +- 23 files changed, 7338 insertions(+), 6506 deletions(-) create mode 100644 src/__init__.py create mode 100644 src/api/__init__.py create mode 100644 src/api/main.py create mode 100644 src/api/routers/__init__.py create mode 100644 src/api/routers/test_data.txt create mode 100644 src/api/routers/validate.py create mode 100644 src/tests/test_schema_functions.py create mode 100644 src/util/__init__.py rename src/{validator => util}/global_data.py (53%) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 876eb4f1..c107859a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -34,7 +34,6 @@ ], "editor.tabSize": 4, "editor.formatOnSave": true, - "python.formatting.provider": "black", "python.envFile": "${workspaceFolder}/.env", "editor.codeActionsOnSave": { "source.organizeImports": true diff --git a/.devcontainer/requirements.txt b/.devcontainer/requirements.txt index ffc429af..3e3b1519 100644 --- a/.devcontainer/requirements.txt +++ b/.devcontainer/requirements.txt @@ -5,4 +5,7 @@ pandas==1.5.3 pandera==0.14.5 ipykernel openpyxl -pytest \ No newline at end of file +pytest +python-multipart +uvicorn +fastapi \ No newline at end of file diff --git a/config.py b/config.py index 00a125f2..c2a46585 100644 --- a/config.py +++ b/config.py @@ -1,16 +1,16 @@ # path to original/raw NAICS excel file -NAICS_EXCEL_PATH = "./data/naics/raw/2-6 digit_2022_Codes.xlsx" +NAICS_EXCEL_PATH = "data/naics/raw/2-6 digit_2022_Codes.xlsx" # path to parsed/filtered naics codes file -NAICS_CSV_PATH = "./data/naics/processed/2022_codes.csv" +NAICS_CSV_PATH = "data/naics/processed/2022_codes.csv" # column header text containing naics code NAICS_CODE_COL = "2022 NAICS US Code" # column header text containing naics title/description NAICS_TITLE_COL = "2022 NAICS US Title" # path to original/raw NAICS zip file -CENSUS_RAW_ZIP_PATH = "./data/census/raw/CensusFlatFile2022.zip" +CENSUS_RAW_ZIP_PATH = "data/census/raw/CensusFlatFile2022.zip" # path to parsed/filtered naics codes file -CENSUS_PROCESSED_CSV_PATH = "./data/census/processed/Census2022.processed.csv" +CENSUS_PROCESSED_CSV_PATH = "data/census/processed/Census2022.processed.csv" # census file col indexes CENSUS_STATE_COL_INDEX = 2 CENSUS_COUNTY_COL_INDEX = 3 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 00000000..f63e9260 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,5 @@ +import os +import sys + +CURR_DIR = os.path.dirname(os.path.abspath(__file__)) # noqa: E402 +sys.path.append(CURR_DIR) # noqa: E402 diff --git a/src/api/__init__.py b/src/api/__init__.py new file mode 100644 index 00000000..854f4390 --- /dev/null +++ b/src/api/__init__.py @@ -0,0 +1,9 @@ +import os +import sys + +CURR_DIR = os.path.dirname(os.path.abspath(__file__)) # noqa: E402 +PARENT_DIR = os.path.dirname(CURR_DIR) # noqa: E402 +ROOT_DIR = os.path.dirname(PARENT_DIR) # noqa: E402 +sys.path.append(CURR_DIR) # noqa: E402 +sys.path.append(PARENT_DIR) # noqa: E402 +sys.path.append(ROOT_DIR) # noqa: E402 \ No newline at end of file diff --git a/src/api/main.py b/src/api/main.py new file mode 100644 index 00000000..52fb6f8d --- /dev/null +++ b/src/api/main.py @@ -0,0 +1,44 @@ +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse +from routers import validate_router + +from util.global_data import read_geoids, read_naics_codes +from validator.create_schemas import get_schemas + +# HOWTO +# run: uvicorn main:app --host 0.0.0.0 --port 8080 --reload + +app = FastAPI() +# get our schems +app.schemas = get_schemas(read_naics_codes(), read_geoids()) + + +# custom exception handlers +@app.exception_handler(Exception) +async def api_exception_handler(request: Request, err: Exception): + """ + handle exception/error that is triggered by incorrect data format + + Args: + request (Request): HTTP request + err (Exception): the actual exception object + + Returns: + returns JSON Response with status code and message + """ + error_code = (400,) + error_msg = str(err) + if type(err) is ValueError: + error_code = 422 + elif type(err) is AttributeError: + error_code = 500 + error_msg = f"Internal Server Error. Type: {str(type(err))}" + + return JSONResponse( + status_code=error_code, + content=[{"response": error_msg}], + ) + + +# add anonymous validation endpoints +app.include_router(validate_router, prefix="/v1") diff --git a/src/api/routers/__init__.py b/src/api/routers/__init__.py new file mode 100644 index 00000000..40a1da29 --- /dev/null +++ b/src/api/routers/__init__.py @@ -0,0 +1,3 @@ +__all__ = ["validate_router"] + +from .validate import router as validate_router diff --git a/src/api/routers/test_data.txt b/src/api/routers/test_data.txt new file mode 100644 index 00000000..5f1fd2ad --- /dev/null +++ b/src/api/routers/test_data.txt @@ -0,0 +1,252 @@ +{ +"uid" : ["000TESTFIUIDDONOTUSEXGXVID11XTC1"], +"app_date" : ["20241201"], +"app_method" : ["1" ] , +"app_recipient" : ["1" ] , +"ct_credit_product" : ["988" ] , +"ct_credit_product_ff" :[ "" ] , +"ct_guarantee" :[ "999"] , +"ct_guarantee_ff" : [""] , +"ct_loan_term_flag" : ["999" ] , +"ct_loan_term" :[ ""] , +"credit_purpose" :[ "999" ] , +"credit_purpose_ff" : [""] , +"amount_applied_for_flag" : ["999"] , +"amount_applied_for" : ["" ] , +"amount_approved" : ["" ] , +"action_taken" : ["5" ] , +"action_taken_date" : ["20241231" ] , +"denial_reasons" : ["999" ] , +"denial_reasons_ff" : ["" ] , +"pricing_interest_rate_type" : ["999"] , +"pricing_init_rate_period" :[ "" ] , +"pricing_fixed_rate" :[ "" ] , +"pricing_adj_margin" :[ "" ] , +"pricing_adj_index_name" :[ "999" ] , +"pricing_adj_index_name_ff" : ["" ] , +"pricing_adj_index_value" : ["" ] , +"pricing_origination_charges" : ["" ] , +"pricing_broker_fees" :[ "" ] , +"pricing_initial_charges" : [""] , +"pricing_mca_addcost_flag" : ["999" ] , +"pricing_mca_addcost" :[ "" ] , +"pricing_prepenalty_allowed" : ["999" ] , +"pricing_prepenalty_exists" : ["999" ] , +"census_tract_adr_type" : ["988" ] , +"census_tract_number" : ["" ] , +"gross_annual_revenue_flag" : ["988" ] , +"gross_annual_revenue" : ["" ] , +"naics_code_flag" : ["988" ] , +"naics_code" :[ "" ] , +"number_of_workers" : ["988" ] , +"time_in_business_type" : ["988" ] , +"time_in_business" :[ "" ] , +"business_ownership_status" : ["988" ] , +"num_principal_owners_flag" : ["988"] , +"num_principal_owners" : ["" ] , +"po_1_ethnicity" : ["" ] , +"po_1_ethnicity_ff" : [""] , +"po_1_race" : ["" ] , +"po_1_race_anai_ff" : ["" ] , +"po_1_race_asian_ff" : ["" ] , +"po_1_race_baa_ff" : ["" ], +"po_1_race_pi_ff" : ["" ], +"po_1_gender_flag" : ["" ] , +"po_1_gender_ff" : ["" ] , +"po_2_ethnicity" : [""] , +"po_2_ethnicity_ff" : ["" ] , +"po_2_race" : ["" ] , +"po_2_race_anai_ff" : ["" ] , +"po_2_race_asian_ff" : ["" ] , +"po_2_race_baa_ff" : [""] , +"po_2_race_pi_ff" : [""] , +"po_2_gender_flag" : ["" ] , +"po_2_gender_ff" : ["" ] , +"po_3_ethnicity" :[ "" ] , +"po_3_ethnicity_ff" : [""] , + "po_3_race" : [""] , + "po_3_race_anai_ff" : ["" ] , + "po_3_race_asian_ff" : ["" ] , + "po_3_race_baa_ff" :[ "" ] , + "po_3_race_pi_ff" : ["" ] , + "po_3_gender_flag" :[ "" ] , + "po_3_gender_ff" : ["" ] , + "po_4_ethnicity" :[ ""] , + "po_4_ethnicity_ff" : ["" ] , + "po_4_race" : [""] , + "po_4_race_anai_ff" : [""] , + "po_4_race_asian_ff" : ["" ] , + "po_4_race_baa_ff" :[ "" ] , + "po_4_race_pi_ff" : ["" ] , + "po_4_gender_flag" :[ "" ] , + "po_4_gender_ff": [""] + +} + + +{ +"uid" : "000TESTFIUIDDONOTUSEXGXVID11XTC1", +"app_date" : "20241201", +"app_method" : "1" , +"app_recipient" : "1" , +"ct_credit_product" : "988" , +"ct_credit_product_ff" : "" , +"ct_guarantee" : "999" , +"ct_guarantee_ff" : "" , +"ct_loan_term_flag" : "999" , +"ct_loan_term" : "" , +"credit_purpose" : "999" , +"credit_purpose_ff" : "" , +"amount_applied_for_flag" : "999" , +"amount_applied_for" : "" , +"amount_approved" : "" , +"action_taken" : "5" , +"action_taken_date" : "20241231" , +"denial_reasons" : "999" , +"denial_reasons_ff" : "" , +"pricing_interest_rate_type" : "999" , +"pricing_init_rate_period" : "" , +"pricing_fixed_rate" : "" , +"pricing_adj_margin" : "" , +"pricing_adj_index_name" : "999" , +"pricing_adj_index_name_ff" : "" , +"pricing_adj_index_value" : "" , +"pricing_origination_charges" : "" , +"pricing_broker_fees" : "" , +"pricing_initial_charges" : "" , +"pricing_mca_addcost_flag" : "999" , +"pricing_mca_addcost" : "" , +"pricing_prepenalty_allowed" : "999" , +"pricing_prepenalty_exists" : "999" , +"census_tract_adr_type" : "988" , +"census_tract_number" : "" , +"gross_annual_revenue_flag" : "988" , +"gross_annual_revenue" : "" , +"naics_code_flag" : "988" , +"naics_code" : "" , +"number_of_workers" : "988" , +"time_in_business_type" : "988" , +"time_in_business" : "" , +"business_ownership_status" : "988" , +"num_principal_owners_flag" : "988" , +"num_principal_owners" : "" , +"po_1_ethnicity" : "" , +"po_1_ethnicity_ff" : "" , +"po_1_race" : "" , +"po_1_race_anai_ff" : "" , +"po_1_race_asian_ff" : "" , +"po_1_race_baa_ff" : "" , +"po_1_race_pi_ff" : "" , +"po_1_gender_flag" : "" , +"po_1_gender_ff" : "" , +"po_2_ethnicity" : "" , +"po_2_ethnicity_ff" : "" , +"po_2_race" : "" , +"po_2_race_anai_ff" : "" , +"po_2_race_asian_ff" : "" , +"po_2_race_baa_ff" : "" , +"po_2_race_pi_ff" : "" , +"po_2_gender_flag" : "" , +"po_2_gender_ff" : "" , +"po_3_ethnicity" : "" , +"po_3_ethnicity_ff" : "" , + "po_3_race" : "" , + "po_3_race_anai_ff" : "" , + "po_3_race_asian_ff" : "" , + "po_3_race_baa_ff" : "" , + "po_3_race_pi_ff" : "" , + "po_3_gender_flag" : "" , + "po_3_gender_ff" : "" , + "po_4_ethnicity" : "" , + "po_4_ethnicity_ff" : "" , + "po_4_race" : "" , + "po_4_race_anai_ff" : "" , + "po_4_race_asian_ff" : "" , + "po_4_race_baa_ff" : "" , + "po_4_race_pi_ff" : "" , + "po_4_gender_flag" : "" , + "po_4_gender_ff": "" +} + + +"uid" +"app_date" +"app_method" +"app_recipient" +"ct_credit_product" +"ct_credit_product_ff" +"ct_guarantee" +"ct_guarantee_ff" +"ct_loan_term_flag" +"ct_loan_term" +"credit_purpose" +"credit_purpose_ff" +"amount_applied_for_flag" +"amount_applied_for" +"amount_approved" +"action_taken" +"action_taken_date" +"denial_reasons" +"denial_reasons_ff" +"pricing_interest_rate_type" +"pricing_init_rate_period" +"pricing_fixed_rate" +"pricing_adj_margin" +"pricing_adj_index_name" +"pricing_adj_index_name_ff" +"pricing_adj_index_value" +"pricing_origination_charges" +"pricing_broker_fees" +"pricing_initial_charges" +"pricing_mca_addcost_flag" +"pricing_mca_addcost" +"pricing_prepenalty_allowed" +"pricing_prepenalty_exists" +"census_tract_adr_type" +"census_tract_number" +"gross_annual_revenue_flag" +"gross_annual_revenue" +"naics_code_flag" +"naics_code" +"number_of_workers" +"time_in_business_type" +"time_in_business" +"business_ownership_status" +"num_principal_owners_flag" +"num_principal_owners" +"po_1_ethnicity" +"po_1_ethnicity_ff" +"po_1_race" +"po_1_race_anai_ff" +"po_1_race_asian_ff" +"po_1_race_baa_ff" +"po_1_race_pi_ff" +"po_1_gender_flag" +"po_1_gender_ff" +"po_2_ethnicity" +"po_2_ethnicity_ff" +"po_2_race" +"po_2_race_anai_ff" +"po_2_race_asian_ff" +"po_2_race_baa_ff" +"po_2_race_pi_ff" +"po_2_gender_flag" +"po_2_gender_ff" +"po_3_ethnicity" +"po_3_ethnicity_ff" + "po_3_race" + "po_3_race_anai_ff" + "po_3_race_asian_ff" + "po_3_race_baa_ff" + "po_3_race_pi_ff" + "po_3_gender_flag" + "po_3_gender_ff" + "po_4_ethnicity" + "po_4_ethnicity_ff" + "po_4_race" + "po_4_race_anai_ff" + "po_4_race_asian_ff" + "po_4_race_baa_ff" + "po_4_race_pi_ff" + "po_4_gender_flag" + "po_4_gender_ff" \ No newline at end of file diff --git a/src/api/routers/validate.py b/src/api/routers/validate.py new file mode 100644 index 00000000..3a57d935 --- /dev/null +++ b/src/api/routers/validate.py @@ -0,0 +1,44 @@ +from fastapi import APIRouter, Request, UploadFile +from fastapi.responses import JSONResponse + +from validator.create_schemas import validate_data_list, validate_raw_csv + +# validate router +router = APIRouter() + + +@router.post("/validate/") +async def validate(request: Request, payload: dict): + """ + handle validate endpoint + + Args: + request (Request): request object + payload (dict): data received from user + + Returns: + JSON response + """ + phase1_schema = request.app.schemas[0] + phase2_schema = request.app.schemas[1] + response = validate_data_list(phase1_schema, phase2_schema, payload) + return JSONResponse(status_code=200, content=response) + + +@router.post("/upload/") +async def upload(request: Request, file: UploadFile): + """ + handle upload request + + Args: + request (Request): request object + file (UploadFile): uploaded file + + Returns: + JSON response + """ + contents = file.file.read() + phase1_schema = request.app.schemas[0] + phase2_schema = request.app.schemas[1] + response = validate_raw_csv(phase1_schema, phase2_schema, contents) + return JSONResponse(status_code=200, content=response) diff --git a/src/tests/__init__.py b/src/tests/__init__.py index 6e9e654f..ce64fc3b 100644 --- a/src/tests/__init__.py +++ b/src/tests/__init__.py @@ -1,6 +1,10 @@ import os import sys -ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - -sys.path.append(os.path.join(ROOT_DIR, "validator")) \ No newline at end of file +CURR_DIR = os.path.dirname(os.path.abspath(__file__)) # noqa: E402 +PARENT_DIR = os.path.dirname(CURR_DIR) # noqa: E402 +ROOT_DIR = os.path.dirname(PARENT_DIR) # noqa: E402 +sys.path.append(CURR_DIR) # noqa: E402 +sys.path.append(PARENT_DIR) # noqa: E402 +sys.path.append(ROOT_DIR) # noqa: E402 +sys.path.append(os.path.join(ROOT_DIR, "validator")) diff --git a/src/tests/test_check_functions.py b/src/tests/test_check_functions.py index f3e96d68..951ad75f 100644 --- a/src/tests/test_check_functions.py +++ b/src/tests/test_check_functions.py @@ -1,25 +1,19 @@ import pandas as pd -from validator import global_data -from validator.check_functions import ( - has_correct_length, - has_no_conditional_field_conflict, - has_valid_enum_pair, - has_valid_fieldset_pair, - has_valid_format, - has_valid_multi_field_value_count, - has_valid_value_count, - is_date, - is_greater_than, - is_greater_than_or_equal_to, - is_less_than, - is_number, - is_unique_column, - is_unique_in_field, - is_valid_code, - is_valid_enum, - meets_multi_value_field_restriction, -) +from util import global_data +from validator.check_functions import (has_correct_length, + has_no_conditional_field_conflict, + has_valid_enum_pair, + has_valid_fieldset_pair, + has_valid_format, + has_valid_multi_field_value_count, + has_valid_value_count, is_date, + is_greater_than, + is_greater_than_or_equal_to, + is_less_than, is_number, + is_unique_column, is_unique_in_field, + is_valid_code, is_valid_enum, + meets_multi_value_field_restriction) class TestInvalidDateFormat: @@ -507,32 +501,29 @@ def test_with_incorrect_length(self): class TestIsValidCode: + naics_codes = global_data.read_naics_codes() def test_with_valid_code(self): - global_data.read_naics_codes() - result = is_valid_code("111", False, global_data.naics_codes) + result = is_valid_code("111", False, self.naics_codes) assert result is True - result = is_valid_code("111", True, global_data.naics_codes) + result = is_valid_code("111", True, self.naics_codes) assert result is True def test_with_invalid_code(self): - global_data.read_naics_codes() - result = is_valid_code("101", False, global_data.naics_codes) + result = is_valid_code("101", False, self.naics_codes) assert result is False - result = is_valid_code("101", True, global_data.naics_codes) + result = is_valid_code("101", True, self.naics_codes) assert result is False def test_with_accepted_blank(self): - global_data.read_naics_codes() - result = is_valid_code("", True, global_data.naics_codes) + result = is_valid_code("", True, self.naics_codes) assert result is True - result = is_valid_code(" ", True, global_data.naics_codes) + result = is_valid_code(" ", True, self.naics_codes) assert result is True def test_with_invalid_blank(self): - global_data.read_naics_codes() - result = is_valid_code("", False, global_data.naics_codes) + result = is_valid_code("", False, self.naics_codes) assert result is False - result = is_valid_code(" ", False, global_data.naics_codes) + result = is_valid_code(" ", False, self.naics_codes) assert result is False diff --git a/src/tests/test_schema_functions.py b/src/tests/test_schema_functions.py new file mode 100644 index 00000000..f021ba43 --- /dev/null +++ b/src/tests/test_schema_functions.py @@ -0,0 +1,298 @@ +import pandas as pd + +from util import global_data +from validator.create_schemas import ( + get_schemas, + validate, + validate_data_list, + validate_raw_csv, +) + + +class TestGetSchemas: + naics_codes = global_data.read_naics_codes() + geoids = global_data.read_geoids() + schemas = get_schemas(naics_codes, {}) + + def test_with_valid_codes(self): + naics_phase1_checks = self.schemas[0].columns["naics_code"].checks + naics_phase2_checks = self.schemas[1].columns["naics_code"].checks + invalid_naics_format = naics_phase1_checks[0].name + invalid_naics_value = naics_phase2_checks[1].name + naics_codes = naics_phase2_checks[1]._check_kwargs["codes"] + assert self.naics_codes == naics_codes + assert invalid_naics_format == "naics_code.invalid_naics_format" + assert invalid_naics_value == "naics_code.invalid_naics_value" + + def test_with_invalid_codes(self): + geoids_phase2_checks = self.schemas[1].columns["census_tract_number"].checks + geoids = geoids_phase2_checks[1]._check_kwargs["codes"] + assert self.geoids != geoids + + +class TestUtil: + naics_codes = global_data.read_naics_codes() + geoids = global_data.read_geoids() + schemas = get_schemas(naics_codes, geoids) + valid_response = {"response": "No validations errors or warnings"} + + def get_data(self, update_data: dict[str, list[str]] = {}) -> dict[str, list[str]]: + default = { + "uid": ["000TESTFIUIDDONOTUSEXGXVID11XTC1"], + "app_date": ["20241201"], + "app_method": ["1"], + "app_recipient": ["1"], + "ct_credit_product": ["988"], + "ct_credit_product_ff": [""], + "ct_guarantee": ["999"], + "ct_guarantee_ff": [""], + "ct_loan_term_flag": ["999"], + "ct_loan_term": [""], + "credit_purpose": ["999"], + "credit_purpose_ff": [""], + "amount_applied_for_flag": ["999"], + "amount_applied_for": [""], + "amount_approved": [""], + "action_taken": ["5"], + "action_taken_date": ["20241231"], + "denial_reasons": ["999"], + "denial_reasons_ff": [""], + "pricing_interest_rate_type": ["999"], + "pricing_init_rate_period": [""], + "pricing_fixed_rate": [""], + "pricing_adj_margin": [""], + "pricing_adj_index_name": ["999"], + "pricing_adj_index_name_ff": [""], + "pricing_adj_index_value": [""], + "pricing_origination_charges": [""], + "pricing_broker_fees": [""], + "pricing_initial_charges": [""], + "pricing_mca_addcost_flag": ["999"], + "pricing_mca_addcost": [""], + "pricing_prepenalty_allowed": ["999"], + "pricing_prepenalty_exists": ["999"], + "census_tract_adr_type": ["988"], + "census_tract_number": [""], + "gross_annual_revenue_flag": ["988"], + "gross_annual_revenue": [""], + "naics_code_flag": ["988"], + "naics_code": [""], + "number_of_workers": ["988"], + "time_in_business_type": ["988"], + "time_in_business": [""], + "business_ownership_status": ["988"], + "num_principal_owners_flag": ["988"], + "num_principal_owners": [""], + "po_1_ethnicity": [""], + "po_1_ethnicity_ff": [""], + "po_1_race": [""], + "po_1_race_anai_ff": [""], + "po_1_race_asian_ff": [""], + "po_1_race_baa_ff": [""], + "po_1_race_pi_ff": [""], + "po_1_gender_flag": [""], + "po_1_gender_ff": [""], + "po_2_ethnicity": [""], + "po_2_ethnicity_ff": [""], + "po_2_race": [""], + "po_2_race_anai_ff": [""], + "po_2_race_asian_ff": [""], + "po_2_race_baa_ff": [""], + "po_2_race_pi_ff": [""], + "po_2_gender_flag": [""], + "po_2_gender_ff": [""], + "po_3_ethnicity": [""], + "po_3_ethnicity_ff": [""], + "po_3_race": [""], + "po_3_race_anai_ff": [""], + "po_3_race_asian_ff": [""], + "po_3_race_baa_ff": [""], + "po_3_race_pi_ff": [""], + "po_3_gender_flag": [""], + "po_3_gender_ff": [""], + "po_4_ethnicity": [""], + "po_4_ethnicity_ff": [""], + "po_4_race": [""], + "po_4_race_anai_ff": [""], + "po_4_race_asian_ff": [""], + "po_4_race_baa_ff": [""], + "po_4_race_pi_ff": [""], + "po_4_gender_flag": [""], + "po_4_gender_ff": [""], + } + default.update(update_data) + return default + + def get_csv_bytes(self, *update_data: str) -> bytes: + data = [ + ( + "uid,app_date,app_method,app_recipient,ct_credit_product," + "ct_credit_product_ff,ct_guarantee,ct_guarantee_ff," + "ct_loan_term_flag,ct_loan_term,credit_purpose,credit_purpose_ff," + "amount_applied_for_flag,amount_applied_for,amount_approved," + "action_taken,action_taken_date,denial_reasons,denial_reasons_ff," + "pricing_interest_rate_type,pricing_init_rate_period," + "pricing_fixed_rate,pricing_adj_margin,pricing_adj_index_name," + "pricing_adj_index_name_ff,pricing_adj_index_value," + "pricing_origination_charges,pricing_broker_fees," + "pricing_initial_charges,pricing_mca_addcost_flag," + "pricing_mca_addcost,pricing_prepenalty_allowed," + "pricing_prepenalty_exists,census_tract_adr_type," + "census_tract_number,gross_annual_revenue_flag," + "gross_annual_revenue,naics_code_flag,naics_code,number_of_workers," + "time_in_business_type,time_in_business,business_ownership_status," + "num_principal_owners_flag,num_principal_owners,po_1_ethnicity," + "po_1_ethnicity_ff,po_1_race,po_1_race_anai_ff,po_1_race_asian_ff," + "po_1_race_baa_ff,po_1_race_pi_ff,po_1_gender_flag,po_1_gender_ff," + "po_2_ethnicity,po_2_ethnicity_ff,po_2_race,po_2_race_anai_ff," + "po_2_race_asian_ff,po_2_race_baa_ff,po_2_race_pi_ff," + "po_2_gender_flag,po_2_gender_ff,po_3_ethnicity,po_3_ethnicity_ff," + "po_3_race,po_3_race_anai_ff,po_3_race_asian_ff,po_3_race_baa_ff," + "po_3_race_pi_ff,po_3_gender_flag,po_3_gender_ff,po_4_ethnicity," + "po_4_ethnicity_ff,po_4_race,po_4_race_anai_ff,po_4_race_asian_ff," + "po_4_race_baa_ff,po_4_race_pi_ff,po_4_gender_flag,po_4_gender_ff" + ) + ] + data.extend(list(update_data)) + result = "\n".join(data) + return bytes(result, "utf-8") + + +class TestValidate: + util = TestUtil() + schemas = util.schemas + phase1_schema = schemas[0] + phase2_schema = schemas[1] + + def test_with_valid_dataframe(self): + df = pd.DataFrame(data=self.util.get_data()) + result = validate(self.phase1_schema, df) + ph2_result = validate(self.phase2_schema, df) + assert len(result) == 0 + assert len(ph2_result) == 0 + + def test_with_invalid_dataframe(self): + df = pd.DataFrame(data=self.util.get_data({"ct_credit_product": ["989"]})) + result = validate(self.phase1_schema, df) + ph2_result = validate(self.phase2_schema, df) + assert len(result) == 1 + assert len(ph2_result) == 0 + + def test_with_multi_invalid_dataframe(self): + df = pd.DataFrame( + data=self.util.get_data( + { + "ct_credit_product": ["989"], + "num_principal_owners": ["1"], + "action_taken": ["2"], + } + ) + ) + result = validate(self.phase1_schema, df) + assert len(result) == 1 + + ph2_result = validate(self.phase2_schema, df) + assert len(ph2_result) == 3 + + +class TestValidateDataList: + util = TestUtil() + schemas = util.schemas + phase1_schema = schemas[0] + phase2_schema = schemas[1] + + def test_with_valid_data(self): + result = validate_data_list( + self.phase1_schema, self.phase2_schema, self.util.get_data() + ) + + assert len(result) == 1 + assert result[0] == self.util.valid_response + + def test_with_invalid_data(self): + result = validate_data_list( + self.phase1_schema, + self.phase2_schema, + self.util.get_data({"ct_credit_product": ["989"]}), + ) + + assert len(result) == 1 + assert result[0] != self.util.valid_response + + def test_with_multi_invalid_data_with_phase1(self): + result = validate_data_list( + self.phase1_schema, + self.phase2_schema, + self.util.get_data( + { + "ct_credit_product": ["989"], + "num_principal_owners": ["1"], + "action_taken": ["2"], + } + ), + ) + # should only return phase 1 validation result since phase1 failed + assert len(result) == 1 + assert result[0] != self.util.valid_response + + def test_with_multi_invalid_data_with_phase2(self): + result = validate_data_list( + self.phase1_schema, + self.phase2_schema, + self.util.get_data( + { + "num_principal_owners": ["1"], + "action_taken": ["2"], + } + ), + ) + # since the data passed phase 1 validations + # this should return phase 2 validations + assert len(result) == 3 + assert result[0] != self.util.valid_response + + +class TestValidateRawCsv: + util = TestUtil() + schemas = util.schemas + phase1_schema = schemas[0] + phase2_schema = schemas[1] + + def test_with_valid_data(self): + data = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + csv_bytes = self.util.get_csv_bytes(data) + result = validate_raw_csv(self.phase1_schema, self.phase2_schema, csv_bytes) + assert len(result) == 1 + assert result[0] == self.util.valid_response + + def test_with_multi_valid_data(self): + data = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + data2 = "000TESTFIUIDDONOTUSEXGXVID13XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + csv_bytes = self.util.get_csv_bytes(data, data2) + result = validate_raw_csv(self.phase1_schema, self.phase2_schema, csv_bytes) + assert len(result) == 1 + assert result[0] == self.util.valid_response + + def test_with_invalid_data(self): + data = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,989,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + data2 = "000TESTFIUIDDONOTUSEXGXVID13XTC1,20241201,1,1,988,,990,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + csv_bytes = self.util.get_csv_bytes(data, data2) + result = validate_raw_csv(self.phase1_schema, self.phase2_schema, csv_bytes) + assert len(result) == 2 + assert result[0] != self.util.valid_response + + def test_with_multi_invalid_data_with_phase1(self): + data = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,2,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + data2 = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + csv_bytes = self.util.get_csv_bytes(data, data2) + result = validate_raw_csv(self.phase1_schema, self.phase2_schema, csv_bytes) + assert len(result) == 1 + assert result[0] != self.util.valid_response + + def test_with_multi_invalid_data_with_phase2(self): + data = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,2,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + data2 = "000TESTFIUIDDONOTUSEXGXVID12XTC1,20241201,1,1,988,,999,,999,,999,,999,,,1,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + csv_bytes = self.util.get_csv_bytes(data, data2) + result = validate_raw_csv(self.phase1_schema, self.phase2_schema, csv_bytes) + assert len(result) == 2 + assert result[0] != self.util.valid_response diff --git a/src/util/__init__.py b/src/util/__init__.py new file mode 100644 index 00000000..854f4390 --- /dev/null +++ b/src/util/__init__.py @@ -0,0 +1,9 @@ +import os +import sys + +CURR_DIR = os.path.dirname(os.path.abspath(__file__)) # noqa: E402 +PARENT_DIR = os.path.dirname(CURR_DIR) # noqa: E402 +ROOT_DIR = os.path.dirname(PARENT_DIR) # noqa: E402 +sys.path.append(CURR_DIR) # noqa: E402 +sys.path.append(PARENT_DIR) # noqa: E402 +sys.path.append(ROOT_DIR) # noqa: E402 \ No newline at end of file diff --git a/src/validator/global_data.py b/src/util/global_data.py similarity index 53% rename from src/validator/global_data.py rename to src/util/global_data.py index 69c1fc40..879fa0db 100644 --- a/src/validator/global_data.py +++ b/src/util/global_data.py @@ -1,39 +1,34 @@ import os -import re -import sys import pandas as pd +from config import CENSUS_PROCESSED_CSV_PATH, NAICS_CSV_PATH + ROOT_DIR = os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -) # noqa: E402 -sys.path.append(ROOT_DIR) # noqa: E402 - -from config import CENSUS_PROCESSED_CSV_PATH, NAICS_CSV_PATH # noqa: E402 - -naics_codes = {} - -# global variable for geoids -census_geoids = {} - +) -def read_naics_codes(): +def read_naics_codes() -> dict: """ read NAICS CSV file with this format: (code, description) and populate global value: naics_codes """ - naics_codes.clear() - df = pd.read_csv(NAICS_CSV_PATH, dtype=str, na_filter=False) + naics_codes = {} + file = os.path.join(ROOT_DIR, NAICS_CSV_PATH) + df = pd.read_csv(file, dtype=str, na_filter=False) for _, row in df.iterrows(): naics_codes.update({row[0]: row[1]}) + return naics_codes -def read_geoids(): +def read_geoids() -> dict: """ read geoids CSV file with this format: (code) and populate global value: census_geoids """ - census_geoids.clear() - df = pd.read_csv(CENSUS_PROCESSED_CSV_PATH, dtype=str, na_filter=False) + census_geoids = {} + file = os.path.join(ROOT_DIR, CENSUS_PROCESSED_CSV_PATH) + df = pd.read_csv(file, dtype=str, na_filter=False) for _, row in df.iterrows(): census_geoids.update({row[0]: None}) + return census_geoids diff --git a/src/validator/__init__.py b/src/validator/__init__.py index 836099bf..854f4390 100644 --- a/src/validator/__init__.py +++ b/src/validator/__init__.py @@ -1,5 +1,9 @@ import os import sys -ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -sys.path.append(ROOT_DIR) +CURR_DIR = os.path.dirname(os.path.abspath(__file__)) # noqa: E402 +PARENT_DIR = os.path.dirname(CURR_DIR) # noqa: E402 +ROOT_DIR = os.path.dirname(PARENT_DIR) # noqa: E402 +sys.path.append(CURR_DIR) # noqa: E402 +sys.path.append(PARENT_DIR) # noqa: E402 +sys.path.append(ROOT_DIR) # noqa: E402 \ No newline at end of file diff --git a/src/validator/create_schemas.py b/src/validator/create_schemas.py index 69cb8777..5df790b7 100644 --- a/src/validator/create_schemas.py +++ b/src/validator/create_schemas.py @@ -1,17 +1,151 @@ """Creates two DataFrameSchema objects by rendering the schema template with validations listed in phase 1 and phase 2.""" +from io import BytesIO + +import pandas as pd from pandera import DataFrameSchema -from phase_validations import phase_1_and_2_validations -from schema_template import get_template +from pandera.errors import SchemaErrors + +from .checks import SBLCheck +from .phase_validations import get_phase_1_and_2_validations +from .schema_template import get_template + + +def get_schemas(naics: dict, geoids: dict) -> (DataFrameSchema, DataFrameSchema): + phase_1_template = get_template() + phase_2_template = get_template() + + for col, validations in get_phase_1_and_2_validations(naics, geoids).items(): + phase_1_template[col].checks = validations["phase_1"] + phase_2_template[col].checks = validations["phase_2"] + + phase_1_schema = DataFrameSchema(phase_1_template) + phase_2_schema = DataFrameSchema(phase_2_template) + + return (phase_1_schema, phase_2_schema) + + +def validate(schema: DataFrameSchema, df: pd.DataFrame): + """ + validate received dataframe with schema and return list of + schema errors + + Args: + schema (DataFrameSchema): schema to be used for validation + df (pd.DataFrame): data parsed into dataframe + + Returns: + list of schema error + """ + findings = [] + try: + schema(df, lazy=True) + except SchemaErrors as errors: + for schema_error in errors.schema_errors: + error = schema_error["error"] + check: SBLCheck = error.check + column_name = error.schema.name + + fields: list[str] = [column_name] + + if hasattr(check, "name"): + check_name: str = check.name + + if check.groupby: + fields += check.groupby # type: ignore + + # This will either be a boolean series or a single bool + check_output = error.check_output + else: + # This means this check's column has unique set to True. + # we shouldn't be using Unique flag as it doesn't return series of + # validation result . it returns just a printout result string/txt + raise AttributeError(f"{str(check)}") + + # Remove duplicates, but keep as `list` for JSON-friendliness + fields = list(set(fields)) + + if check_output is not None: + # `check_output` must be sorted so its index lines up with `df`'s index + check_output.sort_index(inplace=True) + + # Filter records using Pandas's boolean indexing, where all False values + # get filtered out. The `~` does the inverse since it's actually the + # False values we want to keep. + # http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing + failed_check_fields_df = df[~check_output][fields].fillna("") + + # Create list of dicts representing the failed validations and the + # associated field data for each invalid record. + records = [] + for idx, row in failed_check_fields_df.iterrows(): + record = {"number": idx + 1, "field_values": {}} + for field in fields: + record["field_values"][field] = row[field] + records.append(record) + + validation_findings = { + "validation": { + "id": check_name, + "description": check.description, + "fields": fields, + "severity": "warning" if check.warning else "error", + }, + "records": records, + } + + findings.append(validation_findings) + + return findings + + +def _validate_helper( + phase1: DataFrameSchema, phase2: DataFrameSchema, df: pd.DataFrame +) -> list: + phase1_findings = validate(phase1, df) + if phase1_findings: + return phase1_findings + else: + phase2_findings = validate(phase2, df) + if phase2_findings: + return phase2_findings + else: + return [{"response": "No validations errors or warnings"}] + + +def validate_data_list( + phase1: DataFrameSchema, phase2: DataFrameSchema, data: dict +) -> list: + """ + validate data-dictionary entries + + Args: + phase1 (DataFrameSchema): phase 1 schema + phase2 (DataFrameSchema): phase 2 schema + data (dict): data dictionary + + Returns: + list: list of error validation + """ + df = pd.DataFrame.from_dict(data) + return _validate_helper(phase1, phase2, df) + -# Get separate schema templates for phase 1 and 2 -phase_1_template = get_template() -phase_2_template = get_template() +def validate_raw_csv( + phase1: DataFrameSchema, phase2: DataFrameSchema, data: bytes +) -> list: + """ + read raw csv bytes and validate them against + phase 1 and phase 2 checks -for column, validations in phase_1_and_2_validations: - phase_1_template[column].checks = validations["phase_1"] - phase_2_template[column].checks = validations["phase_2"] + Args: + phase1 (DataFrameSchema): phase 1 checks + phase2 (DataFrameSchema): phase 2 checks + data (bytes): csv bytes -phase_1_schema = DataFrameSchema(phase_1_template) -phase_2_schema = DataFrameSchema(phase_2_template) + Returns: + list: list of error checks + """ + df = pd.read_csv(BytesIO(data), dtype=str, na_filter=False) + return _validate_helper(phase1, phase2, df) diff --git a/src/validator/main.py b/src/validator/main.py index 6124ca35..6e9019b1 100644 --- a/src/validator/main.py +++ b/src/validator/main.py @@ -5,18 +5,25 @@ Run from the terminal to see the generated output. """ +import os import sys import pandas as pd from pandera.errors import SchemaErrors -from schema import sblar_schema +from schema import get_sblar_schema + +PARENT_DIR = os.path.dirname( + os.path.dirname(os.path.abspath(__file__)) +) # noqa: E402 +sys.path.append(PARENT_DIR) # noqa: E402 +from util import global_data # noqa: E402 def csv_to_df(path: str) -> pd.DataFrame: return pd.read_csv(path, dtype=str, na_filter=False) -def run_validation_on_df(df: pd.DataFrame) -> None: +def run_validation_on_df(df: pd.DataFrame, naics: dict, geoids: dict) -> None: """ Run validaition on the supplied dataframe and print a report to the terminal. @@ -29,29 +36,34 @@ def run_validation_on_df(df: pd.DataFrame) -> None: print("") try: - sblar_schema(df, lazy=True) + schema = get_sblar_schema(naics, geoids) + schema(df, lazy=True) except SchemaErrors as errors: for error in errors.schema_errors: # Name of the column in the dataframe being checked column_name = error["error"].schema.name - + check = error["error"].check + check_output = error["error"].check_output # built in checks such as unique=True are different than custom # checks unfortunately so the name needs to be accessed differently - try: - check_name = error["error"].check.name - # This will either be a boolean series or a single bool - check_output = error["error"].check_output - except AttributeError: - check_name = error["error"].check - # this is just a string that we'd need to parse manually - check_output = error["error"].args[0] - - print(f"Validation `{check_name}` failed for column `{column_name}`") - print(check_output) - print("") + if hasattr(check, "name"): + check_name = check.name + print(f"Validation `{check_name}` failed for column `{column_name}`") + print(check_output) + print("") + else: + raise AttributeError(f"{check}") + + if __name__ == "__main__": + # read and populate global naics code (this should be called only once) + naics = global_data.read_naics_codes() + + # read and populate global census geoids (this should be called only once) + geoids = global_data.read_geoids() + csv_path = None try: csv_path = sys.argv[1] @@ -59,4 +71,4 @@ def run_validation_on_df(df: pd.DataFrame) -> None: raise ValueError("csv_path arg not provided") df = csv_to_df(csv_path) - run_validation_on_df(df) + run_validation_on_df(df, naics, geoids) diff --git a/src/validator/phase_validations.py b/src/validator/phase_validations.py index 93d3eb0c..790cad39 100644 --- a/src/validator/phase_validations.py +++ b/src/validator/phase_validations.py @@ -3,3103 +3,3132 @@ This mapping is used to populate the schema template object and create an instance of a PanderaSchema object for phase 1 and phase 2.""" -#! NOTE: "pricing_adj_margin", "pricing_adj_index_name": "pricing_adj_index_name_ff", -# and "pricing_adj_index_value" have been renamed. They used to be called -# pricing_var_xyz but are now called pricing_adj_xyz +from .check_functions import (has_correct_length, + has_no_conditional_field_conflict, + has_valid_enum_pair, has_valid_fieldset_pair, + has_valid_format, + has_valid_multi_field_value_count, + has_valid_value_count, is_date, is_date_after, + is_date_before_in_days, is_date_in_range, + is_greater_than, is_greater_than_or_equal_to, + is_less_than, is_number, is_unique_column, + is_unique_in_field, is_valid_code, is_valid_enum, + meets_multi_value_field_restriction) +from .checks import SBLCheck -import global_data -from check_functions import ( - has_correct_length, - has_no_conditional_field_conflict, - has_valid_enum_pair, - has_valid_fieldset_pair, - has_valid_format, - has_valid_multi_field_value_count, - has_valid_value_count, - is_date, - is_date_after, - is_date_before_in_days, - is_date_in_range, - is_greater_than, - is_greater_than_or_equal_to, - is_less_than, - is_number, - is_unique_column, - is_unique_in_field, - is_valid_code, - is_valid_enum, - meets_multi_value_field_restriction, -) -from checks import SBLCheck - -# read and populate global naics code (this should be called only once) -global_data.read_naics_codes() - -phase_1_and_2_validations = { - "uid": { - "phase_1": [ - SBLCheck( - is_unique_column, - name="uid.duplicates_in_dataset", - description=( - "Any 'unique identifier' may not be used in more than one " - "record within a small business lending application register." - ), - groupby="uid", - ), - SBLCheck.str_length( - 21, - 45, - name="uid.invalid_text_length", - description=( - "'Unique identifier' must be at least 21 characters " - "in length and at most 45 characters in length." - ), - ), - SBLCheck( - has_valid_format, - name="uid.invalid_text_pattern", - description=( - "'Unique identifier' may contain any combination of " - "numbers and/or uppercase letters (i.e., 0-9 and A-Z), " - "and must not contain any other characters." - ), - element_wise=True, - regex="^[A-Z0-9]+$", - ), - ], - "phase_2": [], - }, - "app_date": { - "phase_1": [ - SBLCheck( - is_date, - name="app_date.invalid_date_format", - description=( - "'Application date' must be a real calendar " - "date using YYYYMMDD format." - ), - element_wise=True, - ), - ], - "phase_2": [], - }, - "app_method": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="app_method.invalid_enum_value", - description="'Application method' must equal 1, 2, 3, or 4.", - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - ], - ), - ], - "phase_2": [], - }, - "app_recipient": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="app_recipient.invalid_enum_value", - description="'Application recipient' must equal 1 or 2", - element_wise=True, - accepted_values=[ - "1", - "2", - ], - ), - ], - "phase_2": [], - }, - "ct_credit_product": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="ct_credit_product.invalid_enum_value", - description=( - "'Credit product' must equal 1, 2, 3, 4, 5, 6, 7, 8, " - "977, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "977", - "988", - ], - ), - ], - "phase_2": [], - }, - "ct_credit_product_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="ct_credit_product_ff.invalid_text_length", - description=( - "'Free-form text field for other credit products' " - "must not exceed 300 characters in length." - ), - ) - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="ct_credit_product_ff.conditional_field_conflict", - description=( - "When 'credit product' does not equal 977 (other), 'free-form" - " text field for other credit products' must be blank." - "When 'credit product' equals 977, 'free-form text field " - "for other credit products' must not be blank." - ), - groupby="ct_credit_product", - condition_values={"977"}, - ), - ], - }, - "ct_guarantee": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="ct_guarantee.invalid_enum_value", - description=( - "Each value in 'type of guarantee' (separated by " - " semicolons) must equal 1, 2, 3, 4, 5, 6, 7, 8," - " 9, 10, 11, 977, or 999." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "977", - "999", - ], - ), - ], - "phase_2": [ - SBLCheck( - has_valid_value_count, - name="ct_guarantee.invalid_number_of_values", - description=( - "'Type of guarantee' must contain at least one and at" - " most five values, separated by semicolons." - ), - element_wise=True, - min_length=1, - max_length=5, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="ct_guarantee.duplicates_in_field", - description=( - "'Type of guarantee' should not contain " "duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="ct_guarantee.multi_value_field_restriction", - description=( - "When 'type of guarantee' contains 999 (no guarantee)," - " 'type of guarantee' should not contain more than one" - " value." - ), - element_wise=True, - single_values={"999"}, - ), - ], - }, - "ct_guarantee_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="ct_guarantee_ff.invalid_text_length", - description=( - "'Free-form text field for other guarantee' must not " - "exceed 300 characters in length" - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="ct_guarantee_ff.conditional_field_conflict", - description=( - "When 'type of guarantee' does not contain 977 (other), " - "'free-form text field for other guarantee' must be blank. " - "When 'type of guarantee' contains 977, 'free-form text field" - " for other guarantee' must not be blank." - ), - groupby="ct_guarantee", - condition_values={"977"}, - ), - SBLCheck( - has_valid_multi_field_value_count, - warning=True, - name="ct_guarantee_ff.multi_invalid_number_of_values", - description=( - "'Type of guarantee' and 'free-form text field for other " - "guarantee' combined should not contain more than five values. " - "Code 977 (other), within 'type of guarantee', does not count " - "toward the maximum number of values for the purpose of this " - "validation check." - ), - groupby="ct_guarantee", - ignored_values={"977"}, - max_length=5, - ), - ], - }, - "ct_loan_term_flag": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="ct_loan_term_flag.invalid_enum_value", - description=( - "Each value in 'Loan term: NA/NP flag' (separated by " - " semicolons) must equal 900, 988, or 999." - ), - element_wise=True, - accepted_values=[ - "900", - "988", - "999", - ], - ), - ], - "phase_2": [ - SBLCheck( - has_valid_enum_pair, - name="ct_loan_term_flag.enum_value_conflict", - description=( - "When 'credit product' equals 1 (term loan - unsecured) or 2" - "(term loan - secured), 'loan term: NA/NP flag' must not equal" - "999 (not applicable)." - "When 'credit product' equals 988 (not provided by applicant " - "and otherwise undetermined), 'loan term: NA/NP flag' must" - "equal 999." - ), - groupby="ct_credit_product", - conditions=[ - { - "condition_values": {"1", "2"}, - "is_equal_condition": True, - "target_value": "999", - "should_equal_target": False, +def get_phase_1_and_2_validations(naics_codes: dict, census_geoids: dict): + two_phases_schema = { + "uid": { + "phase_1": [ + SBLCheck( + is_unique_column, + name="uid.duplicates_in_dataset", + description=( + "Any 'unique identifier' may not be used in more than one " + "record within a small business lending application register." + ), + groupby="uid", + ), + SBLCheck.str_length( + 21, + 45, + name="uid.invalid_text_length", + description=( + "'Unique identifier' must be at least 21 characters " + "in length and at most 45 characters in length." + ), + ), + SBLCheck( + has_valid_format, + name="uid.invalid_text_pattern", + description=( + "'Unique identifier' may contain any combination of " + "numbers and/or uppercase letters (i.e., 0-9 and A-Z), " + "and must not contain any other characters." + ), + element_wise=True, + regex="^[A-Z0-9]+$", + ), + ], + "phase_2": [], + }, + "app_date": { + "phase_1": [ + SBLCheck( + is_date, + name="app_date.invalid_date_format", + description=( + "'Application date' must be a real calendar " + "date using YYYYMMDD format." + ), + element_wise=True, + ), + ], + "phase_2": [], + }, + "app_method": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="app_method.invalid_enum_value", + description="'Application method' must equal 1, 2, 3, or 4.", + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + ], + ), + ], + "phase_2": [], + }, + "app_recipient": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="app_recipient.invalid_enum_value", + description="'Application recipient' must equal 1 or 2", + element_wise=True, + accepted_values=[ + "1", + "2", + ], + ), + ], + "phase_2": [], + }, + "ct_credit_product": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="ct_credit_product.invalid_enum_value", + description=( + "'Credit product' must equal 1, 2, 3, 4, 5, 6, 7, 8, " + "977, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "977", + "988", + ], + ), + ], + "phase_2": [], + }, + "ct_credit_product_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="ct_credit_product_ff.invalid_text_length", + description=( + "'Free-form text field for other credit products' " + "must not exceed 300 characters in length." + ), + ) + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="ct_credit_product_ff.conditional_field_conflict", + description=( + "When 'credit product' does not equal 977 (other), 'free-form" + " text field for other credit products' must be blank." + "When 'credit product' equals 977, 'free-form text field " + "for other credit products' must not be blank." + ), + groupby="ct_credit_product", + condition_values={"977"}, + ), + ], + }, + "ct_guarantee": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="ct_guarantee.invalid_enum_value", + description=( + "Each value in 'type of guarantee' (separated by " + " semicolons) must equal 1, 2, 3, 4, 5, 6, 7, 8," + " 9, 10, 11, 977, or 999." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "977", + "999", + ], + ), + ], + "phase_2": [ + SBLCheck( + has_valid_value_count, + name="ct_guarantee.invalid_number_of_values", + description=( + "'Type of guarantee' must contain at least one and at" + " most five values, separated by semicolons." + ), + element_wise=True, + min_length=1, + max_length=5, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="ct_guarantee.duplicates_in_field", + description=( + "'Type of guarantee' should not contain " "duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="ct_guarantee.multi_value_field_restriction", + description=( + "When 'type of guarantee' contains 999 (no guarantee)," + " 'type of guarantee' should not contain more than one" + " value." + ), + element_wise=True, + single_values={"999"}, + ), + ], + }, + "ct_guarantee_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="ct_guarantee_ff.invalid_text_length", + description=( + "'Free-form text field for other guarantee' must not " + "exceed 300 characters in length" + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="ct_guarantee_ff.conditional_field_conflict", + description=( + "When 'type of guarantee' does not contain 977 (other), " + "'free-form text field for other guarantee' must be blank. " + "When 'type of guarantee' contains 977, 'free-form text field" + " for other guarantee' must not be blank." + ), + groupby="ct_guarantee", + condition_values={"977"}, + ), + SBLCheck( + has_valid_multi_field_value_count, + warning=True, + name="ct_guarantee_ff.multi_invalid_number_of_values", + description=( + "'Type of guarantee' and 'free-form text field for other " + "guarantee' combined should not contain more than five values. " + "Code 977 (other), within 'type of guarantee', does not count " + "toward the maximum number of values for the purpose of this " + "validation check." + ), + groupby="ct_guarantee", + ignored_values={"977"}, + max_length=5, + ), + ], + }, + "ct_loan_term_flag": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="ct_loan_term_flag.invalid_enum_value", + description=( + "Each value in 'Loan term: NA/NP flag' (separated by " + " semicolons) must equal 900, 988, or 999." + ), + element_wise=True, + accepted_values=[ + "900", + "988", + "999", + ], + ), + ], + "phase_2": [ + SBLCheck( + has_valid_enum_pair, + name="ct_loan_term_flag.enum_value_conflict", + description=( + "When 'credit product' equals 1 (term loan - unsecured) or 2" + "(term loan - secured), 'loan term: NA/NP flag' must not equal" + "999 (not applicable)." + "When 'credit product' equals 988 (not provided by applicant " + "and otherwise undetermined), 'loan term: NA/NP flag' must" + "equal 999." + ), + groupby="ct_credit_product", + conditions=[ + { + "condition_values": {"1", "2"}, + "is_equal_condition": True, + "target_value": "999", + "should_equal_target": False, + }, + { + "condition_values": {"988"}, + "is_equal_condition": True, + "target_value": "999", + "should_equal_target": True, + }, + ], + ), + ], + }, + "ct_loan_term": { + "phase_1": [ + SBLCheck( + is_number, + name="ct_loan_term.invalid_numeric_format", + description="When present, 'loan term' must be a whole number.", + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="ct_loan_term.conditional_field_conflict", + description=( + "When 'loan term: NA/NP flag' does not equal 900 (applicable " + "and reported), 'loan term' must be blank. When 'loan term:" + "NA/NP flag' equals 900, 'loan term' must not be blank." + ), + groupby="ct_loan_term_flag", + condition_values={"900"}, + ), + SBLCheck( + is_greater_than_or_equal_to, + name="ct_loan_term.invalid_numeric_value", + description=( + "When present, 'loan term' must be greater than or equal" "to 1." + ), + element_wise=True, + min_value="1", + accept_blank=True, + ), + SBLCheck( + is_less_than, + name="ct_loan_term.unreasonable_numeric_value", + description=( + "When present, 'loan term' should be less than 1200" "(100 years)." + ), + element_wise=True, + max_value="1200", + accept_blank=True, + ), + ], + }, + "credit_purpose": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="credit_purpose.invalid_enum_value", + description=( + "Each value in 'credit purpose' (separated by " + " semicolons) must equal 1, 2, 3, 4, 5, 6, 7, 8," + " 9, 10, 11, 977, 988, or 999." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "977", + "988", + "999", + ], + ), + ], + "phase_2": [ + SBLCheck( + has_valid_value_count, + name="credit_purpose.invalid_number_of_values", + description=( + "'Credit purpose' must contain at least one and at" + " most three values, separated by semicolons." + ), + element_wise=True, + min_length=1, + max_length=3, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="credit_purpose.multi_value_field_restriction", + description=( + "When 'credit purpose' contains 988 or 999," + " 'credit purpose' should not contain more than one" + " value." + ), + element_wise=True, + single_values={ + "988", + "999", }, - { - "condition_values": {"988"}, - "is_equal_condition": True, - "target_value": "999", - "should_equal_target": True, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="credit_purpose.duplicates_in_field", + description=( + "'Credit purpose' should not contain " " duplicated values." + ), + element_wise=True, + ), + ], + }, + "credit_purpose_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="credit_purpose_ff.invalid_text_length", + description=( + "'Free-form text field for other credit purpose' " + " must not exceed 300 characters in length" + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="credit_purpose_ff.conditional_field_conflict", + description=( + "When 'credit purpose' does not contain 977 (other)," + "'free-form text field for other credit purpose' must be blank." + "When 'credit purpose' contains 977, 'free-form text field for" + "other credit purpose' must not be blank." + ), + groupby="credit_purpose", + condition_values={"977"}, + ), + SBLCheck( + has_valid_value_count, + name="credit_purpose_ff.invalid_number_of_values", + description=( + "'Other Credit purpose' must not contain more " + " than one other credit purpose." + ), + element_wise=True, + min_length=0, + max_length=1, + ), + ], + }, + "amount_applied_for_flag": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="amount_applied_for_flag.invalid_enum_value", + description=( + "'Amount applied For: NA/NP flag' must equal 900, 988, or 999." + ), + element_wise=True, + accepted_values=[ + "900", + "988", + "999", + ], + ), + ], + "phase_2": [], + }, + "amount_applied_for": { + "phase_1": [ + SBLCheck( + is_number, + name="amount_applied_for.invalid_numeric_format", + description=( + "When present, 'amount applied for' must be a numeric" "value." + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="amount_applied_for.conditional_field_conflict", + description=( + "When 'amount applied for: NA/NP flag' does not equal 900 " + "(applicable and reported), 'amount applied for' must be blank." + "When 'amount applied for: NA/NP flag' equals 900, " + "'amount applied for' must not be blank." + ), + groupby="amount_applied_for_flag", + condition_values={"900"}, + ), + SBLCheck( + is_greater_than, + name="amount_applied_for.invalid_numeric_value", + description=( + "When present, 'amount applied for' must be greater than 0." + ), + element_wise=True, + min_value="0", + accept_blank=True, + ), + ], + }, + "amount_approved": { + "phase_1": [ + SBLCheck( + is_number, + name="amount_approved.invalid_numeric_format", + description=( + "When present, 'amount approved or originated' " + "must be a numeric value." + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + is_greater_than, + name="amount_approved.invalid_numeric_value", + description=( + "When present, 'amount approved or originated' " + "must be greater than 0." + ), + element_wise=True, + min_value="0", + accept_blank=True, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="amount_approved.conditional_field_conflict", + description=( + "When 'action taken' does not equal 1 (originated) " + "or 2 (approved but not accepted), 'amount approved " + " or originated' must be blank. When 'action taken' " + "equals 1 or 2, 'amount approved or originated' must " + "not be blank." + ), + groupby="action_taken", + condition_values={"1", "2"}, + ), + ], + }, + "action_taken": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="action_taken.invalid_enum_value", + description="'Action taken' must equal 1, 2, 3, 4, or 5.", + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + ], + ), + ], + "phase_2": [ + SBLCheck( + has_valid_fieldset_pair, + name="pricing_all.conditional_fieldset_conflict", + description=( + "When 'action taken' equals 3 (denied), " + "4 (withdrawn by applicant), or 5 " + "(incomplete), the following fields must" + " all equal 999 (not applicable): " + "'Interest rate type', 'MCA/sales-based: " + "additional cost for merchant cash advances" + " or other sales-based financing: NA flag', " + "'Prepayment penalty could be imposed', " + "'Prepayment penalty exists'). And the " + " following fields must all be blank: " + "'Total origination charges', 'Amount of " + "total broker fees', 'Initial annual charges'" + ), + groupby=[ + "pricing_interest_rate_type", + "pricing_mca_addcost_flag", + "pricing_prepenalty_allowed", + "pricing_prepenalty_exists", + "pricing_origination_charges", + "pricing_broker_fees", + "pricing_initial_charges", + ], + condition_values=["3", "4", "5"], + should_fieldset_key_equal_to={ + "pricing_interest_rate_type": (0, True, "999"), + "pricing_mca_addcost_flag": (1, True, "999"), + "pricing_prepenalty_allowed": (2, True, "999"), + "pricing_prepenalty_exists": (3, True, "999"), + "pricing_origination_charges": (4, True, ""), + "pricing_broker_fees": (5, True, ""), + "pricing_initial_charges": (6, True, ""), }, - ], - ), - ], - }, - "ct_loan_term": { - "phase_1": [ - SBLCheck( - is_number, - name="ct_loan_term.invalid_numeric_format", - description="When present, 'loan term' must be a whole number.", - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="ct_loan_term.conditional_field_conflict", - description=( - "When 'loan term: NA/NP flag' does not equal 900 (applicable " - "and reported), 'loan term' must be blank. When 'loan term:" - "NA/NP flag' equals 900, 'loan term' must not be blank." - ), - groupby="ct_loan_term_flag", - condition_values={"900"}, - ), - SBLCheck( - is_greater_than_or_equal_to, - name="ct_loan_term.invalid_numeric_value", - description=( - "When present, 'loan term' must be greater than or equal" "to 1." - ), - element_wise=True, - min_value="1", - accept_blank=True, - ), - SBLCheck( - is_less_than, - name="ct_loan_term.unreasonable_numeric_value", - description=( - "When present, 'loan term' should be less than 1200" "(100 years)." - ), - element_wise=True, - max_value="1200", - accept_blank=True, - ), - ], - }, - "credit_purpose": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="credit_purpose.invalid_enum_value", - description=( - "Each value in 'credit purpose' (separated by " - " semicolons) must equal 1, 2, 3, 4, 5, 6, 7, 8," - " 9, 10, 11, 977, 988, or 999." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "977", - "988", - "999", - ], - ), - ], - "phase_2": [ - SBLCheck( - has_valid_value_count, - name="credit_purpose.invalid_number_of_values", - description=( - "'Credit purpose' must contain at least one and at" - " most three values, separated by semicolons." - ), - element_wise=True, - min_length=1, - max_length=3, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="credit_purpose.multi_value_field_restriction", - description=( - "When 'credit purpose' contains 988 or 999," - " 'credit purpose' should not contain more than one" - " value." - ), - element_wise=True, - single_values={ - "988", - "999", - }, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="credit_purpose.duplicates_in_field", - description=( - "'Credit purpose' should not contain " " duplicated values." - ), - element_wise=True, - ), - ], - }, - "credit_purpose_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="credit_purpose_ff.invalid_text_length", - description=( - "'Free-form text field for other credit purpose' " - " must not exceed 300 characters in length" - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="credit_purpose_ff.conditional_field_conflict", - description=( - "When 'credit purpose' does not contain 977 (other)," - "'free-form text field for other credit purpose' must be blank." - "When 'credit purpose' contains 977, 'free-form text field for" - "other credit purpose' must not be blank." - ), - groupby="credit_purpose", - condition_values={"977"}, - ), - SBLCheck( - has_valid_value_count, - name="credit_purpose_ff.invalid_number_of_values", - description=( - "'Other Credit purpose' must not contain more " - " than one other credit purpose." - ), - element_wise=True, - min_length=0, - max_length=1, - ), - ], - }, - "amount_applied_for_flag": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="amount_applied_for_flag.invalid_enum_value", - description=( - "'Amount applied For: NA/NP flag' must equal 900, 988, or 999." - ), - element_wise=True, - accepted_values=[ - "900", - "988", - "999", - ], - ), - ], - "phase_2": [], - }, - "amount_applied_for": { - "phase_1": [ - SBLCheck( - is_number, - name="amount_applied_for.invalid_numeric_format", - description=( - "When present, 'amount applied for' must be a numeric" "value." - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="amount_applied_for.conditional_field_conflict", - description=( - "When 'amount applied for: NA/NP flag' does not equal 900 " - "(applicable and reported), 'amount applied for' must be blank." - "When 'amount applied for: NA/NP flag' equals 900, " - "'amount applied for' must not be blank." - ), - groupby="amount_applied_for_flag", - condition_values={"900"}, - ), - SBLCheck( - is_greater_than, - name="amount_applied_for.invalid_numeric_value", - description=( - "When present, 'amount applied for' must be greater than 0." - ), - element_wise=True, - min_value="0", - accept_blank=True, - ), - ], - }, - "amount_approved": { - "phase_1": [ - SBLCheck( - is_number, - name="amount_approved.invalid_numeric_format", - description=( - "When present, 'amount approved or originated' " - "must be a numeric value." - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - is_greater_than, - name="amount_approved.invalid_numeric_value", - description=( - "When present, 'amount approved or originated' " - "must be greater than 0." - ), - element_wise=True, - min_value="0", - accept_blank=True, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="amount_approved.conditional_field_conflict", - description=( - "When 'action taken' does not equal 1 (originated) " - "or 2 (approved but not accepted), 'amount approved " - " or originated' must be blank. When 'action taken' " - "equals 1 or 2, 'amount approved or originated' must " - "not be blank." - ), - groupby="action_taken", - condition_values={"1", "2"}, - ), - ], - }, - "action_taken": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="action_taken.invalid_enum_value", - description="'Action taken' must equal 1, 2, 3, 4, or 5.", - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - ], - ), - ], - "phase_2": [ - SBLCheck( - has_valid_fieldset_pair, - name="pricing_all.conditional_fieldset_conflict", - description=( - "When 'action taken' equals 3 (denied), " - "4 (withdrawn by applicant), or 5 " - "(incomplete), the following fields must" - " all equal 999 (not applicable): " - "'Interest rate type', 'MCA/sales-based: " - "additional cost for merchant cash advances" - " or other sales-based financing: NA flag', " - "'Prepayment penalty could be imposed', " - "'Prepayment penalty exists'). And the " - " following fields must all be blank: " - "'Total origination charges', 'Amount of " - "total broker fees', 'Initial annual charges'" - ), - groupby=[ - "pricing_interest_rate_type", - "pricing_mca_addcost_flag", - "pricing_prepenalty_allowed", - "pricing_prepenalty_exists", - "pricing_origination_charges", - "pricing_broker_fees", - "pricing_initial_charges", - ], - condition_values=["3", "4", "5"], - should_fieldset_key_equal_to={ - "pricing_interest_rate_type": (0, True, "999"), - "pricing_mca_addcost_flag": (1, True, "999"), - "pricing_prepenalty_allowed": (2, True, "999"), - "pricing_prepenalty_exists": (3, True, "999"), - "pricing_origination_charges": (4, True, ""), - "pricing_broker_fees": (5, True, ""), - "pricing_initial_charges": (6, True, ""), - }, - ), - SBLCheck( - has_valid_fieldset_pair, - name="pricing_charges.conditional_fieldset_conflict", - description=( - "When 'action taken' equals 1 (originated)" - " or 2 (approved but not accepted), the " - "following fields all must not be blank: " - "'Total origination charges', 'Amount of " - "total broker fees', 'Initial annual " - "charges'. And the following fields must " - "not equal 999 (not applicable): 'Prepayment " - "penalty could be imposed', 'Prepayment " - "penalty exists'" - ), - groupby=[ - "pricing_origination_charges", - "pricing_broker_fees", - "pricing_initial_charges", - "pricing_prepenalty_allowed", - "pricing_prepenalty_exists", - ], - condition_values=["1", "2"], - should_fieldset_key_equal_to={ - "pricing_origination_charges": (0, False, ""), - "pricing_broker_fees": (1, False, ""), - "pricing_initial_charges": (2, False, ""), - "pricing_prepenalty_allowed": (3, False, "999"), - "pricing_prepenalty_exists": (4, False, "999"), - }, - ), - ], - }, - "action_taken_date": { - "phase_1": [ - SBLCheck( - is_date, - name="action_taken_date.invalid_date_format", - description=( - "'Action taken date' must be a real calendar" - " date using YYYYMMDD format." - ), - element_wise=True, - ), - ], - "phase_2": [ - SBLCheck( - is_date_in_range, - name="action_taken_date.invalid_date_value", - description=( - "The date indicated by 'action taken date' must occur" - " within the current reporting period:" - " October 1, 2024 to December 31, 2024." - ), - element_wise=True, - start_date_value="20241001", - end_date_value="20241231", - ), - SBLCheck( - is_date_after, - name="action_taken_date.date_value_conflict", - description=( - "The date indicated by 'action taken date'" - " must occur on or after 'application date'." - ), - groupby="app_date", - ), - SBLCheck( - is_date_before_in_days, - name="action_taken_date.unreasonable_date_value", - description=( - "The date indicated by 'application date' should" - " generally be less than two years (730 days) before" - " 'action taken date'." - ), - groupby="app_date", - days_value=730, - ), - ], - }, - "denial_reasons": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="denial_reasons.invalid_enum_value", - description=( - "Each value in 'denial reason(s)' (separated by semicolons)" - "must equal 1, 2, 3, 4, 5, 6, 7, 8, 9, 977, or 999." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "977", - "999", - ], - ), - ], - "phase_2": [ - SBLCheck( - has_valid_value_count, - name="denial_reasons.invalid_number_of_values", - description=( - "'Denial reason(s)' must contain at least one and at most four" - "values, separated by semicolons." - ), - element_wise=True, - min_length=1, - max_length=4, - ), - SBLCheck( - has_valid_enum_pair, - name="denial_reasons.enum_value_conflict", - description=( - "When 'action taken' equals 3, 'denial reason(s)' must not" - "contain 999. When 'action taken' does not equal 3, 'denial" - "reason(s)' must equal 999." - ), - groupby="action_taken", - conditions=[ - { - "condition_values": {"3"}, - "is_equal_condition": True, - "target_value": "999", - "should_equal_target": False, + ), + SBLCheck( + has_valid_fieldset_pair, + name="pricing_charges.conditional_fieldset_conflict", + description=( + "When 'action taken' equals 1 (originated)" + " or 2 (approved but not accepted), the " + "following fields all must not be blank: " + "'Total origination charges', 'Amount of " + "total broker fees', 'Initial annual " + "charges'. And the following fields must " + "not equal 999 (not applicable): 'Prepayment " + "penalty could be imposed', 'Prepayment " + "penalty exists'" + ), + groupby=[ + "pricing_origination_charges", + "pricing_broker_fees", + "pricing_initial_charges", + "pricing_prepenalty_allowed", + "pricing_prepenalty_exists", + ], + condition_values=["1", "2"], + should_fieldset_key_equal_to={ + "pricing_origination_charges": (0, False, ""), + "pricing_broker_fees": (1, False, ""), + "pricing_initial_charges": (2, False, ""), + "pricing_prepenalty_allowed": (3, False, "999"), + "pricing_prepenalty_exists": (4, False, "999"), }, - { - "condition_values": {"3"}, - "is_equal_condition": False, - "target_value": "999", - "should_equal_target": True, + ), + ], + }, + "action_taken_date": { + "phase_1": [ + SBLCheck( + is_date, + name="action_taken_date.invalid_date_format", + description=( + "'Action taken date' must be a real calendar" + " date using YYYYMMDD format." + ), + element_wise=True, + ), + ], + "phase_2": [ + SBLCheck( + is_date_in_range, + name="action_taken_date.invalid_date_value", + description=( + "The date indicated by 'action taken date' must occur" + " within the current reporting period:" + " October 1, 2024 to December 31, 2024." + ), + element_wise=True, + start_date_value="20241001", + end_date_value="20241231", + ), + SBLCheck( + is_date_after, + name="action_taken_date.date_value_conflict", + description=( + "The date indicated by 'action taken date'" + " must occur on or after 'application date'." + ), + groupby="app_date", + ), + SBLCheck( + is_date_before_in_days, + name="action_taken_date.unreasonable_date_value", + description=( + "The date indicated by 'application date' should" + " generally be less than two years (730 days) before" + " 'action taken date'." + ), + groupby="app_date", + days_value=730, + ), + ], + }, + "denial_reasons": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="denial_reasons.invalid_enum_value", + description=( + "Each value in 'denial reason(s)' (separated by semicolons)" + "must equal 1, 2, 3, 4, 5, 6, 7, 8, 9, 977, or 999." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "977", + "999", + ], + ), + ], + "phase_2": [ + SBLCheck( + has_valid_value_count, + name="denial_reasons.invalid_number_of_values", + description=( + "'Denial reason(s)' must contain at least one and at most four" + "values, separated by semicolons." + ), + element_wise=True, + min_length=1, + max_length=4, + ), + SBLCheck( + has_valid_enum_pair, + name="denial_reasons.enum_value_conflict", + description=( + "When 'action taken' equals 3, 'denial reason(s)' must not" + "contain 999. When 'action taken' does not equal 3, 'denial" + "reason(s)' must equal 999." + ), + groupby="action_taken", + conditions=[ + { + "condition_values": {"3"}, + "is_equal_condition": True, + "target_value": "999", + "should_equal_target": False, + }, + { + "condition_values": {"3"}, + "is_equal_condition": False, + "target_value": "999", + "should_equal_target": True, + }, + ], + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="denial_reasons.multi_value_field_restriction", + description=( + "When 'denial reason(s)' contains 999 (not applicable)," + "'denial reason(s)' should not contain more than one value." + ), + element_wise=True, + single_values={"999"}, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="denial_reasons.duplicates_in_field", + description=( + "'Denial reason(s)' should not contain duplicated values." + ), + element_wise=True, + ), + ], + }, + "denial_reasons_ff": { + "phase_1": [ + SBLCheck.str_length( + min_value=0, + max_value=300, + name="denial_reasons_ff.invalid_text_length", + description=( + "'Free-form text field for other denial reason(s)'" + "must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="denial_reasons_ff.conditional_field_conflict", + description=( + "When 'denial reason(s)' does not contain 977 (other), field" + "'free-form text field for other denial reason(s)' must be" + "blank. When 'denial reason(s)' contains 977, 'free-form text" + "field for other denial reason(s)' must not be blank." + ), + groupby="denial_reasons", + condition_values={"977"}, + ), + ], + }, + "pricing_interest_rate_type": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="pricing_interest_rate_type.invalid_enum_value", + description=( + "Each value in 'Interest rate type' (separated by " + " semicolons) Must equal 1, 2, 3, 4, 5, 6, or 999" + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "999", + ], + ), + ], + "phase_2": [], + }, + "pricing_init_rate_period": { + "phase_1": [ + SBLCheck( + is_number, + name="pricing_init_rate_period.invalid_numeric_format", + description=( + "When present, 'initial rate period' must be a whole number.", + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_init_rate_period.conditional_field_conflict", + description=( + "When 'interest rate type' does not equal 3 (initial rate " + "period > 12 months, variable interest), 4 (initial rate " + "period > 12 months, fixed interest), 5 (initial rate period " + "<= 12 months, variable interest), or 6 (initial rate period " + "<= 12 months, fixed interest), 'initial rate period' must " + "be blank. When 'interest rate type' equals 3, 4, 5, or 6, " + "'initial rate period' must not be blank" + ), + groupby="pricing_interest_rate_type", + condition_values={"3", "4", "5", "6"}, + ), + SBLCheck( + is_greater_than, + name="pricing_init_rate_period.invalid_numeric_value", + description=( + "When present, 'initial rate period' must be greater than 0", + ), + element_wise=True, + min_value="0", + accept_blank=True, + ), + ], + }, + "pricing_fixed_rate": { + "phase_1": [ + SBLCheck( + is_number, + name="pricing_fixed_rate.invalid_numeric_format", + description=( + "When present, 'fixed rate: interest rate'" + " must be a numeric value." + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_fixed_rate.conditional_field_conflict", + description=( + "When 'interest rate type' does not equal 2" + " (fixed interest rate, no initial rate period)," + " 4 (initial rate period > 12 months, fixed interest" + " rate), or 6 (initial rate period <= 12 months, fixed" + " interest rate), 'fixed rate: interest rate' must be" + " blank. When 'interest rate type' equals 2, 4, or 6," + " 'fixed rate: interest rate' must not be blank." + ), + groupby="pricing_interest_rate_type", + condition_values={"2", "4", "6"}, + ), + SBLCheck( + is_greater_than, + name="pricing_fixed_rate.unreasonable_numeric_value", + description=( + "When present, 'fixed rate: interest rate'" + " should generally be greater than 0.1." + ), + element_wise=True, + min_value="0.1", + accept_blank=True, + ), + ], + }, + "pricing_adj_margin": { + "phase_1": [ + SBLCheck( + is_number, + name="pricing_adj_margin.invalid_numeric_format", + description=( + "When present, 'adjustable rate transaction:" + " margin' must be a numeric value." + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_adj_margin.conditional_field_conflict", + description=( + "When 'interest rate type' does not equal 1" + " (adjustable interest rate, no initial rate period)," + " 3 (initial rate period > 12 months, adjustable interest rate)," + " or 5 (initial rate period <= 12 months, variable interest" + " rate), 'adjustable rate transaction: margin' must be blank." + " When 'interest rate type' equals 1, 3, or 5, 'variable" + " rate transaction: margin' must not be blank." + ), + groupby="pricing_interest_rate_type", + condition_values={"1", "3", "5"}, + ), + SBLCheck( + is_greater_than, + name="pricing_adj_margin.unreasonable_numeric_value", + description=( + "When present, 'adjustable rate transaction:" + " margin' should generally be greater than 0.1." + ), + element_wise=True, + min_value="0.1", + accept_blank=True, + ), + ], + }, + "pricing_adj_index_name": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="pricing_adj_index_name.invalid_enum_value", + description=( + "'Adjustable rate transaction: index name' must equal 1, 2, 3, 4," + "5, 6, 7, 8, 9, 10, 977, or 999." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "977", + "999", + ], + ), + ], + "phase_2": [ + SBLCheck( + has_valid_enum_pair, + name="pricing_adj_index_name.enum_value_conflict", + description=( + "When 'interest rate type' does not equal 1 (variable interest" + "rate, no initial rate period), 3 (initial rate period > 12" + "months, adjustable interest rate), or 5 (initial rate" + "period <= 12 months, adjustable interest rate), 'adjustable" + " rate transaction: index name' must equal 999." + "When 'interest rate type' equals 1, 3, or 5, 'adjustable rate" + "transaction: index name' must not equal 999." + ), + groupby="pricing_interest_rate_type", + conditions=[ + { + "condition_values": {"1", "3", "5"}, + "is_equal_condition": False, + "target_value": "999", + "should_equal_target": True, + }, + { + "condition_values": {"1", "3", "5"}, + "is_equal_condition": True, + "target_value": "999", + "should_equal_target": False, + }, + ], + ), + ], + }, + "pricing_adj_index_name_ff": { + "phase_1": [ + SBLCheck.str_length( + min_value=0, + max_value=300, + name="pricing_adj_index_name_ff.invalid_text_length", + description=( + "'Adjustable rate transaction: index name: other' must not exceed" + "300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_adj_index_name_ff.conditional_field_conflict", + description=( + "When 'adjustable rate transaction: index name' does not equal" + "977 (other), 'adjustable rate transaction: index name: other'" + "must be blank." + "When 'adjustable rate transaction: index name' equals 977," + "'adjustable rate transaction: index name: other' must not be" + "blank." + ), + groupby="pricing_adj_index_name", + condition_values={"977"}, + ), + ], + }, + "pricing_adj_index_value": { + "phase_1": [ + SBLCheck( + is_number, + name="pricing_adj_index_value.invalid_numeric_format", + description="When present, 'adjustable rate transaction:" + " index value' must be a numeric value.", + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_adj_index_value.conditional_field_conflict", + description=( + "When 'interest rate type' does not equal 1 (variable" + " interest rate, no initial rate period)," + " or 3 (initial rate period > 12 months, variable interest" + " rate), 'adjustable rate transaction: index value' must be" + " blank. When 'interest rate type' equals 1 or 3," + " 'adjustable rate transaction: index value' must not be blank." + ), + groupby="pricing_interest_rate_type", + condition_values={"1", "3"}, + ), + ], + }, + "pricing_origination_charges": { + "phase_1": [ + SBLCheck( + is_number, + name="pricing_origination_charges.invalid_numeric_format", + description=( + "When present, 'total origination charges' must be a numeric", + "value.", + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [], + }, + "pricing_broker_fees": { + "phase_1": [ + SBLCheck( + is_number, + name="pricing_broker_fees.invalid_numeric_format", + description=( + "When present, 'amount of total broker fees' must be a", + "numeric value.", + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [], + }, + "pricing_initial_charges": { + "phase_1": [ + SBLCheck( + is_number, + name="pricing_initial_charges.invalid_numeric_format", + description=( + "When present, 'initial annual charges' must be a" "numeric value." + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [], + }, + "pricing_mca_addcost_flag": {"phase_1": [], "phase_2": [ + + SBLCheck( + has_valid_enum_pair, + name="pricing_mca_addcost_flag.enum_value_conflict", + description=( + "When 'credit product' does not equal 7 (merchant cash " + "advance), 8 (other sales-based financing transaction) " + "or 977 (other), 'MCA/sales-based: additional cost for " + "merchant cash advances or other sales-based financing: " + "NA flag' must be 999 (not applicable)." + ), + groupby="ct_credit_product", + conditions=[ + { + "condition_values": {"7", "8", "977"}, + "is_equal_condition": False, + "target_value": "999", + "should_equal_target": True, + } + ], + ), + ] + }, + "pricing_mca_addcost": { + "phase_1": [], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_mca_addcost.conditional_field_conflict", + description=( + "When 'MCA/sales-based: additional cost for merchant " + "cash advances or other sales-based financing: NA flag' " + "does not equal 900 (applicable), 'MCA/sales-based: " + "additional cost for merchant cash advances or other " + "sales-based financing' must be blank. When 'MCA/sales-based: " + "additional cost for merchant cash advances or other " + "sales-based financing: NA flag' equals 900, MCA/sales-based: " + "additional cost for merchant cash advances or other " + "sales-based financing’ must not be blank." + ), + groupby="pricing_mca_addcost_flag", + condition_values={"900"}, + ), + SBLCheck( + is_number, + name="pricing_mca_addcost.invalid_numeric_format", + description=( + "When present, 'MCA/sales-based: additional cost for " + "merchant cash advances or other sales-based financing' " + "must be a numeric value" + ), + element_wise=True, + accept_blank=True, + ), + ] + }, + "pricing_prepenalty_allowed": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="pricing_prepenalty_allowed.invalid_enum_value", + description=( + "'Prepayment penalty could be imposed' must equal 1, 2, or 999." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "999", + ], + ), + ], + "phase_2": [], + }, + "pricing_prepenalty_exists": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="pricing_prepenalty_exists.invalid_enum_value", + description="'Prepayment penalty exists' must equal 1, 2, or 999.", + element_wise=True, + accepted_values=[ + "1", + "2", + "999", + ], + ), + ], + "phase_2": [], + }, + "census_tract_adr_type": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="census_tract_adr_type.invalid_enum_value", + description=( + "'Census tract: type of address' must equal 1, 2, 3, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "988", + ], + ), + ], + "phase_2": [], + }, + "census_tract_number": { + "phase_1": [ + SBLCheck( + has_correct_length, + name="census_tract_number.invalid_text_length", + description=( + "When present, 'census tract: tract number' must " + "be a GEOID with exactly 11 digits." + ), + element_wise=True, + accepted_length=11, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + has_valid_enum_pair, + name="census_tract_number.conditional_field_conflict", + description=( + "When 'census tract: type of address' equals 988 (not " + "provided by applicant and otherwise undetermined), " + "'census tract: tract number' must be blank." + "When 'census tract: type of address' equals 1 (address" + " or location where the loan proceeds will principally " + "be applied), 2 (address or location of borrower's main " + "office or headquarters), or 3 (another address or " + "location associated with the applicant), 'census tract:" + " tract number' must not be blank." + ), + groupby="census_tract_adr_type", + conditions=[ + { + "condition_values": {"1", "2", "3"}, + "is_equal_condition": True, + "target_value": "", + "should_equal_target": False, + }, + { + "condition_values": {"988"}, + "is_equal_condition": True, + "target_value": "", + "should_equal_target": True, + }, + ], + ), + SBLCheck( + is_valid_code, + name="census_tract_number.invalid_geoid", + description=( + "When present, 'census tract: tract number' " + "should be a valid census tract GEOID as defined " + "by the U.S. Census Bureau." + ), + element_wise=True, + accept_blank=True, + codes=census_geoids, + ), + ], + }, + "gross_annual_revenue_flag": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="gross_annual_revenue_flag.invalid_enum_value", + description=("'Gross annual revenue: NP flag' must equal 900 or 988."), + element_wise=True, + accepted_values=[ + "900", + "988", + ], + ), + ], + "phase_2": [], + }, + "gross_annual_revenue": { + "phase_1": [ + SBLCheck( + is_number, + name="gross_annual_revenue.invalid_numeric_format", + description=( + "When present, 'gross annual revenue' must be a numeric value." + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="gross_annual_revenue.conditional_field_conflict", + description=( + "When 'gross annual revenue: NP flag' does not equal 900 " + "(reported), 'gross annual revenue' must be blank. When " + "'gross annual revenue: NP flag' equals 900, " + "'gross annual revenue' must not be blank." + ), + groupby="gross_annual_revenue_flag", + condition_values={"900"}, + ), + ], + }, + "naics_code_flag": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="naics_code_flag.invalid_enum_value", + description=( + "'North American Industry Classification System (NAICS) " + "code: NP flag' must equal 900 or 988." + ), + element_wise=True, + accepted_values=[ + "900", + "988", + ], + ), + ], + "phase_2": [], + }, + "naics_code": { + "phase_1": [ + SBLCheck( + is_number, + name="naics_code.invalid_naics_format", + description=( + "'North American Industry Classification System " + "(NAICS) code' may only contain numeric characters." + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + has_correct_length, + name="naics_code.invalid_text_length", + description=( + "When present, 'North American Industry Classification System " + "(NAICS) code' must be three digits in length." + ), + element_wise=True, + accepted_length=3, + accept_blank=True, + ), + SBLCheck( + is_valid_code, + name="naics_code.invalid_naics_value", + description=( + "When present, 'North American Industry Classification System " + "(NAICS) code' should be a valid NAICS code." + ), + element_wise=True, + accept_blank=True, + codes=naics_codes, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="naics_code.conditional_field_conflict", + description=( + "When 'type of guarantee' does not contain 977 (other), " + "'free-form text field for other guarantee' must be blank. " + "When 'type of guarantee' contains 977, 'free-form text field" + " for other guarantee' must not be blank." + ), + groupby="naics_code_flag", + condition_values={"900"}, + ), + ], + }, + "number_of_workers": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="number_of_workers.invalid_enum_value", + description=( + "'Number of workers' must equal 1, 2, 3, 4, 5, 6, 7, 8, 9," + " or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "988", + ], + ), + ], + "phase_2": [], + }, + "time_in_business_type": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="time_in_business_type.invalid_enum_value", + description=( + "'Time in business: type of response'" + " must equal 1, 2, 3, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "988", + ], + ), + ], + "phase_2": [], + }, + "time_in_business": { + "phase_1": [ + SBLCheck( + is_number, + name="time_in_business.invalid_numeric_format", + description=( + "When present, 'time in business' must be a whole number." + ), + element_wise=True, + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + is_greater_than_or_equal_to, + name="time_in_business.invalid_numeric_value", + description=( + "When present, 'time in business'" + " must be greater than or equal to 0.", + ), + element_wise=True, + min_value="0", + accept_blank=True, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="time_in_business.conditional_field_conflict", + description=( + "When 'time in business: type of response' does not" + " equal 1 (the number of years an applicant has been" + " in business is collected or obtained by the financial" + " institution), 'time in business' must be blank. When" + " 'time in business: type of response' equals 1," + " 'time in business' must not be blank." + ), + groupby="time_in_business_type", + condition_values={"1"}, + ), + ], + }, + "business_ownership_status": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="business_ownership_status.invalid_enum_value", + description=( + "Each value in 'business ownership status'" + " (separated by semicolons) must equal 1, 2, 3," + " 955, 966, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "955", + "966", + "988", + ], + ), + ], + "phase_2": [ + SBLCheck( + has_valid_value_count, + name="business_ownership_status.invalid_number_of_values", + description=( + "'Business ownership status' must" " contain at least one value." + ), + element_wise=True, + min_length=1, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="business_ownership_status.duplicates_in_field", + description=( + "'Business ownership status' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="business_ownership_status.multi_value_field_restriction", + description=( + "When 'business ownership status' contains 966" + " (the applicant responded that they did not wish" + " to provide this information) or 988 (not provided" + " by applicant), 'business ownership status' should" + " not contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + }, + "num_principal_owners_flag": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="num_principal_owners_flag.invalid_enum_value", + description=( + "'Number of principal owners: NP flag' must equal 900 or 988." + ), + element_wise=True, + accepted_values=[ + "900", + "988", + ], + ), + ], + "phase_2": [ + SBLCheck( + has_valid_fieldset_pair, + name="po_demographics_0.conditional_fieldset_conflict", + description=( + "When 'number of principal owners' equals 0 or is blank, " + "demographic fields for principal owners 1, 2, 3, and 4 " + "should be blank." + ), + groupby=[ + "po_1_ethnicity", + "po_1_race", + "po_1_gender_flag", + "po_2_ethnicity", + "po_2_race", + "po_2_gender_flag", + "po_3_ethnicity", + "po_3_race", + "po_3_gender_flag", + "po_4_ethnicity", + "po_4_race", + "po_4_gender_flag", + ], + condition_values=["0", ""], + should_fieldset_key_equal_to={ + "po_1_ethnicity": (0, True, ""), + "po_1_race": (1, True, ""), + "po_1_gender_flag": (2, True, ""), + "po_2_ethnicity": (3, True, ""), + "po_2_race": (4, True, ""), + "po_2_gender_flag": (5, True, ""), + "po_3_ethnicity": (6, True, ""), + "po_3_race": (7, True, ""), + "po_3_gender_flag": (8, True, ""), + "po_4_ethnicity": (9, True, ""), + "po_4_race": (10, True, ""), + "po_4_gender_flag": (11, True, ""), }, - ], - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="denial_reasons.multi_value_field_restriction", - description=( - "When 'denial reason(s)' contains 999 (not applicable)," - "'denial reason(s)' should not contain more than one value." - ), - element_wise=True, - single_values={"999"}, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="denial_reasons.duplicates_in_field", - description=( - "'Denial reason(s)' should not contain duplicated values." - ), - element_wise=True, - ), - ], - }, - "denial_reasons_ff": { - "phase_1": [ - SBLCheck.str_length( - min_value=0, - max_value=300, - name="denial_reasons_ff.invalid_text_length", - description=( - "'Free-form text field for other denial reason(s)'" - "must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="denial_reasons_ff.conditional_field_conflict", - description=( - "When 'denial reason(s)' does not contain 977 (other), field" - "'free-form text field for other denial reason(s)' must be" - "blank. When 'denial reason(s)' contains 977, 'free-form text" - "field for other denial reason(s)' must not be blank." - ), - groupby="denial_reasons", - condition_values={"977"}, - ), - ], - }, - "pricing_interest_rate_type": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="pricing_interest_rate_type.invalid_enum_value", - description=( - "Each value in 'Interest rate type' (separated by " - " semicolons) Must equal 1, 2, 3, 4, 5, 6, or 999" - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "999", - ], - ), - ], - "phase_2": [], - }, - "pricing_init_rate_period": { - "phase_1": [ - SBLCheck( - is_number, - name="pricing_init_rate_period.invalid_numeric_format", - description=( - "When present, 'initial rate period' must be a whole number.", - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_init_rate_period.conditional_field_conflict", - description=( - "When 'interest rate type' does not equal 3 (initial rate " - "period > 12 months, variable interest), 4 (initial rate " - "period > 12 months, fixed interest), 5 (initial rate period " - "<= 12 months, variable interest), or 6 (initial rate period " - "<= 12 months, fixed interest), 'initial rate period' must " - "be blank. When 'interest rate type' equals 3, 4, 5, or 6, " - "'initial rate period' must not be blank" - ), - groupby="pricing_interest_rate_type", - condition_values={"3", "4", "5", "6"}, - ), - SBLCheck( - is_greater_than, - name="pricing_init_rate_period.invalid_numeric_value", - description=( - "When present, 'initial rate period' must be greater than 0", - ), - element_wise=True, - min_value="0", - accept_blank=True, - ), - ], - }, - "pricing_fixed_rate": { - "phase_1": [ - SBLCheck( - is_number, - name="pricing_fixed_rate.invalid_numeric_format", - description=( - "When present, 'fixed rate: interest rate'" - " must be a numeric value." - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_fixed_rate.conditional_field_conflict", - description=( - "When 'interest rate type' does not equal 2" - " (fixed interest rate, no initial rate period)," - " 4 (initial rate period > 12 months, fixed interest" - " rate), or 6 (initial rate period <= 12 months, fixed" - " interest rate), 'fixed rate: interest rate' must be" - " blank. When 'interest rate type' equals 2, 4, or 6," - " 'fixed rate: interest rate' must not be blank." - ), - groupby="pricing_interest_rate_type", - condition_values={"2", "4", "6"}, - ), - SBLCheck( - is_greater_than, - name="pricing_fixed_rate.unreasonable_numeric_value", - description=( - "When present, 'fixed rate: interest rate'" - " should generally be greater than 0.1." - ), - element_wise=True, - min_value="0.1", - accept_blank=True, - ), - ], - }, - "pricing_adj_margin": { - "phase_1": [ - SBLCheck( - is_number, - name="pricing_adj_margin.invalid_numeric_format", - description=( - "When present, 'adjustable rate transaction:" - " margin' must be a numeric value." - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_adj_margin.conditional_field_conflict", - description=( - "When 'interest rate type' does not equal 1" - " (adjustable interest rate, no initial rate period)," - " 3 (initial rate period > 12 months, adjustable interest rate)," - " or 5 (initial rate period <= 12 months, variable interest" - " rate), 'adjustable rate transaction: margin' must be blank." - " When 'interest rate type' equals 1, 3, or 5, 'variable" - " rate transaction: margin' must not be blank." - ), - groupby="pricing_interest_rate_type", - condition_values={"1", "3", "5"}, - ), - SBLCheck( - is_greater_than, - name="pricing_adj_margin.unreasonable_numeric_value", - description=( - "When present, 'adjustable rate transaction:" - " margin' should generally be greater than 0.1." - ), - element_wise=True, - min_value="0.1", - accept_blank=True, - ), - ], - }, - "pricing_adj_index_name": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="pricing_adj_index_name.invalid_enum_value", - description=( - "'Adjustable rate transaction: index name' must equal 1, 2, 3, 4," - "5, 6, 7, 8, 9, 10, 977, or 999." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "977", - "999", - ], - ), - ], - "phase_2": [ - SBLCheck( - has_valid_enum_pair, - name="pricing_adj_index_name.enum_value_conflict", - description=( - "When 'interest rate type' does not equal 1 (variable interest" - "rate, no initial rate period), 3 (initial rate period > 12" - "months, adjustable interest rate), or 5 (initial rate" - "period <= 12 months, adjustable interest rate), 'adjustable" - " rate transaction: index name' must equal 999." - "When 'interest rate type' equals 1, 3, or 5, 'adjustable rate" - "transaction: index name' must not equal 999." - ), - groupby="pricing_interest_rate_type", - conditions=[ - { - "condition_values": {"1", "3", "5"}, - "is_equal_condition": False, - "target_value": "999", - "should_equal_target": True, + ), + SBLCheck( + has_valid_fieldset_pair, + name="po_demographics_1.conditional_fieldset_conflict", + description=( + "When 'number of principal owners' equals 1, " + "'ethnicity of principal owner 1', 'race of principal owner 1'," + " and 'sex/gender of principal owner 1: NP flag' should not be" + " blank. Demographic fields for principal owners 2, 3, and 4 " + "should be blank." + ), + groupby=[ + "po_1_ethnicity", + "po_1_race", + "po_1_gender_flag", + "po_2_ethnicity", + "po_2_race", + "po_2_gender_flag", + "po_3_ethnicity", + "po_3_race", + "po_3_gender_flag", + "po_4_ethnicity", + "po_4_race", + "po_4_gender_flag", + ], + condition_values=["1"], + should_fieldset_key_equal_to={ + "po_1_ethnicity": (0, False, ""), + "po_1_race": (1, False, ""), + "po_1_gender_flag": (2, False, ""), + "po_2_ethnicity": (3, True, ""), + "po_2_race": (4, True, ""), + "po_2_gender_flag": (5, True, ""), + "po_3_ethnicity": (6, True, ""), + "po_3_race": (7, True, ""), + "po_3_gender_flag": (8, True, ""), + "po_4_ethnicity": (9, True, ""), + "po_4_race": (10, True, ""), + "po_4_gender_flag": (11, True, ""), }, - { - "condition_values": {"1", "3", "5"}, - "is_equal_condition": True, - "target_value": "999", - "should_equal_target": False, + ), + SBLCheck( + has_valid_fieldset_pair, + name="po_demographics_2.conditional_fieldset_conflict", + description=( + "When 'number of principal owners' equals 2, " + "'ethnicity of principal owner 1 and 2', 'race of principal " + "owner 1 and 2', and 'sex/gender of principal owner 1 and 2: " + "NP flag' should not be blank." + ), + groupby=[ + "po_1_ethnicity", + "po_1_race", + "po_1_gender_flag", + "po_2_ethnicity", + "po_2_race", + "po_2_gender_flag", + "po_3_ethnicity", + "po_3_race", + "po_3_gender_flag", + "po_4_ethnicity", + "po_4_race", + "po_4_gender_flag", + ], + condition_values=["2"], + should_fieldset_key_equal_to={ + "po_1_ethnicity": (0, False, ""), + "po_1_race": (1, False, ""), + "po_1_gender_flag": (2, False, ""), + "po_2_ethnicity": (3, False, ""), + "po_2_race": (4, False, ""), + "po_2_gender_flag": (5, False, ""), + "po_3_ethnicity": (6, True, ""), + "po_3_race": (7, True, ""), + "po_3_gender_flag": (8, True, ""), + "po_4_ethnicity": (9, True, ""), + "po_4_race": (10, True, ""), + "po_4_gender_flag": (11, True, ""), }, - ], - ), - ], - }, - "pricing_adj_index_name_ff": { - "phase_1": [ - SBLCheck.str_length( - min_value=0, - max_value=300, - name="pricing_adj_index_name_ff.invalid_text_length", - description=( - "'Adjustable rate transaction: index name: other' must not exceed" - "300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_adj_index_name_ff.conditional_field_conflict", - description=( - "When 'adjustable rate transaction: index name' does not equal" - "977 (other), 'adjustable rate transaction: index name: other'" - "must be blank." - "When 'adjustable rate transaction: index name' equals 977," - "'adjustable rate transaction: index name: other' must not be" - "blank." - ), - groupby="pricing_adj_index_name", - condition_values={"977"}, - ), - ], - }, - "pricing_adj_index_value": { - "phase_1": [ - SBLCheck( - is_number, - name="pricing_adj_index_value.invalid_numeric_format", - description="When present, 'adjustable rate transaction:" - " index value' must be a numeric value.", - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_adj_index_value.conditional_field_conflict", - description=( - "When 'interest rate type' does not equal 1 (variable" - " interest rate, no initial rate period)," - " or 3 (initial rate period > 12 months, variable interest" - " rate), 'adjustable rate transaction: index value' must be" - " blank. When 'interest rate type' equals 1 or 3," - " 'adjustable rate transaction: index value' must not be blank." - ), - groupby="pricing_interest_rate_type", - condition_values={"1", "3"}, - ), - ], - }, - "pricing_origination_charges": { - "phase_1": [ - SBLCheck( - is_number, - name="pricing_origination_charges.invalid_numeric_format", - description=( - "When present, 'total origination charges' must be a numeric", - "value.", - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [], - }, - "pricing_broker_fees": { - "phase_1": [ - SBLCheck( - is_number, - name="pricing_broker_fees.invalid_numeric_format", - description=( - "When present, 'amount of total broker fees' must be a", - "numeric value.", - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [], - }, - "pricing_initial_charges": { - "phase_1": [ - SBLCheck( - is_number, - name="pricing_initial_charges.invalid_numeric_format", - description=( - "When present, 'initial annual charges' must be a" "numeric value." - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [], - }, - "pricing_mca_addcost_flag": {"phase_1": [], "phase_2": []}, - "pricing_mca_addcost": {"phase_1": [], "phase_2": []}, - "pricing_prepenalty_allowed": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="pricing_prepenalty_allowed.invalid_enum_value", - description=( - "'Prepayment penalty could be imposed' must equal 1, 2, or 999." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "999", - ], - ), - ], - "phase_2": [ - SBLCheck( - has_valid_enum_pair, - name="pricing_mca_addcost_flag.enum_value_conflict", - description=( - "When 'credit product' does not equal 7 (merchant cash " - "advance), 8 (other sales-based financing transaction) " - "or 977 (other), 'MCA/sales-based: additional cost for " - "merchant cash advances or other sales-based financing: " - "NA flag' must be 999 (not applicable)." - ), - groupby="ct_credit_product", - conditions=[ - { - "condition_values": {"7", "8", "977"}, - "is_equal_condition": False, - "target_value": "999", - "should_equal_target": True, - } - ], - ), - ], - }, - "pricing_prepenalty_exists": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="pricing_prepenalty_exists.invalid_enum_value", - description="'Prepayment penalty exists' must equal 1, 2, or 999.", - element_wise=True, - accepted_values=[ - "1", - "2", - "999", - ], - ), - ], - "phase_2": [], - }, - "census_tract_adr_type": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="census_tract_adr_type.invalid_enum_value", - description=( - "'Census tract: type of address' must equal 1, 2, 3, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "988", - ], - ), - ], - "phase_2": [], - }, - "census_tract_number": { - "phase_1": [ - SBLCheck( - has_correct_length, - name="census_tract_number.invalid_text_length", - description=( - "When present, 'census tract: tract number' must " - "be a GEOID with exactly 11 digits." - ), - element_wise=True, - accepted_length=11, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - has_valid_enum_pair, - name="census_tract_number.conditional_field_conflict", - description=( - "When 'census tract: type of address' equals 988 (not " - "provided by applicant and otherwise undetermined), " - "'census tract: tract number' must be blank." - "When 'census tract: type of address' equals 1 (address" - " or location where the loan proceeds will principally " - "be applied), 2 (address or location of borrower's main " - "office or headquarters), or 3 (another address or " - "location associated with the applicant), 'census tract:" - " tract number' must not be blank." - ), - groupby="census_tract_adr_type", - conditions=[ - { - "condition_values": {"1", "2", "3"}, - "is_equal_condition": True, - "target_value": "", - "should_equal_target": False, + ), + SBLCheck( + has_valid_fieldset_pair, + name="po_demographics_3.conditional_fieldset_conflict", + description=( + "When 'number of principal owners' equals 3, " + "'ethnicity of principal owner 1, 2, and 3', 'race of principal" + " owner 1, 2, and 3', and 'sex/gender of principal owner 1, 2, " + "and 3: NP flag' should not be blank. Demographic fields for " + "principal owner 4 should be blank." + ), + groupby=[ + "po_1_ethnicity", + "po_1_race", + "po_1_gender_flag", + "po_2_ethnicity", + "po_2_race", + "po_2_gender_flag", + "po_3_ethnicity", + "po_3_race", + "po_3_gender_flag", + "po_4_ethnicity", + "po_4_race", + "po_4_gender_flag", + ], + condition_values=["3"], + should_fieldset_key_equal_to={ + "po_1_ethnicity": (0, False, ""), + "po_1_race": (1, False, ""), + "po_1_gender_flag": (2, False, ""), + "po_2_ethnicity": (3, False, ""), + "po_2_race": (4, False, ""), + "po_2_gender_flag": (5, False, ""), + "po_3_ethnicity": (6, False, ""), + "po_3_race": (7, False, ""), + "po_3_gender_flag": (8, False, ""), + "po_4_ethnicity": (9, True, ""), + "po_4_race": (10, True, ""), + "po_4_gender_flag": (11, True, ""), }, - { - "condition_values": {"988"}, - "is_equal_condition": True, - "target_value": "", - "should_equal_target": True, + ), + SBLCheck( + has_valid_fieldset_pair, + name="po_demographics_4.conditional_fieldset_conflict", + description=( + "When 'number of principal owners' equals 4, " + "'ethnicity of principal owner 1, 2, 3, and 4', " + "'race of principal owner 1, 2, 3, and 4', " + "and 'sex/gender of principal owner 1, 2, 3, and 4: NP flag'" + " should not be blank." + ), + groupby=[ + "po_1_ethnicity", + "po_1_race", + "po_1_gender_flag", + "po_2_ethnicity", + "po_2_race", + "po_2_gender_flag", + "po_3_ethnicity", + "po_3_race", + "po_3_gender_flag", + "po_4_ethnicity", + "po_4_race", + "po_4_gender_flag", + ], + condition_values=["4"], + should_fieldset_key_equal_to={ + "po_1_ethnicity": (0, False, ""), + "po_1_race": (1, False, ""), + "po_1_gender_flag": (2, False, ""), + "po_2_ethnicity": (3, False, ""), + "po_2_race": (4, False, ""), + "po_2_gender_flag": (5, False, ""), + "po_3_ethnicity": (6, False, ""), + "po_3_race": (7, False, ""), + "po_3_gender_flag": (8, False, ""), + "po_4_ethnicity": (9, False, ""), + "po_4_race": (10, False, ""), + "po_4_gender_flag": (11, False, ""), }, - ], - ), - ], - }, - "gross_annual_revenue_flag": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="gross_annual_revenue_flag.invalid_enum_value", - description=("'Gross annual revenue: NP flag' must equal 900 or 988."), - element_wise=True, - accepted_values=[ - "900", - "988", - ], - ), - ], - "phase_2": [], - }, - "gross_annual_revenue": { - "phase_1": [ - SBLCheck( - is_number, - name="gross_annual_revenue.invalid_numeric_format", - description=( - "When present, 'gross annual revenue' must be a numeric value." - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="gross_annual_revenue.conditional_field_conflict", - description=( - "When 'gross annual revenue: NP flag' does not equal 900 " - "(reported), 'gross annual revenue' must be blank. When " - "'gross annual revenue: NP flag' equals 900, " - "'gross annual revenue' must not be blank." - ), - groupby="gross_annual_revenue_flag", - condition_values={"900"}, - ), - ], - }, - "naics_code_flag": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="naics_code_flag.invalid_enum_value", - description=( - "'North American Industry Classification System (NAICS) " - "code: NP flag' must equal 900 or 988." - ), - element_wise=True, - accepted_values=[ - "900", - "988", - ], - ), - ], - "phase_2": [], - }, - "naics_code": { - "phase_1": [ - SBLCheck( - is_number, - name="naics_code.invalid_naics_format", - description=( - "'North American Industry Classification System " - "(NAICS) code' may only contain numeric characters." - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - has_correct_length, - name="naics_code.invalid_text_length", - description=( - "When present, 'North American Industry Classification System " - "(NAICS) code' must be three digits in length." - ), - element_wise=True, - accepted_length=3, - accept_blank=True, - ), - SBLCheck( - is_valid_code, - name="naics_code.invalid_naics_value", - description=( - "When present, 'North American Industry Classification System " - "(NAICS) code' should be a valid NAICS code." - ), - element_wise=True, - accept_blank=True, - codes=global_data.naics_codes, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="naics_code.conditional_field_conflict", - description=( - "When 'type of guarantee' does not contain 977 (other), " - "'free-form text field for other guarantee' must be blank. " - "When 'type of guarantee' contains 977, 'free-form text field" - " for other guarantee' must not be blank." - ), - groupby="naics_code_flag", - condition_values={"900"}, - ), - ], - }, - "number_of_workers": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="number_of_workers.invalid_enum_value", - description=( - "'Number of workers' must equal 1, 2, 3, 4, 5, 6, 7, 8, 9," - " or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "988", - ], - ), - ], - "phase_2": [], - }, - "time_in_business_type": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="time_in_business_type.invalid_enum_value", - description=( - "'Time in business: type of response'" - " must equal 1, 2, 3, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "988", - ], - ), - ], - "phase_2": [], - }, - "time_in_business": { - "phase_1": [ - SBLCheck( - is_number, - name="time_in_business.invalid_numeric_format", - description=( - "When present, 'time in business' must be a whole number." - ), - element_wise=True, - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - is_greater_than_or_equal_to, - name="time_in_business.invalid_numeric_value", - description=( - "When present, 'time in business'" - " must be greater than or equal to 0.", - ), - element_wise=True, - min_value="0", - accept_blank=True, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="time_in_business.conditional_field_conflict", - description=( - "When 'time in business: type of response' does not" - " equal 1 (the number of years an applicant has been" - " in business is collected or obtained by the financial" - " institution), 'time in business' must be blank. When" - " 'time in business: type of response' equals 1," - " 'time in business' must not be blank." - ), - groupby="time_in_business_type", - condition_values={"1"}, - ), - ], - }, - "business_ownership_status": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="business_ownership_status.invalid_enum_value", - description=( - "Each value in 'business ownership status'" - " (separated by semicolons) must equal 1, 2, 3," - " 955, 966, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "955", - "966", - "988", - ], - ), - ], - "phase_2": [ - SBLCheck( - has_valid_value_count, - name="business_ownership_status.invalid_number_of_values", - description=( - "'Business ownership status' must" " contain at least one value." - ), - element_wise=True, - min_length=1, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="business_ownership_status.duplicates_in_field", - description=( - "'Business ownership status' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="business_ownership_status.multi_value_field_restriction", - description=( - "When 'business ownership status' contains 966" - " (the applicant responded that they did not wish" - " to provide this information) or 988 (not provided" - " by applicant), 'business ownership status' should" - " not contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - }, - "num_principal_owners_flag": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="num_principal_owners_flag.invalid_enum_value", - description=( - "'Number of principal owners: NP flag' must equal 900 or 988." - ), - element_wise=True, - accepted_values=[ - "900", - "988", - ], - ), - ], - "phase_2": [ - SBLCheck( - has_valid_fieldset_pair, - name="po_demographics_0.conditional_fieldset_conflict", - description=( - "When 'number of principal owners' equals 0 or is blank, " - "demographic fields for principal owners 1, 2, 3, and 4 " - "should be blank." - ), - groupby=[ - "po_1_ethnicity", - "po_1_race", - "po_1_gender_flag", - "po_2_ethnicity", - "po_2_race", - "po_2_gender_flag", - "po_3_ethnicity", - "po_3_race", - "po_3_gender_flag", - "po_4_ethnicity", - "po_4_race", - "po_4_gender_flag", - ], - condition_values=["0", ""], - should_fieldset_key_equal_to={ - "po_1_ethnicity": (0, True, ""), - "po_1_race": (1, True, ""), - "po_1_gender_flag": (2, True, ""), - "po_2_ethnicity": (3, True, ""), - "po_2_race": (4, True, ""), - "po_2_gender_flag": (5, True, ""), - "po_3_ethnicity": (6, True, ""), - "po_3_race": (7, True, ""), - "po_3_gender_flag": (8, True, ""), - "po_4_ethnicity": (9, True, ""), - "po_4_race": (10, True, ""), - "po_4_gender_flag": (11, True, ""), - }, - ), - SBLCheck( - has_valid_fieldset_pair, - name="po_demographics_1.conditional_fieldset_conflict", - description=( - "When 'number of principal owners' equals 1, " - "'ethnicity of principal owner 1', 'race of principal owner 1'," - " and 'sex/gender of principal owner 1: NP flag' should not be" - " blank. Demographic fields for principal owners 2, 3, and 4 " - "should be blank." - ), - groupby=[ - "po_1_ethnicity", - "po_1_race", - "po_1_gender_flag", - "po_2_ethnicity", - "po_2_race", - "po_2_gender_flag", - "po_3_ethnicity", - "po_3_race", - "po_3_gender_flag", - "po_4_ethnicity", - "po_4_race", - "po_4_gender_flag", - ], - condition_values=["1"], - should_fieldset_key_equal_to={ - "po_1_ethnicity": (0, False, ""), - "po_1_race": (1, False, ""), - "po_1_gender_flag": (2, False, ""), - "po_2_ethnicity": (3, True, ""), - "po_2_race": (4, True, ""), - "po_2_gender_flag": (5, True, ""), - "po_3_ethnicity": (6, True, ""), - "po_3_race": (7, True, ""), - "po_3_gender_flag": (8, True, ""), - "po_4_ethnicity": (9, True, ""), - "po_4_race": (10, True, ""), - "po_4_gender_flag": (11, True, ""), - }, - ), - SBLCheck( - has_valid_fieldset_pair, - name="po_demographics_2.conditional_fieldset_conflict", - description=( - "When 'number of principal owners' equals 2, " - "'ethnicity of principal owner 1 and 2', 'race of principal " - "owner 1 and 2', and 'sex/gender of principal owner 1 and 2: " - "NP flag' should not be blank." - ), - groupby=[ - "po_1_ethnicity", - "po_1_race", - "po_1_gender_flag", - "po_2_ethnicity", - "po_2_race", - "po_2_gender_flag", - "po_3_ethnicity", - "po_3_race", - "po_3_gender_flag", - "po_4_ethnicity", - "po_4_race", - "po_4_gender_flag", - ], - condition_values=["2"], - should_fieldset_key_equal_to={ - "po_1_ethnicity": (0, False, ""), - "po_1_race": (1, False, ""), - "po_1_gender_flag": (2, False, ""), - "po_2_ethnicity": (3, False, ""), - "po_2_race": (4, False, ""), - "po_2_gender_flag": (5, False, ""), - "po_3_ethnicity": (6, True, ""), - "po_3_race": (7, True, ""), - "po_3_gender_flag": (8, True, ""), - "po_4_ethnicity": (9, True, ""), - "po_4_race": (10, True, ""), - "po_4_gender_flag": (11, True, ""), - }, - ), - SBLCheck( - has_valid_fieldset_pair, - name="po_demographics_3.conditional_fieldset_conflict", - description=( - "When 'number of principal owners' equals 3, " - "'ethnicity of principal owner 1, 2, and 3', 'race of principal" - " owner 1, 2, and 3', and 'sex/gender of principal owner 1, 2, " - "and 3: NP flag' should not be blank. Demographic fields for " - "principal owner 4 should be blank." - ), - groupby=[ - "po_1_ethnicity", - "po_1_race", - "po_1_gender_flag", - "po_2_ethnicity", - "po_2_race", - "po_2_gender_flag", - "po_3_ethnicity", - "po_3_race", - "po_3_gender_flag", - "po_4_ethnicity", - "po_4_race", - "po_4_gender_flag", - ], - condition_values=["3"], - should_fieldset_key_equal_to={ - "po_1_ethnicity": (0, False, ""), - "po_1_race": (1, False, ""), - "po_1_gender_flag": (2, False, ""), - "po_2_ethnicity": (3, False, ""), - "po_2_race": (4, False, ""), - "po_2_gender_flag": (5, False, ""), - "po_3_ethnicity": (6, False, ""), - "po_3_race": (7, False, ""), - "po_3_gender_flag": (8, False, ""), - "po_4_ethnicity": (9, True, ""), - "po_4_race": (10, True, ""), - "po_4_gender_flag": (11, True, ""), - }, - ), - SBLCheck( - has_valid_fieldset_pair, - name="po_demographics_4.conditional_fieldset_conflict", - description=( - "When 'number of principal owners' equals 4, " - "'ethnicity of principal owner 1, 2, 3, and 4', " - "'race of principal owner 1, 2, 3, and 4', " - "and 'sex/gender of principal owner 1, 2, 3, and 4: NP flag'" - " should not be blank." - ), - groupby=[ - "po_1_ethnicity", - "po_1_race", - "po_1_gender_flag", - "po_2_ethnicity", - "po_2_race", - "po_2_gender_flag", - "po_3_ethnicity", - "po_3_race", - "po_3_gender_flag", - "po_4_ethnicity", - "po_4_race", - "po_4_gender_flag", - ], - condition_values=["4"], - should_fieldset_key_equal_to={ - "po_1_ethnicity": (0, False, ""), - "po_1_race": (1, False, ""), - "po_1_gender_flag": (2, False, ""), - "po_2_ethnicity": (3, False, ""), - "po_2_race": (4, False, ""), - "po_2_gender_flag": (5, False, ""), - "po_3_ethnicity": (6, False, ""), - "po_3_race": (7, False, ""), - "po_3_gender_flag": (8, False, ""), - "po_4_ethnicity": (9, False, ""), - "po_4_race": (10, False, ""), - "po_4_gender_flag": (11, False, ""), - }, - ), - ], - }, - "num_principal_owners": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="num_principal_owners.invalid_enum_value", - description=( - "When present, 'number of principal owners' must equal " - "0, 1, 2, 3, or 4." - ), - element_wise=True, - accepted_values=["0", "1", "2", "3", "4"], - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="num_principal_owners.conditional_field_conflict", - description=( - "When 'number of principal owners: NP flag' does not equal 900 " - "(reported), 'number of principal owners' must be blank." - "When 'number of principal owners: NP flag' equals 900, " - "'number of principal owners' must not be blank." - ), - groupby="num_principal_owners_flag", - condition_values={"900"}, - ), - ], - }, - "po_1_ethnicity": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_1_ethnicity.invalid_enum_value", - description=( - "When present, each value in 'ethnicity" - " of principal owner 1' (separated by" - " semicolons) must equal 1, 11, 12," - " 13, 14, 2, 966, 977, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "11", - "12", - "13", - "14", - "2", - "966", - "977", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - is_unique_in_field, - warning=True, - name="po_1_ethnicity.duplicates_in_field", - description=( - "'Ethnicity of principal owner 1' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_1_ethnicity.multi_value_field_restriction", - description=( - "When 'ethnicity of principal owner 1' contains" - " 966 (the applicant responded that they did" - " not wish to provide this information) or 988" - " (not provided by applicant), 'ethnicity of" - " principal owner 1' should not contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - }, - "po_1_ethnicity_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_1_ethnicity_ff.invalid_text_length", - description=( - "'Ethnicity of principal owner 1: free-form" - " text field for other Hispanic or Latino'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_ethnicity_ff.conditional_field_conflict", - description=( - "When 'ethnicity of principal owner 1' does not" - " contain 977 (the applicant responded in the" - " free-form text field), 'ethnicity of principal" - " owner 1: free-form text field for other Hispanic" - " or Latino' must be blank. When 'ethnicity of principal" - " owner 1' contains 977, 'ethnicity of principal" - " owner 1: free-form text field for other Hispanic" - " or Latino' must not be blank." - ), - groupby="po_1_ethnicity", - condition_values={"977"}, - ), - ], - }, - "po_1_race": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_1_race.invalid_enum_value", - description=( - "When present, each value in 'race" - " of principal owner 1' (separated by" - " semicolons) must equal 1, 2, 21, 22," - " 23, 24, 25, 26, 27, 3, 31, 32, 33," - " 34, 35, 36, 37, 4, 41, 42, 43, 44," - " 5, 966, 971, 972, 973, 974, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "3", - "31", - "32", - "33", - "34", - "35", - "36", - "37", - "4", - "41", - "42", - "43", - "44", - "5", - "966", - "971", - "972", - "973", - "974", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - is_unique_in_field, - warning=True, - name="po_1_race.duplicates_in_field", - description=( - "'Race of principal owner 1' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_1_race.multi_value_field_restriction", - description=( - "When 'race of principal owner 1' contains" - " 966 (the applicant responded that they" - " did not wish to provide this information)" - " or 988 (not provided by applicant)," - " 'race of principal owner 1' should not" - " contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - }, - "po_1_race_anai_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_1_race_anai_ff.invalid_text_length", - description=( - "'Race of principal owner 1: free-form" - " text field for American Indian or Alaska" - " Native Enrolled or Principal Tribe' must" - " not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_race_anai_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 1' does not" - " contain 971 (the applicant responded in" - " the free-form text field for American Indian" - " or Alaska Native Enrolled or Principal Tribe)," - " 'race of principal owner 1: free-form text" - " field for American Indian or Alaska Native" - " Enrolled or Principal Tribe' must be blank." - " When 'race of principal owner 1' contains 971," - " 'race of principal owner 1: free-form text field" - " for American Indian or Alaska Native Enrolled or" - " Principal Tribe' must not be blank." - ), - groupby="po_1_race", - condition_values={"971"}, - ), - ], - }, - "po_1_race_asian_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_1_race_asian_ff.invalid_text_length", - description=( - "'Race of principal owner 1: free-form text" - " field for other Asian' must not exceed 300" - " characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_race_asian_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 1' does not contain" - " 972 (the applicant responded in the free-form text" - " field for other Asian race), 'race of principal" - " owner 1: free-form text field for other Asian' must" - " be blank. When 'race of principal owner 1' contains" - " 972, 'race of principal owner 1: free-form text field" - " for other Asian' must not be blank." - ), - groupby="po_1_race", - condition_values={"972"}, - ), - ], - }, - "po_1_race_baa_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_1_race_baa_ff.invalid_text_length", - description=( - "'Race of principal owner 1: free-form text" - " field for other Black or African American'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_race_baa_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 1' does not contain 973" - " (the applicant responded in the free-form text field" - " for other Black or African race), 'race of principal" - " owner 1: free-form text field for other Black or African" - " American' must be blank. When 'race of principal owner 1'" - " contains 973, 'race of principal owner 1: free-form text" - " field for other Black or African American' must not be blank." - ), - groupby="po_1_race", - condition_values={"973"}, - ), - ], - }, - "po_1_race_pi_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_1_race_pi_ff.invalid_text_length", - description=( - "'Race of principal owner 1: free-form text" - " field for other Pacific Islander race' must" - " not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_race_pi_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 1' does not contain 974" - " (the applicant responded in the free-form text field" - " for other Pacific Islander race), 'race of principal" - " owner 1: free-form text field for other Pacific Islander" - " race' must be blank. When 'race of principal owner 1'" - " contains 974, 'race of principal owner 1: free-form text" - " field for other Pacific Islander race' must not be blank." - ), - groupby="po_1_race", - condition_values={"974"}, - ), - ], - }, - "po_1_gender_flag": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_1_gender_flag.invalid_enum_value", - description=( - "When present, 'sex/gender of principal" - " owner 1: NP flag' must equal 1, 966, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "966", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [], - }, - "po_1_gender_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_1_gender_ff.invalid_text_length", - description=( - "'Sex/gender of principal owner 1: free-form" - " text field for self-identified sex/gender'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_gender_ff.conditional_field_conflict", - description=( - "When 'sex/gender of principal owner 1: NP flag'" - " does not equal 1 (the applicant responded in the" - " free-form text field), 'sex/gender of principal" - " owner 1: free-form text field for self-identified" - " sex/gender' must be blank. When 'sex/gender of" - " principal owner 1: NP flag' equals 1, 'sex/gender" - " of principal owner 1: free-form text field for" - " self-identified sex/gender' must not be blank." - ), - groupby="po_1_gender_flag", - condition_values={"1"}, - ), - ], - }, - "po_2_ethnicity": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_2_ethnicity.invalid_enum_value", - description=( - "When present, each value in 'ethnicity" - " of principal owner 2' (separated by" - " semicolons) must equal 1, 11, 12," - " 13, 14, 2, 966, 977, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "11", - "12", - "13", - "14", - "2", - "966", - "977", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - is_unique_in_field, - warning=True, - name="po_2_ethnicity.duplicates_in_field", - description=( - "'Ethnicity of principal owner 2' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_2_ethnicity.multi_value_field_restriction", - description=( - "When 'ethnicity of principal owner 2' contains" - " 966 (the applicant responded that they did" - " not wish to provide this information) or 988" - " (not provided by applicant), 'ethnicity of" - " principal owner 2' should not contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - }, - "po_2_ethnicity_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_2_ethnicity_ff.invalid_text_length", - description=( - "'Ethnicity of principal owner 2: free-form" - " text field for other Hispanic or Latino'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_ethnicity_ff.conditional_field_conflict", - description=( - "When 'ethnicity of principal owner 2' does not" - " contain 977 (the applicant responded in the" - " free-form text field), 'ethnicity of principal" - " owner 2: free-form text field for other Hispanic" - " or Latino' must be blank. When 'ethnicity of principal" - " owner 2' contains 977, 'ethnicity of principal" - " owner 2: free-form text field for other Hispanic" - " or Latino' must not be blank." - ), - groupby="po_2_ethnicity", - condition_values={"977"}, - ), - ], - }, - "po_2_race": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_2_race.invalid_enum_value", - description=( - "When present, each value in 'race" - " of principal owner 2' (separated by" - " semicolons) must equal 1, 2, 21, 22," - " 23, 24, 25, 26, 27, 3, 31, 32, 33," - " 34, 35, 36, 37, 4, 41, 42, 43, 44," - " 5, 966, 971, 972, 973, 974, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "3", - "31", - "32", - "33", - "34", - "35", - "36", - "37", - "4", - "41", - "42", - "43", - "44", - "5", - "966", - "971", - "972", - "973", - "974", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - is_unique_in_field, - warning=True, - name="po_2_race.duplicates_in_field", - description=( - "'Race of principal owner 2' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_2_race.multi_value_field_restriction", - description=( - "When 'race of principal owner 2' contains" - " 966 (the applicant responded that they" - " did not wish to provide this information)" - " or 988 (not provided by applicant)," - " 'race of principal owner 2' should not" - " contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - }, - "po_2_race_anai_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_2_race_anai_ff.invalid_text_length", - description=( - "'Race of principal owner 2: free-form" - " text field for American Indian or Alaska" - " Native Enrolled or Principal Tribe' must" - " not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_race_anai_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 2' does not" - " contain 971 (the applicant responded in" - " the free-form text field for American Indian" - " or Alaska Native Enrolled or Principal Tribe)," - " 'race of principal owner 2: free-form text" - " field for American Indian or Alaska Native" - " Enrolled or Principal Tribe' must be blank." - " When 'race of principal owner 2' contains 971," - " 'race of principal owner 2: free-form text field" - " for American Indian or Alaska Native Enrolled or" - " Principal Tribe' must not be blank." - ), - groupby="po_2_race", - condition_values={"971"}, - ), - ], - }, - "po_2_race_asian_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_2_race_asian_ff.invalid_text_length", - description=( - "'Race of principal owner 2: free-form text" - " field for other Asian' must not exceed 300" - " characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_race_asian_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 2' does not contain" - " 972 (the applicant responded in the free-form text" - " field for other Asian race), 'race of principal" - " owner 2: free-form text field for other Asian' must" - " be blank. When 'race of principal owner 2' contains" - " 972, 'race of principal owner 2: free-form text field" - " for other Asian' must not be blank." - ), - groupby="po_2_race", - condition_values={"972"}, - ), - ], - }, - "po_2_race_baa_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_2_race_baa_ff.invalid_text_length", - description=( - "'Race of principal owner 2: free-form text" - " field for other Black or African American'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_race_baa_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 2' does not contain 973" - " (the applicant responded in the free-form text field" - " for other Black or African race), 'race of principal" - " owner 2: free-form text field for other Black or African" - " American' must be blank. When 'race of principal owner 2'" - " contains 973, 'race of principal owner 2: free-form text" - " field for other Black or African American' must not be blank." - ), - groupby="po_2_race", - condition_values={"973"}, - ), - ], - }, - "po_2_race_pi_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_2_race_pi_ff.invalid_text_length", - description=( - "'Race of principal owner 2: free-form text" - " field for other Pacific Islander race' must" - " not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_race_pi_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 2' does not contain 974" - " (the applicant responded in the free-form text field" - " for other Pacific Islander race), 'race of principal" - " owner 2: free-form text field for other Pacific Islander" - " race' must be blank. When 'race of principal owner 2'" - " contains 974, 'race of principal owner 2: free-form text" - " field for other Pacific Islander race' must not be blank." - ), - groupby="po_2_race", - condition_values={"974"}, - ), - ], - }, - "po_2_gender_flag": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_2_gender_flag.invalid_enum_value", - description=( - "When present, 'sex/gender of principal" - " owner 2: NP flag' must equal 1, 966, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "966", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [], - }, - "po_2_gender_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_2_gender_ff.invalid_text_length", - description=( - "'Sex/gender of principal owner 2: free-form" - " text field for self-identified sex/gender'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_gender_ff.conditional_field_conflict", - description=( - "When 'sex/gender of principal owner 2: NP flag'" - " does not equal 1 (the applicant responded in the" - " free-form text field), 'sex/gender of principal" - " owner 2: free-form text field for self-identified" - " sex/gender' must be blank. When 'sex/gender of" - " principal owner 2: NP flag' equals 1, 'sex/gender" - " of principal owner 2: free-form text field for" - " self-identified sex/gender' must not be blank." - ), - groupby="po_2_gender_flag", - condition_values={"1"}, - ), - ], - }, - "po_3_ethnicity": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_3_ethnicity.invalid_enum_value", - description=( - "When present, each value in 'ethnicity" - " of principal owner 3' (separated by" - " semicolons) must equal 1, 11, 12," - " 13, 14, 2, 966, 977, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "11", - "12", - "13", - "14", - "2", - "966", - "977", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - is_unique_in_field, - warning=True, - name="po_3_ethnicity.duplicates_in_field", - description=( - "'Ethnicity of principal owner 3' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_3_ethnicity.multi_value_field_restriction", - description=( - "When 'ethnicity of principal owner 3' contains" - " 966 (the applicant responded that they did" - " not wish to provide this information) or 988" - " (not provided by applicant), 'ethnicity of" - " principal owner 3' should not contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - }, - "po_3_ethnicity_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_3_ethnicity_ff.invalid_text_length", - description=( - "'Ethnicity of principal owner 3: free-form" - " text field for other Hispanic or Latino'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_ethnicity_ff.conditional_field_conflict", - description=( - "When 'ethnicity of principal owner 3' does not" - " contain 977 (the applicant responded in the" - " free-form text field), 'ethnicity of principal" - " owner 3: free-form text field for other Hispanic" - " or Latino' must be blank. When 'ethnicity of principal" - " owner 3' contains 977, 'ethnicity of principal" - " owner 3: free-form text field for other Hispanic" - " or Latino' must not be blank." - ), - groupby="po_3_ethnicity", - condition_values={"977"}, - ), - ], - }, - "po_3_race": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_3_race.invalid_enum_value", - description=( - "When present, each value in 'race" - " of principal owner 3' (separated by" - " semicolons) must equal 1, 2, 21, 22," - " 23, 24, 25, 26, 27, 3, 31, 32, 33," - " 34, 35, 36, 37, 4, 41, 42, 43, 44," - " 5, 966, 971, 972, 973, 974, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "3", - "31", - "32", - "33", - "34", - "35", - "36", - "37", - "4", - "41", - "42", - "43", - "44", - "5", - "966", - "971", - "972", - "973", - "974", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - is_unique_in_field, - warning=True, - name="po_3_race.duplicates_in_field", - description=( - "'Race of principal owner 3' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_3_race.multi_value_field_restriction", - description=( - "When 'race of principal owner 3' contains" - " 966 (the applicant responded that they" - " did not wish to provide this information)" - " or 988 (not provided by applicant)," - " 'race of principal owner 3' should not" - " contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - }, - "po_3_race_anai_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_3_race_anai_ff.invalid_text_length", - description=( - "'Race of principal owner 3: free-form" - " text field for American Indian or Alaska" - " Native Enrolled or Principal Tribe' must" - " not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_race_anai_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 3' does not" - " contain 971 (the applicant responded in" - " the free-form text field for American Indian" - " or Alaska Native Enrolled or Principal Tribe)," - " 'race of principal owner 3: free-form text" - " field for American Indian or Alaska Native" - " Enrolled or Principal Tribe' must be blank." - " When 'race of principal owner 3' contains 971," - " 'race of principal owner 3: free-form text field" - " for American Indian or Alaska Native Enrolled or" - " Principal Tribe' must not be blank." - ), - groupby="po_3_race", - condition_values={"971"}, - ), - ], - }, - "po_3_race_asian_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_3_race_asian_ff.invalid_text_length", - description=( - "'Race of principal owner 3: free-form text" - " field for other Asian' must not exceed 300" - " characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_race_asian_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 3' does not contain" - " 972 (the applicant responded in the free-form text" - " field for other Asian race), 'race of principal" - " owner 3: free-form text field for other Asian' must" - " be blank. When 'race of principal owner 3' contains" - " 972, 'race of principal owner 3: free-form text field" - " for other Asian' must not be blank." - ), - groupby="po_3_race", - condition_values={"972"}, - ), - ], - }, - "po_3_race_baa_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_3_race_baa_ff.invalid_text_length", - description=( - "'Race of principal owner 3: free-form text" - " field for other Black or African American'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_race_baa_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 3' does not contain 973" - " (the applicant responded in the free-form text field" - " for other Black or African race), 'race of principal" - " owner 3: free-form text field for other Black or African" - " American' must be blank. When 'race of principal owner 3'" - " contains 973, 'race of principal owner 3: free-form text" - " field for other Black or African American' must not be blank." - ), - groupby="po_3_race", - condition_values={"973"}, - ), - ], - }, - "po_3_race_pi_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_3_race_pi_ff.invalid_text_length", - description=( - "'Race of principal owner 3: free-form text" - " field for other Pacific Islander race' must" - " not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_race_pi_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 3' does not contain 974" - " (the applicant responded in the free-form text field" - " for other Pacific Islander race), 'race of principal" - " owner 3: free-form text field for other Pacific Islander" - " race' must be blank. When 'race of principal owner 3'" - " contains 974, 'race of principal owner 3: free-form text" - " field for other Pacific Islander race' must not be blank." - ), - groupby="po_3_race", - condition_values={"974"}, - ), - ], - }, - "po_3_gender_flag": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_3_gender_flag.invalid_enum_value", - description=( - "When present, 'sex/gender of principal" - " owner 3: NP flag' must equal 1, 966, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "966", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [], - }, - "po_3_gender_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_3_gender_ff.invalid_text_length", - description=( - "'Sex/gender of principal owner 3: free-form" - " text field for self-identified sex/gender'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_gender_ff.conditional_field_conflict", - description=( - "When 'sex/gender of principal owner 3: NP flag'" - " does not equal 1 (the applicant responded in the" - " free-form text field), 'sex/gender of principal" - " owner 3: free-form text field for self-identified" - " sex/gender' must be blank. When 'sex/gender of" - " principal owner 3: NP flag' equals 1, 'sex/gender" - " of principal owner 3: free-form text field for" - " self-identified sex/gender' must not be blank." - ), - groupby="po_3_gender_flag", - condition_values={"1"}, - ), - ], - }, - "po_4_ethnicity": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_4_ethnicity.invalid_enum_value", - description=( - "When present, each value in 'ethnicity" - " of principal owner 4' (separated by" - " semicolons) must equal 1, 11, 12," - " 13, 14, 2, 966, 977, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "11", - "12", - "13", - "14", - "2", - "966", - "977", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - is_unique_in_field, - warning=True, - name="po_4_ethnicity.duplicates_in_field", - description=( - "'Ethnicity of principal owner 4' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_4_ethnicity.multi_value_field_restriction", - description=( - "When 'ethnicity of principal owner 4' contains" - " 966 (the applicant responded that they did" - " not wish to provide this information) or 988" - " (not provided by applicant), 'ethnicity of" - " principal owner 4' should not contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - }, - "po_4_ethnicity_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_4_ethnicity_ff.invalid_text_length", - description=( - "'Ethnicity of principal owner 4: free-form" - " text field for other Hispanic or Latino'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_ethnicity_ff.conditional_field_conflict", - description=( - "When 'ethnicity of principal owner 4' does not" - " contain 977 (the applicant responded in the" - " free-form text field), 'ethnicity of principal" - " owner 4: free-form text field for other Hispanic" - " or Latino' must be blank. When 'ethnicity of principal" - " owner 4' contains 977, 'ethnicity of principal" - " owner 4: free-form text field for other Hispanic" - " or Latino' must not be blank." - ), - groupby="po_4_ethnicity", - condition_values={"977"}, - ), - ], - }, - "po_4_race": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_4_race.invalid_enum_value", - description=( - "When present, each value in 'race" - " of principal owner 4' (separated by" - " semicolons) must equal 1, 2, 21, 22," - " 23, 24, 25, 26, 27, 3, 31, 32, 33," - " 34, 35, 36, 37, 4, 41, 42, 43, 44," - " 5, 966, 971, 972, 973, 974, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "3", - "31", - "32", - "33", - "34", - "35", - "36", - "37", - "4", - "41", - "42", - "43", - "44", - "5", - "966", - "971", - "972", - "973", - "974", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [ - SBLCheck( - is_unique_in_field, - warning=True, - name="po_4_race.duplicates_in_field", - description=( - "'Race of principal owner 4' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_4_race.multi_value_field_restriction", - description=( - "When 'race of principal owner 4' contains" - " 966 (the applicant responded that they" - " did not wish to provide this information)" - " or 988 (not provided by applicant)," - " 'race of principal owner 4' should not" - " contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - }, - "po_4_race_anai_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_4_race_anai_ff.invalid_text_length", - description=( - "'Race of principal owner 4: free-form" - " text field for American Indian or Alaska" - " Native Enrolled or Principal Tribe' must" - " not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_race_anai_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 4' does not" - " contain 971 (the applicant responded in" - " the free-form text field for American Indian" - " or Alaska Native Enrolled or Principal Tribe)," - " 'race of principal owner 4: free-form text" - " field for American Indian or Alaska Native" - " Enrolled or Principal Tribe' must be blank." - " When 'race of principal owner 4' contains 971," - " 'race of principal owner 4: free-form text field" - " for American Indian or Alaska Native Enrolled or" - " Principal Tribe' must not be blank." - ), - groupby="po_4_race", - condition_values={"971"}, - ), - ], - }, - "po_4_race_asian_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_4_race_asian_ff.invalid_text_length", - description=( - "'Race of principal owner 4: free-form text" - " field for other Asian' must not exceed 300" - " characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_race_asian_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 4' does not contain" - " 972 (the applicant responded in the free-form text" - " field for other Asian race), 'race of principal" - " owner 4: free-form text field for other Asian' must" - " be blank. When 'race of principal owner 4' contains" - " 972, 'race of principal owner 4: free-form text field" - " for other Asian' must not be blank." - ), - groupby="po_4_race", - condition_values={"972"}, - ), - ], - }, - "po_4_race_baa_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_4_race_baa_ff.invalid_text_length", - description=( - "'Race of principal owner 4: free-form text" - " field for other Black or African American'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_race_baa_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 4' does not contain 973" - " (the applicant responded in the free-form text field" - " for other Black or African race), 'race of principal" - " owner 4: free-form text field for other Black or African" - " American' must be blank. When 'race of principal owner 4'" - " contains 973, 'race of principal owner 4: free-form text" - " field for other Black or African American' must not be blank." - ), - groupby="po_4_race", - condition_values={"973"}, - ), - ], - }, - "po_4_race_pi_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_4_race_pi_ff.invalid_text_length", - description=( - "'Race of principal owner 4: free-form text" - " field for other Pacific Islander race' must" - " not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_race_pi_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 4' does not contain 974" - " (the applicant responded in the free-form text field" - " for other Pacific Islander race), 'race of principal" - " owner 4: free-form text field for other Pacific Islander" - " race' must be blank. When 'race of principal owner 4'" - " contains 974, 'race of principal owner 4: free-form text" - " field for other Pacific Islander race' must not be blank." - ), - groupby="po_4_race", - condition_values={"974"}, - ), - ], - }, - "po_4_gender_flag": { - "phase_1": [ - SBLCheck( - is_valid_enum, - name="po_4_gender_flag.invalid_enum_value", - description=( - "When present, 'sex/gender of principal" - " owner 4: NP flag' must equal 1, 966, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "966", - "988", - ], - accept_blank=True, - ), - ], - "phase_2": [], - }, - "po_4_gender_ff": { - "phase_1": [ - SBLCheck.str_length( - 0, - 300, - name="po_4_gender_ff.invalid_text_length", - description=( - "'Sex/gender of principal owner 4: free-form" - " text field for self-identified sex/gender'" - " must not exceed 300 characters in length." - ), - ), - ], - "phase_2": [ - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_gender_ff.conditional_field_conflict", - description=( - "When 'sex/gender of principal owner 4: NP flag'" - " does not equal 1 (the applicant responded in the" - " free-form text field), 'sex/gender of principal" - " owner 4: free-form text field for self-identified" - " sex/gender' must be blank. When 'sex/gender of" - " principal owner 4: NP flag' equals 1, 'sex/gender" - " of principal owner 4: free-form text field for" - " self-identified sex/gender' must not be blank." - ), - groupby="po_4_gender_flag", - condition_values={"1"}, - ), - ], - }, -} + ), + ], + }, + "num_principal_owners": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="num_principal_owners.invalid_enum_value", + description=( + "When present, 'number of principal owners' must equal " + "0, 1, 2, 3, or 4." + ), + element_wise=True, + accepted_values=["0", "1", "2", "3", "4"], + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="num_principal_owners.conditional_field_conflict", + description=( + "When 'number of principal owners: NP flag' does not equal 900 " + "(reported), 'number of principal owners' must be blank." + "When 'number of principal owners: NP flag' equals 900, " + "'number of principal owners' must not be blank." + ), + groupby="num_principal_owners_flag", + condition_values={"900"}, + ), + ], + }, + "po_1_ethnicity": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_1_ethnicity.invalid_enum_value", + description=( + "When present, each value in 'ethnicity" + " of principal owner 1' (separated by" + " semicolons) must equal 1, 11, 12," + " 13, 14, 2, 966, 977, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "11", + "12", + "13", + "14", + "2", + "966", + "977", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + is_unique_in_field, + warning=True, + name="po_1_ethnicity.duplicates_in_field", + description=( + "'Ethnicity of principal owner 1' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_1_ethnicity.multi_value_field_restriction", + description=( + "When 'ethnicity of principal owner 1' contains" + " 966 (the applicant responded that they did" + " not wish to provide this information) or 988" + " (not provided by applicant), 'ethnicity of" + " principal owner 1' should not contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + }, + "po_1_ethnicity_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_1_ethnicity_ff.invalid_text_length", + description=( + "'Ethnicity of principal owner 1: free-form" + " text field for other Hispanic or Latino'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_ethnicity_ff.conditional_field_conflict", + description=( + "When 'ethnicity of principal owner 1' does not" + " contain 977 (the applicant responded in the" + " free-form text field), 'ethnicity of principal" + " owner 1: free-form text field for other Hispanic" + " or Latino' must be blank. When 'ethnicity of principal" + " owner 1' contains 977, 'ethnicity of principal" + " owner 1: free-form text field for other Hispanic" + " or Latino' must not be blank." + ), + groupby="po_1_ethnicity", + condition_values={"977"}, + ), + ], + }, + "po_1_race": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_1_race.invalid_enum_value", + description=( + "When present, each value in 'race" + " of principal owner 1' (separated by" + " semicolons) must equal 1, 2, 21, 22," + " 23, 24, 25, 26, 27, 3, 31, 32, 33," + " 34, 35, 36, 37, 4, 41, 42, 43, 44," + " 5, 966, 971, 972, 973, 974, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "3", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "4", + "41", + "42", + "43", + "44", + "5", + "966", + "971", + "972", + "973", + "974", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + is_unique_in_field, + warning=True, + name="po_1_race.duplicates_in_field", + description=( + "'Race of principal owner 1' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_1_race.multi_value_field_restriction", + description=( + "When 'race of principal owner 1' contains" + " 966 (the applicant responded that they" + " did not wish to provide this information)" + " or 988 (not provided by applicant)," + " 'race of principal owner 1' should not" + " contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + }, + "po_1_race_anai_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_1_race_anai_ff.invalid_text_length", + description=( + "'Race of principal owner 1: free-form" + " text field for American Indian or Alaska" + " Native Enrolled or Principal Tribe' must" + " not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_race_anai_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 1' does not" + " contain 971 (the applicant responded in" + " the free-form text field for American Indian" + " or Alaska Native Enrolled or Principal Tribe)," + " 'race of principal owner 1: free-form text" + " field for American Indian or Alaska Native" + " Enrolled or Principal Tribe' must be blank." + " When 'race of principal owner 1' contains 971," + " 'race of principal owner 1: free-form text field" + " for American Indian or Alaska Native Enrolled or" + " Principal Tribe' must not be blank." + ), + groupby="po_1_race", + condition_values={"971"}, + ), + ], + }, + "po_1_race_asian_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_1_race_asian_ff.invalid_text_length", + description=( + "'Race of principal owner 1: free-form text" + " field for other Asian' must not exceed 300" + " characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_race_asian_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 1' does not contain" + " 972 (the applicant responded in the free-form text" + " field for other Asian race), 'race of principal" + " owner 1: free-form text field for other Asian' must" + " be blank. When 'race of principal owner 1' contains" + " 972, 'race of principal owner 1: free-form text field" + " for other Asian' must not be blank." + ), + groupby="po_1_race", + condition_values={"972"}, + ), + ], + }, + "po_1_race_baa_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_1_race_baa_ff.invalid_text_length", + description=( + "'Race of principal owner 1: free-form text" + " field for other Black or African American'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_race_baa_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 1' does not contain 973" + " (the applicant responded in the free-form text field" + " for other Black or African race), 'race of principal" + " owner 1: free-form text field for other Black or African" + " American' must be blank. When 'race of principal owner 1'" + " contains 973, 'race of principal owner 1: free-form text" + " field for other Black or African American' must not be blank." + ), + groupby="po_1_race", + condition_values={"973"}, + ), + ], + }, + "po_1_race_pi_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_1_race_pi_ff.invalid_text_length", + description=( + "'Race of principal owner 1: free-form text" + " field for other Pacific Islander race' must" + " not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_race_pi_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 1' does not contain 974" + " (the applicant responded in the free-form text field" + " for other Pacific Islander race), 'race of principal" + " owner 1: free-form text field for other Pacific Islander" + " race' must be blank. When 'race of principal owner 1'" + " contains 974, 'race of principal owner 1: free-form text" + " field for other Pacific Islander race' must not be blank." + ), + groupby="po_1_race", + condition_values={"974"}, + ), + ], + }, + "po_1_gender_flag": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_1_gender_flag.invalid_enum_value", + description=( + "When present, 'sex/gender of principal" + " owner 1: NP flag' must equal 1, 966, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "966", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [], + }, + "po_1_gender_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_1_gender_ff.invalid_text_length", + description=( + "'Sex/gender of principal owner 1: free-form" + " text field for self-identified sex/gender'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_gender_ff.conditional_field_conflict", + description=( + "When 'sex/gender of principal owner 1: NP flag'" + " does not equal 1 (the applicant responded in the" + " free-form text field), 'sex/gender of principal" + " owner 1: free-form text field for self-identified" + " sex/gender' must be blank. When 'sex/gender of" + " principal owner 1: NP flag' equals 1, 'sex/gender" + " of principal owner 1: free-form text field for" + " self-identified sex/gender' must not be blank." + ), + groupby="po_1_gender_flag", + condition_values={"1"}, + ), + ], + }, + "po_2_ethnicity": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_2_ethnicity.invalid_enum_value", + description=( + "When present, each value in 'ethnicity" + " of principal owner 2' (separated by" + " semicolons) must equal 1, 11, 12," + " 13, 14, 2, 966, 977, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "11", + "12", + "13", + "14", + "2", + "966", + "977", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + is_unique_in_field, + warning=True, + name="po_2_ethnicity.duplicates_in_field", + description=( + "'Ethnicity of principal owner 2' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_2_ethnicity.multi_value_field_restriction", + description=( + "When 'ethnicity of principal owner 2' contains" + " 966 (the applicant responded that they did" + " not wish to provide this information) or 988" + " (not provided by applicant), 'ethnicity of" + " principal owner 2' should not contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + }, + "po_2_ethnicity_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_2_ethnicity_ff.invalid_text_length", + description=( + "'Ethnicity of principal owner 2: free-form" + " text field for other Hispanic or Latino'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_ethnicity_ff.conditional_field_conflict", + description=( + "When 'ethnicity of principal owner 2' does not" + " contain 977 (the applicant responded in the" + " free-form text field), 'ethnicity of principal" + " owner 2: free-form text field for other Hispanic" + " or Latino' must be blank. When 'ethnicity of principal" + " owner 2' contains 977, 'ethnicity of principal" + " owner 2: free-form text field for other Hispanic" + " or Latino' must not be blank." + ), + groupby="po_2_ethnicity", + condition_values={"977"}, + ), + ], + }, + "po_2_race": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_2_race.invalid_enum_value", + description=( + "When present, each value in 'race" + " of principal owner 2' (separated by" + " semicolons) must equal 1, 2, 21, 22," + " 23, 24, 25, 26, 27, 3, 31, 32, 33," + " 34, 35, 36, 37, 4, 41, 42, 43, 44," + " 5, 966, 971, 972, 973, 974, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "3", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "4", + "41", + "42", + "43", + "44", + "5", + "966", + "971", + "972", + "973", + "974", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + is_unique_in_field, + warning=True, + name="po_2_race.duplicates_in_field", + description=( + "'Race of principal owner 2' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_2_race.multi_value_field_restriction", + description=( + "When 'race of principal owner 2' contains" + " 966 (the applicant responded that they" + " did not wish to provide this information)" + " or 988 (not provided by applicant)," + " 'race of principal owner 2' should not" + " contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + }, + "po_2_race_anai_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_2_race_anai_ff.invalid_text_length", + description=( + "'Race of principal owner 2: free-form" + " text field for American Indian or Alaska" + " Native Enrolled or Principal Tribe' must" + " not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_race_anai_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 2' does not" + " contain 971 (the applicant responded in" + " the free-form text field for American Indian" + " or Alaska Native Enrolled or Principal Tribe)," + " 'race of principal owner 2: free-form text" + " field for American Indian or Alaska Native" + " Enrolled or Principal Tribe' must be blank." + " When 'race of principal owner 2' contains 971," + " 'race of principal owner 2: free-form text field" + " for American Indian or Alaska Native Enrolled or" + " Principal Tribe' must not be blank." + ), + groupby="po_2_race", + condition_values={"971"}, + ), + ], + }, + "po_2_race_asian_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_2_race_asian_ff.invalid_text_length", + description=( + "'Race of principal owner 2: free-form text" + " field for other Asian' must not exceed 300" + " characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_race_asian_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 2' does not contain" + " 972 (the applicant responded in the free-form text" + " field for other Asian race), 'race of principal" + " owner 2: free-form text field for other Asian' must" + " be blank. When 'race of principal owner 2' contains" + " 972, 'race of principal owner 2: free-form text field" + " for other Asian' must not be blank." + ), + groupby="po_2_race", + condition_values={"972"}, + ), + ], + }, + "po_2_race_baa_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_2_race_baa_ff.invalid_text_length", + description=( + "'Race of principal owner 2: free-form text" + " field for other Black or African American'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_race_baa_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 2' does not contain 973" + " (the applicant responded in the free-form text field" + " for other Black or African race), 'race of principal" + " owner 2: free-form text field for other Black or African" + " American' must be blank. When 'race of principal owner 2'" + " contains 973, 'race of principal owner 2: free-form text" + " field for other Black or African American' must not be blank." + ), + groupby="po_2_race", + condition_values={"973"}, + ), + ], + }, + "po_2_race_pi_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_2_race_pi_ff.invalid_text_length", + description=( + "'Race of principal owner 2: free-form text" + " field for other Pacific Islander race' must" + " not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_race_pi_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 2' does not contain 974" + " (the applicant responded in the free-form text field" + " for other Pacific Islander race), 'race of principal" + " owner 2: free-form text field for other Pacific Islander" + " race' must be blank. When 'race of principal owner 2'" + " contains 974, 'race of principal owner 2: free-form text" + " field for other Pacific Islander race' must not be blank." + ), + groupby="po_2_race", + condition_values={"974"}, + ), + ], + }, + "po_2_gender_flag": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_2_gender_flag.invalid_enum_value", + description=( + "When present, 'sex/gender of principal" + " owner 2: NP flag' must equal 1, 966, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "966", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [], + }, + "po_2_gender_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_2_gender_ff.invalid_text_length", + description=( + "'Sex/gender of principal owner 2: free-form" + " text field for self-identified sex/gender'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_gender_ff.conditional_field_conflict", + description=( + "When 'sex/gender of principal owner 2: NP flag'" + " does not equal 1 (the applicant responded in the" + " free-form text field), 'sex/gender of principal" + " owner 2: free-form text field for self-identified" + " sex/gender' must be blank. When 'sex/gender of" + " principal owner 2: NP flag' equals 1, 'sex/gender" + " of principal owner 2: free-form text field for" + " self-identified sex/gender' must not be blank." + ), + groupby="po_2_gender_flag", + condition_values={"1"}, + ), + ], + }, + "po_3_ethnicity": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_3_ethnicity.invalid_enum_value", + description=( + "When present, each value in 'ethnicity" + " of principal owner 3' (separated by" + " semicolons) must equal 1, 11, 12," + " 13, 14, 2, 966, 977, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "11", + "12", + "13", + "14", + "2", + "966", + "977", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + is_unique_in_field, + warning=True, + name="po_3_ethnicity.duplicates_in_field", + description=( + "'Ethnicity of principal owner 3' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_3_ethnicity.multi_value_field_restriction", + description=( + "When 'ethnicity of principal owner 3' contains" + " 966 (the applicant responded that they did" + " not wish to provide this information) or 988" + " (not provided by applicant), 'ethnicity of" + " principal owner 3' should not contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + }, + "po_3_ethnicity_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_3_ethnicity_ff.invalid_text_length", + description=( + "'Ethnicity of principal owner 3: free-form" + " text field for other Hispanic or Latino'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_ethnicity_ff.conditional_field_conflict", + description=( + "When 'ethnicity of principal owner 3' does not" + " contain 977 (the applicant responded in the" + " free-form text field), 'ethnicity of principal" + " owner 3: free-form text field for other Hispanic" + " or Latino' must be blank. When 'ethnicity of principal" + " owner 3' contains 977, 'ethnicity of principal" + " owner 3: free-form text field for other Hispanic" + " or Latino' must not be blank." + ), + groupby="po_3_ethnicity", + condition_values={"977"}, + ), + ], + }, + "po_3_race": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_3_race.invalid_enum_value", + description=( + "When present, each value in 'race" + " of principal owner 3' (separated by" + " semicolons) must equal 1, 2, 21, 22," + " 23, 24, 25, 26, 27, 3, 31, 32, 33," + " 34, 35, 36, 37, 4, 41, 42, 43, 44," + " 5, 966, 971, 972, 973, 974, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "3", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "4", + "41", + "42", + "43", + "44", + "5", + "966", + "971", + "972", + "973", + "974", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + is_unique_in_field, + warning=True, + name="po_3_race.duplicates_in_field", + description=( + "'Race of principal owner 3' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_3_race.multi_value_field_restriction", + description=( + "When 'race of principal owner 3' contains" + " 966 (the applicant responded that they" + " did not wish to provide this information)" + " or 988 (not provided by applicant)," + " 'race of principal owner 3' should not" + " contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + }, + "po_3_race_anai_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_3_race_anai_ff.invalid_text_length", + description=( + "'Race of principal owner 3: free-form" + " text field for American Indian or Alaska" + " Native Enrolled or Principal Tribe' must" + " not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_race_anai_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 3' does not" + " contain 971 (the applicant responded in" + " the free-form text field for American Indian" + " or Alaska Native Enrolled or Principal Tribe)," + " 'race of principal owner 3: free-form text" + " field for American Indian or Alaska Native" + " Enrolled or Principal Tribe' must be blank." + " When 'race of principal owner 3' contains 971," + " 'race of principal owner 3: free-form text field" + " for American Indian or Alaska Native Enrolled or" + " Principal Tribe' must not be blank." + ), + groupby="po_3_race", + condition_values={"971"}, + ), + ], + }, + "po_3_race_asian_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_3_race_asian_ff.invalid_text_length", + description=( + "'Race of principal owner 3: free-form text" + " field for other Asian' must not exceed 300" + " characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_race_asian_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 3' does not contain" + " 972 (the applicant responded in the free-form text" + " field for other Asian race), 'race of principal" + " owner 3: free-form text field for other Asian' must" + " be blank. When 'race of principal owner 3' contains" + " 972, 'race of principal owner 3: free-form text field" + " for other Asian' must not be blank." + ), + groupby="po_3_race", + condition_values={"972"}, + ), + ], + }, + "po_3_race_baa_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_3_race_baa_ff.invalid_text_length", + description=( + "'Race of principal owner 3: free-form text" + " field for other Black or African American'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_race_baa_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 3' does not contain 973" + " (the applicant responded in the free-form text field" + " for other Black or African race), 'race of principal" + " owner 3: free-form text field for other Black or African" + " American' must be blank. When 'race of principal owner 3'" + " contains 973, 'race of principal owner 3: free-form text" + " field for other Black or African American' must not be blank." + ), + groupby="po_3_race", + condition_values={"973"}, + ), + ], + }, + "po_3_race_pi_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_3_race_pi_ff.invalid_text_length", + description=( + "'Race of principal owner 3: free-form text" + " field for other Pacific Islander race' must" + " not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_race_pi_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 3' does not contain 974" + " (the applicant responded in the free-form text field" + " for other Pacific Islander race), 'race of principal" + " owner 3: free-form text field for other Pacific Islander" + " race' must be blank. When 'race of principal owner 3'" + " contains 974, 'race of principal owner 3: free-form text" + " field for other Pacific Islander race' must not be blank." + ), + groupby="po_3_race", + condition_values={"974"}, + ), + ], + }, + "po_3_gender_flag": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_3_gender_flag.invalid_enum_value", + description=( + "When present, 'sex/gender of principal" + " owner 3: NP flag' must equal 1, 966, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "966", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [], + }, + "po_3_gender_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_3_gender_ff.invalid_text_length", + description=( + "'Sex/gender of principal owner 3: free-form" + " text field for self-identified sex/gender'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_gender_ff.conditional_field_conflict", + description=( + "When 'sex/gender of principal owner 3: NP flag'" + " does not equal 1 (the applicant responded in the" + " free-form text field), 'sex/gender of principal" + " owner 3: free-form text field for self-identified" + " sex/gender' must be blank. When 'sex/gender of" + " principal owner 3: NP flag' equals 1, 'sex/gender" + " of principal owner 3: free-form text field for" + " self-identified sex/gender' must not be blank." + ), + groupby="po_3_gender_flag", + condition_values={"1"}, + ), + ], + }, + "po_4_ethnicity": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_4_ethnicity.invalid_enum_value", + description=( + "When present, each value in 'ethnicity" + " of principal owner 4' (separated by" + " semicolons) must equal 1, 11, 12," + " 13, 14, 2, 966, 977, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "11", + "12", + "13", + "14", + "2", + "966", + "977", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + is_unique_in_field, + warning=True, + name="po_4_ethnicity.duplicates_in_field", + description=( + "'Ethnicity of principal owner 4' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_4_ethnicity.multi_value_field_restriction", + description=( + "When 'ethnicity of principal owner 4' contains" + " 966 (the applicant responded that they did" + " not wish to provide this information) or 988" + " (not provided by applicant), 'ethnicity of" + " principal owner 4' should not contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + }, + "po_4_ethnicity_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_4_ethnicity_ff.invalid_text_length", + description=( + "'Ethnicity of principal owner 4: free-form" + " text field for other Hispanic or Latino'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_ethnicity_ff.conditional_field_conflict", + description=( + "When 'ethnicity of principal owner 4' does not" + " contain 977 (the applicant responded in the" + " free-form text field), 'ethnicity of principal" + " owner 4: free-form text field for other Hispanic" + " or Latino' must be blank. When 'ethnicity of principal" + " owner 4' contains 977, 'ethnicity of principal" + " owner 4: free-form text field for other Hispanic" + " or Latino' must not be blank." + ), + groupby="po_4_ethnicity", + condition_values={"977"}, + ), + ], + }, + "po_4_race": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_4_race.invalid_enum_value", + description=( + "When present, each value in 'race" + " of principal owner 4' (separated by" + " semicolons) must equal 1, 2, 21, 22," + " 23, 24, 25, 26, 27, 3, 31, 32, 33," + " 34, 35, 36, 37, 4, 41, 42, 43, 44," + " 5, 966, 971, 972, 973, 974, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "3", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "4", + "41", + "42", + "43", + "44", + "5", + "966", + "971", + "972", + "973", + "974", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [ + SBLCheck( + is_unique_in_field, + warning=True, + name="po_4_race.duplicates_in_field", + description=( + "'Race of principal owner 4' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_4_race.multi_value_field_restriction", + description=( + "When 'race of principal owner 4' contains" + " 966 (the applicant responded that they" + " did not wish to provide this information)" + " or 988 (not provided by applicant)," + " 'race of principal owner 4' should not" + " contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + }, + "po_4_race_anai_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_4_race_anai_ff.invalid_text_length", + description=( + "'Race of principal owner 4: free-form" + " text field for American Indian or Alaska" + " Native Enrolled or Principal Tribe' must" + " not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_race_anai_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 4' does not" + " contain 971 (the applicant responded in" + " the free-form text field for American Indian" + " or Alaska Native Enrolled or Principal Tribe)," + " 'race of principal owner 4: free-form text" + " field for American Indian or Alaska Native" + " Enrolled or Principal Tribe' must be blank." + " When 'race of principal owner 4' contains 971," + " 'race of principal owner 4: free-form text field" + " for American Indian or Alaska Native Enrolled or" + " Principal Tribe' must not be blank." + ), + groupby="po_4_race", + condition_values={"971"}, + ), + ], + }, + "po_4_race_asian_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_4_race_asian_ff.invalid_text_length", + description=( + "'Race of principal owner 4: free-form text" + " field for other Asian' must not exceed 300" + " characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_race_asian_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 4' does not contain" + " 972 (the applicant responded in the free-form text" + " field for other Asian race), 'race of principal" + " owner 4: free-form text field for other Asian' must" + " be blank. When 'race of principal owner 4' contains" + " 972, 'race of principal owner 4: free-form text field" + " for other Asian' must not be blank." + ), + groupby="po_4_race", + condition_values={"972"}, + ), + ], + }, + "po_4_race_baa_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_4_race_baa_ff.invalid_text_length", + description=( + "'Race of principal owner 4: free-form text" + " field for other Black or African American'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_race_baa_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 4' does not contain 973" + " (the applicant responded in the free-form text field" + " for other Black or African race), 'race of principal" + " owner 4: free-form text field for other Black or African" + " American' must be blank. When 'race of principal owner 4'" + " contains 973, 'race of principal owner 4: free-form text" + " field for other Black or African American' must not be blank." + ), + groupby="po_4_race", + condition_values={"973"}, + ), + ], + }, + "po_4_race_pi_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_4_race_pi_ff.invalid_text_length", + description=( + "'Race of principal owner 4: free-form text" + " field for other Pacific Islander race' must" + " not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_race_pi_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 4' does not contain 974" + " (the applicant responded in the free-form text field" + " for other Pacific Islander race), 'race of principal" + " owner 4: free-form text field for other Pacific Islander" + " race' must be blank. When 'race of principal owner 4'" + " contains 974, 'race of principal owner 4: free-form text" + " field for other Pacific Islander race' must not be blank." + ), + groupby="po_4_race", + condition_values={"974"}, + ), + ], + }, + "po_4_gender_flag": { + "phase_1": [ + SBLCheck( + is_valid_enum, + name="po_4_gender_flag.invalid_enum_value", + description=( + "When present, 'sex/gender of principal" + " owner 4: NP flag' must equal 1, 966, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "966", + "988", + ], + accept_blank=True, + ), + ], + "phase_2": [], + }, + "po_4_gender_ff": { + "phase_1": [ + SBLCheck.str_length( + 0, + 300, + name="po_4_gender_ff.invalid_text_length", + description=( + "'Sex/gender of principal owner 4: free-form" + " text field for self-identified sex/gender'" + " must not exceed 300 characters in length." + ), + ), + ], + "phase_2": [ + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_gender_ff.conditional_field_conflict", + description=( + "When 'sex/gender of principal owner 4: NP flag'" + " does not equal 1 (the applicant responded in the" + " free-form text field), 'sex/gender of principal" + " owner 4: free-form text field for self-identified" + " sex/gender' must be blank. When 'sex/gender of" + " principal owner 4: NP flag' equals 1, 'sex/gender" + " of principal owner 4: free-form text field for" + " self-identified sex/gender' must not be blank." + ), + groupby="po_4_gender_flag", + condition_values={"1"}, + ), + ], + }, + } + return two_phases_schema diff --git a/src/validator/schema.py b/src/validator/schema.py index 3f8c4156..8aac81d5 100644 --- a/src/validator/schema.py +++ b/src/validator/schema.py @@ -6,3335 +6,3319 @@ The only major modification from native Pandera is the use of custom Check classes to differentiate between warnings and errors. """ -import global_data -from check_functions import ( - has_correct_length, - has_no_conditional_field_conflict, - has_valid_enum_pair, - has_valid_fieldset_pair, - has_valid_format, - has_valid_multi_field_value_count, - has_valid_value_count, - is_date, - is_date_after, - is_date_before_in_days, - is_date_in_range, - is_greater_than, - is_greater_than_or_equal_to, - is_less_than, - is_number, - is_unique_column, - is_unique_in_field, - is_valid_code, - is_valid_enum, - meets_multi_value_field_restriction, -) +from check_functions import (has_correct_length, + has_no_conditional_field_conflict, + has_valid_enum_pair, has_valid_fieldset_pair, + has_valid_format, + has_valid_multi_field_value_count, + has_valid_value_count, is_date, is_date_after, + is_date_before_in_days, is_date_in_range, + is_greater_than, is_greater_than_or_equal_to, + is_less_than, is_number, is_unique_column, + is_unique_in_field, is_valid_code, is_valid_enum, + meets_multi_value_field_restriction) from checks import SBLCheck from pandera import Column, DataFrameSchema -# read and populate global naics code (this should be called only once) -global_data.read_naics_codes() -# read and populate global census geoids (this should be called only once) -global_data.read_geoids() - -sblar_schema = DataFrameSchema( - { - "uid": Column( - str, - title="Field 1: Unique identifier", - checks=[ - SBLCheck.str_length( - 21, - 45, - name="uid.invalid_text_length", - description=( - "'Unique identifier' must be at least 21 characters " - "in length and at most 45 characters in length." - ), - ), - SBLCheck( - has_valid_format, - name="uid.invalid_text_pattern", - description=( - "'Unique identifier' may contain any combination of " - "numbers and/or uppercase letters (i.e., 0-9 and A-Z), " - "and must not contain any other characters." - ), - element_wise=True, - regex="^[A-Z0-9]+$", - ), - SBLCheck( - is_unique_column, - name="uid.duplicates_in_dataset", - description=( - "Any 'unique identifier' may not be used in more than one " - "record within a small business lending application register." - ), - groupby="uid", - ), - ], - ), - "app_date": Column( - str, - title="Field 2: Application date", - checks=[ - SBLCheck( - is_date, - name="app_date.invalid_date_format", - description=( - "'Application date' must be a real calendar " - "date using YYYYMMDD format." - ), - element_wise=True, - ), - ], - ), - "app_method": Column( - str, - title="Field 3: Application method", - checks=[ - SBLCheck( - is_valid_enum, - name="app_method.invalid_enum_value", - description="'Application method' must equal 1, 2, 3, or 4.", - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - ], - ), - ], - ), - "app_recipient": Column( - str, - title="Field 4: Application recipient", - checks=[ - SBLCheck( - is_valid_enum, - name="app_recipient.invalid_enum_value", - description="'Application recipient' must equal 1 or 2", - element_wise=True, - accepted_values=[ - "1", - "2", - ], - ), - ], - ), - "ct_credit_product": Column( - str, - title="Field 5: Credit product", - checks=[ - SBLCheck( - is_valid_enum, - name="ct_credit_product.invalid_enum_value", - description=( - "'Credit product' must equal 1, 2, 3, 4, 5, 6, 7, 8, " - "977, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "977", - "988", - ], - ), - ], - ), - "ct_credit_product_ff": Column( - str, - title="Field 6: Free-form text field for other credit products", - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="ct_credit_product_ff.invalid_text_length", - description=( - "'Free-form text field for other credit products' " - "must not exceed 300 characters in length." +def get_sblar_schema(naics: dict, geoids: dict): + return DataFrameSchema( + { + "uid": Column( + str, + title="Field 1: Unique identifier", + checks=[ + SBLCheck.str_length( + 21, + 45, + name="uid.invalid_text_length", + description=( + "'Unique identifier' must be at least 21 characters " + "in length and at most 45 characters in length." + ), + ), + SBLCheck( + has_valid_format, + name="uid.invalid_text_pattern", + description=( + "'Unique identifier' may contain any combination of " + "numbers and/or uppercase letters (i.e., 0-9 and A-Z), " + "and must not contain any other characters." + ), + element_wise=True, + regex="^[A-Z0-9]+$", + ), + SBLCheck( + is_unique_column, + name="uid.duplicates_in_dataset", + description=( + "Any 'unique identifier' may not be used in more than one " + "record within a small business lending application register." + ), + groupby="uid", + ), + ], + ), + "app_date": Column( + str, + title="Field 2: Application date", + checks=[ + SBLCheck( + is_date, + name="app_date.invalid_date_format", + description=( + "'Application date' must be a real calendar " + "date using YYYYMMDD format." + ), + element_wise=True, + ), + ], + ), + "app_method": Column( + str, + title="Field 3: Application method", + checks=[ + SBLCheck( + is_valid_enum, + name="app_method.invalid_enum_value", + description="'Application method' must equal 1, 2, 3, or 4.", + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + ], + ), + ], + ), + "app_recipient": Column( + str, + title="Field 4: Application recipient", + checks=[ + SBLCheck( + is_valid_enum, + name="app_recipient.invalid_enum_value", + description="'Application recipient' must equal 1 or 2", + element_wise=True, + accepted_values=[ + "1", + "2", + ], + ), + ], + ), + "ct_credit_product": Column( + str, + title="Field 5: Credit product", + checks=[ + SBLCheck( + is_valid_enum, + name="ct_credit_product.invalid_enum_value", + description=( + "'Credit product' must equal 1, 2, 3, 4, 5, 6, 7, 8, " + "977, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "977", + "988", + ], + ), + ], + ), + "ct_credit_product_ff": Column( + str, + title="Field 6: Free-form text field for other credit products", + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="ct_credit_product_ff.invalid_text_length", + description=( + "'Free-form text field for other credit products' " + "must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="ct_credit_product_ff.conditional_field_conflict", + description=( + "When 'credit product' does not equal 977 (other), 'free-form" + " text field for other credit products' must be blank." + "When 'credit product' equals 977, 'free-form text field " + "for other credit products' must not be blank." + ), + groupby="ct_credit_product", + condition_values={"977"}, + ), + ], + ), + "ct_guarantee": Column( + str, + title="Field 7: Type of guarantee", + nullable=True, + checks=[ + SBLCheck( + has_valid_value_count, + name="ct_guarantee.invalid_number_of_values", + description=( + "'Type of guarantee' must contain at least one and at" + " most five values, separated by semicolons." + ), + element_wise=True, + min_length=1, + max_length=5, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="ct_guarantee.duplicates_in_field", + description=( + "'Type of guarantee' should not contain " "duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="ct_guarantee.multi_value_field_restriction", + description=( + "When 'type of guarantee' contains 999 (no guarantee)," + " 'type of guarantee' should not contain more than one" + " value." + ), + element_wise=True, + single_values={"999"}, + ), + SBLCheck( + is_valid_enum, + name="ct_guarantee.invalid_enum_value", + description=( + "Each value in 'type of guarantee' (separated by " + " semicolons) must equal 1, 2, 3, 4, 5, 6, 7, 8," + " 9, 10, 11, 977, or 999." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "977", + "999", + ], + ), + ], + ), + "ct_guarantee_ff": Column( + str, + title="Field 8: Free-form text field for other guarantee", + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="ct_guarantee_ff.invalid_text_length", + description=( + "'Free-form text field for other guarantee' must not " + "exceed 300 characters in length" + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="ct_guarantee_ff.conditional_field_conflict", + description=( + "When 'type of guarantee' does not contain 977 (other), " + "'free-form text field for other guarantee' must be blank. " + "When 'type of guarantee' contains 977, 'free-form text field" + " for other guarantee' must not be blank." + ), + groupby="ct_guarantee", + condition_values={"977"}, + ), + SBLCheck( + has_valid_multi_field_value_count, + warning=True, + name="ct_guarantee_ff.multi_invalid_number_of_values", + description=( + "'Type of guarantee' and 'free-form text field for other " + "guarantee' combined should not contain more than five values. " + "Code 977 (other), within 'type of guarantee', does not count " + "toward the maximum number of values for the purpose of this " + "validation check." + ), + groupby="ct_guarantee", + ignored_values={"977"}, + max_length=5, + ), + ], + ), + "ct_loan_term_flag": Column( + str, + title="Field 9: Loan term: NA/NP flag", + checks=[ + SBLCheck( + is_valid_enum, + name="ct_loan_term_flag.invalid_enum_value", + description=( + "Each value in 'Loan term: NA/NP flag' (separated by " + " semicolons) must equal 900, 988, or 999." + ), + element_wise=True, + accepted_values=[ + "900", + "988", + "999", + ], + ), + SBLCheck( + has_valid_enum_pair, + name="ct_loan_term_flag.enum_value_conflict", + description=( + "When 'credit product' equals 1 (term loan - unsecured) or 2" + "(term loan - secured), 'loan term: NA/NP flag' must not equal" + "999 (not applicable)." + "When 'credit product' equals 988 (not provided by applicant " + "and otherwise undetermined), 'loan term: NA/NP flag' must" + "equal 999." + ), + groupby="ct_credit_product", + conditions=[ + { + "condition_values": {"1", "2"}, + "is_equal_condition": True, + "target_value": "999", + "should_equal_target": False, + }, + { + "condition_values": {"988"}, + "is_equal_condition": True, + "target_value": "999", + "should_equal_target": True, + }, + ], + ), + ], + ), + "ct_loan_term": Column( + str, + title="Field 10: Loan term", + nullable=True, + checks=[ + SBLCheck( + has_no_conditional_field_conflict, + name="ct_loan_term.conditional_field_conflict", + description=( + "When 'loan term: NA/NP flag' does not equal 900 (applicable " + "and reported), 'loan term' must be blank. When 'loan term:" + "NA/NP flag' equals 900, 'loan term' must not be blank." + ), + groupby="ct_loan_term_flag", + condition_values={"900"}, + ), + SBLCheck( + is_number, + name="ct_loan_term.invalid_numeric_format", + description="When present, 'loan term' must be a whole number.", + element_wise=True, + accept_blank=True, + ), + SBLCheck( + is_greater_than_or_equal_to, + name="ct_loan_term.invalid_numeric_value", + description=( + "When present, 'loan term' must be greater than or equal" + "to 1." + ), + element_wise=True, + min_value="1", + accept_blank=True, + ), + SBLCheck( + is_less_than, + name="ct_loan_term.unreasonable_numeric_value", + description=( + "When present, 'loan term' should be less than 1200" + "(100 years)." + ), + element_wise=True, + max_value="1200", + accept_blank=True, + ), + ], + ), + "credit_purpose": Column( + str, + title="Field 11: Credit purpose", + checks=[ + SBLCheck( + is_valid_enum, + name="credit_purpose.invalid_enum_value", + description=( + "Each value in 'credit purpose' (separated by " + " semicolons) must equal 1, 2, 3, 4, 5, 6, 7, 8," + " 9, 10, 11, 977, 988, or 999." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "977", + "988", + "999", + ], + ), + SBLCheck( + has_valid_value_count, + name="credit_purpose.invalid_number_of_values", + description=( + "'Credit purpose' must contain at least one and at" + " most three values, separated by semicolons." + ), + element_wise=True, + min_length=1, + max_length=3, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="credit_purpose.multi_value_field_restriction", + description=( + "When 'credit purpose' contains 988 or 999," + " 'credit purpose' should not contain more than one" + " value." + ), + element_wise=True, + single_values={ + "988", + "999", + }, ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="ct_credit_product_ff.conditional_field_conflict", - description=( - "When 'credit product' does not equal 977 (other), 'free-form" - " text field for other credit products' must be blank." - "When 'credit product' equals 977, 'free-form text field " - "for other credit products' must not be blank." - ), - groupby="ct_credit_product", - condition_values={"977"}, - ), - ], - ), - "ct_guarantee": Column( - str, - title="Field 7: Type of guarantee", - nullable=True, - checks=[ - SBLCheck( - has_valid_value_count, - name="ct_guarantee.invalid_number_of_values", - description=( - "'Type of guarantee' must contain at least one and at" - " most five values, separated by semicolons." - ), - element_wise=True, - min_length=1, - max_length=5, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="ct_guarantee.duplicates_in_field", - description=( - "'Type of guarantee' should not contain " "duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="ct_guarantee.multi_value_field_restriction", - description=( - "When 'type of guarantee' contains 999 (no guarantee)," - " 'type of guarantee' should not contain more than one" - " value." - ), - element_wise=True, - single_values={"999"}, - ), - SBLCheck( - is_valid_enum, - name="ct_guarantee.invalid_enum_value", - description=( - "Each value in 'type of guarantee' (separated by " - " semicolons) must equal 1, 2, 3, 4, 5, 6, 7, 8," - " 9, 10, 11, 977, or 999." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "977", - "999", - ], - ), - ], - ), - "ct_guarantee_ff": Column( - str, - title="Field 8: Free-form text field for other guarantee", - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="ct_guarantee_ff.invalid_text_length", - description=( - "'Free-form text field for other guarantee' must not " - "exceed 300 characters in length" + SBLCheck( + is_unique_in_field, + warning=True, + name="credit_purpose.duplicates_in_field", + description=( + "'Credit purpose' should not contain " " duplicated values." + ), + element_wise=True, ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="ct_guarantee_ff.conditional_field_conflict", - description=( - "When 'type of guarantee' does not contain 977 (other), " - "'free-form text field for other guarantee' must be blank. " - "When 'type of guarantee' contains 977, 'free-form text field" - " for other guarantee' must not be blank." - ), - groupby="ct_guarantee", - condition_values={"977"}, - ), - SBLCheck( - has_valid_multi_field_value_count, - warning=True, - name="ct_guarantee_ff.multi_invalid_number_of_values", - description=( - "'Type of guarantee' and 'free-form text field for other " - "guarantee' combined should not contain more than five values. " - "Code 977 (other), within 'type of guarantee', does not count " - "toward the maximum number of values for the purpose of this " - "validation check." - ), - groupby="ct_guarantee", - ignored_values={"977"}, - max_length=5, - ), - ], - ), - "ct_loan_term_flag": Column( - str, - title="Field 9: Loan term: NA/NP flag", - checks=[ - SBLCheck( - is_valid_enum, - name="ct_loan_term_flag.invalid_enum_value", - description=( - "Each value in 'Loan term: NA/NP flag' (separated by " - " semicolons) must equal 900, 988, or 999." - ), - element_wise=True, - accepted_values=[ - "900", - "988", - "999", - ], - ), - SBLCheck( - has_valid_enum_pair, - name="ct_loan_term_flag.enum_value_conflict", - description=( - "When 'credit product' equals 1 (term loan - unsecured) or 2" - "(term loan - secured), 'loan term: NA/NP flag' must not equal" - "999 (not applicable)." - "When 'credit product' equals 988 (not provided by applicant " - "and otherwise undetermined), 'loan term: NA/NP flag' must" - "equal 999." - ), - groupby="ct_credit_product", - conditions=[ - { - "condition_values": {"1", "2"}, - "is_equal_condition": True, - "target_value": "999", - "should_equal_target": False, - }, - { - "condition_values": {"988"}, - "is_equal_condition": True, - "target_value": "999", - "should_equal_target": True, + ], + ), + "credit_purpose_ff": Column( + str, + title="Field 12: Free-form text field for other credit purpose", + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="credit_purpose_ff.invalid_text_length", + description=( + "'Free-form text field for other credit purpose' " + " must not exceed 300 characters in length" + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="credit_purpose_ff.conditional_field_conflict", + description=( + "When 'credit purpose' does not contain 977 (other)," + "'free-form text field for other credit purpose' must be blank." + "When 'credit purpose' contains 977, 'free-form text field for" + "other credit purpose' must not be blank." + ), + groupby="credit_purpose", + condition_values={"977"}, + ), + SBLCheck( + has_valid_value_count, + name="credit_purpose_ff.invalid_number_of_values", + description=( + "'Other Credit purpose' must not contain more " + " than one other credit purpose." + ), + element_wise=True, + min_length=0, + max_length=1, + ), + ], + ), + "amount_applied_for_flag": Column( + str, + title="Field 13: Amount applied for: NA/NP flag", + checks=[ + SBLCheck( + is_valid_enum, + name="amount_applied_for_flag.invalid_enum_value", + description=( + "'Amount applied For: NA/NP flag' must equal 900, 988, or 999." + ), + element_wise=True, + accepted_values=[ + "900", + "988", + "999", + ], + ), + ], + ), + "amount_applied_for": Column( + str, + title="Field 14: Amount applied for", + nullable=True, + checks=[ + SBLCheck( + has_no_conditional_field_conflict, + name="amount_applied_for.conditional_field_conflict", + description=( + "When 'amount applied for: NA/NP flag' does not equal 900 " + "(applicable and reported), 'amount applied for' must be blank." + "When 'amount applied for: NA/NP flag' equals 900, " + "'amount applied for' must not be blank." + ), + groupby="amount_applied_for_flag", + condition_values={"900"}, + ), + SBLCheck( + is_number, + name="amount_applied_for.invalid_numeric_format", + description=( + "When present, 'amount applied for' must be a numeric" "value." + ), + element_wise=True, + accept_blank=True, + ), + SBLCheck( + is_greater_than, + name="amount_applied_for.invalid_numeric_value", + description=( + "When present, 'amount applied for' must be greater than 0." + ), + element_wise=True, + min_value="0", + accept_blank=True, + ), + ], + ), + "amount_approved": Column( + str, + title="Field 15: Amount approved or originated", + nullable=True, + checks=[ + SBLCheck( + is_number, + name="amount_approved.invalid_numeric_format", + description=( + "When present, 'amount approved or originated' " + "must be a numeric value." + ), + element_wise=True, + accept_blank=True, + ), + SBLCheck( + is_greater_than, + name="amount_approved.invalid_numeric_value", + description=( + "When present, 'amount approved or originated' " + "must be greater than 0." + ), + element_wise=True, + min_value="0", + accept_blank=True, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="amount_approved.conditional_field_conflict", + description=( + "When 'action taken' does not equal 1 (originated) " + "or 2 (approved but not accepted), 'amount approved " + " or originated' must be blank. When 'action taken' " + "equals 1 or 2, 'amount approved or originated' must " + "not be blank." + ), + groupby="action_taken", + condition_values={"1", "2"}, + ), + ], + ), + "action_taken": Column( + str, + title="Field 16: Action taken", + checks=[ + SBLCheck( + is_valid_enum, + name="action_taken.invalid_enum_value", + description="'Action taken' must equal 1, 2, 3, 4, or 5.", + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + ], + ), + SBLCheck( + has_valid_fieldset_pair, + name="pricing_all.conditional_fieldset_conflict", + description=( + "When 'action taken' equals 3 (denied), " + "4 (withdrawn by applicant), or 5 " + "(incomplete), the following fields must" + " all equal 999 (not applicable): " + "'Interest rate type', 'MCA/sales-based: " + "additional cost for merchant cash advances" + " or other sales-based financing: NA flag', " + "'Prepayment penalty could be imposed', " + "'Prepayment penalty exists'). And the " + " following fields must all be blank: " + "'Total origination charges', 'Amount of " + "total broker fees', 'Initial annual charges'" + ), + groupby=[ + "pricing_interest_rate_type", + "pricing_mca_addcost_flag", + "pricing_prepenalty_allowed", + "pricing_prepenalty_exists", + "pricing_origination_charges", + "pricing_broker_fees", + "pricing_initial_charges", + ], + condition_values=["3", "4", "5"], + should_fieldset_key_equal_to={ + "pricing_interest_rate_type": (0, True, "999"), + "pricing_mca_addcost_flag": (1, True, "999"), + "pricing_prepenalty_allowed": (2, True, "999"), + "pricing_prepenalty_exists": (3, True, "999"), + "pricing_origination_charges": (4, True, ""), + "pricing_broker_fees": (5, True, ""), + "pricing_initial_charges": (6, True, ""), }, - ], - ), - ], - ), - "ct_loan_term": Column( - str, - title="Field 10: Loan term", - nullable=True, - checks=[ - SBLCheck( - has_no_conditional_field_conflict, - name="ct_loan_term.conditional_field_conflict", - description=( - "When 'loan term: NA/NP flag' does not equal 900 (applicable " - "and reported), 'loan term' must be blank. When 'loan term:" - "NA/NP flag' equals 900, 'loan term' must not be blank." - ), - groupby="ct_loan_term_flag", - condition_values={"900"}, - ), - SBLCheck( - is_number, - name="ct_loan_term.invalid_numeric_format", - description="When present, 'loan term' must be a whole number.", - element_wise=True, - accept_blank=True, - ), - SBLCheck( - is_greater_than_or_equal_to, - name="ct_loan_term.invalid_numeric_value", - description=( - "When present, 'loan term' must be greater than or equal" - "to 1." - ), - element_wise=True, - min_value="1", - accept_blank=True, - ), - SBLCheck( - is_less_than, - name="ct_loan_term.unreasonable_numeric_value", - description=( - "When present, 'loan term' should be less than 1200" - "(100 years)." - ), - element_wise=True, - max_value="1200", - accept_blank=True, - ), - ], - ), - "credit_purpose": Column( - str, - title="Field 11: Credit purpose", - checks=[ - SBLCheck( - is_valid_enum, - name="credit_purpose.invalid_enum_value", - description=( - "Each value in 'credit purpose' (separated by " - " semicolons) must equal 1, 2, 3, 4, 5, 6, 7, 8," - " 9, 10, 11, 977, 988, or 999." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "977", - "988", - "999", - ], - ), - SBLCheck( - has_valid_value_count, - name="credit_purpose.invalid_number_of_values", - description=( - "'Credit purpose' must contain at least one and at" - " most three values, separated by semicolons." - ), - element_wise=True, - min_length=1, - max_length=3, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="credit_purpose.multi_value_field_restriction", - description=( - "When 'credit purpose' contains 988 or 999," - " 'credit purpose' should not contain more than one" - " value." - ), - element_wise=True, - single_values={ - "988", - "999", - }, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="credit_purpose.duplicates_in_field", - description=( - "'Credit purpose' should not contain " " duplicated values." - ), - element_wise=True, - ), - ], - ), - "credit_purpose_ff": Column( - str, - title="Field 12: Free-form text field for other credit purpose", - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="credit_purpose_ff.invalid_text_length", - description=( - "'Free-form text field for other credit purpose' " - " must not exceed 300 characters in length" ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="credit_purpose_ff.conditional_field_conflict", - description=( - "When 'credit purpose' does not contain 977 (other)," - "'free-form text field for other credit purpose' must be blank." - "When 'credit purpose' contains 977, 'free-form text field for" - "other credit purpose' must not be blank." - ), - groupby="credit_purpose", - condition_values={"977"}, - ), - SBLCheck( - has_valid_value_count, - name="credit_purpose_ff.invalid_number_of_values", - description=( - "'Other Credit purpose' must not contain more " - " than one other credit purpose." - ), - element_wise=True, - min_length=0, - max_length=1, - ), - ], - ), - "amount_applied_for_flag": Column( - str, - title="Field 13: Amount applied for: NA/NP flag", - checks=[ - SBLCheck( - is_valid_enum, - name="amount_applied_for_flag.invalid_enum_value", - description=( - "'Amount applied For: NA/NP flag' must equal 900, 988, or 999." - ), - element_wise=True, - accepted_values=[ - "900", - "988", - "999", - ], - ), - ], - ), - "amount_applied_for": Column( - str, - title="Field 14: Amount applied for", - nullable=True, - checks=[ - SBLCheck( - has_no_conditional_field_conflict, - name="amount_applied_for.conditional_field_conflict", - description=( - "When 'amount applied for: NA/NP flag' does not equal 900 " - "(applicable and reported), 'amount applied for' must be blank." - "When 'amount applied for: NA/NP flag' equals 900, " - "'amount applied for' must not be blank." - ), - groupby="amount_applied_for_flag", - condition_values={"900"}, - ), - SBLCheck( - is_number, - name="amount_applied_for.invalid_numeric_format", - description=( - "When present, 'amount applied for' must be a numeric" "value." - ), - element_wise=True, - accept_blank=True, - ), - SBLCheck( - is_greater_than, - name="amount_applied_for.invalid_numeric_value", - description=( - "When present, 'amount applied for' must be greater than 0." - ), - element_wise=True, - min_value="0", - accept_blank=True, - ), - ], - ), - "amount_approved": Column( - str, - title="Field 15: Amount approved or originated", - nullable=True, - checks=[ - SBLCheck( - is_number, - name="amount_approved.invalid_numeric_format", - description=( - "When present, 'amount approved or originated' " - "must be a numeric value." - ), - element_wise=True, - accept_blank=True, - ), - SBLCheck( - is_greater_than, - name="amount_approved.invalid_numeric_value", - description=( - "When present, 'amount approved or originated' " - "must be greater than 0." - ), - element_wise=True, - min_value="0", - accept_blank=True, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="amount_approved.conditional_field_conflict", - description=( - "When 'action taken' does not equal 1 (originated) " - "or 2 (approved but not accepted), 'amount approved " - " or originated' must be blank. When 'action taken' " - "equals 1 or 2, 'amount approved or originated' must " - "not be blank." - ), - groupby="action_taken", - condition_values={"1", "2"}, - ), - ], - ), - "action_taken": Column( - str, - title="Field 16: Action taken", - checks=[ - SBLCheck( - is_valid_enum, - name="action_taken.invalid_enum_value", - description="'Action taken' must equal 1, 2, 3, 4, or 5.", - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - ], - ), - SBLCheck( - has_valid_fieldset_pair, - name="pricing_all.conditional_fieldset_conflict", - description=( - "When 'action taken' equals 3 (denied), " - "4 (withdrawn by applicant), or 5 " - "(incomplete), the following fields must" - " all equal 999 (not applicable): " - "'Interest rate type', 'MCA/sales-based: " - "additional cost for merchant cash advances" - " or other sales-based financing: NA flag', " - "'Prepayment penalty could be imposed', " - "'Prepayment penalty exists'). And the " - " following fields must all be blank: " - "'Total origination charges', 'Amount of " - "total broker fees', 'Initial annual charges'" - ), - groupby=[ - "pricing_interest_rate_type", - "pricing_mca_addcost_flag", - "pricing_prepenalty_allowed", - "pricing_prepenalty_exists", - "pricing_origination_charges", - "pricing_broker_fees", - "pricing_initial_charges", - ], - condition_values=["3", "4", "5"], - should_fieldset_key_equal_to={ - "pricing_interest_rate_type": (0, True, "999"), - "pricing_mca_addcost_flag": (1, True, "999"), - "pricing_prepenalty_allowed": (2, True, "999"), - "pricing_prepenalty_exists": (3, True, "999"), - "pricing_origination_charges": (4, True, ""), - "pricing_broker_fees": (5, True, ""), - "pricing_initial_charges": (6, True, ""), - }, - ), - SBLCheck( - has_valid_fieldset_pair, - name="pricing_charges.conditional_fieldset_conflict", - description=( - "When 'action taken' equals 1 (originated)" - " or 2 (approved but not accepted), the " - "following fields all must not be blank: " - "'Total origination charges', 'Amount of " - "total broker fees', 'Initial annual " - "charges'. And the following fields must " - "not equal 999 (not applicable): 'Prepayment " - "penalty could be imposed', 'Prepayment " - "penalty exists'" - ), - groupby=[ - "pricing_origination_charges", - "pricing_broker_fees", - "pricing_initial_charges", - "pricing_prepenalty_allowed", - "pricing_prepenalty_exists", - ], - condition_values=["1", "2"], - should_fieldset_key_equal_to={ - "pricing_origination_charges": (0, False, ""), - "pricing_broker_fees": (1, False, ""), - "pricing_initial_charges": (2, False, ""), - "pricing_prepenalty_allowed": (3, False, "999"), - "pricing_prepenalty_exists": (4, False, "999"), - }, - ), - ], - ), - "action_taken_date": Column( - str, - title="Field 17: Action taken date", - checks=[ - SBLCheck( - is_date, - name="action_taken_date.invalid_date_format", - description=( - "'Action taken date' must be a real calendar" - " date using YYYYMMDD format." - ), - element_wise=True, - ), - SBLCheck( - is_date_in_range, - name="action_taken_date.invalid_date_value", - description=( - "The date indicated by 'action taken date' must occur" - " within the current reporting period:" - " October 1, 2024 to December 31, 2024." - ), - element_wise=True, - start_date_value="20241001", - end_date_value="20241231", - ), - SBLCheck( - is_date_after, - name="action_taken_date.date_value_conflict", - description=( - "The date indicated by 'action taken date'" - " must occur on or after 'application date'." - ), - groupby="app_date", - ), - SBLCheck( - is_date_before_in_days, - name="action_taken_date.unreasonable_date_value", - description=( - "The date indicated by 'application date' should" - " generally be less than two years (730 days) before" - " 'action taken date'." - ), - groupby="app_date", - days_value=730, - ), - ], - ), - "denial_reasons": Column( - str, - title="Field 18: Denial reason(s)", - checks=[ - SBLCheck( - is_valid_enum, - name="denial_reasons.invalid_enum_value", - description=( - "Each value in 'denial reason(s)' (separated by semicolons)" - "must equal 1, 2, 3, 4, 5, 6, 7, 8, 9, 977, or 999." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "977", - "999", - ], - ), - SBLCheck( - has_valid_value_count, - name="denial_reasons.invalid_number_of_values", - description=( - "'Denial reason(s)' must contain at least one and at most four" - "values, separated by semicolons." - ), - element_wise=True, - min_length=1, - max_length=4, - ), - SBLCheck( - has_valid_enum_pair, - name="denial_reasons.enum_value_conflict", - description=( - "When 'action taken' equals 3, 'denial reason(s)' must not" - "contain 999. When 'action taken' does not equal 3, 'denial" - "reason(s)' must equal 999." - ), - groupby="action_taken", - conditions=[ - { - "condition_values": {"3"}, - "is_equal_condition": True, - "target_value": "999", - "should_equal_target": False, + SBLCheck( + has_valid_fieldset_pair, + name="pricing_charges.conditional_fieldset_conflict", + description=( + "When 'action taken' equals 1 (originated)" + " or 2 (approved but not accepted), the " + "following fields all must not be blank: " + "'Total origination charges', 'Amount of " + "total broker fees', 'Initial annual " + "charges'. And the following fields must " + "not equal 999 (not applicable): 'Prepayment " + "penalty could be imposed', 'Prepayment " + "penalty exists'" + ), + groupby=[ + "pricing_origination_charges", + "pricing_broker_fees", + "pricing_initial_charges", + "pricing_prepenalty_allowed", + "pricing_prepenalty_exists", + ], + condition_values=["1", "2"], + should_fieldset_key_equal_to={ + "pricing_origination_charges": (0, False, ""), + "pricing_broker_fees": (1, False, ""), + "pricing_initial_charges": (2, False, ""), + "pricing_prepenalty_allowed": (3, False, "999"), + "pricing_prepenalty_exists": (4, False, "999"), }, - { - "condition_values": {"3"}, - "is_equal_condition": False, - "target_value": "999", - "should_equal_target": True, - }, - ], - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="denial_reasons.multi_value_field_restriction", - description=( - "When 'denial reason(s)' contains 999 (not applicable)," - "'denial reason(s)' should not contain more than one value." - ), - element_wise=True, - single_values={"999"}, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="denial_reasons.duplicates_in_field", - description=( - "'Denial reason(s)' should not contain duplicated values." - ), - element_wise=True, - ), - ], - ), - "denial_reasons_ff": Column( - str, - title="Field 19: Free-form text field for other denial reason(s)", - nullable=True, - checks=[ - SBLCheck.str_length( - min_value=0, - max_value=300, - name="denial_reasons_ff.invalid_text_length", - description=( - "'Free-form text field for other denial reason(s)'" - "must not exceed 300 characters in length." ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="denial_reasons_ff.conditional_field_conflict", - description=( - "When 'denial reason(s)' does not contain 977 (other), field" - "'free-form text field for other denial reason(s)' must be" - "blank. When 'denial reason(s)' contains 977, 'free-form text" - "field for other denial reason(s)' must not be blank." - ), - groupby="denial_reasons", - condition_values={"977"}, - ), - ], - ), - "pricing_interest_rate_type": Column( - str, - title="Field 20: Interest rate type", - checks=[ - SBLCheck( - is_valid_enum, - name="pricing_interest_rate_type.invalid_enum_value", - description=( - "Each value in 'Interest rate type' (separated by " - " semicolons) Must equal 1, 2, 3, 4, 5, 6, or 999" - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "999", - ], - ), - ], - ), - "pricing_init_rate_period": Column( - str, - title="Field 21: Initial rate period", - nullable=True, - checks=[ - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_init_rate_period.conditional_field_conflict", - description=( - "When 'interest rate type' does not equal 3 (initial rate " - "period > 12 months, variable interest), 4 (initial rate " - "period > 12 months, fixed interest), 5 (initial rate period " - "<= 12 months, variable interest), or 6 (initial rate period " - "<= 12 months, fixed interest), 'initial rate period' must " - "be blank. When 'interest rate type' equals 3, 4, 5, or 6, " - "'initial rate period' must not be blank" - ), - groupby="pricing_interest_rate_type", - condition_values={"3", "4", "5", "6"}, - ), - SBLCheck( - is_number, - name="pricing_init_rate_period.invalid_numeric_format", - description=( - "When present, 'initial rate period' must be a whole number.", - ), - element_wise=True, - accept_blank=True, - ), - SBLCheck( - is_greater_than, - name="pricing_init_rate_period.invalid_numeric_value", - description=( - "When present, 'initial rate period' must be greater than 0", - ), - element_wise=True, - min_value="0", - accept_blank=True, - ), - ], - ), - "pricing_fixed_rate": Column( - str, - title="Field 22: Fixed rate: interest rate", - nullable=True, - checks=[ - SBLCheck( - is_number, - name="pricing_fixed_rate.invalid_numeric_format", - description=( - "When present, 'fixed rate: interest rate'" - " must be a numeric value." - ), - element_wise=True, - accept_blank=True, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_fixed_rate.conditional_field_conflict", - description=( - "When 'interest rate type' does not equal 2" - " (fixed interest rate, no initial rate period)," - " 4 (initial rate period > 12 months, fixed interest" - " rate), or 6 (initial rate period <= 12 months, fixed" - " interest rate), 'fixed rate: interest rate' must be" - " blank. When 'interest rate type' equals 2, 4, or 6," - " 'fixed rate: interest rate' must not be blank." - ), - groupby="pricing_interest_rate_type", - condition_values={"2", "4", "6"}, - ), - SBLCheck( - is_greater_than, - name="pricing_fixed_rate.unreasonable_numeric_value", - description=( - "When present, 'fixed rate: interest rate'" - " should generally be greater than 0.1." - ), - element_wise=True, - min_value="0.1", - accept_blank=True, - ), - ], - ), - "pricing_adj_margin": Column( - str, - title="Field 23: Adjustable rate transaction: margin", - nullable=True, - checks=[ - SBLCheck( - is_number, - name="pricing_adj_margin.invalid_numeric_format", - description=( - "When present, 'adjustable rate transaction:" - " margin' must be a numeric value." - ), - element_wise=True, - accept_blank=True, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_adj_margin.conditional_field_conflict", - description=( - "When 'interest rate type' does not equal 1" - " (adjustable interest rate, no initial rate period), 3 " - "(initial rate period > 12 months, adjustable interest rate)," - " or 5 (initial rate period <= 12 months, variable interest" - " rate), 'adjustable rate transaction: margin' must be blank." - " When 'interest rate type' equals 1, 3, or 5, 'variable" - " rate transaction: margin' must not be blank." - ), - groupby="pricing_interest_rate_type", - condition_values={"1", "3", "5"}, - ), - SBLCheck( - is_greater_than, - name="pricing_adj_margin.unreasonable_numeric_value", - description=( - "When present, 'adjustable rate transaction:" - " margin' should generally be greater than 0.1." - ), - element_wise=True, - min_value="0.1", - accept_blank=True, - ), - ], - ), - "pricing_adj_index_name": Column( - str, - title="Field 24: Adjustable rate transaction: index name", - checks=[ - SBLCheck( - is_valid_enum, - name="pricing_adj_index_name.invalid_enum_value", - description=( - "'Adjustable rate transaction: index name' must equal 1, 2, 3," - " 4, 5, 6, 7, 8, 9, 10, 977, or 999." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "977", - "999", - ], - ), - SBLCheck( - has_valid_enum_pair, - name="pricing_adj_index_name.enum_value_conflict", - description=( - "When 'interest rate type' does not equal 1 (variable interest" - "rate, no initial rate period), 3 (initial rate period > 12" - "months, adjustable interest rate), or 5 (initial rate" - "period <= 12 months, adjustable interest rate), 'adjustable" - " rate transaction: index name' must equal 999." - "When 'interest rate type' equals 1, 3, or 5, 'adjustable rate" - "transaction: index name' must not equal 999." - ), - groupby="pricing_interest_rate_type", - conditions=[ - { - "condition_values": {"1", "3", "5"}, - "is_equal_condition": False, - "target_value": "999", - "should_equal_target": True, - }, - { - "condition_values": {"1", "3", "5"}, - "is_equal_condition": True, - "target_value": "999", - "should_equal_target": False, + ], + ), + "action_taken_date": Column( + str, + title="Field 17: Action taken date", + checks=[ + SBLCheck( + is_date, + name="action_taken_date.invalid_date_format", + description=( + "'Action taken date' must be a real calendar" + " date using YYYYMMDD format." + ), + element_wise=True, + ), + SBLCheck( + is_date_in_range, + name="action_taken_date.invalid_date_value", + description=( + "The date indicated by 'action taken date' must occur" + " within the current reporting period:" + " October 1, 2024 to December 31, 2024." + ), + element_wise=True, + start_date_value="20241001", + end_date_value="20241231", + ), + SBLCheck( + is_date_after, + name="action_taken_date.date_value_conflict", + description=( + "The date indicated by 'action taken date'" + " must occur on or after 'application date'." + ), + groupby="app_date", + ), + SBLCheck( + is_date_before_in_days, + name="action_taken_date.unreasonable_date_value", + description=( + "The date indicated by 'application date' should" + " generally be less than two years (730 days) before" + " 'action taken date'." + ), + groupby="app_date", + days_value=730, + ), + ], + ), + "denial_reasons": Column( + str, + title="Field 18: Denial reason(s)", + checks=[ + SBLCheck( + is_valid_enum, + name="denial_reasons.invalid_enum_value", + description=( + "Each value in 'denial reason(s)' (separated by semicolons)" + "must equal 1, 2, 3, 4, 5, 6, 7, 8, 9, 977, or 999." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "977", + "999", + ], + ), + SBLCheck( + has_valid_value_count, + name="denial_reasons.invalid_number_of_values", + description=( + "'Denial reason(s)' must contain at least one and at most four" + "values, separated by semicolons." + ), + element_wise=True, + min_length=1, + max_length=4, + ), + SBLCheck( + has_valid_enum_pair, + name="denial_reasons.enum_value_conflict", + description=( + "When 'action taken' equals 3, 'denial reason(s)' must not" + "contain 999. When 'action taken' does not equal 3, 'denial" + "reason(s)' must equal 999." + ), + groupby="action_taken", + conditions=[ + { + "condition_values": {"3"}, + "is_equal_condition": True, + "target_value": "999", + "should_equal_target": False, + }, + { + "condition_values": {"3"}, + "is_equal_condition": False, + "target_value": "999", + "should_equal_target": True, + }, + ], + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="denial_reasons.multi_value_field_restriction", + description=( + "When 'denial reason(s)' contains 999 (not applicable)," + "'denial reason(s)' should not contain more than one value." + ), + element_wise=True, + single_values={"999"}, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="denial_reasons.duplicates_in_field", + description=( + "'Denial reason(s)' should not contain duplicated values." + ), + element_wise=True, + ), + ], + ), + "denial_reasons_ff": Column( + str, + title="Field 19: Free-form text field for other denial reason(s)", + nullable=True, + checks=[ + SBLCheck.str_length( + min_value=0, + max_value=300, + name="denial_reasons_ff.invalid_text_length", + description=( + "'Free-form text field for other denial reason(s)'" + "must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="denial_reasons_ff.conditional_field_conflict", + description=( + "When 'denial reason(s)' does not contain 977 (other), field" + "'free-form text field for other denial reason(s)' must be" + "blank. When 'denial reason(s)' contains 977, 'free-form text" + "field for other denial reason(s)' must not be blank." + ), + groupby="denial_reasons", + condition_values={"977"}, + ), + ], + ), + "pricing_interest_rate_type": Column( + str, + title="Field 20: Interest rate type", + checks=[ + SBLCheck( + is_valid_enum, + name="pricing_interest_rate_type.invalid_enum_value", + description=( + "Each value in 'Interest rate type' (separated by " + " semicolons) Must equal 1, 2, 3, 4, 5, 6, or 999" + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "999", + ], + ), + ], + ), + "pricing_init_rate_period": Column( + str, + title="Field 21: Initial rate period", + nullable=True, + checks=[ + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_init_rate_period.conditional_field_conflict", + description=( + "When 'interest rate type' does not equal 3 (initial rate " + "period > 12 months, variable interest), 4 (initial rate " + "period > 12 months, fixed interest), 5 (initial rate period " + "<= 12 months, variable interest), or 6 (initial rate period " + "<= 12 months, fixed interest), 'initial rate period' must " + "be blank. When 'interest rate type' equals 3, 4, 5, or 6, " + "'initial rate period' must not be blank" + ), + groupby="pricing_interest_rate_type", + condition_values={"3", "4", "5", "6"}, + ), + SBLCheck( + is_number, + name="pricing_init_rate_period.invalid_numeric_format", + description=( + "When present, 'initial rate period' must be a whole number.", + ), + element_wise=True, + accept_blank=True, + ), + SBLCheck( + is_greater_than, + name="pricing_init_rate_period.invalid_numeric_value", + description=( + "When present, 'initial rate period' must be greater than 0", + ), + element_wise=True, + min_value="0", + accept_blank=True, + ), + ], + ), + "pricing_fixed_rate": Column( + str, + title="Field 22: Fixed rate: interest rate", + nullable=True, + checks=[ + SBLCheck( + is_number, + name="pricing_fixed_rate.invalid_numeric_format", + description=( + "When present, 'fixed rate: interest rate'" + " must be a numeric value." + ), + element_wise=True, + accept_blank=True, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_fixed_rate.conditional_field_conflict", + description=( + "When 'interest rate type' does not equal 2" + " (fixed interest rate, no initial rate period)," + " 4 (initial rate period > 12 months, fixed interest" + " rate), or 6 (initial rate period <= 12 months, fixed" + " interest rate), 'fixed rate: interest rate' must be" + " blank. When 'interest rate type' equals 2, 4, or 6," + " 'fixed rate: interest rate' must not be blank." + ), + groupby="pricing_interest_rate_type", + condition_values={"2", "4", "6"}, + ), + SBLCheck( + is_greater_than, + name="pricing_fixed_rate.unreasonable_numeric_value", + description=( + "When present, 'fixed rate: interest rate'" + " should generally be greater than 0.1." + ), + element_wise=True, + min_value="0.1", + accept_blank=True, + ), + ], + ), + "pricing_adj_margin": Column( + str, + title="Field 23: Adjustable rate transaction: margin", + nullable=True, + checks=[ + SBLCheck( + is_number, + name="pricing_adj_margin.invalid_numeric_format", + description=( + "When present, 'adjustable rate transaction:" + " margin' must be a numeric value." + ), + element_wise=True, + accept_blank=True, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_adj_margin.conditional_field_conflict", + description=( + "When 'interest rate type' does not equal 1" + " (adjustable interest rate, no initial rate period), 3 " + "(initial rate period > 12 months, adjustable interest rate)," + " or 5 (initial rate period <= 12 months, variable interest" + " rate), 'adjustable rate transaction: margin' must be blank." + " When 'interest rate type' equals 1, 3, or 5, 'variable" + " rate transaction: margin' must not be blank." + ), + groupby="pricing_interest_rate_type", + condition_values={"1", "3", "5"}, + ), + SBLCheck( + is_greater_than, + name="pricing_adj_margin.unreasonable_numeric_value", + description=( + "When present, 'adjustable rate transaction:" + " margin' should generally be greater than 0.1." + ), + element_wise=True, + min_value="0.1", + accept_blank=True, + ), + ], + ), + "pricing_adj_index_name": Column( + str, + title="Field 24: Adjustable rate transaction: index name", + checks=[ + SBLCheck( + is_valid_enum, + name="pricing_adj_index_name.invalid_enum_value", + description=( + "'Adjustable rate transaction: index name' must equal 1, 2, 3," + " 4, 5, 6, 7, 8, 9, 10, 977, or 999." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "977", + "999", + ], + ), + SBLCheck( + has_valid_enum_pair, + name="pricing_adj_index_name.enum_value_conflict", + description=( + "When 'interest rate type' does not equal 1 (variable interest" + "rate, no initial rate period), 3 (initial rate period > 12" + "months, adjustable interest rate), or 5 (initial rate" + "period <= 12 months, adjustable interest rate), 'adjustable" + " rate transaction: index name' must equal 999." + "When 'interest rate type' equals 1, 3, or 5, 'adjustable rate" + "transaction: index name' must not equal 999." + ), + groupby="pricing_interest_rate_type", + conditions=[ + { + "condition_values": {"1", "3", "5"}, + "is_equal_condition": False, + "target_value": "999", + "should_equal_target": True, + }, + { + "condition_values": {"1", "3", "5"}, + "is_equal_condition": True, + "target_value": "999", + "should_equal_target": False, + }, + ], + ), + ], + ), + "pricing_adj_index_name_ff": Column( + str, + title="Field 25: Adjustable rate transaction: index name: other", + nullable=True, + checks=[ + SBLCheck.str_length( + min_value=0, + max_value=300, + name="pricing_adj_index_name_ff.invalid_text_length", + description=( + "'Adjustable rate transaction: index name: other' must " + "not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_adj_index_name_ff.conditional_field_conflict", + description=( + "When 'adjustable rate transaction: index name' does not equal" + "977 (other), 'adjustable rate transaction: index name: other'" + "must be blank." + "When 'adjustable rate transaction: index name' equals 977," + "'adjustable rate transaction: index name: other' must not be" + "blank." + ), + groupby="pricing_adj_index_name", + condition_values={"977"}, + ), + ], + ), + "pricing_adj_index_value": Column( + str, + title="Field 26: Adjustable rate transaction: index value", + checks=[ + SBLCheck( + is_number, + name="pricing_adj_index_value.invalid_numeric_format", + description="When present, 'adjustable rate transaction:" + " index value' must be a numeric value.", + element_wise=True, + accept_blank=True, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_adj_index_value.conditional_field_conflict", + description=( + "When 'interest rate type' does not equal 1 (variable" + " interest rate, no initial rate period)," + " or 3 (initial rate period > 12 months, variable interest" + " rate), 'adjustable rate transaction: index value' must be" + " blank. When 'interest rate type' equals 1 or 3," + " 'adjustable rate transaction: index value' must not be blank." + ), + groupby="pricing_interest_rate_type", + condition_values={"1", "3"}, + ), + ], + ), + "pricing_origination_charges": Column( + str, + title="Field 27: Total origination charges", + nullable=True, + checks=[ + SBLCheck( + is_number, + name="pricing_origination_charges.invalid_numeric_format", + description=( + "When present, 'total origination charges' must be a numeric", + "value.", + ), + element_wise=True, + accept_blank=True, + ), + ], + ), + "pricing_broker_fees": Column( + str, + title="Field 28: Amount of total broker fees", + nullable=True, + checks=[ + SBLCheck( + is_number, + name="pricing_broker_fees.invalid_numeric_format", + description=( + "When present, 'amount of total broker fees' must be a", + "numeric value.", + ), + element_wise=True, + accept_blank=True, + ), + ], + ), + "pricing_initial_charges": Column( + str, + title="Field 29: Initial annual charges", + nullable=True, + checks=[ + SBLCheck( + is_number, + name="pricing_initial_charges.invalid_numeric_format", + description=( + "When present, 'initial annual charges' must be a" + "numeric value." + ), + element_wise=True, + accept_blank=True, + ), + ], + ), + "pricing_mca_addcost_flag": Column( + str, + title=( + "Field 30: MCA/sales-based: additional cost for merchant cash " + "advances or other sales-based financing: NA flag" + ), + checks=[ + SBLCheck( + is_valid_enum, + name="pricing_mca_addcost_flag.invalid_enum_value", + description=( + "'MCA/sales-based: additional cost for merchant cash " + "advances or other sales-based financing: NA flag' " + "must equal 900 or 999." + ), + element_wise=True, + accepted_values=[ + "900", + "999", + ], + ), + SBLCheck( + has_valid_enum_pair, + name="pricing_mca_addcost_flag.enum_value_conflict", + description=( + "When 'credit product' does not equal 7 (merchant cash " + "advance), 8 (other sales-based financing transaction) " + "or 977 (other), 'MCA/sales-based: additional cost for " + "merchant cash advances or other sales-based financing: " + "NA flag' must be 999 (not applicable)." + ), + groupby="ct_credit_product", + conditions=[ + { + "condition_values": {"7", "8", "977"}, + "is_equal_condition": False, + "target_value": "999", + "should_equal_target": True, + } + ], + ), + ], + ), + "pricing_mca_addcost": Column( + str, + title=( + "Field 31: MCA/sales-based: additional cost for merchant cash ", + "advances or other sales-based financing", + ), + checks=[ + SBLCheck( + has_no_conditional_field_conflict, + name="pricing_mca_addcost.conditional_field_conflict", + description=( + "When 'MCA/sales-based: additional cost for merchant " + "cash advances or other sales-based financing: NA flag' " + "does not equal 900 (applicable), 'MCA/sales-based: " + "additional cost for merchant cash advances or other " + "sales-based financing' must be blank. When 'MCA/sales-based: " + "additional cost for merchant cash advances or other " + "sales-based financing: NA flag' equals 900, MCA/sales-based: " + "additional cost for merchant cash advances or other " + "sales-based financing’ must not be blank." + ), + groupby="pricing_mca_addcost_flag", + condition_values={"900"}, + ), + SBLCheck( + is_number, + name="pricing_mca_addcost.invalid_numeric_format", + description=( + "When present, 'MCA/sales-based: additional cost for " + "merchant cash advances or other sales-based financing' " + "must be a numeric value" + ), + element_wise=True, + accept_blank=True, + ), + ], + ), + "pricing_prepenalty_allowed": Column( + str, + title="Field 32: Prepayment penalty could be imposed", + checks=[ + SBLCheck( + is_valid_enum, + name="pricing_prepenalty_allowed.invalid_enum_value", + description=( + "'Prepayment penalty could be imposed' must equal 1, 2, or 999." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "999", + ], + ), + ], + ), + "pricing_prepenalty_exists": Column( + str, + title="Field 33: Prepayment penalty exists", + checks=[ + SBLCheck( + is_valid_enum, + name="pricing_prepenalty_exists.invalid_enum_value", + description="'Prepayment penalty exists' must equal 1, 2, or 999.", + element_wise=True, + accepted_values=[ + "1", + "2", + "999", + ], + ), + ], + ), + "census_tract_adr_type": Column( + str, + title="Field 34: Type of address", + checks=[ + SBLCheck( + is_valid_enum, + name="census_tract_adr_type.invalid_enum_value", + description=( + "'Census tract: type of address' must equal 1, 2, 3, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "988", + ], + ), + ], + ), + "census_tract_number": Column( + str, + title="Field 35: Tract number", + nullable=True, + checks=[ + SBLCheck( + has_correct_length, + name="census_tract_number.invalid_text_length", + description=( + "When present, 'census tract: tract number' must " + "be a GEOID with exactly 11 digits." + ), + element_wise=True, + accepted_length=11, + accept_blank=True, + ), + SBLCheck( + has_valid_enum_pair, + name="census_tract_number.conditional_field_conflict", + description=( + "When 'census tract: type of address' equals 988 (not " + "provided by applicant and otherwise undetermined), " + "'census tract: tract number' must be blank." + "When 'census tract: type of address' equals 1 (address" + " or location where the loan proceeds will principally " + "be applied), 2 (address or location of borrower's main " + "office or headquarters), or 3 (another address or " + "location associated with the applicant), 'census tract:" + " tract number' must not be blank." + ), + groupby="census_tract_adr_type", + conditions=[ + { + "condition_values": {"1", "2", "3"}, + "is_equal_condition": True, + "target_value": "", + "should_equal_target": False, + }, + { + "condition_values": {"988"}, + "is_equal_condition": True, + "target_value": "", + "should_equal_target": True, + }, + ], + ), + SBLCheck( + is_valid_code, + name="census_tract_number.invalid_geoid", + description=( + "When present, 'census tract: tract number' " + "should be a valid census tract GEOID as defined " + "by the U.S. Census Bureau." + ), + element_wise=True, + accept_blank=True, + codes=geoids, + ), + ], + ), + "gross_annual_revenue_flag": Column( + str, + title="Field 36: Gross annual revenue: NP flag", + checks=[ + SBLCheck( + is_valid_enum, + name="gross_annual_revenue_flag.invalid_enum_value", + description=( + "'Gross annual revenue: NP flag' must equal 900 or 988." + ), + element_wise=True, + accepted_values=[ + "900", + "988", + ], + ), + ], + ), + "gross_annual_revenue": Column( + str, + title="Field 37: Gross annual revenue", + nullable=True, + checks=[ + SBLCheck( + is_number, + name="gross_annual_revenue.invalid_numeric_format", + description=( + "When present, 'gross annual revenue' must be a numeric value." + ), + element_wise=True, + accept_blank=True, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="gross_annual_revenue.conditional_field_conflict", + description=( + "When 'gross annual revenue: NP flag' does not equal 900 " + "(reported), 'gross annual revenue' must be blank. When " + "'gross annual revenue: NP flag' equals 900, " + "'gross annual revenue' must not be blank." + ), + groupby="gross_annual_revenue_flag", + condition_values={"900"}, + ), + ], + ), + "naics_code_flag": Column( + str, + title=( + "Field 38: North American Industry Classification System (NAICS)" + "code: NP flag" + ), + checks=[ + SBLCheck( + is_valid_enum, + name="naics_code_flag.invalid_enum_value", + description=( + "'North American Industry Classification System (NAICS) " + "code: NP flag' must equal 900 or 988." + ), + element_wise=True, + accepted_values=[ + "900", + "988", + ], + ), + ], + ), + "naics_code": Column( + str, + title=( + "Field 39: North American Industry Classification" "System (NAICS) code" + ), + nullable=True, + checks=[ + SBLCheck( + is_number, + name="naics_code.invalid_naics_format", + description=( + "'North American Industry Classification System " + "(NAICS) code' may only contain numeric characters." + ), + element_wise=True, + accept_blank=True, + ), + SBLCheck( + has_correct_length, + name="naics_code.invalid_text_length", + description=( + "When present, 'North American Industry Classification System " + "(NAICS) code' must be three digits in length." + ), + element_wise=True, + accepted_length=3, + accept_blank=True, + ), + SBLCheck( + is_valid_code, + name="naics_code.invalid_naics_value", + description=( + "When present, 'North American Industry Classification System " + "(NAICS) code' should be a valid NAICS code." + ), + element_wise=True, + accept_blank=True, + codes=naics, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="naics_code.conditional_field_conflict", + description=( + "When 'North American Industry Classification System (NAICS) " + " code: NP flag' does not equal 900 (reported), 'North American" + " Industry Classification System (NAICS) code' must be blank." + "When 'North American Industry Classification System (NAICS) " + "code: NP flag' equals 900, 'North American Industry " + "Classification System (NAICS) code' must not be blank." + ), + groupby="naics_code_flag", + condition_values={"900"}, + ), + ], + ), + "number_of_workers": Column( + str, + title="Field 40: Number of workers", + checks=[ + SBLCheck( + is_valid_enum, + name="number_of_workers.invalid_enum_value", + description=( + "'Number of workers' must equal 1, 2, 3, 4, 5, 6, 7, 8, 9," + " or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "988", + ], + ), + ], + ), + "time_in_business_type": Column( + str, + title="Field 41: Type of response", + checks=[ + SBLCheck( + is_valid_enum, + name="time_in_business_type.invalid_enum_value", + description=( + "'Time in business: type of response'" + " must equal 1, 2, 3, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "988", + ], + ), + ], + ), + "time_in_business": Column( + str, + title="Field 42: Time in business", + nullable=True, + checks=[ + SBLCheck( + is_number, + name="time_in_business.invalid_numeric_format", + description=( + "When present, 'time in business' must be a whole number." + ), + element_wise=True, + accept_blank=True, + ), + SBLCheck( + is_greater_than_or_equal_to, + name="time_in_business.invalid_numeric_value", + description=( + "When present, 'time in business'" + " must be greater than or equal to 0.", + ), + element_wise=True, + min_value="0", + accept_blank=True, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="time_in_business.conditional_field_conflict", + description=( + "When 'time in business: type of response' does not" + " equal 1 (the number of years an applicant has been" + " in business is collected or obtained by the financial" + " institution), 'time in business' must be blank. When" + " 'time in business: type of response' equals 1," + " 'time in business' must not be blank." + ), + groupby="time_in_business_type", + condition_values={"1"}, + ), + ], + ), + "business_ownership_status": Column( + str, + title="Field 43: Business ownership status", + checks=[ + SBLCheck( + is_valid_enum, + name="business_ownership_status.invalid_enum_value", + description=( + "Each value in 'business ownership status'" + " (separated by semicolons) must equal 1, 2, 3," + " 955, 966, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "3", + "955", + "966", + "988", + ], + ), + SBLCheck( + has_valid_value_count, + name="business_ownership_status.invalid_number_of_values", + description=( + "'Business ownership status' must" + " contain at least one value." + ), + element_wise=True, + min_length=1, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="business_ownership_status.duplicates_in_field", + description=( + "'Business ownership status' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="business_ownership_status.multi_value_field_restriction", + description=( + "When 'business ownership status' contains 966" + " (the applicant responded that they did not wish" + " to provide this information) or 988 (not provided" + " by applicant), 'business ownership status' should" + " not contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + ), + "num_principal_owners_flag": Column( + str, + title="Field 44: Number of principal owners: NP flag", + checks=[ + SBLCheck( + is_valid_enum, + name="num_principal_owners_flag.invalid_enum_value", + description=( + "'Number of principal owners: NP flag' must equal 900 or 988." + ), + element_wise=True, + accepted_values=[ + "900", + "988", + ], + ), + ], + ), + "num_principal_owners": Column( + str, + title="Field 45: Number of principal owners", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="num_principal_owners.invalid_enum_value", + description=( + "When present, 'number of principal owners' must equal " + "0, 1, 2, 3, or 4." + ), + element_wise=True, + accepted_values=["0", "1", "2", "3", "4"], + accept_blank=True, + ), + SBLCheck( + has_no_conditional_field_conflict, + name="num_principal_owners.conditional_field_conflict", + description=( + "When 'number of principal owners: NP flag' does not equal 900 " + "(reported), 'number of principal owners' must be blank." + "When 'number of principal owners: NP flag' equals 900, " + "'number of principal owners' must not be blank." + ), + groupby="num_principal_owners_flag", + condition_values={"900"}, + ), + SBLCheck( + has_valid_fieldset_pair, + name="po_demographics_0.conditional_fieldset_conflict", + description=( + "When 'number of principal owners' equals 0 or is blank, " + "demographic fields for principal owners 1, 2, 3, and 4 " + "should be blank." + ), + groupby=[ + "po_1_ethnicity", + "po_1_race", + "po_1_gender_flag", + "po_2_ethnicity", + "po_2_race", + "po_2_gender_flag", + "po_3_ethnicity", + "po_3_race", + "po_3_gender_flag", + "po_4_ethnicity", + "po_4_race", + "po_4_gender_flag", + ], + condition_values=["0", ""], + should_fieldset_key_equal_to={ + "po_1_ethnicity": (0, True, ""), + "po_1_race": (1, True, ""), + "po_1_gender_flag": (2, True, ""), + "po_2_ethnicity": (3, True, ""), + "po_2_race": (4, True, ""), + "po_2_gender_flag": (5, True, ""), + "po_3_ethnicity": (6, True, ""), + "po_3_race": (7, True, ""), + "po_3_gender_flag": (8, True, ""), + "po_4_ethnicity": (9, True, ""), + "po_4_race": (10, True, ""), + "po_4_gender_flag": (11, True, ""), }, - ], - ), - ], - ), - "pricing_adj_index_name_ff": Column( - str, - title="Field 25: Adjustable rate transaction: index name: other", - nullable=True, - checks=[ - SBLCheck.str_length( - min_value=0, - max_value=300, - name="pricing_adj_index_name_ff.invalid_text_length", - description=( - "'Adjustable rate transaction: index name: other' must " - "not exceed 300 characters in length." ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_adj_index_name_ff.conditional_field_conflict", - description=( - "When 'adjustable rate transaction: index name' does not equal" - "977 (other), 'adjustable rate transaction: index name: other'" - "must be blank." - "When 'adjustable rate transaction: index name' equals 977," - "'adjustable rate transaction: index name: other' must not be" - "blank." - ), - groupby="pricing_adj_index_name", - condition_values={"977"}, - ), - ], - ), - "pricing_adj_index_value": Column( - str, - title="Field 26: Adjustable rate transaction: index value", - checks=[ - SBLCheck( - is_number, - name="pricing_adj_index_value.invalid_numeric_format", - description="When present, 'adjustable rate transaction:" - " index value' must be a numeric value.", - element_wise=True, - accept_blank=True, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_adj_index_value.conditional_field_conflict", - description=( - "When 'interest rate type' does not equal 1 (variable" - " interest rate, no initial rate period)," - " or 3 (initial rate period > 12 months, variable interest" - " rate), 'adjustable rate transaction: index value' must be" - " blank. When 'interest rate type' equals 1 or 3," - " 'adjustable rate transaction: index value' must not be blank." - ), - groupby="pricing_interest_rate_type", - condition_values={"1", "3"}, - ), - ], - ), - "pricing_origination_charges": Column( - str, - title="Field 27: Total origination charges", - nullable=True, - checks=[ - SBLCheck( - is_number, - name="pricing_origination_charges.invalid_numeric_format", - description=( - "When present, 'total origination charges' must be a numeric", - "value.", - ), - element_wise=True, - accept_blank=True, - ), - ], - ), - "pricing_broker_fees": Column( - str, - title="Field 28: Amount of total broker fees", - nullable=True, - checks=[ - SBLCheck( - is_number, - name="pricing_broker_fees.invalid_numeric_format", - description=( - "When present, 'amount of total broker fees' must be a", - "numeric value.", - ), - element_wise=True, - accept_blank=True, - ), - ], - ), - "pricing_initial_charges": Column( - str, - title="Field 29: Initial annual charges", - nullable=True, - checks=[ - SBLCheck( - is_number, - name="pricing_initial_charges.invalid_numeric_format", - description=( - "When present, 'initial annual charges' must be a" - "numeric value." - ), - element_wise=True, - accept_blank=True, - ), - ], - ), - "pricing_mca_addcost_flag": Column( - str, - title=( - "Field 30: MCA/sales-based: additional cost for merchant cash " - "advances or other sales-based financing: NA flag" - ), - checks=[ - SBLCheck( - is_valid_enum, - name="pricing_mca_addcost_flag.invalid_enum_value", - description=( - "'MCA/sales-based: additional cost for merchant cash " - "advances or other sales-based financing: NA flag' " - "must equal 900 or 999." - ), - element_wise=True, - accepted_values=[ - "900", - "999", - ], - ), - SBLCheck( - has_valid_enum_pair, - name="pricing_mca_addcost_flag.enum_value_conflict", - description=( - "When 'credit product' does not equal 7 (merchant cash " - "advance), 8 (other sales-based financing transaction) " - "or 977 (other), 'MCA/sales-based: additional cost for " - "merchant cash advances or other sales-based financing: " - "NA flag' must be 999 (not applicable)." - ), - groupby="ct_credit_product", - conditions=[ - { - "condition_values": {"7", "8", "977"}, - "is_equal_condition": False, - "target_value": "999", - "should_equal_target": True, - } - ], - ), - ], - ), - "pricing_mca_addcost": Column( - str, - title=( - "Field 31: MCA/sales-based: additional cost for merchant cash ", - "advances or other sales-based financing", - ), - checks=[ - SBLCheck( - has_no_conditional_field_conflict, - name="pricing_mca_addcost.conditional_field_conflict", - description=( - "When 'MCA/sales-based: additional cost for merchant " - "cash advances or other sales-based financing: NA flag' " - "does not equal 900 (applicable), 'MCA/sales-based: " - "additional cost for merchant cash advances or other " - "sales-based financing' must be blank. When 'MCA/sales-based: " - "additional cost for merchant cash advances or other " - "sales-based financing: NA flag' equals 900, MCA/sales-based: " - "additional cost for merchant cash advances or other " - "sales-based financing’ must not be blank." - ), - groupby="pricing_mca_addcost_flag", - condition_values={"900"}, - ), - SBLCheck( - is_number, - name="pricing_mca_addcost.invalid_numeric_format", - description=( - "When present, 'MCA/sales-based: additional cost for " - "merchant cash advances or other sales-based financing' " - "must be a numeric value" - ), - element_wise=True, - accept_blank=True, - ), - ], - ), - "pricing_prepenalty_allowed": Column( - str, - title="Field 32: Prepayment penalty could be imposed", - checks=[ - SBLCheck( - is_valid_enum, - name="pricing_prepenalty_allowed.invalid_enum_value", - description=( - "'Prepayment penalty could be imposed' must equal 1, 2, or 999." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "999", - ], - ), - ], - ), - "pricing_prepenalty_exists": Column( - str, - title="Field 33: Prepayment penalty exists", - checks=[ - SBLCheck( - is_valid_enum, - name="pricing_prepenalty_exists.invalid_enum_value", - description="'Prepayment penalty exists' must equal 1, 2, or 999.", - element_wise=True, - accepted_values=[ - "1", - "2", - "999", - ], - ), - ], - ), - "census_tract_adr_type": Column( - str, - title="Field 34: Type of address", - checks=[ - SBLCheck( - is_valid_enum, - name="census_tract_adr_type.invalid_enum_value", - description=( - "'Census tract: type of address' must equal 1, 2, 3, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "988", - ], - ), - ], - ), - "census_tract_number": Column( - str, - title="Field 35: Tract number", - nullable=True, - checks=[ - SBLCheck( - has_correct_length, - name="census_tract_number.invalid_text_length", - description=( - "When present, 'census tract: tract number' must " - "be a GEOID with exactly 11 digits." - ), - element_wise=True, - accepted_length=11, - accept_blank=True, - ), - SBLCheck( - has_valid_enum_pair, - name="census_tract_number.conditional_field_conflict", - description=( - "When 'census tract: type of address' equals 988 (not " - "provided by applicant and otherwise undetermined), " - "'census tract: tract number' must be blank." - "When 'census tract: type of address' equals 1 (address" - " or location where the loan proceeds will principally " - "be applied), 2 (address or location of borrower's main " - "office or headquarters), or 3 (another address or " - "location associated with the applicant), 'census tract:" - " tract number' must not be blank." - ), - groupby="census_tract_adr_type", - conditions=[ - { - "condition_values": {"1", "2", "3"}, - "is_equal_condition": True, - "target_value": "", - "should_equal_target": False, - }, - { - "condition_values": {"988"}, - "is_equal_condition": True, - "target_value": "", - "should_equal_target": True, + SBLCheck( + has_valid_fieldset_pair, + name="po_demographics_1.conditional_fieldset_conflict", + description=( + "When 'number of principal owners' equals 1, " + "'ethnicity of principal owner 1', 'race of principal owner 1'," + " and 'sex/gender of principal owner 1: NP flag' should not be" + " blank. Demographic fields for principal owners 2, 3, and 4 " + "should be blank." + ), + groupby=[ + "po_1_ethnicity", + "po_1_race", + "po_1_gender_flag", + "po_2_ethnicity", + "po_2_race", + "po_2_gender_flag", + "po_3_ethnicity", + "po_3_race", + "po_3_gender_flag", + "po_4_ethnicity", + "po_4_race", + "po_4_gender_flag", + ], + condition_values=["1"], + should_fieldset_key_equal_to={ + "po_1_ethnicity": (0, False, ""), + "po_1_race": (1, False, ""), + "po_1_gender_flag": (2, False, ""), + "po_2_ethnicity": (3, True, ""), + "po_2_race": (4, True, ""), + "po_2_gender_flag": (5, True, ""), + "po_3_ethnicity": (6, True, ""), + "po_3_race": (7, True, ""), + "po_3_gender_flag": (8, True, ""), + "po_4_ethnicity": (9, True, ""), + "po_4_race": (10, True, ""), + "po_4_gender_flag": (11, True, ""), }, - ], - ), - SBLCheck( - is_valid_code, - name="census_tract_number.invalid_geoid", - description=( - "When present, 'census tract: tract number' " - "should be a valid census tract GEOID as defined " - "by the U.S. Census Bureau." - ), - element_wise=True, - accept_blank=True, - codes=global_data.census_geoids, - ), - ], - ), - "gross_annual_revenue_flag": Column( - str, - title="Field 36: Gross annual revenue: NP flag", - checks=[ - SBLCheck( - is_valid_enum, - name="gross_annual_revenue_flag.invalid_enum_value", - description=( - "'Gross annual revenue: NP flag' must equal 900 or 988." - ), - element_wise=True, - accepted_values=[ - "900", - "988", - ], - ), - ], - ), - "gross_annual_revenue": Column( - str, - title="Field 37: Gross annual revenue", - nullable=True, - checks=[ - SBLCheck( - is_number, - name="gross_annual_revenue.invalid_numeric_format", - description=( - "When present, 'gross annual revenue' must be a numeric value." - ), - element_wise=True, - accept_blank=True, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="gross_annual_revenue.conditional_field_conflict", - description=( - "When 'gross annual revenue: NP flag' does not equal 900 " - "(reported), 'gross annual revenue' must be blank. When " - "'gross annual revenue: NP flag' equals 900, " - "'gross annual revenue' must not be blank." - ), - groupby="gross_annual_revenue_flag", - condition_values={"900"}, - ), - ], - ), - "naics_code_flag": Column( - str, - title=( - "Field 38: North American Industry Classification System (NAICS)" - "code: NP flag" - ), - checks=[ - SBLCheck( - is_valid_enum, - name="naics_code_flag.invalid_enum_value", - description=( - "'North American Industry Classification System (NAICS) " - "code: NP flag' must equal 900 or 988." - ), - element_wise=True, - accepted_values=[ - "900", - "988", - ], - ), - ], - ), - "naics_code": Column( - str, - title=( - "Field 39: North American Industry Classification" "System (NAICS) code" - ), - nullable=True, - checks=[ - SBLCheck( - is_number, - name="naics_code.invalid_naics_format", - description=( - "'North American Industry Classification System " - "(NAICS) code' may only contain numeric characters." - ), - element_wise=True, - accept_blank=True, - ), - SBLCheck( - has_correct_length, - name="naics_code.invalid_text_length", - description=( - "When present, 'North American Industry Classification System " - "(NAICS) code' must be three digits in length." - ), - element_wise=True, - accepted_length=3, - accept_blank=True, - ), - SBLCheck( - is_valid_code, - name="naics_code.invalid_naics_value", - description=( - "When present, 'North American Industry Classification System " - "(NAICS) code' should be a valid NAICS code." - ), - element_wise=True, - accept_blank=True, - codes=global_data.naics_codes, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="naics_code.conditional_field_conflict", - description=( - "When 'North American Industry Classification System (NAICS) " - " code: NP flag' does not equal 900 (reported), 'North American" - " Industry Classification System (NAICS) code' must be blank." - "When 'North American Industry Classification System (NAICS) " - "code: NP flag' equals 900, 'North American Industry " - "Classification System (NAICS) code' must not be blank." - ), - groupby="naics_code_flag", - condition_values={"900"}, - ), - ], - ), - "number_of_workers": Column( - str, - title="Field 40: Number of workers", - checks=[ - SBLCheck( - is_valid_enum, - name="number_of_workers.invalid_enum_value", - description=( - "'Number of workers' must equal 1, 2, 3, 4, 5, 6, 7, 8, 9," - " or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "988", - ], - ), - ], - ), - "time_in_business_type": Column( - str, - title="Field 41: Type of response", - checks=[ - SBLCheck( - is_valid_enum, - name="time_in_business_type.invalid_enum_value", - description=( - "'Time in business: type of response'" - " must equal 1, 2, 3, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "988", - ], - ), - ], - ), - "time_in_business": Column( - str, - title="Field 42: Time in business", - nullable=True, - checks=[ - SBLCheck( - is_number, - name="time_in_business.invalid_numeric_format", - description=( - "When present, 'time in business' must be a whole number." - ), - element_wise=True, - accept_blank=True, - ), - SBLCheck( - is_greater_than_or_equal_to, - name="time_in_business.invalid_numeric_value", - description=( - "When present, 'time in business'" - " must be greater than or equal to 0.", - ), - element_wise=True, - min_value="0", - accept_blank=True, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="time_in_business.conditional_field_conflict", - description=( - "When 'time in business: type of response' does not" - " equal 1 (the number of years an applicant has been" - " in business is collected or obtained by the financial" - " institution), 'time in business' must be blank. When" - " 'time in business: type of response' equals 1," - " 'time in business' must not be blank." - ), - groupby="time_in_business_type", - condition_values={"1"}, - ), - ], - ), - "business_ownership_status": Column( - str, - title="Field 43: Business ownership status", - checks=[ - SBLCheck( - is_valid_enum, - name="business_ownership_status.invalid_enum_value", - description=( - "Each value in 'business ownership status'" - " (separated by semicolons) must equal 1, 2, 3," - " 955, 966, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "3", - "955", - "966", - "988", - ], - ), - SBLCheck( - has_valid_value_count, - name="business_ownership_status.invalid_number_of_values", - description=( - "'Business ownership status' must" - " contain at least one value." - ), - element_wise=True, - min_length=1, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="business_ownership_status.duplicates_in_field", - description=( - "'Business ownership status' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="business_ownership_status.multi_value_field_restriction", - description=( - "When 'business ownership status' contains 966" - " (the applicant responded that they did not wish" - " to provide this information) or 988 (not provided" - " by applicant), 'business ownership status' should" - " not contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - ), - "num_principal_owners_flag": Column( - str, - title="Field 44: Number of principal owners: NP flag", - checks=[ - SBLCheck( - is_valid_enum, - name="num_principal_owners_flag.invalid_enum_value", - description=( - "'Number of principal owners: NP flag' must equal 900 or 988." - ), - element_wise=True, - accepted_values=[ - "900", - "988", - ], - ), - ], - ), - "num_principal_owners": Column( - str, - title="Field 45: Number of principal owners", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="num_principal_owners.invalid_enum_value", - description=( - "When present, 'number of principal owners' must equal " - "0, 1, 2, 3, or 4." - ), - element_wise=True, - accepted_values=["0", "1", "2", "3", "4"], - accept_blank=True, - ), - SBLCheck( - has_no_conditional_field_conflict, - name="num_principal_owners.conditional_field_conflict", - description=( - "When 'number of principal owners: NP flag' does not equal 900 " - "(reported), 'number of principal owners' must be blank." - "When 'number of principal owners: NP flag' equals 900, " - "'number of principal owners' must not be blank." - ), - groupby="num_principal_owners_flag", - condition_values={"900"}, - ), - SBLCheck( - has_valid_fieldset_pair, - name="po_demographics_0.conditional_fieldset_conflict", - description=( - "When 'number of principal owners' equals 0 or is blank, " - "demographic fields for principal owners 1, 2, 3, and 4 " - "should be blank." - ), - groupby=[ - "po_1_ethnicity", - "po_1_race", - "po_1_gender_flag", - "po_2_ethnicity", - "po_2_race", - "po_2_gender_flag", - "po_3_ethnicity", - "po_3_race", - "po_3_gender_flag", - "po_4_ethnicity", - "po_4_race", - "po_4_gender_flag", - ], - condition_values=["0", ""], - should_fieldset_key_equal_to={ - "po_1_ethnicity": (0, True, ""), - "po_1_race": (1, True, ""), - "po_1_gender_flag": (2, True, ""), - "po_2_ethnicity": (3, True, ""), - "po_2_race": (4, True, ""), - "po_2_gender_flag": (5, True, ""), - "po_3_ethnicity": (6, True, ""), - "po_3_race": (7, True, ""), - "po_3_gender_flag": (8, True, ""), - "po_4_ethnicity": (9, True, ""), - "po_4_race": (10, True, ""), - "po_4_gender_flag": (11, True, ""), - }, - ), - SBLCheck( - has_valid_fieldset_pair, - name="po_demographics_1.conditional_fieldset_conflict", - description=( - "When 'number of principal owners' equals 1, " - "'ethnicity of principal owner 1', 'race of principal owner 1'," - " and 'sex/gender of principal owner 1: NP flag' should not be" - " blank. Demographic fields for principal owners 2, 3, and 4 " - "should be blank." - ), - groupby=[ - "po_1_ethnicity", - "po_1_race", - "po_1_gender_flag", - "po_2_ethnicity", - "po_2_race", - "po_2_gender_flag", - "po_3_ethnicity", - "po_3_race", - "po_3_gender_flag", - "po_4_ethnicity", - "po_4_race", - "po_4_gender_flag", - ], - condition_values=["1"], - should_fieldset_key_equal_to={ - "po_1_ethnicity": (0, False, ""), - "po_1_race": (1, False, ""), - "po_1_gender_flag": (2, False, ""), - "po_2_ethnicity": (3, True, ""), - "po_2_race": (4, True, ""), - "po_2_gender_flag": (5, True, ""), - "po_3_ethnicity": (6, True, ""), - "po_3_race": (7, True, ""), - "po_3_gender_flag": (8, True, ""), - "po_4_ethnicity": (9, True, ""), - "po_4_race": (10, True, ""), - "po_4_gender_flag": (11, True, ""), - }, - ), - SBLCheck( - has_valid_fieldset_pair, - name="po_demographics_2.conditional_fieldset_conflict", - description=( - "When 'number of principal owners' equals 2, " - "'ethnicity of principal owner 1 and 2', 'race of principal " - "owner 1 and 2', and 'sex/gender of principal owner 1 and 2: " - "NP flag' should not be blank." - ), - groupby=[ - "po_1_ethnicity", - "po_1_race", - "po_1_gender_flag", - "po_2_ethnicity", - "po_2_race", - "po_2_gender_flag", - "po_3_ethnicity", - "po_3_race", - "po_3_gender_flag", - "po_4_ethnicity", - "po_4_race", - "po_4_gender_flag", - ], - condition_values=["2"], - should_fieldset_key_equal_to={ - "po_1_ethnicity": (0, False, ""), - "po_1_race": (1, False, ""), - "po_1_gender_flag": (2, False, ""), - "po_2_ethnicity": (3, False, ""), - "po_2_race": (4, False, ""), - "po_2_gender_flag": (5, False, ""), - "po_3_ethnicity": (6, True, ""), - "po_3_race": (7, True, ""), - "po_3_gender_flag": (8, True, ""), - "po_4_ethnicity": (9, True, ""), - "po_4_race": (10, True, ""), - "po_4_gender_flag": (11, True, ""), - }, - ), - SBLCheck( - has_valid_fieldset_pair, - name="po_demographics_3.conditional_fieldset_conflict", - description=( - "When 'number of principal owners' equals 3, " - "'ethnicity of principal owner 1, 2, and 3', 'race of principal" - " owner 1, 2, and 3', and 'sex/gender of principal owner 1, 2, " - "and 3: NP flag' should not be blank. Demographic fields for " - "principal owner 4 should be blank." - ), - groupby=[ - "po_1_ethnicity", - "po_1_race", - "po_1_gender_flag", - "po_2_ethnicity", - "po_2_race", - "po_2_gender_flag", - "po_3_ethnicity", - "po_3_race", - "po_3_gender_flag", - "po_4_ethnicity", - "po_4_race", - "po_4_gender_flag", - ], - condition_values=["3"], - should_fieldset_key_equal_to={ - "po_1_ethnicity": (0, False, ""), - "po_1_race": (1, False, ""), - "po_1_gender_flag": (2, False, ""), - "po_2_ethnicity": (3, False, ""), - "po_2_race": (4, False, ""), - "po_2_gender_flag": (5, False, ""), - "po_3_ethnicity": (6, False, ""), - "po_3_race": (7, False, ""), - "po_3_gender_flag": (8, False, ""), - "po_4_ethnicity": (9, True, ""), - "po_4_race": (10, True, ""), - "po_4_gender_flag": (11, True, ""), - }, - ), - SBLCheck( - has_valid_fieldset_pair, - name="po_demographics_4.conditional_fieldset_conflict", - description=( - "When 'number of principal owners' equals 4, " - "'ethnicity of principal owner 1, 2, 3, and 4', " - "'race of principal owner 1, 2, 3, and 4', " - "and 'sex/gender of principal owner 1, 2, 3, and 4: NP flag'" - " should not be blank." - ), - groupby=[ - "po_1_ethnicity", - "po_1_race", - "po_1_gender_flag", - "po_2_ethnicity", - "po_2_race", - "po_2_gender_flag", - "po_3_ethnicity", - "po_3_race", - "po_3_gender_flag", - "po_4_ethnicity", - "po_4_race", - "po_4_gender_flag", - ], - condition_values=["4"], - should_fieldset_key_equal_to={ - "po_1_ethnicity": (0, False, ""), - "po_1_race": (1, False, ""), - "po_1_gender_flag": (2, False, ""), - "po_2_ethnicity": (3, False, ""), - "po_2_race": (4, False, ""), - "po_2_gender_flag": (5, False, ""), - "po_3_ethnicity": (6, False, ""), - "po_3_race": (7, False, ""), - "po_3_gender_flag": (8, False, ""), - "po_4_ethnicity": (9, False, ""), - "po_4_race": (10, False, ""), - "po_4_gender_flag": (11, False, ""), - }, - ), - ], - ), - "po_1_ethnicity": Column( - str, - title="Field 46: Ethnicity of principal owner 1", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_1_ethnicity.invalid_enum_value", - description=( - "When present, each value in 'ethnicity" - " of principal owner 1' (separated by" - " semicolons) must equal 1, 11, 12," - " 13, 14, 2, 966, 977, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "11", - "12", - "13", - "14", - "2", - "966", - "977", - "988", - ], - accept_blank=True, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="po_1_ethnicity.duplicates_in_field", - description=( - "'Ethnicity of principal owner 1' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_1_ethnicity.multi_value_field_restriction", - description=( - "When 'ethnicity of principal owner 1' contains" - " 966 (the applicant responded that they did" - " not wish to provide this information) or 988" - " (not provided by applicant), 'ethnicity of" - " principal owner 1' should not contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - ), - "po_1_ethnicity_ff": Column( - str, - title=( - "Field 47: Ethnicity of principal owner 1: free-form text field for" - "other Hispanic or Latino ethnicity" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_1_ethnicity_ff.invalid_text_length", - description=( - "'Ethnicity of principal owner 1: free-form" - " text field for other Hispanic or Latino'" - " must not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_ethnicity_ff.conditional_field_conflict", - description=( - "When 'ethnicity of principal owner 1' does not" - " contain 977 (the applicant responded in the" - " free-form text field), 'ethnicity of principal" - " owner 1: free-form text field for other Hispanic" - " or Latino' must be blank. When 'ethnicity of principal" - " owner 1' contains 977, 'ethnicity of principal" - " owner 1: free-form text field for other Hispanic" - " or Latino' must not be blank." - ), - groupby="po_1_ethnicity", - condition_values={"977"}, - ), - ], - ), - "po_1_race": Column( - str, - title="Field 48: Race of principal owner 1", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_1_race.invalid_enum_value", - description=( - "When present, each value in 'race" - " of principal owner 1' (separated by" - " semicolons) must equal 1, 2, 21, 22," - " 23, 24, 25, 26, 27, 3, 31, 32, 33," - " 34, 35, 36, 37, 4, 41, 42, 43, 44," - " 5, 966, 971, 972, 973, 974, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "3", - "31", - "32", - "33", - "34", - "35", - "36", - "37", - "4", - "41", - "42", - "43", - "44", - "5", - "966", - "971", - "972", - "973", - "974", - "988", - ], - accept_blank=True, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="po_1_race.duplicates_in_field", - description=( - "'Race of principal owner 1' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_1_race.multi_value_field_restriction", - description=( - "When 'race of principal owner 1' contains" - " 966 (the applicant responded that they" - " did not wish to provide this information)" - " or 988 (not provided by applicant)," - " 'race of principal owner 1' should not" - " contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - ), - "po_1_race_anai_ff": Column( - str, - title=( - "Field 49: Race of principal owner 1: free-form text field for" - "American Indian or Alaska Native Enrolled or Principal Tribe" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_1_race_anai_ff.invalid_text_length", - description=( - "'Race of principal owner 1: free-form" - " text field for American Indian or Alaska" - " Native Enrolled or Principal Tribe' must" - " not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_race_anai_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 1' does not" - " contain 971 (the applicant responded in" - " the free-form text field for American Indian" - " or Alaska Native Enrolled or Principal Tribe)," - " 'race of principal owner 1: free-form text" - " field for American Indian or Alaska Native" - " Enrolled or Principal Tribe' must be blank." - " When 'race of principal owner 1' contains 971," - " 'race of principal owner 1: free-form text field" - " for American Indian or Alaska Native Enrolled or" - " Principal Tribe' must not be blank." - ), - groupby="po_1_race", - condition_values={"971"}, - ), - ], - ), - "po_1_race_asian_ff": Column( - str, - title=( - "Field 50: Race of principal owner 1: free-form text field for other" - "Asian race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_1_race_asian_ff.invalid_text_length", - description=( - "'Race of principal owner 1: free-form text" - " field for other Asian' must not exceed 300" - " characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_race_asian_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 1' does not contain" - " 972 (the applicant responded in the free-form text" - " field for other Asian race), 'race of principal" - " owner 1: free-form text field for other Asian' must" - " be blank. When 'race of principal owner 1' contains" - " 972, 'race of principal owner 1: free-form text field" - " for other Asian' must not be blank." - ), - groupby="po_1_race", - condition_values={"972"}, - ), - ], - ), - "po_1_race_baa_ff": Column( - str, - title=( - "Field 51: Race of principal owner 1: free-form text field for other" - "Black or African American race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_1_race_baa_ff.invalid_text_length", - description=( - "'Race of principal owner 1: free-form text" - " field for other Black or African American'" - " must not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_race_baa_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 1' does not contain 973" - " (the applicant responded in the free-form text field" - " for other Black or African race), 'race of principal" - " owner 1: free-form text field for other Black or African" - " American' must be blank. When 'race of principal owner 1'" - " contains 973, 'race of principal owner 1: free-form text" - " field for other Black or African American' must not be blank." - ), - groupby="po_1_race", - condition_values={"973"}, - ), - ], - ), - "po_1_race_pi_ff": Column( - str, - title=( - "Field 52: Race of principal owner 1: free-form text field for other" - "Pacific Islander race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_1_race_pi_ff.invalid_text_length", - description=( - "'Race of principal owner 1: free-form text" - " field for other Pacific Islander race' must" - " not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_race_pi_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 1' does not contain 974" - " (the applicant responded in the free-form text field" - " for other Pacific Islander race), 'race of principal" - " owner 1: free-form text field for other Pacific Islander" - " race' must be blank. When 'race of principal owner 1'" - " contains 974, 'race of principal owner 1: free-form text" - " field for other Pacific Islander race' must not be blank." - ), - groupby="po_1_race", - condition_values={"974"}, - ), - ], - ), - "po_1_gender_flag": Column( - str, - title="Field 53: Sex/gender of principal owner 1: NP flag", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_1_gender_flag.invalid_enum_value", - description=( - "When present, 'sex/gender of principal" - " owner 1: NP flag' must equal 1, 966, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "966", - "988", - ], - accept_blank=True, - ), - ], - ), - "po_1_gender_ff": Column( - str, - title=( - "Field 54: Sex/gender of principal owner 1: free-form text field for" - "self-identified sex/gender" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_1_gender_ff.invalid_text_length", - description=( - "'Sex/gender of principal owner 1: free-form" - " text field for self-identified sex/gender'" - " must not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_1_gender_ff.conditional_field_conflict", - description=( - "When 'sex/gender of principal owner 1: NP flag'" - " does not equal 1 (the applicant responded in the" - " free-form text field), 'sex/gender of principal" - " owner 1: free-form text field for self-identified" - " sex/gender' must be blank. When 'sex/gender of" - " principal owner 1: NP flag' equals 1, 'sex/gender" - " of principal owner 1: free-form text field for" - " self-identified sex/gender' must not be blank." - ), - groupby="po_1_gender_flag", - condition_values={"1"}, - ), - ], - ), - "po_2_ethnicity": Column( - str, - title="Field 55: Ethnicity of principal owner 2", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_2_ethnicity.invalid_enum_value", - description=( - "When present, each value in 'ethnicity" - " of principal owner 2' (separated by" - " semicolons) must equal 1, 11, 12," - " 13, 14, 2, 966, 977, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "11", - "12", - "13", - "14", - "2", - "966", - "977", - "988", - ], - accept_blank=True, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="po_2_ethnicity.duplicates_in_field", - description=( - "'Ethnicity of principal owner 2' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_2_ethnicity.multi_value_field_restriction", - description=( - "When 'ethnicity of principal owner 2' contains" - " 966 (the applicant responded that they did" - " not wish to provide this information) or 988" - " (not provided by applicant), 'ethnicity of" - " principal owner 2' should not contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - ), - "po_2_ethnicity_ff": Column( - str, - title=( - "Field 56: Ethnicity of principal owner 2: free-form text field for" - "other Hispanic or Latino ethnicity" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_2_ethnicity_ff.invalid_text_length", - description=( - "'Ethnicity of principal owner 2: free-form" - " text field for other Hispanic or Latino'" - " must not exceed 300 characters in length." ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_ethnicity_ff.conditional_field_conflict", - description=( - "When 'ethnicity of principal owner 2' does not" - " contain 977 (the applicant responded in the" - " free-form text field), 'ethnicity of principal" - " owner 2: free-form text field for other Hispanic" - " or Latino' must be blank. When 'ethnicity of principal" - " owner 2' contains 977, 'ethnicity of principal" - " owner 2: free-form text field for other Hispanic" - " or Latino' must not be blank." - ), - groupby="po_2_ethnicity", - condition_values={"977"}, - ), - ], - ), - "po_2_race": Column( - str, - title="Field 57: Race of principal owner 2", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_2_race.invalid_enum_value", - description=( - "When present, each value in 'race" - " of principal owner 2' (separated by" - " semicolons) must equal 1, 2, 21, 22," - " 23, 24, 25, 26, 27, 3, 31, 32, 33," - " 34, 35, 36, 37, 4, 41, 42, 43, 44," - " 5, 966, 971, 972, 973, 974, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "3", - "31", - "32", - "33", - "34", - "35", - "36", - "37", - "4", - "41", - "42", - "43", - "44", - "5", - "966", - "971", - "972", - "973", - "974", - "988", - ], - accept_blank=True, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="po_2_race.duplicates_in_field", - description=( - "'Race of principal owner 2' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_2_race.multi_value_field_restriction", - description=( - "When 'race of principal owner 2' contains" - " 966 (the applicant responded that they" - " did not wish to provide this information)" - " or 988 (not provided by applicant)," - " 'race of principal owner 2' should not" - " contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - ), - "po_2_race_anai_ff": Column( - str, - title=( - "Field 58: Race of principal owner 2: free-form text field for" - "American Indian or Alaska Native Enrolled or Principal Tribe" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_2_race_anai_ff.invalid_text_length", - description=( - "'Race of principal owner 2: free-form" - " text field for American Indian or Alaska" - " Native Enrolled or Principal Tribe' must" - " not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_race_anai_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 2' does not" - " contain 971 (the applicant responded in" - " the free-form text field for American Indian" - " or Alaska Native Enrolled or Principal Tribe)," - " 'race of principal owner 2: free-form text" - " field for American Indian or Alaska Native" - " Enrolled or Principal Tribe' must be blank." - " When 'race of principal owner 2' contains 971," - " 'race of principal owner 2: free-form text field" - " for American Indian or Alaska Native Enrolled or" - " Principal Tribe' must not be blank." - ), - groupby="po_2_race", - condition_values={"971"}, - ), - ], - ), - "po_2_race_asian_ff": Column( - str, - title=( - "Field 59: Race of principal owner 2: free-form text field for other" - "Asian race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_2_race_asian_ff.invalid_text_length", - description=( - "'Race of principal owner 2: free-form text" - " field for other Asian' must not exceed 300" - " characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_race_asian_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 2' does not contain" - " 972 (the applicant responded in the free-form text" - " field for other Asian race), 'race of principal" - " owner 2: free-form text field for other Asian' must" - " be blank. When 'race of principal owner 2' contains" - " 972, 'race of principal owner 2: free-form text field" - " for other Asian' must not be blank." - ), - groupby="po_2_race", - condition_values={"972"}, - ), - ], - ), - "po_2_race_baa_ff": Column( - str, - title=( - "Field 60: Race of principal owner 2: free-form text field for other" - "Black or African American race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_2_race_baa_ff.invalid_text_length", - description=( - "'Race of principal owner 2: free-form text" - " field for other Black or African American'" - " must not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_race_baa_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 2' does not contain 973" - " (the applicant responded in the free-form text field" - " for other Black or African race), 'race of principal" - " owner 2: free-form text field for other Black or African" - " American' must be blank. When 'race of principal owner 2'" - " contains 973, 'race of principal owner 2: free-form text" - " field for other Black or African American' must not be blank." - ), - groupby="po_2_race", - condition_values={"973"}, - ), - ], - ), - "po_2_race_pi_ff": Column( - str, - title=( - "Field 61: Race of principal owner 2: free-form text field for other" - "Pacific Islander race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_2_race_pi_ff.invalid_text_length", - description=( - "'Race of principal owner 2: free-form text" - " field for other Pacific Islander race' must" - " not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_race_pi_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 2' does not contain 974" - " (the applicant responded in the free-form text field" - " for other Pacific Islander race), 'race of principal" - " owner 2: free-form text field for other Pacific Islander" - " race' must be blank. When 'race of principal owner 2'" - " contains 974, 'race of principal owner 2: free-form text" - " field for other Pacific Islander race' must not be blank." - ), - groupby="po_2_race", - condition_values={"974"}, - ), - ], - ), - "po_2_gender_flag": Column( - str, - title="Field 62: Sex/gender of principal owner 2: NP flag", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_2_gender_flag.invalid_enum_value", - description=( - "When present, 'sex/gender of principal" - " owner 2: NP flag' must equal 1, 966, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "966", - "988", - ], - accept_blank=True, - ), - ], - ), - "po_2_gender_ff": Column( - str, - title=( - "Field 63: Sex/gender of principal owner 2: free-form text field for" - "self-identified sex/gender" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_2_gender_ff.invalid_text_length", - description=( - "'Sex/gender of principal owner 2: free-form" - " text field for self-identified sex/gender'" - " must not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_2_gender_ff.conditional_field_conflict", - description=( - "When 'sex/gender of principal owner 2: NP flag'" - " does not equal 1 (the applicant responded in the" - " free-form text field), 'sex/gender of principal" - " owner 2: free-form text field for self-identified" - " sex/gender' must be blank. When 'sex/gender of" - " principal owner 2: NP flag' equals 1, 'sex/gender" - " of principal owner 2: free-form text field for" - " self-identified sex/gender' must not be blank." - ), - groupby="po_2_gender_flag", - condition_values={"1"}, - ), - ], - ), - "po_3_ethnicity": Column( - str, - title="Field 64: Ethnicity of principal owner 3", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_3_ethnicity.invalid_enum_value", - description=( - "When present, each value in 'ethnicity" - " of principal owner 3' (separated by" - " semicolons) must equal 1, 11, 12," - " 13, 14, 2, 966, 977, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "11", - "12", - "13", - "14", - "2", - "966", - "977", - "988", - ], - accept_blank=True, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="po_3_ethnicity.duplicates_in_field", - description=( - "'Ethnicity of principal owner 3' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_3_ethnicity.multi_value_field_restriction", - description=( - "When 'ethnicity of principal owner 3' contains" - " 966 (the applicant responded that they did" - " not wish to provide this information) or 988" - " (not provided by applicant), 'ethnicity of" - " principal owner 3' should not contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - ), - "po_3_ethnicity_ff": Column( - str, - title=( - "Field 65: Ethnicity of principal owner 3: free-form text field for" - "other Hispanic or Latino ethnicity" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_3_ethnicity_ff.invalid_text_length", - description=( - "'Ethnicity of principal owner 3: free-form" - " text field for other Hispanic or Latino'" - " must not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_ethnicity_ff.conditional_field_conflict", - description=( - "When 'ethnicity of principal owner 3' does not" - " contain 977 (the applicant responded in the" - " free-form text field), 'ethnicity of principal" - " owner 3: free-form text field for other Hispanic" - " or Latino' must be blank. When 'ethnicity of principal" - " owner 3' contains 977, 'ethnicity of principal" - " owner 3: free-form text field for other Hispanic" - " or Latino' must not be blank." - ), - groupby="po_3_ethnicity", - condition_values={"977"}, - ), - ], - ), - "po_3_race": Column( - str, - title="Field 66: Race of principal owner 3", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_3_race.invalid_enum_value", - description=( - "When present, each value in 'race" - " of principal owner 3' (separated by" - " semicolons) must equal 1, 2, 21, 22," - " 23, 24, 25, 26, 27, 3, 31, 32, 33," - " 34, 35, 36, 37, 4, 41, 42, 43, 44," - " 5, 966, 971, 972, 973, 974, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "3", - "31", - "32", - "33", - "34", - "35", - "36", - "37", - "4", - "41", - "42", - "43", - "44", - "5", - "966", - "971", - "972", - "973", - "974", - "988", - ], - accept_blank=True, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="po_3_race.duplicates_in_field", - description=( - "'Race of principal owner 3' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_3_race.multi_value_field_restriction", - description=( - "When 'race of principal owner 3' contains" - " 966 (the applicant responded that they" - " did not wish to provide this information)" - " or 988 (not provided by applicant)," - " 'race of principal owner 3' should not" - " contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - ), - "po_3_race_anai_ff": Column( - str, - title=( - "Field 67: Race of principal owner 3: free-form text field for" - "American Indian or Alaska Native Enrolled or Principal Tribe" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_3_race_anai_ff.invalid_text_length", - description=( - "'Race of principal owner 3: free-form" - " text field for American Indian or Alaska" - " Native Enrolled or Principal Tribe' must" - " not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_race_anai_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 3' does not" - " contain 971 (the applicant responded in" - " the free-form text field for American Indian" - " or Alaska Native Enrolled or Principal Tribe)," - " 'race of principal owner 3: free-form text" - " field for American Indian or Alaska Native" - " Enrolled or Principal Tribe' must be blank." - " When 'race of principal owner 3' contains 971," - " 'race of principal owner 3: free-form text field" - " for American Indian or Alaska Native Enrolled or" - " Principal Tribe' must not be blank." - ), - groupby="po_3_race", - condition_values={"971"}, - ), - ], - ), - "po_3_race_asian_ff": Column( - str, - title=( - "Field 68: Race of principal owner 3: free-form text field for other" - "Asian race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_3_race_asian_ff.invalid_text_length", - description=( - "'Race of principal owner 3: free-form text" - " field for other Asian' must not exceed 300" - " characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_race_asian_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 3' does not contain" - " 972 (the applicant responded in the free-form text" - " field for other Asian race), 'race of principal" - " owner 3: free-form text field for other Asian' must" - " be blank. When 'race of principal owner 3' contains" - " 972, 'race of principal owner 3: free-form text field" - " for other Asian' must not be blank." - ), - groupby="po_3_race", - condition_values={"972"}, - ), - ], - ), - "po_3_race_baa_ff": Column( - str, - title=( - "Field 69: Race of principal owner 3: free-form text field for other" - "Black or African American race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_3_race_baa_ff.invalid_text_length", - description=( - "'Race of principal owner 3: free-form text" - " field for other Black or African American'" - " must not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_race_baa_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 3' does not contain 973" - " (the applicant responded in the free-form text field" - " for other Black or African race), 'race of principal" - " owner 3: free-form text field for other Black or African" - " American' must be blank. When 'race of principal owner 3'" - " contains 973, 'race of principal owner 3: free-form text" - " field for other Black or African American' must not be blank." - ), - groupby="po_3_race", - condition_values={"973"}, - ), - ], - ), - "po_3_race_pi_ff": Column( - str, - title=( - "Field 70: Race of principal owner 3: free-form text field for other" - "Pacific Islander race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_3_race_pi_ff.invalid_text_length", - description=( - "'Race of principal owner 3: free-form text" - " field for other Pacific Islander race' must" - " not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_race_pi_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 3' does not contain 974" - " (the applicant responded in the free-form text field" - " for other Pacific Islander race), 'race of principal" - " owner 3: free-form text field for other Pacific Islander" - " race' must be blank. When 'race of principal owner 3'" - " contains 974, 'race of principal owner 3: free-form text" - " field for other Pacific Islander race' must not be blank." - ), - groupby="po_3_race", - condition_values={"974"}, - ), - ], - ), - "po_3_gender_flag": Column( - str, - title="Field 71: Sex/gender of principal owner 3: NP flag", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_3_gender_flag.invalid_enum_value", - description=( - "When present, 'sex/gender of principal" - " owner 3: NP flag' must equal 1, 966, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "966", - "988", - ], - accept_blank=True, - ), - ], - ), - "po_3_gender_ff": Column( - str, - title=( - "Field 72: Sex/gender of principal owner 3: free-form text field for" - "self-identified sex/gender" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_3_gender_ff.invalid_text_length", - description=( - "'Sex/gender of principal owner 3: free-form" - " text field for self-identified sex/gender'" - " must not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_3_gender_ff.conditional_field_conflict", - description=( - "When 'sex/gender of principal owner 3: NP flag'" - " does not equal 1 (the applicant responded in the" - " free-form text field), 'sex/gender of principal" - " owner 3: free-form text field for self-identified" - " sex/gender' must be blank. When 'sex/gender of" - " principal owner 3: NP flag' equals 1, 'sex/gender" - " of principal owner 3: free-form text field for" - " self-identified sex/gender' must not be blank." - ), - groupby="po_3_gender_flag", - condition_values={"1"}, - ), - ], - ), - "po_4_ethnicity": Column( - str, - title="Field 73: Ethnicity of principal owner 4", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_4_ethnicity.invalid_enum_value", - description=( - "When present, each value in 'ethnicity" - " of principal owner 4' (separated by" - " semicolons) must equal 1, 11, 12," - " 13, 14, 2, 966, 977, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "11", - "12", - "13", - "14", - "2", - "966", - "977", - "988", - ], - accept_blank=True, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="po_4_ethnicity.duplicates_in_field", - description=( - "'Ethnicity of principal owner 4' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_4_ethnicity.multi_value_field_restriction", - description=( - "When 'ethnicity of principal owner 4' contains" - " 966 (the applicant responded that they did" - " not wish to provide this information) or 988" - " (not provided by applicant), 'ethnicity of" - " principal owner 4' should not contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - ), - "po_4_ethnicity_ff": Column( - str, - title=( - "Field 74: Ethnicity of principal owner 4: free-form text field for" - "other Hispanic or Latino ethnicity" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_4_ethnicity_ff.invalid_text_length", - description=( - "'Ethnicity of principal owner 4: free-form" - " text field for other Hispanic or Latino'" - " must not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_ethnicity_ff.conditional_field_conflict", - description=( - "When 'ethnicity of principal owner 4' does not" - " contain 977 (the applicant responded in the" - " free-form text field), 'ethnicity of principal" - " owner 4: free-form text field for other Hispanic" - " or Latino' must be blank. When 'ethnicity of principal" - " owner 4' contains 977, 'ethnicity of principal" - " owner 4: free-form text field for other Hispanic" - " or Latino' must not be blank." - ), - groupby="po_4_ethnicity", - condition_values={"977"}, - ), - ], - ), - "po_4_race": Column( - str, - title="Field 75: Race of principal owner 4", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_4_race.invalid_enum_value", - description=( - "When present, each value in 'race" - " of principal owner 4' (separated by" - " semicolons) must equal 1, 2, 21, 22," - " 23, 24, 25, 26, 27, 3, 31, 32, 33," - " 34, 35, 36, 37, 4, 41, 42, 43, 44," - " 5, 966, 971, 972, 973, 974, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "2", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "3", - "31", - "32", - "33", - "34", - "35", - "36", - "37", - "4", - "41", - "42", - "43", - "44", - "5", - "966", - "971", - "972", - "973", - "974", - "988", - ], - accept_blank=True, - ), - SBLCheck( - is_unique_in_field, - warning=True, - name="po_4_race.duplicates_in_field", - description=( - "'Race of principal owner 4' should" - " not contain duplicated values." - ), - element_wise=True, - ), - SBLCheck( - meets_multi_value_field_restriction, - warning=True, - name="po_4_race.multi_value_field_restriction", - description=( - "When 'race of principal owner 4' contains" - " 966 (the applicant responded that they" - " did not wish to provide this information)" - " or 988 (not provided by applicant)," - " 'race of principal owner 4' should not" - " contain more than one value." - ), - element_wise=True, - single_values={"966", "988"}, - ), - ], - ), - "po_4_race_anai_ff": Column( - str, - title=( - "Field 76: Race of principal owner 4: free-form text field for" - "American Indian or Alaska Native Enrolled or Principal Tribe" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_4_race_anai_ff.invalid_text_length", - description=( - "'Race of principal owner 4: free-form" - " text field for American Indian or Alaska" - " Native Enrolled or Principal Tribe' must" - " not exceed 300 characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_race_anai_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 4' does not" - " contain 971 (the applicant responded in" - " the free-form text field for American Indian" - " or Alaska Native Enrolled or Principal Tribe)," - " 'race of principal owner 4: free-form text" - " field for American Indian or Alaska Native" - " Enrolled or Principal Tribe' must be blank." - " When 'race of principal owner 4' contains 971," - " 'race of principal owner 4: free-form text field" - " for American Indian or Alaska Native Enrolled or" - " Principal Tribe' must not be blank." - ), - groupby="po_4_race", - condition_values={"971"}, - ), - ], - ), - "po_4_race_asian_ff": Column( - str, - title=( - "Field 77: Race of principal owner 4: free-form text field for other" - "Asian race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_4_race_asian_ff.invalid_text_length", - description=( - "'Race of principal owner 4: free-form text" - " field for other Asian' must not exceed 300" - " characters in length." - ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_race_asian_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 4' does not contain" - " 972 (the applicant responded in the free-form text" - " field for other Asian race), 'race of principal" - " owner 4: free-form text field for other Asian' must" - " be blank. When 'race of principal owner 4' contains" - " 972, 'race of principal owner 4: free-form text field" - " for other Asian' must not be blank." - ), - groupby="po_4_race", - condition_values={"972"}, - ), - ], - ), - "po_4_race_baa_ff": Column( - str, - title=( - "Field 78: Race of principal owner 4: free-form text field for other" - "Black or African American race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_4_race_baa_ff.invalid_text_length", - description=( - "'Race of principal owner 4: free-form text" - " field for other Black or African American'" - " must not exceed 300 characters in length." + SBLCheck( + has_valid_fieldset_pair, + name="po_demographics_2.conditional_fieldset_conflict", + description=( + "When 'number of principal owners' equals 2, " + "'ethnicity of principal owner 1 and 2', 'race of principal " + "owner 1 and 2', and 'sex/gender of principal owner 1 and 2: " + "NP flag' should not be blank." + ), + groupby=[ + "po_1_ethnicity", + "po_1_race", + "po_1_gender_flag", + "po_2_ethnicity", + "po_2_race", + "po_2_gender_flag", + "po_3_ethnicity", + "po_3_race", + "po_3_gender_flag", + "po_4_ethnicity", + "po_4_race", + "po_4_gender_flag", + ], + condition_values=["2"], + should_fieldset_key_equal_to={ + "po_1_ethnicity": (0, False, ""), + "po_1_race": (1, False, ""), + "po_1_gender_flag": (2, False, ""), + "po_2_ethnicity": (3, False, ""), + "po_2_race": (4, False, ""), + "po_2_gender_flag": (5, False, ""), + "po_3_ethnicity": (6, True, ""), + "po_3_race": (7, True, ""), + "po_3_gender_flag": (8, True, ""), + "po_4_ethnicity": (9, True, ""), + "po_4_race": (10, True, ""), + "po_4_gender_flag": (11, True, ""), + }, ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_race_baa_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 4' does not contain 973" - " (the applicant responded in the free-form text field" - " for other Black or African race), 'race of principal" - " owner 4: free-form text field for other Black or African" - " American' must be blank. When 'race of principal owner 4'" - " contains 973, 'race of principal owner 4: free-form text" - " field for other Black or African American' must not be blank." - ), - groupby="po_4_race", - condition_values={"973"}, - ), - ], - ), - "po_4_race_pi_ff": Column( - str, - title=( - "Field 79: Race of principal owner 4: free-form text field for other" - "Pacific Islander race" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_4_race_pi_ff.invalid_text_length", - description=( - "'Race of principal owner 4: free-form text" - " field for other Pacific Islander race' must" - " not exceed 300 characters in length." + SBLCheck( + has_valid_fieldset_pair, + name="po_demographics_3.conditional_fieldset_conflict", + description=( + "When 'number of principal owners' equals 3, " + "'ethnicity of principal owner 1, 2, and 3', 'race of principal" + " owner 1, 2, and 3', and 'sex/gender of principal owner 1, 2, " + "and 3: NP flag' should not be blank. Demographic fields for " + "principal owner 4 should be blank." + ), + groupby=[ + "po_1_ethnicity", + "po_1_race", + "po_1_gender_flag", + "po_2_ethnicity", + "po_2_race", + "po_2_gender_flag", + "po_3_ethnicity", + "po_3_race", + "po_3_gender_flag", + "po_4_ethnicity", + "po_4_race", + "po_4_gender_flag", + ], + condition_values=["3"], + should_fieldset_key_equal_to={ + "po_1_ethnicity": (0, False, ""), + "po_1_race": (1, False, ""), + "po_1_gender_flag": (2, False, ""), + "po_2_ethnicity": (3, False, ""), + "po_2_race": (4, False, ""), + "po_2_gender_flag": (5, False, ""), + "po_3_ethnicity": (6, False, ""), + "po_3_race": (7, False, ""), + "po_3_gender_flag": (8, False, ""), + "po_4_ethnicity": (9, True, ""), + "po_4_race": (10, True, ""), + "po_4_gender_flag": (11, True, ""), + }, ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_race_pi_ff.conditional_field_conflict", - description=( - "When 'race of principal owner 4' does not contain 974" - " (the applicant responded in the free-form text field" - " for other Pacific Islander race), 'race of principal" - " owner 4: free-form text field for other Pacific Islander" - " race' must be blank. When 'race of principal owner 4'" - " contains 974, 'race of principal owner 4: free-form text" - " field for other Pacific Islander race' must not be blank." - ), - groupby="po_4_race", - condition_values={"974"}, - ), - ], - ), - "po_4_gender_flag": Column( - str, - title="Field 80: Sex/gender of principal owner 4: NP flag", - nullable=True, - checks=[ - SBLCheck( - is_valid_enum, - name="po_4_gender_flag.invalid_enum_value", - description=( - "When present, 'sex/gender of principal" - " owner 4: NP flag' must equal 1, 966, or 988." - ), - element_wise=True, - accepted_values=[ - "1", - "966", - "988", - ], - accept_blank=True, - ), - ], - ), - "po_4_gender_ff": Column( - str, - title=( - "Field 81: Sex/gender of principal owner 4: free-form text field for" - "self-identified sex/gender" - ), - nullable=True, - checks=[ - SBLCheck.str_length( - 0, - 300, - name="po_4_gender_ff.invalid_text_length", - description=( - "'Sex/gender of principal owner 4: free-form" - " text field for self-identified sex/gender'" - " must not exceed 300 characters in length." + SBLCheck( + has_valid_fieldset_pair, + name="po_demographics_4.conditional_fieldset_conflict", + description=( + "When 'number of principal owners' equals 4, " + "'ethnicity of principal owner 1, 2, 3, and 4', " + "'race of principal owner 1, 2, 3, and 4', " + "and 'sex/gender of principal owner 1, 2, 3, and 4: NP flag'" + " should not be blank." + ), + groupby=[ + "po_1_ethnicity", + "po_1_race", + "po_1_gender_flag", + "po_2_ethnicity", + "po_2_race", + "po_2_gender_flag", + "po_3_ethnicity", + "po_3_race", + "po_3_gender_flag", + "po_4_ethnicity", + "po_4_race", + "po_4_gender_flag", + ], + condition_values=["4"], + should_fieldset_key_equal_to={ + "po_1_ethnicity": (0, False, ""), + "po_1_race": (1, False, ""), + "po_1_gender_flag": (2, False, ""), + "po_2_ethnicity": (3, False, ""), + "po_2_race": (4, False, ""), + "po_2_gender_flag": (5, False, ""), + "po_3_ethnicity": (6, False, ""), + "po_3_race": (7, False, ""), + "po_3_gender_flag": (8, False, ""), + "po_4_ethnicity": (9, False, ""), + "po_4_race": (10, False, ""), + "po_4_gender_flag": (11, False, ""), + }, ), - ), - SBLCheck( - has_no_conditional_field_conflict, - name="po_4_gender_ff.conditional_field_conflict", - description=( - "When 'sex/gender of principal owner 4: NP flag'" - " does not equal 1 (the applicant responded in the" - " free-form text field), 'sex/gender of principal" - " owner 4: free-form text field for self-identified" - " sex/gender' must be blank. When 'sex/gender of" - " principal owner 4: NP flag' equals 1, 'sex/gender" - " of principal owner 4: free-form text field for" - " self-identified sex/gender' must not be blank." - ), - groupby="po_4_gender_flag", - condition_values={"1"}, - ), - ], - ), - }, -) + ], + ), + "po_1_ethnicity": Column( + str, + title="Field 46: Ethnicity of principal owner 1", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_1_ethnicity.invalid_enum_value", + description=( + "When present, each value in 'ethnicity" + " of principal owner 1' (separated by" + " semicolons) must equal 1, 11, 12," + " 13, 14, 2, 966, 977, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "11", + "12", + "13", + "14", + "2", + "966", + "977", + "988", + ], + accept_blank=True, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="po_1_ethnicity.duplicates_in_field", + description=( + "'Ethnicity of principal owner 1' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_1_ethnicity.multi_value_field_restriction", + description=( + "When 'ethnicity of principal owner 1' contains" + " 966 (the applicant responded that they did" + " not wish to provide this information) or 988" + " (not provided by applicant), 'ethnicity of" + " principal owner 1' should not contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + ), + "po_1_ethnicity_ff": Column( + str, + title=( + "Field 47: Ethnicity of principal owner 1: free-form text field for" + "other Hispanic or Latino ethnicity" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_1_ethnicity_ff.invalid_text_length", + description=( + "'Ethnicity of principal owner 1: free-form" + " text field for other Hispanic or Latino'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_ethnicity_ff.conditional_field_conflict", + description=( + "When 'ethnicity of principal owner 1' does not" + " contain 977 (the applicant responded in the" + " free-form text field), 'ethnicity of principal" + " owner 1: free-form text field for other Hispanic" + " or Latino' must be blank. When 'ethnicity of principal" + " owner 1' contains 977, 'ethnicity of principal" + " owner 1: free-form text field for other Hispanic" + " or Latino' must not be blank." + ), + groupby="po_1_ethnicity", + condition_values={"977"}, + ), + ], + ), + "po_1_race": Column( + str, + title="Field 48: Race of principal owner 1", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_1_race.invalid_enum_value", + description=( + "When present, each value in 'race" + " of principal owner 1' (separated by" + " semicolons) must equal 1, 2, 21, 22," + " 23, 24, 25, 26, 27, 3, 31, 32, 33," + " 34, 35, 36, 37, 4, 41, 42, 43, 44," + " 5, 966, 971, 972, 973, 974, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "3", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "4", + "41", + "42", + "43", + "44", + "5", + "966", + "971", + "972", + "973", + "974", + "988", + ], + accept_blank=True, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="po_1_race.duplicates_in_field", + description=( + "'Race of principal owner 1' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_1_race.multi_value_field_restriction", + description=( + "When 'race of principal owner 1' contains" + " 966 (the applicant responded that they" + " did not wish to provide this information)" + " or 988 (not provided by applicant)," + " 'race of principal owner 1' should not" + " contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + ), + "po_1_race_anai_ff": Column( + str, + title=( + "Field 49: Race of principal owner 1: free-form text field for" + "American Indian or Alaska Native Enrolled or Principal Tribe" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_1_race_anai_ff.invalid_text_length", + description=( + "'Race of principal owner 1: free-form" + " text field for American Indian or Alaska" + " Native Enrolled or Principal Tribe' must" + " not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_race_anai_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 1' does not" + " contain 971 (the applicant responded in" + " the free-form text field for American Indian" + " or Alaska Native Enrolled or Principal Tribe)," + " 'race of principal owner 1: free-form text" + " field for American Indian or Alaska Native" + " Enrolled or Principal Tribe' must be blank." + " When 'race of principal owner 1' contains 971," + " 'race of principal owner 1: free-form text field" + " for American Indian or Alaska Native Enrolled or" + " Principal Tribe' must not be blank." + ), + groupby="po_1_race", + condition_values={"971"}, + ), + ], + ), + "po_1_race_asian_ff": Column( + str, + title=( + "Field 50: Race of principal owner 1: free-form text field for other" + "Asian race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_1_race_asian_ff.invalid_text_length", + description=( + "'Race of principal owner 1: free-form text" + " field for other Asian' must not exceed 300" + " characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_race_asian_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 1' does not contain" + " 972 (the applicant responded in the free-form text" + " field for other Asian race), 'race of principal" + " owner 1: free-form text field for other Asian' must" + " be blank. When 'race of principal owner 1' contains" + " 972, 'race of principal owner 1: free-form text field" + " for other Asian' must not be blank." + ), + groupby="po_1_race", + condition_values={"972"}, + ), + ], + ), + "po_1_race_baa_ff": Column( + str, + title=( + "Field 51: Race of principal owner 1: free-form text field for other" + "Black or African American race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_1_race_baa_ff.invalid_text_length", + description=( + "'Race of principal owner 1: free-form text" + " field for other Black or African American'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_race_baa_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 1' does not contain 973" + " (the applicant responded in the free-form text field" + " for other Black or African race), 'race of principal" + " owner 1: free-form text field for other Black or African" + " American' must be blank. When 'race of principal owner 1'" + " contains 973, 'race of principal owner 1: free-form text" + " field for other Black or African American' must not be blank." + ), + groupby="po_1_race", + condition_values={"973"}, + ), + ], + ), + "po_1_race_pi_ff": Column( + str, + title=( + "Field 52: Race of principal owner 1: free-form text field for other" + "Pacific Islander race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_1_race_pi_ff.invalid_text_length", + description=( + "'Race of principal owner 1: free-form text" + " field for other Pacific Islander race' must" + " not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_race_pi_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 1' does not contain 974" + " (the applicant responded in the free-form text field" + " for other Pacific Islander race), 'race of principal" + " owner 1: free-form text field for other Pacific Islander" + " race' must be blank. When 'race of principal owner 1'" + " contains 974, 'race of principal owner 1: free-form text" + " field for other Pacific Islander race' must not be blank." + ), + groupby="po_1_race", + condition_values={"974"}, + ), + ], + ), + "po_1_gender_flag": Column( + str, + title="Field 53: Sex/gender of principal owner 1: NP flag", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_1_gender_flag.invalid_enum_value", + description=( + "When present, 'sex/gender of principal" + " owner 1: NP flag' must equal 1, 966, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "966", + "988", + ], + accept_blank=True, + ), + ], + ), + "po_1_gender_ff": Column( + str, + title=( + "Field 54: Sex/gender of principal owner 1: free-form text field for" + "self-identified sex/gender" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_1_gender_ff.invalid_text_length", + description=( + "'Sex/gender of principal owner 1: free-form" + " text field for self-identified sex/gender'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_1_gender_ff.conditional_field_conflict", + description=( + "When 'sex/gender of principal owner 1: NP flag'" + " does not equal 1 (the applicant responded in the" + " free-form text field), 'sex/gender of principal" + " owner 1: free-form text field for self-identified" + " sex/gender' must be blank. When 'sex/gender of" + " principal owner 1: NP flag' equals 1, 'sex/gender" + " of principal owner 1: free-form text field for" + " self-identified sex/gender' must not be blank." + ), + groupby="po_1_gender_flag", + condition_values={"1"}, + ), + ], + ), + "po_2_ethnicity": Column( + str, + title="Field 55: Ethnicity of principal owner 2", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_2_ethnicity.invalid_enum_value", + description=( + "When present, each value in 'ethnicity" + " of principal owner 2' (separated by" + " semicolons) must equal 1, 11, 12," + " 13, 14, 2, 966, 977, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "11", + "12", + "13", + "14", + "2", + "966", + "977", + "988", + ], + accept_blank=True, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="po_2_ethnicity.duplicates_in_field", + description=( + "'Ethnicity of principal owner 2' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_2_ethnicity.multi_value_field_restriction", + description=( + "When 'ethnicity of principal owner 2' contains" + " 966 (the applicant responded that they did" + " not wish to provide this information) or 988" + " (not provided by applicant), 'ethnicity of" + " principal owner 2' should not contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + ), + "po_2_ethnicity_ff": Column( + str, + title=( + "Field 56: Ethnicity of principal owner 2: free-form text field for" + "other Hispanic or Latino ethnicity" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_2_ethnicity_ff.invalid_text_length", + description=( + "'Ethnicity of principal owner 2: free-form" + " text field for other Hispanic or Latino'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_ethnicity_ff.conditional_field_conflict", + description=( + "When 'ethnicity of principal owner 2' does not" + " contain 977 (the applicant responded in the" + " free-form text field), 'ethnicity of principal" + " owner 2: free-form text field for other Hispanic" + " or Latino' must be blank. When 'ethnicity of principal" + " owner 2' contains 977, 'ethnicity of principal" + " owner 2: free-form text field for other Hispanic" + " or Latino' must not be blank." + ), + groupby="po_2_ethnicity", + condition_values={"977"}, + ), + ], + ), + "po_2_race": Column( + str, + title="Field 57: Race of principal owner 2", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_2_race.invalid_enum_value", + description=( + "When present, each value in 'race" + " of principal owner 2' (separated by" + " semicolons) must equal 1, 2, 21, 22," + " 23, 24, 25, 26, 27, 3, 31, 32, 33," + " 34, 35, 36, 37, 4, 41, 42, 43, 44," + " 5, 966, 971, 972, 973, 974, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "3", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "4", + "41", + "42", + "43", + "44", + "5", + "966", + "971", + "972", + "973", + "974", + "988", + ], + accept_blank=True, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="po_2_race.duplicates_in_field", + description=( + "'Race of principal owner 2' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_2_race.multi_value_field_restriction", + description=( + "When 'race of principal owner 2' contains" + " 966 (the applicant responded that they" + " did not wish to provide this information)" + " or 988 (not provided by applicant)," + " 'race of principal owner 2' should not" + " contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + ), + "po_2_race_anai_ff": Column( + str, + title=( + "Field 58: Race of principal owner 2: free-form text field for" + "American Indian or Alaska Native Enrolled or Principal Tribe" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_2_race_anai_ff.invalid_text_length", + description=( + "'Race of principal owner 2: free-form" + " text field for American Indian or Alaska" + " Native Enrolled or Principal Tribe' must" + " not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_race_anai_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 2' does not" + " contain 971 (the applicant responded in" + " the free-form text field for American Indian" + " or Alaska Native Enrolled or Principal Tribe)," + " 'race of principal owner 2: free-form text" + " field for American Indian or Alaska Native" + " Enrolled or Principal Tribe' must be blank." + " When 'race of principal owner 2' contains 971," + " 'race of principal owner 2: free-form text field" + " for American Indian or Alaska Native Enrolled or" + " Principal Tribe' must not be blank." + ), + groupby="po_2_race", + condition_values={"971"}, + ), + ], + ), + "po_2_race_asian_ff": Column( + str, + title=( + "Field 59: Race of principal owner 2: free-form text field for other" + "Asian race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_2_race_asian_ff.invalid_text_length", + description=( + "'Race of principal owner 2: free-form text" + " field for other Asian' must not exceed 300" + " characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_race_asian_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 2' does not contain" + " 972 (the applicant responded in the free-form text" + " field for other Asian race), 'race of principal" + " owner 2: free-form text field for other Asian' must" + " be blank. When 'race of principal owner 2' contains" + " 972, 'race of principal owner 2: free-form text field" + " for other Asian' must not be blank." + ), + groupby="po_2_race", + condition_values={"972"}, + ), + ], + ), + "po_2_race_baa_ff": Column( + str, + title=( + "Field 60: Race of principal owner 2: free-form text field for other" + "Black or African American race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_2_race_baa_ff.invalid_text_length", + description=( + "'Race of principal owner 2: free-form text" + " field for other Black or African American'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_race_baa_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 2' does not contain 973" + " (the applicant responded in the free-form text field" + " for other Black or African race), 'race of principal" + " owner 2: free-form text field for other Black or African" + " American' must be blank. When 'race of principal owner 2'" + " contains 973, 'race of principal owner 2: free-form text" + " field for other Black or African American' must not be blank." + ), + groupby="po_2_race", + condition_values={"973"}, + ), + ], + ), + "po_2_race_pi_ff": Column( + str, + title=( + "Field 61: Race of principal owner 2: free-form text field for other" + "Pacific Islander race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_2_race_pi_ff.invalid_text_length", + description=( + "'Race of principal owner 2: free-form text" + " field for other Pacific Islander race' must" + " not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_race_pi_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 2' does not contain 974" + " (the applicant responded in the free-form text field" + " for other Pacific Islander race), 'race of principal" + " owner 2: free-form text field for other Pacific Islander" + " race' must be blank. When 'race of principal owner 2'" + " contains 974, 'race of principal owner 2: free-form text" + " field for other Pacific Islander race' must not be blank." + ), + groupby="po_2_race", + condition_values={"974"}, + ), + ], + ), + "po_2_gender_flag": Column( + str, + title="Field 62: Sex/gender of principal owner 2: NP flag", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_2_gender_flag.invalid_enum_value", + description=( + "When present, 'sex/gender of principal" + " owner 2: NP flag' must equal 1, 966, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "966", + "988", + ], + accept_blank=True, + ), + ], + ), + "po_2_gender_ff": Column( + str, + title=( + "Field 63: Sex/gender of principal owner 2: free-form text field for" + "self-identified sex/gender" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_2_gender_ff.invalid_text_length", + description=( + "'Sex/gender of principal owner 2: free-form" + " text field for self-identified sex/gender'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_2_gender_ff.conditional_field_conflict", + description=( + "When 'sex/gender of principal owner 2: NP flag'" + " does not equal 1 (the applicant responded in the" + " free-form text field), 'sex/gender of principal" + " owner 2: free-form text field for self-identified" + " sex/gender' must be blank. When 'sex/gender of" + " principal owner 2: NP flag' equals 1, 'sex/gender" + " of principal owner 2: free-form text field for" + " self-identified sex/gender' must not be blank." + ), + groupby="po_2_gender_flag", + condition_values={"1"}, + ), + ], + ), + "po_3_ethnicity": Column( + str, + title="Field 64: Ethnicity of principal owner 3", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_3_ethnicity.invalid_enum_value", + description=( + "When present, each value in 'ethnicity" + " of principal owner 3' (separated by" + " semicolons) must equal 1, 11, 12," + " 13, 14, 2, 966, 977, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "11", + "12", + "13", + "14", + "2", + "966", + "977", + "988", + ], + accept_blank=True, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="po_3_ethnicity.duplicates_in_field", + description=( + "'Ethnicity of principal owner 3' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_3_ethnicity.multi_value_field_restriction", + description=( + "When 'ethnicity of principal owner 3' contains" + " 966 (the applicant responded that they did" + " not wish to provide this information) or 988" + " (not provided by applicant), 'ethnicity of" + " principal owner 3' should not contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + ), + "po_3_ethnicity_ff": Column( + str, + title=( + "Field 65: Ethnicity of principal owner 3: free-form text field for" + "other Hispanic or Latino ethnicity" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_3_ethnicity_ff.invalid_text_length", + description=( + "'Ethnicity of principal owner 3: free-form" + " text field for other Hispanic or Latino'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_ethnicity_ff.conditional_field_conflict", + description=( + "When 'ethnicity of principal owner 3' does not" + " contain 977 (the applicant responded in the" + " free-form text field), 'ethnicity of principal" + " owner 3: free-form text field for other Hispanic" + " or Latino' must be blank. When 'ethnicity of principal" + " owner 3' contains 977, 'ethnicity of principal" + " owner 3: free-form text field for other Hispanic" + " or Latino' must not be blank." + ), + groupby="po_3_ethnicity", + condition_values={"977"}, + ), + ], + ), + "po_3_race": Column( + str, + title="Field 66: Race of principal owner 3", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_3_race.invalid_enum_value", + description=( + "When present, each value in 'race" + " of principal owner 3' (separated by" + " semicolons) must equal 1, 2, 21, 22," + " 23, 24, 25, 26, 27, 3, 31, 32, 33," + " 34, 35, 36, 37, 4, 41, 42, 43, 44," + " 5, 966, 971, 972, 973, 974, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "3", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "4", + "41", + "42", + "43", + "44", + "5", + "966", + "971", + "972", + "973", + "974", + "988", + ], + accept_blank=True, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="po_3_race.duplicates_in_field", + description=( + "'Race of principal owner 3' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_3_race.multi_value_field_restriction", + description=( + "When 'race of principal owner 3' contains" + " 966 (the applicant responded that they" + " did not wish to provide this information)" + " or 988 (not provided by applicant)," + " 'race of principal owner 3' should not" + " contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + ), + "po_3_race_anai_ff": Column( + str, + title=( + "Field 67: Race of principal owner 3: free-form text field for" + "American Indian or Alaska Native Enrolled or Principal Tribe" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_3_race_anai_ff.invalid_text_length", + description=( + "'Race of principal owner 3: free-form" + " text field for American Indian or Alaska" + " Native Enrolled or Principal Tribe' must" + " not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_race_anai_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 3' does not" + " contain 971 (the applicant responded in" + " the free-form text field for American Indian" + " or Alaska Native Enrolled or Principal Tribe)," + " 'race of principal owner 3: free-form text" + " field for American Indian or Alaska Native" + " Enrolled or Principal Tribe' must be blank." + " When 'race of principal owner 3' contains 971," + " 'race of principal owner 3: free-form text field" + " for American Indian or Alaska Native Enrolled or" + " Principal Tribe' must not be blank." + ), + groupby="po_3_race", + condition_values={"971"}, + ), + ], + ), + "po_3_race_asian_ff": Column( + str, + title=( + "Field 68: Race of principal owner 3: free-form text field for other" + "Asian race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_3_race_asian_ff.invalid_text_length", + description=( + "'Race of principal owner 3: free-form text" + " field for other Asian' must not exceed 300" + " characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_race_asian_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 3' does not contain" + " 972 (the applicant responded in the free-form text" + " field for other Asian race), 'race of principal" + " owner 3: free-form text field for other Asian' must" + " be blank. When 'race of principal owner 3' contains" + " 972, 'race of principal owner 3: free-form text field" + " for other Asian' must not be blank." + ), + groupby="po_3_race", + condition_values={"972"}, + ), + ], + ), + "po_3_race_baa_ff": Column( + str, + title=( + "Field 69: Race of principal owner 3: free-form text field for other" + "Black or African American race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_3_race_baa_ff.invalid_text_length", + description=( + "'Race of principal owner 3: free-form text" + " field for other Black or African American'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_race_baa_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 3' does not contain 973" + " (the applicant responded in the free-form text field" + " for other Black or African race), 'race of principal" + " owner 3: free-form text field for other Black or African" + " American' must be blank. When 'race of principal owner 3'" + " contains 973, 'race of principal owner 3: free-form text" + " field for other Black or African American' must not be blank." + ), + groupby="po_3_race", + condition_values={"973"}, + ), + ], + ), + "po_3_race_pi_ff": Column( + str, + title=( + "Field 70: Race of principal owner 3: free-form text field for other" + "Pacific Islander race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_3_race_pi_ff.invalid_text_length", + description=( + "'Race of principal owner 3: free-form text" + " field for other Pacific Islander race' must" + " not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_race_pi_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 3' does not contain 974" + " (the applicant responded in the free-form text field" + " for other Pacific Islander race), 'race of principal" + " owner 3: free-form text field for other Pacific Islander" + " race' must be blank. When 'race of principal owner 3'" + " contains 974, 'race of principal owner 3: free-form text" + " field for other Pacific Islander race' must not be blank." + ), + groupby="po_3_race", + condition_values={"974"}, + ), + ], + ), + "po_3_gender_flag": Column( + str, + title="Field 71: Sex/gender of principal owner 3: NP flag", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_3_gender_flag.invalid_enum_value", + description=( + "When present, 'sex/gender of principal" + " owner 3: NP flag' must equal 1, 966, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "966", + "988", + ], + accept_blank=True, + ), + ], + ), + "po_3_gender_ff": Column( + str, + title=( + "Field 72: Sex/gender of principal owner 3: free-form text field for" + "self-identified sex/gender" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_3_gender_ff.invalid_text_length", + description=( + "'Sex/gender of principal owner 3: free-form" + " text field for self-identified sex/gender'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_3_gender_ff.conditional_field_conflict", + description=( + "When 'sex/gender of principal owner 3: NP flag'" + " does not equal 1 (the applicant responded in the" + " free-form text field), 'sex/gender of principal" + " owner 3: free-form text field for self-identified" + " sex/gender' must be blank. When 'sex/gender of" + " principal owner 3: NP flag' equals 1, 'sex/gender" + " of principal owner 3: free-form text field for" + " self-identified sex/gender' must not be blank." + ), + groupby="po_3_gender_flag", + condition_values={"1"}, + ), + ], + ), + "po_4_ethnicity": Column( + str, + title="Field 73: Ethnicity of principal owner 4", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_4_ethnicity.invalid_enum_value", + description=( + "When present, each value in 'ethnicity" + " of principal owner 4' (separated by" + " semicolons) must equal 1, 11, 12," + " 13, 14, 2, 966, 977, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "11", + "12", + "13", + "14", + "2", + "966", + "977", + "988", + ], + accept_blank=True, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="po_4_ethnicity.duplicates_in_field", + description=( + "'Ethnicity of principal owner 4' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_4_ethnicity.multi_value_field_restriction", + description=( + "When 'ethnicity of principal owner 4' contains" + " 966 (the applicant responded that they did" + " not wish to provide this information) or 988" + " (not provided by applicant), 'ethnicity of" + " principal owner 4' should not contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + ), + "po_4_ethnicity_ff": Column( + str, + title=( + "Field 74: Ethnicity of principal owner 4: free-form text field for" + "other Hispanic or Latino ethnicity" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_4_ethnicity_ff.invalid_text_length", + description=( + "'Ethnicity of principal owner 4: free-form" + " text field for other Hispanic or Latino'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_ethnicity_ff.conditional_field_conflict", + description=( + "When 'ethnicity of principal owner 4' does not" + " contain 977 (the applicant responded in the" + " free-form text field), 'ethnicity of principal" + " owner 4: free-form text field for other Hispanic" + " or Latino' must be blank. When 'ethnicity of principal" + " owner 4' contains 977, 'ethnicity of principal" + " owner 4: free-form text field for other Hispanic" + " or Latino' must not be blank." + ), + groupby="po_4_ethnicity", + condition_values={"977"}, + ), + ], + ), + "po_4_race": Column( + str, + title="Field 75: Race of principal owner 4", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_4_race.invalid_enum_value", + description=( + "When present, each value in 'race" + " of principal owner 4' (separated by" + " semicolons) must equal 1, 2, 21, 22," + " 23, 24, 25, 26, 27, 3, 31, 32, 33," + " 34, 35, 36, 37, 4, 41, 42, 43, 44," + " 5, 966, 971, 972, 973, 974, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "2", + "21", + "22", + "23", + "24", + "25", + "26", + "27", + "3", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "4", + "41", + "42", + "43", + "44", + "5", + "966", + "971", + "972", + "973", + "974", + "988", + ], + accept_blank=True, + ), + SBLCheck( + is_unique_in_field, + warning=True, + name="po_4_race.duplicates_in_field", + description=( + "'Race of principal owner 4' should" + " not contain duplicated values." + ), + element_wise=True, + ), + SBLCheck( + meets_multi_value_field_restriction, + warning=True, + name="po_4_race.multi_value_field_restriction", + description=( + "When 'race of principal owner 4' contains" + " 966 (the applicant responded that they" + " did not wish to provide this information)" + " or 988 (not provided by applicant)," + " 'race of principal owner 4' should not" + " contain more than one value." + ), + element_wise=True, + single_values={"966", "988"}, + ), + ], + ), + "po_4_race_anai_ff": Column( + str, + title=( + "Field 76: Race of principal owner 4: free-form text field for" + "American Indian or Alaska Native Enrolled or Principal Tribe" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_4_race_anai_ff.invalid_text_length", + description=( + "'Race of principal owner 4: free-form" + " text field for American Indian or Alaska" + " Native Enrolled or Principal Tribe' must" + " not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_race_anai_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 4' does not" + " contain 971 (the applicant responded in" + " the free-form text field for American Indian" + " or Alaska Native Enrolled or Principal Tribe)," + " 'race of principal owner 4: free-form text" + " field for American Indian or Alaska Native" + " Enrolled or Principal Tribe' must be blank." + " When 'race of principal owner 4' contains 971," + " 'race of principal owner 4: free-form text field" + " for American Indian or Alaska Native Enrolled or" + " Principal Tribe' must not be blank." + ), + groupby="po_4_race", + condition_values={"971"}, + ), + ], + ), + "po_4_race_asian_ff": Column( + str, + title=( + "Field 77: Race of principal owner 4: free-form text field for other" + "Asian race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_4_race_asian_ff.invalid_text_length", + description=( + "'Race of principal owner 4: free-form text" + " field for other Asian' must not exceed 300" + " characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_race_asian_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 4' does not contain" + " 972 (the applicant responded in the free-form text" + " field for other Asian race), 'race of principal" + " owner 4: free-form text field for other Asian' must" + " be blank. When 'race of principal owner 4' contains" + " 972, 'race of principal owner 4: free-form text field" + " for other Asian' must not be blank." + ), + groupby="po_4_race", + condition_values={"972"}, + ), + ], + ), + "po_4_race_baa_ff": Column( + str, + title=( + "Field 78: Race of principal owner 4: free-form text field for other" + "Black or African American race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_4_race_baa_ff.invalid_text_length", + description=( + "'Race of principal owner 4: free-form text" + " field for other Black or African American'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_race_baa_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 4' does not contain 973" + " (the applicant responded in the free-form text field" + " for other Black or African race), 'race of principal" + " owner 4: free-form text field for other Black or African" + " American' must be blank. When 'race of principal owner 4'" + " contains 973, 'race of principal owner 4: free-form text" + " field for other Black or African American' must not be blank." + ), + groupby="po_4_race", + condition_values={"973"}, + ), + ], + ), + "po_4_race_pi_ff": Column( + str, + title=( + "Field 79: Race of principal owner 4: free-form text field for other" + "Pacific Islander race" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_4_race_pi_ff.invalid_text_length", + description=( + "'Race of principal owner 4: free-form text" + " field for other Pacific Islander race' must" + " not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_race_pi_ff.conditional_field_conflict", + description=( + "When 'race of principal owner 4' does not contain 974" + " (the applicant responded in the free-form text field" + " for other Pacific Islander race), 'race of principal" + " owner 4: free-form text field for other Pacific Islander" + " race' must be blank. When 'race of principal owner 4'" + " contains 974, 'race of principal owner 4: free-form text" + " field for other Pacific Islander race' must not be blank." + ), + groupby="po_4_race", + condition_values={"974"}, + ), + ], + ), + "po_4_gender_flag": Column( + str, + title="Field 80: Sex/gender of principal owner 4: NP flag", + nullable=True, + checks=[ + SBLCheck( + is_valid_enum, + name="po_4_gender_flag.invalid_enum_value", + description=( + "When present, 'sex/gender of principal" + " owner 4: NP flag' must equal 1, 966, or 988." + ), + element_wise=True, + accepted_values=[ + "1", + "966", + "988", + ], + accept_blank=True, + ), + ], + ), + "po_4_gender_ff": Column( + str, + title=( + "Field 81: Sex/gender of principal owner 4: free-form text field for" + "self-identified sex/gender" + ), + nullable=True, + checks=[ + SBLCheck.str_length( + 0, + 300, + name="po_4_gender_ff.invalid_text_length", + description=( + "'Sex/gender of principal owner 4: free-form" + " text field for self-identified sex/gender'" + " must not exceed 300 characters in length." + ), + ), + SBLCheck( + has_no_conditional_field_conflict, + name="po_4_gender_ff.conditional_field_conflict", + description=( + "When 'sex/gender of principal owner 4: NP flag'" + " does not equal 1 (the applicant responded in the" + " free-form text field), 'sex/gender of principal" + " owner 4: free-form text field for self-identified" + " sex/gender' must be blank. When 'sex/gender of" + " principal owner 4: NP flag' equals 1, 'sex/gender" + " of principal owner 4: free-form text field for" + " self-identified sex/gender' must not be blank." + ), + groupby="po_4_gender_flag", + condition_values={"1"}, + ), + ], + ), + }, + ) diff --git a/src/validator/schema_template.py b/src/validator/schema_template.py index 2b320047..9f86dc7a 100644 --- a/src/validator/schema_template.py +++ b/src/validator/schema_template.py @@ -14,6 +14,8 @@ from pandera import Column +# NOTE: we should not use unique flag in column definitions. +# to check for uniqueness, add `is_unique_column` check to column _schema_template = { "uid": Column( str, diff --git a/tools/__init__.py b/tools/__init__.py index e69de29b..eba2d7dc 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -0,0 +1,7 @@ +import os +import sys + +CURR_DIR = os.path.dirname(os.path.abspath(__file__)) # noqa: E402 +PARENT_DIR = os.path.dirname(CURR_DIR) # noqa: E402 +sys.path.append(CURR_DIR) # noqa: E402 +sys.path.append(PARENT_DIR) # noqa: E402 \ No newline at end of file diff --git a/tools/process_census.py b/tools/process_census.py index 0686b9c5..062e8217 100644 --- a/tools/process_census.py +++ b/tools/process_census.py @@ -5,8 +5,8 @@ import pandas as pd -ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # noqa: E402 -sys.path.append(ROOT_DIR) # noqa: E402 +PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # noqa: E402 +sys.path.append(PARENT_DIR) # noqa: E402 import config # noqa: E402 @@ -24,11 +24,13 @@ def _is_number(s): def _extract_census_zip_file(): CENSUS_TMP_CSV_PATH = config.CENSUS_RAW_ZIP_PATH + ".tmp.csv" # unzip and extract csv files - with zipfile.ZipFile(config.CENSUS_RAW_ZIP_PATH, "r") as zip_ref: + input_file = os.path.join(PARENT_DIR, config.CENSUS_RAW_ZIP_PATH) + output_file = os.path.join(PARENT_DIR, CENSUS_TMP_CSV_PATH) + with zipfile.ZipFile(input_file, "r") as zip_ref: for file in zip_ref.namelist(): # iterate over files in archive if file[-4:] == ".csv": - print("Extracting CSV to {}".format(CENSUS_TMP_CSV_PATH)) - with open(CENSUS_TMP_CSV_PATH, "wb") as outfile: + print("Extracting CSV to {}".format(output_file)) + with open(output_file, "wb") as outfile: outfile.write(zip_ref.read(file)) # it should only have one csv file return CENSUS_TMP_CSV_PATH @@ -85,13 +87,13 @@ def _read_census_csv(src_path: str, csv_path: str): """ if __name__ == "__main__": CSV_PATH = config.CENSUS_PROCESSED_CSV_PATH - - if os.path.isfile(CSV_PATH): - error_msg = "Output {} csv file existed".format(CSV_PATH) + file = os.path.join(PARENT_DIR, CSV_PATH) + if os.path.isfile(file): + error_msg = "Output {} csv file existed".format(file) raise FileExistsError(error_msg) tmp_census_csv_file = _extract_census_zip_file() print("Reading extracted CSV File . {}".format(tmp_census_csv_file)) - _read_census_csv(tmp_census_csv_file, CSV_PATH) + _read_census_csv(tmp_census_csv_file, file) print("Removing extracted CSV File") os.remove(tmp_census_csv_file) diff --git a/tools/process_naics.py b/tools/process_naics.py index 8ac162ae..b179effc 100644 --- a/tools/process_naics.py +++ b/tools/process_naics.py @@ -23,14 +23,16 @@ TITLE_COL = config.NAICS_TITLE_COL #check for paths - if not os.path.isfile(EXCEL_PATH): + excel_file = os.path.join(ROOT_DIR, EXCEL_PATH) + csv_file = os.path.join(ROOT_DIR, CSV_PATH) + if not os.path.isfile(excel_file): error_msg = "Input excel file not existed" raise FileNotFoundError(error_msg) - if os.path.isfile(CSV_PATH): + if os.path.isfile(csv_file): error_msg = "Output csv file existed" raise FileExistsError(error_msg) - df = pd.read_excel(EXCEL_PATH, dtype=str, na_filter=False) + df = pd.read_excel(excel_file, dtype=str, na_filter=False) #add header result = [["code", "title"]] @@ -44,7 +46,7 @@ result.append(a_row) #output data to csv file - with open(CSV_PATH, 'w') as f: + with open(csv_file, 'w') as f: writer = csv.writer(f) writer.writerows(result) \ No newline at end of file From fdd781e3cf5d77c16736e3fa48ee56af17b5e3d4 Mon Sep 17 00:00:00 2001 From: Aldrian Harjati Date: Thu, 17 Aug 2023 15:34:51 -0400 Subject: [PATCH 2/4] add api endpoint tests --- .devcontainer/devcontainer.json | 3 + .devcontainer/requirements.txt | 3 +- .../invalid_phase1_sample_data.csv | 128 ++++++++++++++++++ .../invalid_phase2_sample_data.csv | 128 ++++++++++++++++++ .../sample_csv_files/valid_sample_data.csv | 128 ++++++++++++++++++ src/tests/test_api_functions.py | 121 +++++++++++++++++ src/tests/test_schema_functions.py | 54 ++++++-- 7 files changed, 555 insertions(+), 10 deletions(-) create mode 100644 src/tests/sample_csv_files/invalid_phase1_sample_data.csv create mode 100644 src/tests/sample_csv_files/invalid_phase2_sample_data.csv create mode 100644 src/tests/sample_csv_files/valid_sample_data.csv create mode 100644 src/tests/test_api_functions.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c107859a..ad833099 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -34,12 +34,15 @@ ], "editor.tabSize": 4, "editor.formatOnSave": true, + "python.formatting.provider": "black", + "python.formatting.black": true, "python.envFile": "${workspaceFolder}/.env", "editor.codeActionsOnSave": { "source.organizeImports": true }, "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, + "python.testing.nosetestsEnabled": false, "python.testing.pytestArgs": [ "--rootdir", "${workspaceFolder}/src/tests" diff --git a/.devcontainer/requirements.txt b/.devcontainer/requirements.txt index 3e3b1519..3389cb83 100644 --- a/.devcontainer/requirements.txt +++ b/.devcontainer/requirements.txt @@ -8,4 +8,5 @@ openpyxl pytest python-multipart uvicorn -fastapi \ No newline at end of file +fastapi +httpx \ No newline at end of file diff --git a/src/tests/sample_csv_files/invalid_phase1_sample_data.csv b/src/tests/sample_csv_files/invalid_phase1_sample_data.csv new file mode 100644 index 00000000..5107047f --- /dev/null +++ b/src/tests/sample_csv_files/invalid_phase1_sample_data.csv @@ -0,0 +1,128 @@ +uid,app_date,app_method,app_recipient,ct_credit_product,ct_credit_product_ff,ct_guarantee,ct_guarantee_ff,ct_loan_term_flag,ct_loan_term,credit_purpose,credit_purpose_ff,amount_applied_for_flag,amount_applied_for,amount_approved,action_taken,action_taken_date,denial_reasons,denial_reasons_ff,pricing_interest_rate_type,pricing_init_rate_period,pricing_fixed_rate,pricing_adj_margin,pricing_adj_index_name,pricing_adj_index_name_ff,pricing_adj_index_value,pricing_origination_charges,pricing_broker_fees,pricing_initial_charges,pricing_mca_addcost_flag,pricing_mca_addcost,pricing_prepenalty_allowed,pricing_prepenalty_exists,census_tract_adr_type,census_tract_number,gross_annual_revenue_flag,gross_annual_revenue,naics_code_flag,naics_code,number_of_workers,time_in_business_type,time_in_business,business_ownership_status,num_principal_owners_flag,num_principal_owners,po_1_ethnicity,po_1_ethnicity_ff,po_1_race,po_1_race_anai_ff,po_1_race_asian_ff,po_1_race_baa_ff,po_1_race_pi_ff,po_1_gender_flag,po_1_gender_ff,po_2_ethnicity,po_2_ethnicity_ff,po_2_race,po_2_race_anai_ff,po_2_race_asian_ff,po_2_race_baa_ff,po_2_race_pi_ff,po_2_gender_flag,po_2_gender_ff,po_3_ethnicity,po_3_ethnicity_ff,po_3_race,po_3_race_anai_ff,po_3_race_asian_ff,po_3_race_baa_ff,po_3_race_pi_ff,po_3_gender_flag,po_3_gender_ff,po_4_ethnicity,po_4_ethnicity_ff,po_4_race,po_4_race_anai_ff,po_4_race_asian_ff,po_4_race_baa_ff,po_4_race_pi_ff,po_4_gender_flag,po_4_gender_ff +000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID13XTC1,20241201,1,1,988,,999,,999,,999,,999,,,2,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID14XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID21XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC2,20241201,2,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC3,20241201,3,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC4,20241201,4,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID41XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID41XTC2,20241201,1,2,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC1,20241201,1,1,1,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC2,20241201,1,1,2,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC3,20241201,1,1,3,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC4,20241201,1,1,4,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC5,20241201,1,1,5,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC6,20241201,1,1,6,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC7,20241201,1,1,7,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC8,20241201,1,1,8,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC9,20241201,1,1,977,is not blank,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC10,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID61XTC1,20241201,1,1,977,less than 300 characters,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID62XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID62XTC2,20241201,1,1,977,is not blank,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC1,20241201,1,1,988,,1,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC2,20241201,1,1,988,,2,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC3,20241201,1,1,988,,3,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC4,20241201,1,1,988,,4,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC5,20241201,1,1,988,,5,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC6,20241201,1,1,988,,6,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC7,20241201,1,1,988,,7,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC8,20241201,1,1,988,,8,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC9,20241201,1,1,988,,9,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC10,20241201,1,1,988,,10,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC11,20241201,1,1,988,,11,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC12,20241201,1,1,988,,977,not blank,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC13,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID72XTC1,20241201,1,1,988,,1,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID73XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID74XTC1,20241201,1,1,988,,1;2;3;4;5,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID81XTC1,20241201,1,1,988,,977,less than 300 characters,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID82XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID82XTC2,20241201,1,1,988,,977,is not blank,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXBXVID83XTC2,20241201,1,1,988,,1;2;3;4;977,value1,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID91XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID91XTC2,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID91XTC3,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID92XTC1,20241201,1,1,1,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID92XTC2,20241201,1,1,2,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID92XTC3,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID101XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID102XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID103XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID103XTC2,20241201,1,1,1,,999,,900,1,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID104XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC1,20241201,1,1,988,,999,,999,,1,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC2,20241201,1,1,988,,999,,999,,2,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC3,20241201,1,1,988,,999,,999,,3,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC4,20241201,1,1,988,,999,,999,,4,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC5,20241201,1,1,988,,999,,999,,5,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC6,20241201,1,1,988,,999,,999,,6,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC7,20241201,1,1,988,,999,,999,,7,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC8,20241201,1,1,988,,999,,999,,8,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC9,20241201,1,1,988,,999,,999,,9,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC10,20241201,1,1,988,,999,,999,,10,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC11,20241201,1,1,988,,999,,999,,11,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC12,20241201,1,1,988,,999,,999,,977,not blank,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC13,20241201,1,1,988,,999,,999,,988,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC14,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID112XTC1,20241201,1,1,988,,999,,999,,1,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID112XTC2,20241201,1,1,988,,999,,999,,1;2;3,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID113XTC1,20241201,1,1,988,,999,,999,,988,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID113XTC2,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID114XTC1,20241201,1,1,988,,999,,999,,1;2;3,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID121XTC1,20241201,1,1,988,,999,,999,,977,less than 300 characters,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID122XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID122XTC2,20241201,1,1,988,,999,,999,,977,is not blank,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID123XTC1,20241201,1,1,988,,999,,999,,1;2;977,value1,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID131XTC1,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID131XTC2,20241201,1,1,988,,999,,999,,999,,988,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID131XTC3,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID141XTC1,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID142XTC1,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID143XTC1,20241201,1,1,988,,999,,999,,999,,988,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID143XTC2,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID151XTC1,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID152XTC1,20241201,1,1,988,,999,,999,,999,,999,,1,1,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC2,20241201,1,1,988,,999,,999,,999,,999,,,4,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC4,20241201,1,1,988,,999,,999,,999,,999,,7777,2,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC5,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC2,20241201,1,1,988,,999,,999,,999,,999,,,4,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC4,20241201,1,1,988,,999,,999,,999,,999,,7777,2,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC5,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID171XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID172XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID173XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID174XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241202,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC2,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,2,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,3,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC4,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,4,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC5,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,5,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC6,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,6,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC7,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,7,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC8,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,8,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC9,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,9,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC10,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,977,not blank,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC11,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC2,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC4,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3;4,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC2,20241201,1,1,988,,999,,999,,999,,999,,,4,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC3,20241201,1,1,988,,999,,999,,999,,999,,7777,2,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC4,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID184XTC1,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID185XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3;4,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID191XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,977,less than 300 characters,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID192XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID192XTC2,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,977,"is, not, blank",999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXBXVID193XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3;977,value1,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXBXVID201XTC1,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,1,,,1,1,,1,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/src/tests/sample_csv_files/invalid_phase2_sample_data.csv b/src/tests/sample_csv_files/invalid_phase2_sample_data.csv new file mode 100644 index 00000000..aa3620f9 --- /dev/null +++ b/src/tests/sample_csv_files/invalid_phase2_sample_data.csv @@ -0,0 +1,128 @@ +uid,app_date,app_method,app_recipient,ct_credit_product,ct_credit_product_ff,ct_guarantee,ct_guarantee_ff,ct_loan_term_flag,ct_loan_term,credit_purpose,credit_purpose_ff,amount_applied_for_flag,amount_applied_for,amount_approved,action_taken,action_taken_date,denial_reasons,denial_reasons_ff,pricing_interest_rate_type,pricing_init_rate_period,pricing_fixed_rate,pricing_adj_margin,pricing_adj_index_name,pricing_adj_index_name_ff,pricing_adj_index_value,pricing_origination_charges,pricing_broker_fees,pricing_initial_charges,pricing_mca_addcost_flag,pricing_mca_addcost,pricing_prepenalty_allowed,pricing_prepenalty_exists,census_tract_adr_type,census_tract_number,gross_annual_revenue_flag,gross_annual_revenue,naics_code_flag,naics_code,number_of_workers,time_in_business_type,time_in_business,business_ownership_status,num_principal_owners_flag,num_principal_owners,po_1_ethnicity,po_1_ethnicity_ff,po_1_race,po_1_race_anai_ff,po_1_race_asian_ff,po_1_race_baa_ff,po_1_race_pi_ff,po_1_gender_flag,po_1_gender_ff,po_2_ethnicity,po_2_ethnicity_ff,po_2_race,po_2_race_anai_ff,po_2_race_asian_ff,po_2_race_baa_ff,po_2_race_pi_ff,po_2_gender_flag,po_2_gender_ff,po_3_ethnicity,po_3_ethnicity_ff,po_3_race,po_3_race_anai_ff,po_3_race_asian_ff,po_3_race_baa_ff,po_3_race_pi_ff,po_3_gender_flag,po_3_gender_ff,po_4_ethnicity,po_4_ethnicity_ff,po_4_race,po_4_race_anai_ff,po_4_race_asian_ff,po_4_race_baa_ff,po_4_race_pi_ff,po_4_gender_flag,po_4_gender_ff +000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID12XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID13XTC1,20241201,1,1,988,,999,,999,,999,,999,,,2,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID14XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID21XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC2,20241201,2,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC3,20241201,3,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC4,20241201,4,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID41XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID41XTC2,20241201,1,2,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC1,20241201,1,1,1,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC2,20241201,1,1,2,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC3,20241201,1,1,3,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC4,20241201,1,1,4,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC5,20241201,1,1,5,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC6,20241201,1,1,6,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC7,20241201,1,1,7,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC8,20241201,1,1,8,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC9,20241201,1,1,977,is not blank,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC10,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID61XTC1,20241201,1,1,977,less than 300 characters,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID62XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID62XTC2,20241201,1,1,977,is not blank,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC1,20241201,1,1,988,,1,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC2,20241201,1,1,988,,2,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC3,20241201,1,1,988,,3,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC4,20241201,1,1,988,,4,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC5,20241201,1,1,988,,5,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC6,20241201,1,1,988,,6,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC7,20241201,1,1,988,,7,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC8,20241201,1,1,988,,8,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC9,20241201,1,1,988,,9,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC10,20241201,1,1,988,,10,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC11,20241201,1,1,988,,11,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC12,20241201,1,1,988,,977,not blank,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC13,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID72XTC1,20241201,1,1,988,,1,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID73XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID74XTC1,20241201,1,1,988,,1;2;3;4;5,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID81XTC1,20241201,1,1,988,,977,less than 300 characters,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID82XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID82XTC2,20241201,1,1,988,,977,is not blank,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXBXVID83XTC2,20241201,1,1,988,,1;2;3;4;977,value1,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID91XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID91XTC2,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID91XTC3,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID92XTC1,20241201,1,1,1,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID92XTC2,20241201,1,1,2,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID92XTC3,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID101XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID102XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID103XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID103XTC2,20241201,1,1,1,,999,,900,1,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID104XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC1,20241201,1,1,988,,999,,999,,1,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC2,20241201,1,1,988,,999,,999,,2,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC3,20241201,1,1,988,,999,,999,,3,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC4,20241201,1,1,988,,999,,999,,4,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC5,20241201,1,1,988,,999,,999,,5,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC6,20241201,1,1,988,,999,,999,,6,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC7,20241201,1,1,988,,999,,999,,7,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC8,20241201,1,1,988,,999,,999,,8,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC9,20241201,1,1,988,,999,,999,,9,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC10,20241201,1,1,988,,999,,999,,10,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC11,20241201,1,1,988,,999,,999,,11,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC12,20241201,1,1,988,,999,,999,,977,not blank,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC13,20241201,1,1,988,,999,,999,,988,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC14,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID112XTC1,20241201,1,1,988,,999,,999,,1,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID112XTC2,20241201,1,1,988,,999,,999,,1;2;3,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID113XTC1,20241201,1,1,988,,999,,999,,988,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID113XTC2,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID114XTC1,20241201,1,1,988,,999,,999,,1;2;3,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID121XTC1,20241201,1,1,988,,999,,999,,977,less than 300 characters,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID122XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID122XTC2,20241201,1,1,988,,999,,999,,977,is not blank,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID123XTC1,20241201,1,1,988,,999,,999,,1;2;977,value1,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID131XTC1,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID131XTC2,20241201,1,1,988,,999,,999,,999,,988,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID131XTC3,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID141XTC1,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID142XTC1,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID143XTC1,20241201,1,1,988,,999,,999,,999,,988,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID143XTC2,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID151XTC1,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID152XTC1,20241201,1,1,988,,999,,999,,999,,999,,1,1,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC2,20241201,1,1,988,,999,,999,,999,,999,,,4,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC4,20241201,1,1,988,,999,,999,,999,,999,,7777,2,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC5,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC2,20241201,1,1,988,,999,,999,,999,,999,,,4,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC4,20241201,1,1,988,,999,,999,,999,,999,,7777,2,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC5,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID171XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID172XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID173XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID174XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241202,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC2,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,2,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,3,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC4,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,4,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC5,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,5,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC6,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,6,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC7,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,7,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC8,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,8,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC9,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,9,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC10,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,977,not blank,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC11,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC2,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC4,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3;4,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC2,20241201,1,1,988,,999,,999,,999,,999,,,4,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC3,20241201,1,1,988,,999,,999,,999,,999,,7777,2,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC4,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID184XTC1,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID185XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3;4,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID191XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,977,less than 300 characters,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID192XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID192XTC2,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,977,"is, not, blank",999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXBXVID193XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3;977,value1,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXBXVID201XTC1,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,1,,,1,1,,1,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, \ No newline at end of file diff --git a/src/tests/sample_csv_files/valid_sample_data.csv b/src/tests/sample_csv_files/valid_sample_data.csv new file mode 100644 index 00000000..fc92f943 --- /dev/null +++ b/src/tests/sample_csv_files/valid_sample_data.csv @@ -0,0 +1,128 @@ +uid,app_date,app_method,app_recipient,ct_credit_product,ct_credit_product_ff,ct_guarantee,ct_guarantee_ff,ct_loan_term_flag,ct_loan_term,credit_purpose,credit_purpose_ff,amount_applied_for_flag,amount_applied_for,amount_approved,action_taken,action_taken_date,denial_reasons,denial_reasons_ff,pricing_interest_rate_type,pricing_init_rate_period,pricing_fixed_rate,pricing_adj_margin,pricing_adj_index_name,pricing_adj_index_name_ff,pricing_adj_index_value,pricing_origination_charges,pricing_broker_fees,pricing_initial_charges,pricing_mca_addcost_flag,pricing_mca_addcost,pricing_prepenalty_allowed,pricing_prepenalty_exists,census_tract_adr_type,census_tract_number,gross_annual_revenue_flag,gross_annual_revenue,naics_code_flag,naics_code,number_of_workers,time_in_business_type,time_in_business,business_ownership_status,num_principal_owners_flag,num_principal_owners,po_1_ethnicity,po_1_ethnicity_ff,po_1_race,po_1_race_anai_ff,po_1_race_asian_ff,po_1_race_baa_ff,po_1_race_pi_ff,po_1_gender_flag,po_1_gender_ff,po_2_ethnicity,po_2_ethnicity_ff,po_2_race,po_2_race_anai_ff,po_2_race_asian_ff,po_2_race_baa_ff,po_2_race_pi_ff,po_2_gender_flag,po_2_gender_ff,po_3_ethnicity,po_3_ethnicity_ff,po_3_race,po_3_race_anai_ff,po_3_race_asian_ff,po_3_race_baa_ff,po_3_race_pi_ff,po_3_gender_flag,po_3_gender_ff,po_4_ethnicity,po_4_ethnicity_ff,po_4_race,po_4_race_anai_ff,po_4_race_asian_ff,po_4_race_baa_ff,po_4_race_pi_ff,po_4_gender_flag,po_4_gender_ff +000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID12XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID13XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID14XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID21XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC2,20241201,2,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC3,20241201,3,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID31XTC4,20241201,4,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID41XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID41XTC2,20241201,1,2,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC1,20241201,1,1,1,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC2,20241201,1,1,2,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC3,20241201,1,1,3,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC4,20241201,1,1,4,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC5,20241201,1,1,5,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC6,20241201,1,1,6,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC7,20241201,1,1,7,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC8,20241201,1,1,8,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC9,20241201,1,1,977,is not blank,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID51XTC10,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID61XTC1,20241201,1,1,977,less than 300 characters,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID62XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID62XTC2,20241201,1,1,977,is not blank,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC1,20241201,1,1,988,,1,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC2,20241201,1,1,988,,2,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC3,20241201,1,1,988,,3,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC4,20241201,1,1,988,,4,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC5,20241201,1,1,988,,5,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC6,20241201,1,1,988,,6,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC7,20241201,1,1,988,,7,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC8,20241201,1,1,988,,8,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC9,20241201,1,1,988,,9,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC10,20241201,1,1,988,,10,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC11,20241201,1,1,988,,11,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC12,20241201,1,1,988,,977,not blank,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID71XTC13,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID72XTC1,20241201,1,1,988,,1,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID73XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID74XTC1,20241201,1,1,988,,1;2;3;4;5,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID81XTC1,20241201,1,1,988,,977,less than 300 characters,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID82XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID82XTC2,20241201,1,1,988,,977,is not blank,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXBXVID83XTC2,20241201,1,1,988,,1;2;3;4;977,value1,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID91XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID91XTC2,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID91XTC3,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID92XTC1,20241201,1,1,1,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID92XTC2,20241201,1,1,2,,999,,988,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID92XTC3,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID101XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID102XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID103XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID103XTC2,20241201,1,1,1,,999,,900,1,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID104XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC1,20241201,1,1,988,,999,,999,,1,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC2,20241201,1,1,988,,999,,999,,2,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC3,20241201,1,1,988,,999,,999,,3,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC4,20241201,1,1,988,,999,,999,,4,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC5,20241201,1,1,988,,999,,999,,5,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC6,20241201,1,1,988,,999,,999,,6,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC7,20241201,1,1,988,,999,,999,,7,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC8,20241201,1,1,988,,999,,999,,8,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC9,20241201,1,1,988,,999,,999,,9,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC10,20241201,1,1,988,,999,,999,,10,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC11,20241201,1,1,988,,999,,999,,11,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC12,20241201,1,1,988,,999,,999,,977,not blank,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC13,20241201,1,1,988,,999,,999,,988,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID111XTC14,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID112XTC1,20241201,1,1,988,,999,,999,,1,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID112XTC2,20241201,1,1,988,,999,,999,,1;2;3,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID113XTC1,20241201,1,1,988,,999,,999,,988,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID113XTC2,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID114XTC1,20241201,1,1,988,,999,,999,,1;2;3,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID121XTC1,20241201,1,1,988,,999,,999,,977,less than 300 characters,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID122XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID122XTC2,20241201,1,1,988,,999,,999,,977,is not blank,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID123XTC1,20241201,1,1,988,,999,,999,,1;2;977,value1,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID131XTC1,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID131XTC2,20241201,1,1,988,,999,,999,,999,,988,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID131XTC3,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID141XTC1,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID142XTC1,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID143XTC1,20241201,1,1,988,,999,,999,,999,,988,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID143XTC2,20241201,1,1,988,,999,,999,,999,,900,7777,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID151XTC1,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID152XTC1,20241201,1,1,988,,999,,999,,999,,999,,1,1,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC2,20241201,1,1,988,,999,,999,,999,,999,,,4,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC4,20241201,1,1,988,,999,,999,,999,,999,,7777,2,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID153XTC5,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC2,20241201,1,1,988,,999,,999,,999,,999,,,4,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC4,20241201,1,1,988,,999,,999,,999,,999,,7777,2,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID161XTC5,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID171XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID172XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID173XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID174XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241202,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC2,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,2,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,3,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC4,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,4,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC5,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,5,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC6,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,6,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC7,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,7,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC8,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,8,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC9,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,9,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC10,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,977,not blank,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID181XTC11,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC2,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC3,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID182XTC4,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3;4,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC2,20241201,1,1,988,,999,,999,,999,,999,,,4,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC3,20241201,1,1,988,,999,,999,,999,,999,,7777,2,20241231,999,,999,,,,999,,,0,0,0,999,,2,2,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID183XTC4,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID184XTC1,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,999,,,,999,,,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID185XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3;4,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID191XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,977,less than 300 characters,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID192XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXGXVID192XTC2,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,977,"is, not, blank",999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXBXVID193XTC1,20241201,1,1,988,,999,,999,,999,,999,,,3,20241231,1;2;3;977,value1,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +000TESTFIUIDDONOTUSEXBXVID201XTC1,20241201,1,1,988,,999,,999,,999,,999,,7777,1,20241231,999,,1,,,1,1,,1,0,0,0,999,,1,1,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/src/tests/test_api_functions.py b/src/tests/test_api_functions.py new file mode 100644 index 00000000..7cc5ff5b --- /dev/null +++ b/src/tests/test_api_functions.py @@ -0,0 +1,121 @@ +import os + +import pytest +from fastapi.testclient import TestClient +from test_schema_functions import TestUtil + +from api.main import app + + +class TestApiSchemas: + client = TestClient(app) + util = TestUtil() + CURR_DIR = os.path.dirname(os.path.abspath(__file__)) + + def test_valid_json_data(self): + res = self.client.post("/v1/validate", json=self.util.get_data()) + assert res.json()[0] == self.util.valid_response + assert res.status_code == 200 + + def test_invalid_json_data(self): + res = self.client.post( + "/v1/validate", json=self.util.get_data({"ct_credit_product": ["989"]}) + ) + assert ( + res.json()[0]["validation"]["id"] == "ct_credit_product.invalid_enum_value" + ) + assert res.status_code == 200 + + def test_multi_invalid_json_data(self): + res = self.client.post( + "/v1/validate", + json=self.util.get_data( + { + "ct_credit_product": ["989"], + "num_principal_owners": ["1"], + "action_taken": ["2"], + } + ), + ) + assert len(res.json()) == 1 + assert ( + res.json()[0]["validation"]["id"] == "ct_credit_product.invalid_enum_value" + ) + assert res.status_code == 200 + + def test_multi_invalid_twophase_json_data(self): + res = self.client.post( + "/v1/validate", + json=self.util.get_data( + { + "num_principal_owners": ["1"], + "action_taken": ["2"], + } + ), + ) + assert len(res.json()) == 3 + assert ( + res.json()[0]["validation"]["id"] + == "amount_approved.conditional_field_conflict" + ) + assert ( + res.json()[1]["validation"]["id"] + == "pricing_charges.conditional_fieldset_conflict" + ) + assert ( + res.json()[2]["validation"]["id"] + == "num_principal_owners.conditional_field_conflict" + ) + assert res.status_code == 200 + + def test_upload_valid_file(self): + print("CURR_DIR:", self.CURR_DIR) + valid_file_path = os.path.join( + self.CURR_DIR, "sample_csv_files/valid_sample_data.csv" + ) + # valid_file_path = "./sample_csv_files/valid_sample_data.csv" + if os.path.isfile(valid_file_path): + files = {"file": open(valid_file_path, "rb")} + res = self.client.post("/v1/upload", files=files) + assert res.json()[0] == self.util.valid_response + assert res.status_code == 200 + else: + pytest.fail(f"{valid_file_path} does not exist.") + + def test_upload_invalid_phase1_file(self): + invalid_file_path = os.path.join( + self.CURR_DIR, "sample_csv_files/invalid_phase1_sample_data.csv" + ) + # invalid_file_path = "./sample_csv_files/invalid_phase1_sample_data.csv" + if os.path.isfile(invalid_file_path): + files = {"file": open(invalid_file_path, "rb")} + res = self.client.post("/v1/upload", files=files) + assert len(res.json()) == 1 + assert res.json()[0]["validation"]["id"] == "uid.duplicates_in_dataset" + assert res.status_code == 200 + else: + pytest.fail(f"{invalid_file_path} does not exist.") + + def test_upload_invalid_phase2_file(self): + invalid_file_path = os.path.join( + self.CURR_DIR, "sample_csv_files/invalid_phase2_sample_data.csv" + ) + if os.path.isfile(invalid_file_path): + files = {"file": open(invalid_file_path, "rb")} + res = self.client.post("/v1/upload", files=files) + assert len(res.json()) == 3 + assert ( + res.json()[0]["validation"]["id"] + == "amount_approved.conditional_field_conflict" + ) + assert ( + res.json()[1]["validation"]["id"] + == "pricing_charges.conditional_fieldset_conflict" + ) + assert ( + res.json()[2]["validation"]["id"] + == "num_principal_owners.conditional_field_conflict" + ) + assert res.status_code == 200 + else: + pytest.fail(f"{invalid_file_path} does not exist.") diff --git a/src/tests/test_schema_functions.py b/src/tests/test_schema_functions.py index f021ba43..3ceb64dc 100644 --- a/src/tests/test_schema_functions.py +++ b/src/tests/test_schema_functions.py @@ -259,39 +259,75 @@ class TestValidateRawCsv: phase2_schema = schemas[1] def test_with_valid_data(self): - data = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + data = ( + "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999," + ",999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988," + ",988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + ) csv_bytes = self.util.get_csv_bytes(data) result = validate_raw_csv(self.phase1_schema, self.phase2_schema, csv_bytes) assert len(result) == 1 assert result[0] == self.util.valid_response def test_with_multi_valid_data(self): - data = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," - data2 = "000TESTFIUIDDONOTUSEXGXVID13XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + data = ( + "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999," + ",999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988," + ",988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + ) + data2 = ( + "000TESTFIUIDDONOTUSEXGXVID13XTC1,20241201,1,1,988,,999,,999," + ",999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988," + ",988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + ) csv_bytes = self.util.get_csv_bytes(data, data2) result = validate_raw_csv(self.phase1_schema, self.phase2_schema, csv_bytes) assert len(result) == 1 assert result[0] == self.util.valid_response def test_with_invalid_data(self): - data = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,989,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," - data2 = "000TESTFIUIDDONOTUSEXGXVID13XTC1,20241201,1,1,988,,990,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + data = ( + "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,989,,999,,999," + ",999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988," + ",988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + ) + data2 = ( + "000TESTFIUIDDONOTUSEXGXVID13XTC1,20241201,1,1,988,,990,,999," + ",999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988," + ",988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + ) csv_bytes = self.util.get_csv_bytes(data, data2) result = validate_raw_csv(self.phase1_schema, self.phase2_schema, csv_bytes) assert len(result) == 2 assert result[0] != self.util.valid_response def test_with_multi_invalid_data_with_phase1(self): - data = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,2,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," - data2 = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + data = ( + "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999," + ",999,,,2,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988," + ",988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + ) + data2 = ( + "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999," + ",999,,,5,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988," + ",988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + ) csv_bytes = self.util.get_csv_bytes(data, data2) result = validate_raw_csv(self.phase1_schema, self.phase2_schema, csv_bytes) assert len(result) == 1 assert result[0] != self.util.valid_response def test_with_multi_invalid_data_with_phase2(self): - data = "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999,,999,,,2,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," - data2 = "000TESTFIUIDDONOTUSEXGXVID12XTC1,20241201,1,1,988,,999,,999,,999,,999,,,1,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988,,988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + data = ( + "000TESTFIUIDDONOTUSEXGXVID11XTC1,20241201,1,1,988,,999,,999,,999," + ",999,,,2,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988," + ",988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + ) + data2 = ( + "000TESTFIUIDDONOTUSEXGXVID12XTC1,20241201,1,1,988,,999,,999,,999," + ",999,,,1,20241231,999,,999,,,,999,,,,,,999,,999,999,988,,988,,988," + ",988,988,,988,988,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," + ) csv_bytes = self.util.get_csv_bytes(data, data2) result = validate_raw_csv(self.phase1_schema, self.phase2_schema, csv_bytes) assert len(result) == 2 From 0f6b7b3b7cdd5cf0d4a7c3a3afab1cfe83db5d52 Mon Sep 17 00:00:00 2001 From: Aldrian Harjati Date: Thu, 17 Aug 2023 15:37:48 -0400 Subject: [PATCH 3/4] removed test file --- .devcontainer/devcontainer.json | 1 - src/api/routers/test_data.txt | 252 -------------------------------- 2 files changed, 253 deletions(-) delete mode 100644 src/api/routers/test_data.txt diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ad833099..560e2243 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -42,7 +42,6 @@ }, "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, - "python.testing.nosetestsEnabled": false, "python.testing.pytestArgs": [ "--rootdir", "${workspaceFolder}/src/tests" diff --git a/src/api/routers/test_data.txt b/src/api/routers/test_data.txt deleted file mode 100644 index 5f1fd2ad..00000000 --- a/src/api/routers/test_data.txt +++ /dev/null @@ -1,252 +0,0 @@ -{ -"uid" : ["000TESTFIUIDDONOTUSEXGXVID11XTC1"], -"app_date" : ["20241201"], -"app_method" : ["1" ] , -"app_recipient" : ["1" ] , -"ct_credit_product" : ["988" ] , -"ct_credit_product_ff" :[ "" ] , -"ct_guarantee" :[ "999"] , -"ct_guarantee_ff" : [""] , -"ct_loan_term_flag" : ["999" ] , -"ct_loan_term" :[ ""] , -"credit_purpose" :[ "999" ] , -"credit_purpose_ff" : [""] , -"amount_applied_for_flag" : ["999"] , -"amount_applied_for" : ["" ] , -"amount_approved" : ["" ] , -"action_taken" : ["5" ] , -"action_taken_date" : ["20241231" ] , -"denial_reasons" : ["999" ] , -"denial_reasons_ff" : ["" ] , -"pricing_interest_rate_type" : ["999"] , -"pricing_init_rate_period" :[ "" ] , -"pricing_fixed_rate" :[ "" ] , -"pricing_adj_margin" :[ "" ] , -"pricing_adj_index_name" :[ "999" ] , -"pricing_adj_index_name_ff" : ["" ] , -"pricing_adj_index_value" : ["" ] , -"pricing_origination_charges" : ["" ] , -"pricing_broker_fees" :[ "" ] , -"pricing_initial_charges" : [""] , -"pricing_mca_addcost_flag" : ["999" ] , -"pricing_mca_addcost" :[ "" ] , -"pricing_prepenalty_allowed" : ["999" ] , -"pricing_prepenalty_exists" : ["999" ] , -"census_tract_adr_type" : ["988" ] , -"census_tract_number" : ["" ] , -"gross_annual_revenue_flag" : ["988" ] , -"gross_annual_revenue" : ["" ] , -"naics_code_flag" : ["988" ] , -"naics_code" :[ "" ] , -"number_of_workers" : ["988" ] , -"time_in_business_type" : ["988" ] , -"time_in_business" :[ "" ] , -"business_ownership_status" : ["988" ] , -"num_principal_owners_flag" : ["988"] , -"num_principal_owners" : ["" ] , -"po_1_ethnicity" : ["" ] , -"po_1_ethnicity_ff" : [""] , -"po_1_race" : ["" ] , -"po_1_race_anai_ff" : ["" ] , -"po_1_race_asian_ff" : ["" ] , -"po_1_race_baa_ff" : ["" ], -"po_1_race_pi_ff" : ["" ], -"po_1_gender_flag" : ["" ] , -"po_1_gender_ff" : ["" ] , -"po_2_ethnicity" : [""] , -"po_2_ethnicity_ff" : ["" ] , -"po_2_race" : ["" ] , -"po_2_race_anai_ff" : ["" ] , -"po_2_race_asian_ff" : ["" ] , -"po_2_race_baa_ff" : [""] , -"po_2_race_pi_ff" : [""] , -"po_2_gender_flag" : ["" ] , -"po_2_gender_ff" : ["" ] , -"po_3_ethnicity" :[ "" ] , -"po_3_ethnicity_ff" : [""] , - "po_3_race" : [""] , - "po_3_race_anai_ff" : ["" ] , - "po_3_race_asian_ff" : ["" ] , - "po_3_race_baa_ff" :[ "" ] , - "po_3_race_pi_ff" : ["" ] , - "po_3_gender_flag" :[ "" ] , - "po_3_gender_ff" : ["" ] , - "po_4_ethnicity" :[ ""] , - "po_4_ethnicity_ff" : ["" ] , - "po_4_race" : [""] , - "po_4_race_anai_ff" : [""] , - "po_4_race_asian_ff" : ["" ] , - "po_4_race_baa_ff" :[ "" ] , - "po_4_race_pi_ff" : ["" ] , - "po_4_gender_flag" :[ "" ] , - "po_4_gender_ff": [""] - -} - - -{ -"uid" : "000TESTFIUIDDONOTUSEXGXVID11XTC1", -"app_date" : "20241201", -"app_method" : "1" , -"app_recipient" : "1" , -"ct_credit_product" : "988" , -"ct_credit_product_ff" : "" , -"ct_guarantee" : "999" , -"ct_guarantee_ff" : "" , -"ct_loan_term_flag" : "999" , -"ct_loan_term" : "" , -"credit_purpose" : "999" , -"credit_purpose_ff" : "" , -"amount_applied_for_flag" : "999" , -"amount_applied_for" : "" , -"amount_approved" : "" , -"action_taken" : "5" , -"action_taken_date" : "20241231" , -"denial_reasons" : "999" , -"denial_reasons_ff" : "" , -"pricing_interest_rate_type" : "999" , -"pricing_init_rate_period" : "" , -"pricing_fixed_rate" : "" , -"pricing_adj_margin" : "" , -"pricing_adj_index_name" : "999" , -"pricing_adj_index_name_ff" : "" , -"pricing_adj_index_value" : "" , -"pricing_origination_charges" : "" , -"pricing_broker_fees" : "" , -"pricing_initial_charges" : "" , -"pricing_mca_addcost_flag" : "999" , -"pricing_mca_addcost" : "" , -"pricing_prepenalty_allowed" : "999" , -"pricing_prepenalty_exists" : "999" , -"census_tract_adr_type" : "988" , -"census_tract_number" : "" , -"gross_annual_revenue_flag" : "988" , -"gross_annual_revenue" : "" , -"naics_code_flag" : "988" , -"naics_code" : "" , -"number_of_workers" : "988" , -"time_in_business_type" : "988" , -"time_in_business" : "" , -"business_ownership_status" : "988" , -"num_principal_owners_flag" : "988" , -"num_principal_owners" : "" , -"po_1_ethnicity" : "" , -"po_1_ethnicity_ff" : "" , -"po_1_race" : "" , -"po_1_race_anai_ff" : "" , -"po_1_race_asian_ff" : "" , -"po_1_race_baa_ff" : "" , -"po_1_race_pi_ff" : "" , -"po_1_gender_flag" : "" , -"po_1_gender_ff" : "" , -"po_2_ethnicity" : "" , -"po_2_ethnicity_ff" : "" , -"po_2_race" : "" , -"po_2_race_anai_ff" : "" , -"po_2_race_asian_ff" : "" , -"po_2_race_baa_ff" : "" , -"po_2_race_pi_ff" : "" , -"po_2_gender_flag" : "" , -"po_2_gender_ff" : "" , -"po_3_ethnicity" : "" , -"po_3_ethnicity_ff" : "" , - "po_3_race" : "" , - "po_3_race_anai_ff" : "" , - "po_3_race_asian_ff" : "" , - "po_3_race_baa_ff" : "" , - "po_3_race_pi_ff" : "" , - "po_3_gender_flag" : "" , - "po_3_gender_ff" : "" , - "po_4_ethnicity" : "" , - "po_4_ethnicity_ff" : "" , - "po_4_race" : "" , - "po_4_race_anai_ff" : "" , - "po_4_race_asian_ff" : "" , - "po_4_race_baa_ff" : "" , - "po_4_race_pi_ff" : "" , - "po_4_gender_flag" : "" , - "po_4_gender_ff": "" -} - - -"uid" -"app_date" -"app_method" -"app_recipient" -"ct_credit_product" -"ct_credit_product_ff" -"ct_guarantee" -"ct_guarantee_ff" -"ct_loan_term_flag" -"ct_loan_term" -"credit_purpose" -"credit_purpose_ff" -"amount_applied_for_flag" -"amount_applied_for" -"amount_approved" -"action_taken" -"action_taken_date" -"denial_reasons" -"denial_reasons_ff" -"pricing_interest_rate_type" -"pricing_init_rate_period" -"pricing_fixed_rate" -"pricing_adj_margin" -"pricing_adj_index_name" -"pricing_adj_index_name_ff" -"pricing_adj_index_value" -"pricing_origination_charges" -"pricing_broker_fees" -"pricing_initial_charges" -"pricing_mca_addcost_flag" -"pricing_mca_addcost" -"pricing_prepenalty_allowed" -"pricing_prepenalty_exists" -"census_tract_adr_type" -"census_tract_number" -"gross_annual_revenue_flag" -"gross_annual_revenue" -"naics_code_flag" -"naics_code" -"number_of_workers" -"time_in_business_type" -"time_in_business" -"business_ownership_status" -"num_principal_owners_flag" -"num_principal_owners" -"po_1_ethnicity" -"po_1_ethnicity_ff" -"po_1_race" -"po_1_race_anai_ff" -"po_1_race_asian_ff" -"po_1_race_baa_ff" -"po_1_race_pi_ff" -"po_1_gender_flag" -"po_1_gender_ff" -"po_2_ethnicity" -"po_2_ethnicity_ff" -"po_2_race" -"po_2_race_anai_ff" -"po_2_race_asian_ff" -"po_2_race_baa_ff" -"po_2_race_pi_ff" -"po_2_gender_flag" -"po_2_gender_ff" -"po_3_ethnicity" -"po_3_ethnicity_ff" - "po_3_race" - "po_3_race_anai_ff" - "po_3_race_asian_ff" - "po_3_race_baa_ff" - "po_3_race_pi_ff" - "po_3_gender_flag" - "po_3_gender_ff" - "po_4_ethnicity" - "po_4_ethnicity_ff" - "po_4_race" - "po_4_race_anai_ff" - "po_4_race_asian_ff" - "po_4_race_baa_ff" - "po_4_race_pi_ff" - "po_4_gender_flag" - "po_4_gender_ff" \ No newline at end of file From 38fab3b96bd50a7accb2261e2ba09df2ad7d1693 Mon Sep 17 00:00:00 2001 From: Aldrian Harjati Date: Thu, 17 Aug 2023 16:18:20 -0400 Subject: [PATCH 4/4] fixed default error code --- src/api/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/main.py b/src/api/main.py index 52fb6f8d..4eee50bf 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -26,7 +26,7 @@ async def api_exception_handler(request: Request, err: Exception): Returns: returns JSON Response with status code and message """ - error_code = (400,) + error_code = 400 error_msg = str(err) if type(err) is ValueError: error_code = 422