From a5d71331154b9f4601a4e704c2a2dc9fcb022e1a Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 8 Mar 2024 15:10:38 -0500 Subject: [PATCH 01/24] add cat4 aabd_and_ssi validator --- .../parsers/case_consistency_validator.py | 93 ++- tdrs-backend/tdpservice/parsers/parse.py | 6 +- .../tdpservice/parsers/test/factories.py | 4 +- .../parsers/test/test_case_consistency.py | 538 +++++++++++++++++- 4 files changed, 607 insertions(+), 34 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 20c07d379..ef1310643 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -1,7 +1,9 @@ """Class definition for Category Four validator.""" +from datetime import datetime from .models import ParserErrorCategoryChoices from .util import get_rpt_month_year_list +from tdpservice.stts.models import STT from tdpservice.parsers.schema_defs.utils import get_program_model import logging @@ -44,7 +46,7 @@ def __add_record_to_sorted_object(self, record_schema_pair): class CaseConsistencyValidator: """Caches records of the same case to perform category four validation while actively parsing.""" - def __init__(self, header, generate_error): + def __init__(self, header, stt_type, generate_error): self.header = header self.record_schema_pairs = SortedRecordSchemaPairs() self.current_case = None @@ -57,6 +59,7 @@ def __init__(self, header, generate_error): self.generated_errors = [] self.total_cases_cached = 0 self.total_cases_validated = 0 + self.stt_type = stt_type def __get_model(self, model_str): """Return a model for the current program type/section given the model's string name.""" @@ -131,6 +134,7 @@ def __validate_section2(self, num_errors): """Perform TANF Section 2 category four validation on all cached records.""" num_errors += self.__validate_header_with_records() num_errors += self.__validate_s2_records_are_related() + num_errors += self.__validate_t5_aabd_and_ssi() return num_errors def __validate_header_with_records(self): @@ -282,21 +286,21 @@ def __validate_s2_records_are_related(self): t5_model_name = 'M5' if is_ssp else 'T5' t5_model = self.__get_model(t5_model_name) - logger.debug('validating records are related') - logger.debug(f'program type: {self.program_type}') - logger.debug(f'section: {self.section}') - logger.debug(f'is_ssp: {is_ssp}') - logger.debug(f'models - T4: {t4_model}; T5: {t5_model};') + # logger.debug('validating records are related') + # logger.debug(f'program type: {self.program_type}') + # logger.debug(f'section: {self.section}') + # logger.debug(f'is_ssp: {is_ssp}') + # logger.debug(f'models - T4: {t4_model}; T5: {t5_model};') cases = self.record_schema_pairs.sorted_cases - logger.debug(f'cases obj: {cases}') + # logger.debug(f'cases obj: {cases}') for rpt_month_year, reporting_year_cases in cases.items(): t4s = reporting_year_cases.get(t4_model, []) t5s = reporting_year_cases.get(t5_model, []) - logger.debug(f't4s: {t4s}') - logger.debug(f't5s: {t5s}') + # logger.debug(f't4s: {t4s}') + # logger.debug(f't5s: {t5s}') if len(t4s) > 0: if len(t5s) == 0: @@ -330,3 +334,74 @@ def __validate_s2_records_are_related(self): num_errors += 1 return num_errors + + def __validate_t5_aabd_and_ssi(self): + print('validate t5') + num_errors = 0 + is_ssp = self.program_type == 'SSP' + + t5_model_name = 'M5' if is_ssp else 'T5' + t5_model = self.__get_model(t5_model_name) + + is_state = self.stt_type == STT.EntityType.STATE + is_territory = self.stt_type == STT.EntityType.TERRITORY + + for rpt_month_year, reporting_year_cases in self.record_schema_pairs.sorted_cases.items(): + t5s = reporting_year_cases.get(t5_model, []) + + for record, schema in t5s: + rec_aabd = getattr(record, 'REC_AID_TOTALLY_DISABLED') + rec_ssi = getattr(record, 'REC_SSI') + family_affiliation = getattr(record, 'FAMILY_AFFILIATION') + dob = getattr(record, 'DATE_OF_BIRTH') + + rpt_month_year_dd = f'{rpt_month_year}01' + rpt_date = datetime.strptime(rpt_month_year_dd, '%Y%m%d') + dob_date = datetime.strptime(dob, '%Y%m%d') + delta = rpt_date - dob_date + age = delta.days/365.25 + is_adult = age >= 18 + + if is_territory and is_adult and rec_aabd != 1: + self.__generate_and_add_error( + schema, + record, + field='REC_AID_TOTALLY_DISABLED', + msg=( + f'{t5_model_name} Adults in territories must have a valid value for 19C.' + ) + ) + num_errors += 1 + elif is_state and rec_aabd != 2: + self.__generate_and_add_error( + schema, + record, + field='REC_AID_TOTALLY_DISABLED', + msg=( + f'{t5_model_name} People in states shouldn\'t have a value of 1.' + ) + ) + num_errors += 1 + + if is_territory and rec_ssi != 2: + self.__generate_and_add_error( + schema, + record, + field='REC_SSI', + msg=( + f'{t5_model_name} People in territories must have a valid value for 19E.' + ) + ) + num_errors += 1 + elif is_state and family_affiliation == 1 and rec_ssi != 1: + self.__generate_and_add_error( + schema, + record, + field='REC_SSI', + msg=( + f'{t5_model_name} People in states must have a valid value.' + ) + ) + num_errors += 1 + + return num_errors diff --git a/tdrs-backend/tdpservice/parsers/parse.py b/tdrs-backend/tdpservice/parsers/parse.py index 5370ae60a..34ccb42f5 100644 --- a/tdrs-backend/tdpservice/parsers/parse.py +++ b/tdrs-backend/tdpservice/parsers/parse.py @@ -31,7 +31,11 @@ def parse_datafile(datafile): return errors # TODO: write a test for this line - case_consistency_validator = CaseConsistencyValidator(header, util.make_generate_parser_error(datafile, -1)) + case_consistency_validator = CaseConsistencyValidator( + header, + datafile.stt.type, + util.make_generate_parser_error(datafile, -1) + ) field_values = schema_defs.header.get_field_values_by_names(header_line, {"encryption", "tribe_code", "state_fips"}) diff --git a/tdrs-backend/tdpservice/parsers/test/factories.py b/tdrs-backend/tdpservice/parsers/test/factories.py index bf20bbefe..173c92970 100644 --- a/tdrs-backend/tdpservice/parsers/test/factories.py +++ b/tdrs-backend/tdpservice/parsers/test/factories.py @@ -284,7 +284,7 @@ class Meta: RPT_MONTH_YEAR = 202301 CASE_NUMBER = "1" FAMILY_AFFILIATION = 1 - DATE_OF_BIRTH = "02091997" + DATE_OF_BIRTH = "19970209" SSN = "123456789" RACE_HISPANIC = 1 RACE_AMER_INDIAN = 1 @@ -530,7 +530,7 @@ class Meta: RPT_MONTH_YEAR = 202301 CASE_NUMBER = "1" FAMILY_AFFILIATION = 1 - DATE_OF_BIRTH = "02091997" + DATE_OF_BIRTH = "19970209" SSN = "123456789" RACE_HISPANIC = 1 RACE_AMER_INDIAN = 1 diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index 4c88239b6..bed144e7b 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -6,6 +6,7 @@ from .. import schema_defs, util from ..case_consistency_validator import CaseConsistencyValidator from tdpservice.parsers.models import ParserErrorCategoryChoices +from tdpservice.stts.models import STT logger = logging.getLogger(__name__) @@ -61,8 +62,11 @@ def small_correct_file_header(self, small_correct_file): @pytest.mark.django_db def test_add_record(self, small_correct_file_header, small_correct_file, tanf_s1_records, tanf_s1_schemas): """Test add_record logic.""" - case_consistency_validator = CaseConsistencyValidator(small_correct_file_header, - util.make_generate_parser_error(small_correct_file, None)) + case_consistency_validator = CaseConsistencyValidator( + small_correct_file_header, + STT.EntityType.STATE, + util.make_generate_parser_error(small_correct_file, None) + ) for record, schema in zip(tanf_s1_records, tanf_s1_schemas): case_consistency_validator.add_record(record, schema, True) @@ -103,8 +107,11 @@ def test_add_record(self, small_correct_file_header, small_correct_file, tanf_s1 @pytest.mark.django_db def test_section1_fail(self, small_correct_file_header, small_correct_file, tanf_s1_records, tanf_s1_schemas): """Test TANF Section 1 records RPT_MONTH_YEAR don't align with header year and quarter.""" - case_consistency_validator = CaseConsistencyValidator(small_correct_file_header, - util.make_generate_parser_error(small_correct_file, None)) + case_consistency_validator = CaseConsistencyValidator( + small_correct_file_header, + STT.EntityType.STATE, + util.make_generate_parser_error(small_correct_file, None) + ) for record, schema in zip(tanf_s1_records, tanf_s1_schemas): case_consistency_validator.add_record(record, schema, False) @@ -116,8 +123,11 @@ def test_section1_fail(self, small_correct_file_header, small_correct_file, tanf @pytest.mark.django_db def test_section1_pass(self, small_correct_file_header, small_correct_file, tanf_s1_records, tanf_s1_schemas): """Test TANF Section 1 records RPT_MONTH_YEAR do align with header year and quarter.""" - case_consistency_validator = CaseConsistencyValidator(small_correct_file_header, - util.make_generate_parser_error(small_correct_file, None)) + case_consistency_validator = CaseConsistencyValidator( + small_correct_file_header, + STT.EntityType.STATE, + util.make_generate_parser_error(small_correct_file, None) + ) for record, schema in zip(tanf_s1_records, tanf_s1_schemas): record.RPT_MONTH_YEAR = 202010 @@ -127,28 +137,31 @@ def test_section1_pass(self, small_correct_file_header, small_correct_file, tanf assert 0 == num_errors - @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff", [ + @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff,stt_type", [ ( {"type": "A", "program_type": "TAN", "year": 2020, "quarter": "4"}, (factories.TanfT1Factory, schema_defs.tanf.t1.schemas[0], 'T1'), (factories.TanfT2Factory, schema_defs.tanf.t2.schemas[0], 'T2'), (factories.TanfT3Factory, schema_defs.tanf.t3.schemas[0], 'T3'), + STT.EntityType.STATE, ), ( {"type": "A", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, (factories.TribalTanfT1Factory, schema_defs.tribal_tanf.t1.schemas[0], 'T1'), (factories.TribalTanfT2Factory, schema_defs.tribal_tanf.t2.schemas[0], 'T2'), (factories.TribalTanfT3Factory, schema_defs.tribal_tanf.t3.schemas[0], 'T3'), + STT.EntityType.TRIBE, ), ( {"type": "A", "program_type": "SSP", "year": 2020, "quarter": "4"}, (factories.SSPM1Factory, schema_defs.ssp.m1.schemas[0], 'M1'), (factories.SSPM2Factory, schema_defs.ssp.m2.schemas[0], 'M2'), (factories.SSPM3Factory, schema_defs.ssp.m3.schemas[0], 'M3'), + STT.EntityType.STATE, ), ]) @pytest.mark.django_db - def test_records_are_related_section1_validator_pass(self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff): + def test_section1_records_are_related_validator_pass(self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff, stt_type): """Test records are related validator success case.""" (T1Factory, t1_schema, t1_model_name) = T1Stuff (T2Factory, t2_schema, t2_model_name) = T2Stuff @@ -156,6 +169,7 @@ def test_records_are_related_section1_validator_pass(self, small_correct_file, h case_consistency_validator = CaseConsistencyValidator( header, + stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -209,29 +223,32 @@ def test_records_are_related_section1_validator_pass(self, small_correct_file, h assert len(errors) == 0 assert num_errors == 0 - @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff", [ + @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff,stt_type", [ ( {"type": "A", "program_type": "TAN", "year": 2020, "quarter": "4"}, (factories.TanfT1Factory, schema_defs.tanf.t1.schemas[0], 'T1'), (factories.TanfT2Factory, schema_defs.tanf.t2.schemas[0], 'T2'), (factories.TanfT3Factory, schema_defs.tanf.t3.schemas[0], 'T3'), + STT.EntityType.STATE, ), ( {"type": "A", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, (factories.TribalTanfT1Factory, schema_defs.tribal_tanf.t1.schemas[0], 'T1'), (factories.TribalTanfT2Factory, schema_defs.tribal_tanf.t2.schemas[0], 'T2'), (factories.TribalTanfT3Factory, schema_defs.tribal_tanf.t3.schemas[0], 'T3'), + STT.EntityType.TRIBE, ), ( {"type": "A", "program_type": "SSP", "year": 2020, "quarter": "4"}, (factories.SSPM1Factory, schema_defs.ssp.m1.schemas[0], 'M1'), (factories.SSPM2Factory, schema_defs.ssp.m2.schemas[0], 'M2'), (factories.SSPM3Factory, schema_defs.ssp.m3.schemas[0], 'M3'), + STT.EntityType.STATE, ), ]) @pytest.mark.django_db - def test_records_are_related_s1_validator_fail_no_t2_or_t3( - self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff): + def test_section1_records_are_related_validator_fail_no_t2_or_t3( + self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff, stt_type): """Test records are related validator fails with no t2s or t3s.""" (T1Factory, t1_schema, t1_model_name) = T1Stuff (T2Factory, t2_schema, t2_model_name) = T2Stuff @@ -239,6 +256,7 @@ def test_records_are_related_s1_validator_fail_no_t2_or_t3( case_consistency_validator = CaseConsistencyValidator( header, + stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -272,28 +290,31 @@ def test_records_are_related_s1_validator_fail_no_t2_or_t3( f'{t2_model_name} or {t3_model_name} record with the same RPT_MONTH_YEAR and CASE_NUMBER.' ) - @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff", [ + @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff,stt_type", [ ( {"type": "A", "program_type": "TAN", "year": 2020, "quarter": "4"}, (factories.TanfT1Factory, schema_defs.tanf.t1.schemas[0], 'T1'), (factories.TanfT2Factory, schema_defs.tanf.t2.schemas[0], 'T2'), (factories.TanfT3Factory, schema_defs.tanf.t3.schemas[0], 'T3'), + STT.EntityType.STATE, ), ( {"type": "A", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, (factories.TribalTanfT1Factory, schema_defs.tribal_tanf.t1.schemas[0], 'T1'), (factories.TribalTanfT2Factory, schema_defs.tribal_tanf.t2.schemas[0], 'T2'), (factories.TribalTanfT3Factory, schema_defs.tribal_tanf.t3.schemas[0], 'T3'), + STT.EntityType.TRIBE, ), ( {"type": "A", "program_type": "SSP", "year": 2020, "quarter": "4"}, (factories.SSPM1Factory, schema_defs.ssp.m1.schemas[0], 'M1'), (factories.SSPM2Factory, schema_defs.ssp.m2.schemas[0], 'M2'), (factories.SSPM3Factory, schema_defs.ssp.m3.schemas[0], 'M3'), + STT.EntityType.STATE, ), ]) @pytest.mark.django_db - def test_records_are_related_s1_validator_fail_no_t1(self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff): + def test_section1_records_are_related_validator_fail_no_t1(self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff, stt_type): """Test records are related validator fails with no t1s.""" (T1Factory, t1_schema, t1_model_name) = T1Stuff (T2Factory, t2_schema, t2_model_name) = T2Stuff @@ -301,6 +322,7 @@ def test_records_are_related_s1_validator_fail_no_t1(self, small_correct_file, h case_consistency_validator = CaseConsistencyValidator( header, + stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -361,29 +383,32 @@ def test_records_are_related_s1_validator_fail_no_t1(self, small_correct_file, h f'{t1_model_name} record with the same RPT_MONTH_YEAR and CASE_NUMBER.' ) - @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff", [ + @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff,stt_type", [ ( {"type": "A", "program_type": "TAN", "year": 2020, "quarter": "4"}, (factories.TanfT1Factory, schema_defs.tanf.t1.schemas[0], 'T1'), (factories.TanfT2Factory, schema_defs.tanf.t2.schemas[0], 'T2'), (factories.TanfT3Factory, schema_defs.tanf.t3.schemas[0], 'T3'), + STT.EntityType.STATE, ), ( {"type": "A", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, (factories.TribalTanfT1Factory, schema_defs.tribal_tanf.t1.schemas[0], 'T1'), (factories.TribalTanfT2Factory, schema_defs.tribal_tanf.t2.schemas[0], 'T2'), (factories.TribalTanfT3Factory, schema_defs.tribal_tanf.t3.schemas[0], 'T3'), + STT.EntityType.TRIBE, ), ( {"type": "A", "program_type": "SSP", "year": 2020, "quarter": "4"}, (factories.SSPM1Factory, schema_defs.ssp.m1.schemas[0], 'M1'), (factories.SSPM2Factory, schema_defs.ssp.m2.schemas[0], 'M2'), (factories.SSPM3Factory, schema_defs.ssp.m3.schemas[0], 'M3'), + STT.EntityType.STATE, ), ]) @pytest.mark.django_db - def test_records_are_related_validator_s1_fail_no_family_affiliation( - self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff): + def test_section1_records_are_related_validator_fail_no_family_affiliation( + self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff, stt_type): """Test records are related validator fails when no t2 or t3 has family_affiliation == 1.""" (T1Factory, t1_schema, t1_model_name) = T1Stuff (T2Factory, t2_schema, t2_model_name) = T2Stuff @@ -391,6 +416,7 @@ def test_records_are_related_validator_s1_fail_no_family_affiliation( case_consistency_validator = CaseConsistencyValidator( header, + stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -456,31 +482,35 @@ def test_records_are_related_validator_s1_fail_no_family_affiliation( f'CASE_NUMBER, where FAMILY_AFFILIATION==1' ) - @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ + @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [ ( {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + STT.EntityType.STATE, ), ( {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + STT.EntityType.TRIBE, ), ( {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + STT.EntityType.STATE, ), ]) @pytest.mark.django_db - def test_records_are_related_section2_validator_pass(self, small_correct_file, header, T4Stuff, T5Stuff): + def test_section2_validator_pass(self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): """Test records are related validator section 2 success case.""" (T4Factory, t4_schema, t4_model_name) = T4Stuff (T5Factory, t5_schema, t5_model_name) = T5Stuff case_consistency_validator = CaseConsistencyValidator( header, + stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -502,11 +532,15 @@ def test_records_are_related_section2_validator_pass(self, small_correct_file, h RPT_MONTH_YEAR=202010, CASE_NUMBER='123', FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=1 ), T5Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', FAMILY_AFFILIATION=2, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=1 ), ] for t5 in t5s: @@ -519,31 +553,35 @@ def test_records_are_related_section2_validator_pass(self, small_correct_file, h assert len(errors) == 0 assert num_errors == 0 - @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ + @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [ ( {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + STT.EntityType.STATE, ), ( {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + STT.EntityType.TRIBE, ), ( {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + STT.EntityType.STATE, ), ]) @pytest.mark.django_db - def test_records_are_related_section2_validator_fail_no_t5s(self, small_correct_file, header, T4Stuff, T5Stuff): + def test_section2_records_are_related_validator_fail_no_t5s(self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): """Test records are related validator fails with no t5s.""" (T4Factory, t4_schema, t4_model_name) = T4Stuff (T5Factory, t5_schema, t5_model_name) = T5Stuff case_consistency_validator = CaseConsistencyValidator( header, + stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -577,31 +615,35 @@ def test_records_are_related_section2_validator_fail_no_t5s(self, small_correct_ f'{t5_model_name} record with the same RPT_MONTH_YEAR and CASE_NUMBER.' ) - @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ + @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [ ( {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + STT.EntityType.STATE, ), ( {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + STT.EntityType.TRIBE, ), ( {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + STT.EntityType.STATE, ), ]) @pytest.mark.django_db - def test_records_are_related_section2_validator_fail_no_t4s(self, small_correct_file, header, T4Stuff, T5Stuff): + def test_section2_records_are_related_validator_fail_no_t4s(self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): """Test records are related validator fails with no t4s.""" (T4Factory, t4_schema, t4_model_name) = T4Stuff (T5Factory, t5_schema, t5_model_name) = T5Stuff case_consistency_validator = CaseConsistencyValidator( header, + stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -610,11 +652,15 @@ def test_records_are_related_section2_validator_fail_no_t4s(self, small_correct_ RPT_MONTH_YEAR=202010, CASE_NUMBER='123', FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=1 ), T5Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', FAMILY_AFFILIATION=2, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=1 ), ] for t5 in t5s: @@ -636,3 +682,451 @@ def test_records_are_related_section2_validator_fail_no_t4s(self, small_correct_ f'Every {t5_model_name} record should have at least one corresponding ' f'{t4_model_name} record with the same RPT_MONTH_YEAR and CASE_NUMBER.' ) + + @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ + ( + {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, + (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), + (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, + (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), + (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, + (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), + (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + ), + ]) + @pytest.mark.django_db + def test_section2_aabd_ssi_validator_pass_territory_adult_aadb(self, small_correct_file, header, T4Stuff, T5Stuff): + """Test records are related validator section 2 success case.""" + (T4Factory, t4_schema, t4_model_name) = T4Stuff + (T5Factory, t5_schema, t5_model_name) = T5Stuff + + case_consistency_validator = CaseConsistencyValidator( + header, + STT.EntityType.TERRITORY, + util.make_generate_parser_error(small_correct_file, None) + ) + + t4s = [ + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + ), + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123' + ), + ] + for t4 in t4s: + case_consistency_validator.add_record(t4, t4_schema, False) + + t5s = [ + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="19970209", + FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=1, + REC_SSI=2 + ), + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="19970209", + FAMILY_AFFILIATION=2, + REC_AID_TOTALLY_DISABLED=1, + REC_SSI=2 + ), + ] + for t5 in t5s: + case_consistency_validator.add_record(t5, t5_schema, False) + + num_errors = case_consistency_validator.validate() + + errors = case_consistency_validator.get_generated_errors() + + assert len(errors) == 0 + assert num_errors == 0 + + @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ + ( + {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, + (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), + (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, + (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), + (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, + (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), + (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + ), + ]) + @pytest.mark.django_db + def test_section2_aabd_ssi_validator_fail_territory_adult_aabd(self, small_correct_file, header, T4Stuff, T5Stuff): + """Test records are related validator section 2 success case.""" + (T4Factory, t4_schema, t4_model_name) = T4Stuff + (T5Factory, t5_schema, t5_model_name) = T5Stuff + + case_consistency_validator = CaseConsistencyValidator( + header, + STT.EntityType.TERRITORY, + util.make_generate_parser_error(small_correct_file, None) + ) + + t4s = [ + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + ), + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123' + ), + ] + for t4 in t4s: + case_consistency_validator.add_record(t4, t4_schema, False) + + t5s = [ + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="19970209", + FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=2 + ), + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="19970209", + FAMILY_AFFILIATION=2, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=2 + ), + ] + for t5 in t5s: + case_consistency_validator.add_record(t5, t5_schema, False) + + num_errors = case_consistency_validator.validate() + + errors = case_consistency_validator.get_generated_errors() + + assert len(errors) == 2 + assert num_errors == 2 + assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[0].error_message == ( + f'{t5_model_name} Adults in territories must have a valid value for 19C.' + ) + assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[1].error_message == ( + f'{t5_model_name} Adults in territories must have a valid value for 19C.' + ) + + @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ + ( + {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, + (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), + (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, + (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), + (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, + (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), + (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + ), + ]) + @pytest.mark.django_db + def test_section2_aabd_ssi_validator_pass_territory_child_aabd(self, small_correct_file, header, T4Stuff, T5Stuff): + """Test records are related validator section 2 success case.""" + (T4Factory, t4_schema, t4_model_name) = T4Stuff + (T5Factory, t5_schema, t5_model_name) = T5Stuff + + case_consistency_validator = CaseConsistencyValidator( + header, + STT.EntityType.TERRITORY, + util.make_generate_parser_error(small_correct_file, None) + ) + + t4s = [ + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + ), + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123' + ), + ] + for t4 in t4s: + case_consistency_validator.add_record(t4, t4_schema, False) + + t5s = [ + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="20170209", + FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=2 + ), + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="20170209", + FAMILY_AFFILIATION=2, + REC_AID_TOTALLY_DISABLED=1, + REC_SSI=2 + ), + ] + for t5 in t5s: + case_consistency_validator.add_record(t5, t5_schema, False) + + num_errors = case_consistency_validator.validate() + + errors = case_consistency_validator.get_generated_errors() + + assert len(errors) == 0 + assert num_errors == 0 + + @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ + ( + {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, + (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), + (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, + (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), + (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, + (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), + (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + ), + ]) + @pytest.mark.django_db + def test_section2_aabd_ssi_validator_fail_state_aabd(self, small_correct_file, header, T4Stuff, T5Stuff): + """Test records are related validator section 2 success case.""" + (T4Factory, t4_schema, t4_model_name) = T4Stuff + (T5Factory, t5_schema, t5_model_name) = T5Stuff + + case_consistency_validator = CaseConsistencyValidator( + header, + STT.EntityType.STATE, + util.make_generate_parser_error(small_correct_file, None) + ) + + t4s = [ + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + ), + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123' + ), + ] + for t4 in t4s: + case_consistency_validator.add_record(t4, t4_schema, False) + + t5s = [ + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="19970209", + FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=1, + REC_SSI=1 + ), + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="20170209", + FAMILY_AFFILIATION=2, + REC_AID_TOTALLY_DISABLED=1, + REC_SSI=1 + ), + ] + for t5 in t5s: + case_consistency_validator.add_record(t5, t5_schema, False) + + num_errors = case_consistency_validator.validate() + + errors = case_consistency_validator.get_generated_errors() + + assert len(errors) == 2 + assert num_errors == 2 + assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[0].error_message == ( + f'{t5_model_name} People in states shouldn\'t have a value of 1.' + ) + assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[1].error_message == ( + f'{t5_model_name} People in states shouldn\'t have a value of 1.' + ) + + @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ + ( + {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, + (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), + (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, + (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), + (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, + (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), + (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + ), + ]) + @pytest.mark.django_db + def test_section2_aabd_ssi_validator_fail_territory_ssi(self, small_correct_file, header, T4Stuff, T5Stuff): + """Test records are related validator section 2 success case.""" + (T4Factory, t4_schema, t4_model_name) = T4Stuff + (T5Factory, t5_schema, t5_model_name) = T5Stuff + + case_consistency_validator = CaseConsistencyValidator( + header, + STT.EntityType.TERRITORY, + util.make_generate_parser_error(small_correct_file, None) + ) + + t4s = [ + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + ), + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123' + ), + ] + for t4 in t4s: + case_consistency_validator.add_record(t4, t4_schema, False) + + t5s = [ + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="19970209", + FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=1, + REC_SSI=1 + ), + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="19970209", + FAMILY_AFFILIATION=2, + REC_AID_TOTALLY_DISABLED=1, + REC_SSI=1 + ), + ] + for t5 in t5s: + case_consistency_validator.add_record(t5, t5_schema, False) + + num_errors = case_consistency_validator.validate() + + errors = case_consistency_validator.get_generated_errors() + + assert len(errors) == 2 + assert num_errors == 2 + assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[0].error_message == ( + f'{t5_model_name} People in territories must have a valid value for 19E.' + ) + assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[1].error_message == ( + f'{t5_model_name} People in territories must have a valid value for 19E.' + ) + + @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ + ( + {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, + (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), + (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, + (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), + (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + ), + ( + {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, + (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), + (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + ), + ]) + @pytest.mark.django_db + def test_section2_aabd_ssi_validator_fail_state_ssi(self, small_correct_file, header, T4Stuff, T5Stuff): + """Test records are related validator section 2 success case.""" + (T4Factory, t4_schema, t4_model_name) = T4Stuff + (T5Factory, t5_schema, t5_model_name) = T5Stuff + + case_consistency_validator = CaseConsistencyValidator( + header, + STT.EntityType.STATE, + util.make_generate_parser_error(small_correct_file, None) + ) + + t4s = [ + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + ), + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123' + ), + ] + for t4 in t4s: + case_consistency_validator.add_record(t4, t4_schema, False) + + t5s = [ + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="19970209", + FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=2 + ), + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + DATE_OF_BIRTH="19970209", + FAMILY_AFFILIATION=2, # validator only applies to fam_affil = 1; won't generate error + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=2 + ), + ] + for t5 in t5s: + case_consistency_validator.add_record(t5, t5_schema, False) + + num_errors = case_consistency_validator.validate() + + errors = case_consistency_validator.get_generated_errors() + + assert len(errors) == 1 + assert num_errors == 1 + assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[0].error_message == ( + f'{t5_model_name} People in states must have a valid value.' + ) From 86809c3c4348d2559a76283fded83771b4d0f2e7 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 8 Mar 2024 15:16:42 -0500 Subject: [PATCH 02/24] uncomment logs --- .../parsers/case_consistency_validator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index c5cee5286..923df0cfc 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -286,21 +286,21 @@ def __validate_s2_records_are_related(self): t5_model_name = 'M5' if is_ssp else 'T5' t5_model = self.__get_model(t5_model_name) - # logger.debug('validating records are related') - # logger.debug(f'program type: {self.program_type}') - # logger.debug(f'section: {self.section}') - # logger.debug(f'is_ssp: {is_ssp}') - # logger.debug(f'models - T4: {t4_model}; T5: {t5_model};') + logger.debug('validating records are related') + logger.debug(f'program type: {self.program_type}') + logger.debug(f'section: {self.section}') + logger.debug(f'is_ssp: {is_ssp}') + logger.debug(f'models - T4: {t4_model}; T5: {t5_model};') cases = self.record_schema_pairs.sorted_cases - # logger.debug(f'cases obj: {cases}') + logger.debug(f'cases obj: {cases}') for reporting_year_cases in cases.values(): t4s = reporting_year_cases.get(t4_model, []) t5s = reporting_year_cases.get(t5_model, []) - # logger.debug(f't4s: {t4s}') - # logger.debug(f't5s: {t5s}') + logger.debug(f't4s: {t4s}') + logger.debug(f't5s: {t5s}') if len(t4s) > 0: if len(t5s) == 0: From 7e2ce95446be6898f8013b89e252df95879cc549 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 8 Mar 2024 15:25:36 -0500 Subject: [PATCH 03/24] up timeout --- .circleci/util/commands.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/util/commands.yml b/.circleci/util/commands.yml index a68a0bf82..304cc7455 100644 --- a/.circleci/util/commands.yml +++ b/.circleci/util/commands.yml @@ -18,7 +18,7 @@ command: cd tdrs-backend; docker network create external-net; docker-compose --profile elastic_setup up -d --build - run: name: Reindex elasticsearch - command: cd tdrs-backend; docker-compose exec web wait-for-it --service http://localhost:8080 --timeout 60 -- python manage.py search_index --rebuild -f + command: cd tdrs-backend; docker-compose exec web wait-for-it --service http://localhost:8080 --timeout 180 -- python manage.py search_index --rebuild -f cf-check: steps: From fe50af77889f2450f4ff7a5d3b2b10135eadd0f3 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Tue, 19 Mar 2024 13:37:52 -0400 Subject: [PATCH 04/24] validate case closure reasons --- .../parsers/case_consistency_validator.py | 104 ++++- .../tdpservice/parsers/test/factories.py | 6 +- .../parsers/test/test_case_consistency.py | 389 +++++++++++++++--- 3 files changed, 430 insertions(+), 69 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 4a01f4b62..23f4cfed3 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -158,8 +158,9 @@ def __validate_header_with_records(self): return num_errors - def __validate_family_affiliation(self, num_errors, t1s, t2s, t3s, error_msg): + def __validate_family_affiliation(self, t1s, t2s, t3s, error_msg): """Validate at least one record in t2s+t3s has FAMILY_AFFILIATION == 1.""" + num_errors = 0 passed = False for record, schema in t2s + t3s: family_affiliation = getattr(record, 'FAMILY_AFFILIATION') @@ -172,7 +173,7 @@ def __validate_family_affiliation(self, num_errors, t1s, t2s, t3s, error_msg): self.__generate_and_add_error( schema, record, - field='FAMILY_AFFILIATION', + field='FAMILY_AFFILIATION', # technically not a field on t4 msg=error_msg ) num_errors += 1 @@ -204,6 +205,19 @@ def __validate_s1_records_are_related(self): t3s = reporting_year_cases.get(t3_model, []) if len(t1s) > 0: + if len(t1s) > 1: # likely to be captured by "no duplicates" validator + for record, schema in t1s[1:]: + self.__generate_and_add_error( + schema, + record, + field='RPT_MONTH_YEAR', + msg=( + f'There should only be one {t1_model_name} record ' + f'for a RPT_MONTH_YEAR and CASE_NUMBER.' + ) + ) + num_errors += 1 + if len(t2s) == 0 and len(t3s) == 0: for record, schema in t1s: self.__generate_and_add_error( @@ -221,7 +235,7 @@ def __validate_s1_records_are_related(self): else: # loop through all t2s and t3s # to find record where FAMILY_AFFILIATION == 1 - num_errors += self.__validate_family_affiliation(num_errors, t1s, t2s, t3s, ( + num_errors += self.__validate_family_affiliation(t1s, t2s, t3s, ( f'Every {t1_model_name} record should have at least one corresponding ' f'{t2_model_name} or {t3_model_name} record with the same RPT_MONTH_YEAR and ' f'CASE_NUMBER, where FAMILY_AFFILIATION==1' @@ -256,6 +270,65 @@ def __validate_s1_records_are_related(self): return num_errors + def __validate_case_closure_employment(self, t4, t5s, error_msg): + """ + Validate case closure. + + If case closure reason = 01:employment, then at least one person on + the case must have employment status = 1:Yes in the same month. + """ + num_errors = 0 + t4_record, t4_schema = t4 + + passed = False + for record, schema in t5s: + employment_status = getattr(record, 'EMPLOYMENT_STATUS') + + if employment_status == 1: + passed = True + break + + if not passed: + self.__generate_and_add_error( + t4_schema, + t4_record, + 'EMPLOYMENT_STATUS', # technically not a field on t4 + error_msg + ) + num_errors += 1 + + return num_errors + + def __validate_case_closure_ftl(self, t4, t5s, error_msg): + """ + Validate case closure. + + If closure reason = FTL, then at least one person who is HoH + or spouse of HoH on case must have FTL months >=60. + """ + num_errors = 0 + t4_record, t4_schema = t4 + + passed = False + for record, schema in t5s: + relationship_hoh = getattr(record, 'RELATIONSHIP_HOH') + ftl_months = getattr(record, 'COUNTABLE_MONTH_FED_TIME') # does not exist on m5, only validate ftl for tribal/tanf (employment for all sections) + + if (relationship_hoh == '01' or relationship_hoh == '02') and int(ftl_months) >= 60: # convert COUNTABLE_MONTH_FED_TIME to number (potential migration, put in cat 1 cleanup) + passed = True + break + + if not passed: + self.__generate_and_add_error( + t4_schema, + t4_record, + 'COUNTABLE_MONTH_FED_TIME', # technically not a field on t4 + error_msg + ) + num_errors += 1 + + return num_errors + def __validate_s2_records_are_related(self): """ Validate section 2 records are related. @@ -278,6 +351,31 @@ def __validate_s2_records_are_related(self): t5s = reporting_year_cases.get(t5_model, []) if len(t4s) > 0: + if len(t4s) > 1: + for record, schema in t4s[1:]: + self.__generate_and_add_error( + schema, + record, + field='RPT_MONTH_YEAR', + msg=( + f'There should only be one {t4_model_name} record ' + f'for a RPT_MONTH_YEAR and CASE_NUMBER.' + ) + ) + num_errors += 1 + else: + t4 = t4s[0] + t4_record, t4_schema = t4 + closure_reason = getattr(t4_record, 'CLOSURE_REASON') + + if closure_reason == '01': + num_errors += self.__validate_case_closure_employment(t4, t5s, ( + 'At least one person on the case must have employment status = 1:Yes in the same month.' + )) + elif closure_reason == '99' and not is_ssp: # ? + num_errors += self.__validate_case_closure_ftl(t4, t5s, ( + 'At least one person who is HoH or spouse of HoH on case must have FTL months >=60.' + )) if len(t5s) == 0: for record, schema in t4s: self.__generate_and_add_error( diff --git a/tdrs-backend/tdpservice/parsers/test/factories.py b/tdrs-backend/tdpservice/parsers/test/factories.py index 173c92970..c882252f7 100644 --- a/tdrs-backend/tdpservice/parsers/test/factories.py +++ b/tdrs-backend/tdpservice/parsers/test/factories.py @@ -266,7 +266,7 @@ class Meta: STRATUM = 1 ZIP_CODE = "11111" DISPOSITION = 1 - CLOSURE_REASON = '01' + CLOSURE_REASON = '02' REC_SUB_HOUSING = 1 REC_MED_ASSIST = 1 REC_FOOD_STAMPS = 1 @@ -512,7 +512,7 @@ class Meta: STRATUM = 1 ZIP_CODE = "11111" DISPOSITION = 1 - CLOSURE_REASON = '01' + CLOSURE_REASON = '02' REC_SUB_HOUSING = 1 REC_MED_ASSIST = 1 REC_FOOD_STAMPS = 1 @@ -770,7 +770,7 @@ class Meta: STRATUM = 1 ZIP_CODE = "11111" DISPOSITION = 1 - CLOSURE_REASON = '01' + CLOSURE_REASON = '02' REC_SUB_HOUSING = 1 REC_MED_ASSIST = 1 REC_FOOD_STAMPS = 1 diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index bed144e7b..b50eec047 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -161,7 +161,8 @@ def test_section1_pass(self, small_correct_file_header, small_correct_file, tanf ), ]) @pytest.mark.django_db - def test_section1_records_are_related_validator_pass(self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff, stt_type): + def test_section1_records_are_related_validator_pass( + self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff, stt_type): """Test records are related validator success case.""" (T1Factory, t1_schema, t1_model_name) = T1Stuff (T2Factory, t2_schema, t2_model_name) = T2Stuff @@ -178,10 +179,6 @@ def test_section1_records_are_related_validator_pass(self, small_correct_file, h RPT_MONTH_YEAR=202010, CASE_NUMBER='123', ), - T1Factory.create( - RPT_MONTH_YEAR=202010, - CASE_NUMBER='123' - ), ] for t1 in t1s: case_consistency_validator.add_record(t1, t1_schema, False) @@ -265,10 +262,6 @@ def test_section1_records_are_related_validator_fail_no_t2_or_t3( RPT_MONTH_YEAR=202010, CASE_NUMBER='123' ), - T1Factory.create( - RPT_MONTH_YEAR=202010, - CASE_NUMBER='123' - ), ] for t1 in t1s: case_consistency_validator.add_record(t1, t1_schema, False) @@ -277,18 +270,13 @@ def test_section1_records_are_related_validator_fail_no_t2_or_t3( errors = case_consistency_validator.get_generated_errors() - assert len(errors) == 2 - assert num_errors == 2 + assert len(errors) == 1 + assert num_errors == 1 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( f'Every {t1_model_name} record should have at least one corresponding ' f'{t2_model_name} or {t3_model_name} record with the same RPT_MONTH_YEAR and CASE_NUMBER.' ) - assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY - assert errors[1].error_message == ( - f'Every {t1_model_name} record should have at least one corresponding ' - f'{t2_model_name} or {t3_model_name} record with the same RPT_MONTH_YEAR and CASE_NUMBER.' - ) @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff,stt_type", [ ( @@ -314,7 +302,8 @@ def test_section1_records_are_related_validator_fail_no_t2_or_t3( ), ]) @pytest.mark.django_db - def test_section1_records_are_related_validator_fail_no_t1(self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff, stt_type): + def test_section1_records_are_related_validator_fail_no_t1( + self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff, stt_type): """Test records are related validator fails with no t1s.""" (T1Factory, t1_schema, t1_model_name) = T1Stuff (T2Factory, t2_schema, t2_model_name) = T2Stuff @@ -407,9 +396,9 @@ def test_section1_records_are_related_validator_fail_no_t1(self, small_correct_f ), ]) @pytest.mark.django_db - def test_section1_records_are_related_validator_fail_no_family_affiliation( + def test_section1_records_are_related_validator_fail_multiple_t1s( self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff, stt_type): - """Test records are related validator fails when no t2 or t3 has family_affiliation == 1.""" + """Test records are related validator fails when there are multiple t1s.""" (T1Factory, t1_schema, t1_model_name) = T1Stuff (T2Factory, t2_schema, t2_model_name) = T2Stuff (T3Factory, t3_schema, t3_model_name) = T3Stuff @@ -433,6 +422,94 @@ def test_section1_records_are_related_validator_fail_no_family_affiliation( for t1 in t1s: case_consistency_validator.add_record(t1, t1_schema, False) + t2s = [ + T2Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + FAMILY_AFFILIATION=1, + ), + T2Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + FAMILY_AFFILIATION=2, + ), + ] + for t2 in t2s: + case_consistency_validator.add_record(t2, t2_schema, False) + + t3s = [ + T3Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + FAMILY_AFFILIATION=1, + ), + T3Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + FAMILY_AFFILIATION=2, + ), + ] + for t3 in t3s: + case_consistency_validator.add_record(t3, t3_schema, False) + + num_errors = case_consistency_validator.validate() + + errors = case_consistency_validator.get_generated_errors() + + assert len(errors) == 1 + assert num_errors == 1 + assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[0].error_message == ( + f'There should only be one {t1_model_name} record ' + f'for a RPT_MONTH_YEAR and CASE_NUMBER.' + ) + + @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff,stt_type", [ + ( + {"type": "A", "program_type": "TAN", "year": 2020, "quarter": "4"}, + (factories.TanfT1Factory, schema_defs.tanf.t1.schemas[0], 'T1'), + (factories.TanfT2Factory, schema_defs.tanf.t2.schemas[0], 'T2'), + (factories.TanfT3Factory, schema_defs.tanf.t3.schemas[0], 'T3'), + STT.EntityType.STATE, + ), + ( + {"type": "A", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, + (factories.TribalTanfT1Factory, schema_defs.tribal_tanf.t1.schemas[0], 'T1'), + (factories.TribalTanfT2Factory, schema_defs.tribal_tanf.t2.schemas[0], 'T2'), + (factories.TribalTanfT3Factory, schema_defs.tribal_tanf.t3.schemas[0], 'T3'), + STT.EntityType.TRIBE, + ), + ( + {"type": "A", "program_type": "SSP", "year": 2020, "quarter": "4"}, + (factories.SSPM1Factory, schema_defs.ssp.m1.schemas[0], 'M1'), + (factories.SSPM2Factory, schema_defs.ssp.m2.schemas[0], 'M2'), + (factories.SSPM3Factory, schema_defs.ssp.m3.schemas[0], 'M3'), + STT.EntityType.STATE, + ), + ]) + @pytest.mark.django_db + def test_section1_records_are_related_validator_fail_no_family_affiliation( + self, small_correct_file, header, T1Stuff, T2Stuff, T3Stuff, stt_type): + """Test records are related validator fails when no t2 or t3 has family_affiliation == 1.""" + (T1Factory, t1_schema, t1_model_name) = T1Stuff + (T2Factory, t2_schema, t2_model_name) = T2Stuff + (T3Factory, t3_schema, t3_model_name) = T3Stuff + + case_consistency_validator = CaseConsistencyValidator( + header, + stt_type, + util.make_generate_parser_error(small_correct_file, None) + ) + + t1s = [ + T1Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123' + ), + ] + for t1 in t1s: + case_consistency_validator.add_record(t1, t1_schema, False) + t2s = [ T2Factory.create( RPT_MONTH_YEAR=202010, @@ -467,20 +544,14 @@ def test_section1_records_are_related_validator_fail_no_family_affiliation( errors = case_consistency_validator.get_generated_errors() - assert len(errors) == 2 - assert num_errors == 2 + assert len(errors) == 1 + assert num_errors == 1 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( f'Every {t1_model_name} record should have at least one corresponding ' f'{t2_model_name} or {t3_model_name} record with the same RPT_MONTH_YEAR and ' f'CASE_NUMBER, where FAMILY_AFFILIATION==1' ) - assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY - assert errors[1].error_message == ( - f'Every {t1_model_name} record should have at least one corresponding ' - f'{t2_model_name} or {t3_model_name} record with the same RPT_MONTH_YEAR and ' - f'CASE_NUMBER, where FAMILY_AFFILIATION==1' - ) @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [ ( @@ -514,6 +585,73 @@ def test_section2_validator_pass(self, small_correct_file, header, T4Stuff, T5St util.make_generate_parser_error(small_correct_file, None) ) + t4s = [ + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + ), + ] + for t4 in t4s: + case_consistency_validator.add_record(t4, t4_schema, False) + + t5s = [ + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=1 + ), + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + FAMILY_AFFILIATION=2, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=1 + ), + ] + for t5 in t5s: + case_consistency_validator.add_record(t5, t5_schema, False) + + num_errors = case_consistency_validator.validate() + + errors = case_consistency_validator.get_generated_errors() + + assert len(errors) == 0 + assert num_errors == 0 + + @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [ + ( + {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, + (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), + (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + STT.EntityType.STATE, + ), + ( + {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, + (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), + (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + STT.EntityType.TRIBE, + ), + ( + {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, + (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), + (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + STT.EntityType.STATE, + ), + ]) + @pytest.mark.django_db + def test_section2_validator_fail_multiple_t4s(self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): + """Test records are related validator section 2 success case.""" + (T4Factory, t4_schema, t4_model_name) = T4Stuff + (T5Factory, t5_schema, t5_model_name) = T5Stuff + + case_consistency_validator = CaseConsistencyValidator( + header, + stt_type, + util.make_generate_parser_error(small_correct_file, None) + ) + t4s = [ T4Factory.create( RPT_MONTH_YEAR=202010, @@ -550,8 +688,13 @@ def test_section2_validator_pass(self, small_correct_file, header, T4Stuff, T5St errors = case_consistency_validator.get_generated_errors() - assert len(errors) == 0 - assert num_errors == 0 + assert len(errors) == 1 + assert num_errors == 1 + assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[0].error_message == ( + f'There should only be one {t4_model_name} record ' + f'for a RPT_MONTH_YEAR and CASE_NUMBER.' + ) @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [ ( @@ -574,8 +717,9 @@ def test_section2_validator_pass(self, small_correct_file, header, T4Stuff, T5St ), ]) @pytest.mark.django_db - def test_section2_records_are_related_validator_fail_no_t5s(self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): - """Test records are related validator fails with no t5s.""" + def test_section2_validator_fail_case_closure_employment( + self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): + """Test records are related validator section 2 success case.""" (T4Factory, t4_schema, t4_model_name) = T4Stuff (T5Factory, t5_schema, t5_model_name) = T5Stuff @@ -589,28 +733,170 @@ def test_section2_records_are_related_validator_fail_no_t5s(self, small_correct_ T4Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', + CLOSURE_REASON='01' ), + ] + for t4 in t4s: + case_consistency_validator.add_record(t4, t4_schema, False) + + t5s = [ + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=1, + EMPLOYMENT_STATUS=3, + ), + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + FAMILY_AFFILIATION=2, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=1, + EMPLOYMENT_STATUS=2, + ), + ] + for t5 in t5s: + case_consistency_validator.add_record(t5, t5_schema, False) + + num_errors = case_consistency_validator.validate() + + errors = case_consistency_validator.get_generated_errors() + + assert len(errors) == 1 + assert num_errors == 1 + assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[0].error_message == ( + 'At least one person on the case must have employment status = 1:Yes in the same month.' + ) + + @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [ + ( + {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, + (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), + (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + STT.EntityType.STATE, + ), + ( + {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, + (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), + (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + STT.EntityType.TRIBE, + ), + ( + {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, + (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), + (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + STT.EntityType.STATE, + ), + ]) + @pytest.mark.django_db + def test_section2_validator_fail_case_closure_ftl(self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): + """Test records are related validator section 2 success case.""" + (T4Factory, t4_schema, t4_model_name) = T4Stuff + (T5Factory, t5_schema, t5_model_name) = T5Stuff + + case_consistency_validator = CaseConsistencyValidator( + header, + stt_type, + util.make_generate_parser_error(small_correct_file, None) + ) + + t4s = [ T4Factory.create( RPT_MONTH_YEAR=202010, - CASE_NUMBER='123' + CASE_NUMBER='123', + CLOSURE_REASON='99' ), ] for t4 in t4s: case_consistency_validator.add_record(t4, t4_schema, False) + t5s = [ + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + FAMILY_AFFILIATION=1, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=1, + RELATIONSHIP_HOH='10', + COUNTABLE_MONTH_FED_TIME='059', + ), + T5Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + FAMILY_AFFILIATION=2, + REC_AID_TOTALLY_DISABLED=2, + REC_SSI=1, + RELATIONSHIP_HOH='03', + COUNTABLE_MONTH_FED_TIME='001', + ), + ] + for t5 in t5s: + case_consistency_validator.add_record(t5, t5_schema, False) + num_errors = case_consistency_validator.validate() errors = case_consistency_validator.get_generated_errors() - assert len(errors) == 2 - assert num_errors == 2 + assert len(errors) == 1 + assert num_errors == 1 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( - f'Every {t4_model_name} record should have at least one corresponding ' - f'{t5_model_name} record with the same RPT_MONTH_YEAR and CASE_NUMBER.' + 'At least one person who is HoH or spouse of HoH on case must have FTL months >=60.' ) - assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY - assert errors[1].error_message == ( + + @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [ + ( + {"type": "C", "program_type": "TAN", "year": 2020, "quarter": "4"}, + (factories.TanfT4Factory, schema_defs.tanf.t4.schemas[0], 'T4'), + (factories.TanfT5Factory, schema_defs.tanf.t5.schemas[0], 'T5'), + STT.EntityType.STATE, + ), + ( + {"type": "C", "program_type": "Tribal TAN", "year": 2020, "quarter": "4"}, + (factories.TribalTanfT4Factory, schema_defs.tribal_tanf.t4.schemas[0], 'T4'), + (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), + STT.EntityType.TRIBE, + ), + ( + {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, + (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), + (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), + STT.EntityType.STATE, + ), + ]) + @pytest.mark.django_db + def test_section2_records_are_related_validator_fail_no_t5s( + self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): + """Test records are related validator fails with no t5s.""" + (T4Factory, t4_schema, t4_model_name) = T4Stuff + (T5Factory, t5_schema, t5_model_name) = T5Stuff + + case_consistency_validator = CaseConsistencyValidator( + header, + stt_type, + util.make_generate_parser_error(small_correct_file, None) + ) + + t4s = [ + T4Factory.create( + RPT_MONTH_YEAR=202010, + CASE_NUMBER='123', + ), + ] + for t4 in t4s: + case_consistency_validator.add_record(t4, t4_schema, False) + + num_errors = case_consistency_validator.validate() + + errors = case_consistency_validator.get_generated_errors() + + assert len(errors) == 1 + assert num_errors == 1 + assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY + assert errors[0].error_message == ( f'Every {t4_model_name} record should have at least one corresponding ' f'{t5_model_name} record with the same RPT_MONTH_YEAR and CASE_NUMBER.' ) @@ -636,7 +922,8 @@ def test_section2_records_are_related_validator_fail_no_t5s(self, small_correct_ ), ]) @pytest.mark.django_db - def test_section2_records_are_related_validator_fail_no_t4s(self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): + def test_section2_records_are_related_validator_fail_no_t4s( + self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): """Test records are related validator fails with no t4s.""" (T4Factory, t4_schema, t4_model_name) = T4Stuff (T5Factory, t5_schema, t5_model_name) = T5Stuff @@ -717,10 +1004,6 @@ def test_section2_aabd_ssi_validator_pass_territory_adult_aadb(self, small_corre RPT_MONTH_YEAR=202010, CASE_NUMBER='123', ), - T4Factory.create( - RPT_MONTH_YEAR=202010, - CASE_NUMBER='123' - ), ] for t4 in t4s: case_consistency_validator.add_record(t4, t4_schema, False) @@ -787,10 +1070,6 @@ def test_section2_aabd_ssi_validator_fail_territory_adult_aabd(self, small_corre RPT_MONTH_YEAR=202010, CASE_NUMBER='123', ), - T4Factory.create( - RPT_MONTH_YEAR=202010, - CASE_NUMBER='123' - ), ] for t4 in t4s: case_consistency_validator.add_record(t4, t4_schema, False) @@ -865,10 +1144,6 @@ def test_section2_aabd_ssi_validator_pass_territory_child_aabd(self, small_corre RPT_MONTH_YEAR=202010, CASE_NUMBER='123', ), - T4Factory.create( - RPT_MONTH_YEAR=202010, - CASE_NUMBER='123' - ), ] for t4 in t4s: case_consistency_validator.add_record(t4, t4_schema, False) @@ -935,10 +1210,6 @@ def test_section2_aabd_ssi_validator_fail_state_aabd(self, small_correct_file, h RPT_MONTH_YEAR=202010, CASE_NUMBER='123', ), - T4Factory.create( - RPT_MONTH_YEAR=202010, - CASE_NUMBER='123' - ), ] for t4 in t4s: case_consistency_validator.add_record(t4, t4_schema, False) @@ -1013,10 +1284,6 @@ def test_section2_aabd_ssi_validator_fail_territory_ssi(self, small_correct_file RPT_MONTH_YEAR=202010, CASE_NUMBER='123', ), - T4Factory.create( - RPT_MONTH_YEAR=202010, - CASE_NUMBER='123' - ), ] for t4 in t4s: case_consistency_validator.add_record(t4, t4_schema, False) @@ -1091,10 +1358,6 @@ def test_section2_aabd_ssi_validator_fail_state_ssi(self, small_correct_file, he RPT_MONTH_YEAR=202010, CASE_NUMBER='123', ), - T4Factory.create( - RPT_MONTH_YEAR=202010, - CASE_NUMBER='123' - ), ] for t4 in t4s: case_consistency_validator.add_record(t4, t4_schema, False) From a34bc11465667acf90932d85992863c5f22635dd Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Wed, 10 Apr 2024 09:04:09 -0400 Subject: [PATCH 05/24] fix tests --- .../tdpservice/parsers/test/test_case_consistency.py | 2 +- tdrs-backend/tdpservice/parsers/test/test_parse.py | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index 08cb823da..ff6865b46 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -460,7 +460,7 @@ def test_section1_records_are_related_validator_fail_multiple_t1s( assert num_errors == 1 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( - f'There should only be one {t1_model_name} record ' + f'There should only be one {t1_model_name} record ' f'for a RPT_MONTH_YEAR and CASE_NUMBER.' ) diff --git a/tdrs-backend/tdpservice/parsers/test/test_parse.py b/tdrs-backend/tdpservice/parsers/test/test_parse.py index 9eb5d3f88..aef853725 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_parse.py +++ b/tdrs-backend/tdpservice/parsers/test/test_parse.py @@ -1012,10 +1012,7 @@ def test_parse_tanf_section2_file(tanf_section2_file, dfs): parser_errors = ParserError.objects.filter(file=tanf_section2_file) err = parser_errors.first() - assert err.error_type == ParserErrorCategoryChoices.FIELD_VALUE - assert err.error_message == "REC_OASDI_INSURANCE is required but a value was not provided." - assert err.content_type.model == "tanf_t5" - assert err.object_id is not None + assert err.error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY @pytest.fixture From 14487610f72477eb799055e7079ec3c97c20746f Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Wed, 10 Apr 2024 09:32:18 -0400 Subject: [PATCH 06/24] rm irrelevant test case --- .../tdpservice/parsers/test/test_case_consistency.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index ff6865b46..ee0b67bfe 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -784,12 +784,6 @@ def test_section2_validator_fail_case_closure_employment( (factories.TribalTanfT5Factory, schema_defs.tribal_tanf.t5.schemas[0], 'T5'), STT.EntityType.TRIBE, ), - ( - {"type": "C", "program_type": "SSP", "year": 2020, "quarter": "4"}, - (factories.SSPM4Factory, schema_defs.ssp.m4.schemas[0], 'M4'), - (factories.SSPM5Factory, schema_defs.ssp.m5.schemas[0], 'M5'), - STT.EntityType.STATE, - ), ]) @pytest.mark.django_db def test_section2_validator_fail_case_closure_ftl(self, small_correct_file, header, T4Stuff, T5Stuff, stt_type): From efdaa2c4725ae72f519e4685b8bca4c4b5d1e2e1 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Wed, 10 Apr 2024 09:32:57 -0400 Subject: [PATCH 07/24] rm comment --- tdrs-backend/tdpservice/parsers/case_consistency_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 4d627d52e..b3b919969 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -382,7 +382,7 @@ def __validate_s2_records_are_related(self): num_errors += self.__validate_case_closure_employment(t4, t5s, ( 'At least one person on the case must have employment status = 1:Yes in the same month.' )) - elif closure_reason == '99' and not is_ssp: # ? + elif closure_reason == '99' and not is_ssp: num_errors += self.__validate_case_closure_ftl(t4, t5s, ( 'At least one person who is HoH or spouse of HoH on case must have FTL months >=60.' )) From 05bbb7abb493b08e8f66c9b71aa3efb3bca1e76f Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Wed, 10 Apr 2024 10:02:32 -0400 Subject: [PATCH 08/24] clean up comments --- .../tdpservice/parsers/case_consistency_validator.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index b3b919969..86bb8e405 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -183,7 +183,7 @@ def __validate_family_affiliation(self, t1s, t2s, t3s, error_msg): self.__generate_and_add_error( schema, record, - field='FAMILY_AFFILIATION', # technically not a field on t4 + field='FAMILY_AFFILIATION', msg=error_msg ) num_errors += 1 @@ -302,7 +302,7 @@ def __validate_case_closure_employment(self, t4, t5s, error_msg): self.__generate_and_add_error( t4_schema, t4_record, - 'EMPLOYMENT_STATUS', # technically not a field on t4 + 'EMPLOYMENT_STATUS', error_msg ) num_errors += 1 @@ -322,9 +322,9 @@ def __validate_case_closure_ftl(self, t4, t5s, error_msg): passed = False for record, schema in t5s: relationship_hoh = getattr(record, 'RELATIONSHIP_HOH') - ftl_months = getattr(record, 'COUNTABLE_MONTH_FED_TIME') # does not exist on m5, only validate ftl for tribal/tanf (employment for all sections) + ftl_months = getattr(record, 'COUNTABLE_MONTH_FED_TIME') - if (relationship_hoh == '01' or relationship_hoh == '02') and int(ftl_months) >= 60: # convert COUNTABLE_MONTH_FED_TIME to number (potential migration, put in cat 1 cleanup) + if (relationship_hoh == '01' or relationship_hoh == '02') and int(ftl_months) >= 60: passed = True break @@ -332,7 +332,7 @@ def __validate_case_closure_ftl(self, t4, t5s, error_msg): self.__generate_and_add_error( t4_schema, t4_record, - 'COUNTABLE_MONTH_FED_TIME', # technically not a field on t4 + 'COUNTABLE_MONTH_FED_TIME', error_msg ) num_errors += 1 From c64a8cb4f289205664674448f3cade57e79ae2c3 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Mon, 15 Apr 2024 08:52:41 -0400 Subject: [PATCH 09/24] fix tests --- tdrs-backend/tdpservice/parsers/case_consistency_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 512012789..022c26bdd 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -221,7 +221,7 @@ def __validate_s1_records_are_related(self): else: # loop through all t2s and t3s # to find record where FAMILY_AFFILIATION == 1 - num_errors += self.__validate_family_affiliation(t1s, t2s, t3s, ( + num_errors += self.__validate_family_affiliation(num_errors, t1s, t2s, t3s, ( f'Every {t1_model_name} record should have at least one corresponding ' f'{t2_model_name} or {t3_model_name} record with the same RPT_MONTH_YEAR and ' f'CASE_NUMBER, where FAMILY_AFFILIATION==1' From 881fcec6a5d4de5fb6ee406ad8b5691b8704aa5e Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 19 Apr 2024 10:56:22 -0400 Subject: [PATCH 10/24] add 2 as option for territory aabd --- tdrs-backend/tdpservice/parsers/case_consistency_validator.py | 2 +- tdrs-backend/tdpservice/parsers/test/test_case_consistency.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index f909dd72e..6f6d4d18d 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -422,7 +422,7 @@ def __validate_t5_aabd_and_ssi(self): age = delta.days/365.25 is_adult = age >= 18 - if is_territory and is_adult and rec_aabd != 1: + if is_territory and is_adult and (rec_aabd != 1 or rec_aabd != 2): self.__generate_and_add_error( schema, record, diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index 6d5319f8b..b853cf5f5 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -983,7 +983,7 @@ def test_section2_aabd_ssi_validator_pass_territory_adult_aadb(self, small_corre CASE_NUMBER='123', DATE_OF_BIRTH="19970209", FAMILY_AFFILIATION=2, - REC_AID_TOTALLY_DISABLED=1, + REC_AID_TOTALLY_DISABLED=2, REC_SSI=2 ), ] From 680f62e3c23c770ab255c95eeb023eb33d045038 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 19 Apr 2024 11:12:28 -0400 Subject: [PATCH 11/24] extrapolate `get_years_apart` to utils --- .../parsers/case_consistency_validator.py | 5 ++--- .../tdpservice/parsers/test/test_util.py | 16 +++++++++++++++- tdrs-backend/tdpservice/parsers/util.py | 7 +++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 6f6d4d18d..f892755dc 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -2,6 +2,7 @@ from datetime import datetime from .models import ParserErrorCategoryChoices +from .util import get_years_apart from tdpservice.stts.models import STT from tdpservice.parsers.schema_defs.utils import get_program_model import logging @@ -418,9 +419,7 @@ def __validate_t5_aabd_and_ssi(self): rpt_month_year_dd = f'{rpt_month_year}01' rpt_date = datetime.strptime(rpt_month_year_dd, '%Y%m%d') dob_date = datetime.strptime(dob, '%Y%m%d') - delta = rpt_date - dob_date - age = delta.days/365.25 - is_adult = age >= 18 + is_adult = get_years_apart(rpt_date, dob_date) >= 18 if is_territory and is_adult and (rec_aabd != 1 or rec_aabd != 2): self.__generate_and_add_error( diff --git a/tdrs-backend/tdpservice/parsers/test/test_util.py b/tdrs-backend/tdpservice/parsers/test/test_util.py index cd50f9c4a..84797958e 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_util.py +++ b/tdrs-backend/tdpservice/parsers/test/test_util.py @@ -1,9 +1,10 @@ """Test the methods of RowSchema to ensure parsing and validation work in all individual cases.""" import pytest +from datetime import datetime from ..fields import Field from ..row_schema import RowSchema, SchemaManager -from ..util import make_generate_parser_error, create_test_datafile +from ..util import make_generate_parser_error, create_test_datafile, get_years_apart def passing_validator(): @@ -525,3 +526,16 @@ def postparse_validator(): assert is_valid is False assert errors[0].fields_json == {'friendly_name': {'FIRST': 'first', 'SECOND': 'second'}} assert errors[0].error_message == "an Error" + + +@pytest.mark.parametrize("rpt_date_str,date_str,expected", [ + ('20200102', '20100101', 10), + ('20200102', '20100106', 9), + ('20200101', '20200102', 0), + ('20200101', '20210102', -1), +]) +def test_get_years_apart(rpt_date_str, date_str, expected): + """Test the get_years_apart util function.""" + rpt_date = datetime.strptime(rpt_date_str, '%Y%m%d') + date = datetime.strptime(date_str, '%Y%m%d') + assert int(get_years_apart(rpt_date, date)) == expected diff --git a/tdrs-backend/tdpservice/parsers/util.py b/tdrs-backend/tdpservice/parsers/util.py index 3a5528391..70fb75b66 100644 --- a/tdrs-backend/tdpservice/parsers/util.py +++ b/tdrs-backend/tdpservice/parsers/util.py @@ -167,3 +167,10 @@ def get_quarter_from_month(month): month = year_month[4:] quarter = get_quarter_from_month(month) return year, quarter + + +def get_years_apart(rpt_month_year_date, date): + """Return the number of years (double) between rpt_month_year_date and the target date - both should be `datetime`s.""" + delta = rpt_month_year_date - date + age = delta.days/365.25 + return age From 664d0c9971930312824098cce912cdef2e12bce4 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 19 Apr 2024 11:15:06 -0400 Subject: [PATCH 12/24] or -> and --- tdrs-backend/tdpservice/parsers/case_consistency_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index f892755dc..ed0ac136e 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -421,7 +421,7 @@ def __validate_t5_aabd_and_ssi(self): dob_date = datetime.strptime(dob, '%Y%m%d') is_adult = get_years_apart(rpt_date, dob_date) >= 18 - if is_territory and is_adult and (rec_aabd != 1 or rec_aabd != 2): + if is_territory and is_adult and (rec_aabd != 1 and rec_aabd != 2): self.__generate_and_add_error( schema, record, From 7346a3e4afcc9093f460fb16561a5e1b8082cc7e Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 19 Apr 2024 11:35:14 -0400 Subject: [PATCH 13/24] fix test --- tdrs-backend/tdpservice/parsers/test/test_case_consistency.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index b853cf5f5..59f23d945 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -1041,7 +1041,7 @@ def test_section2_aabd_ssi_validator_fail_territory_adult_aabd(self, small_corre CASE_NUMBER='123', DATE_OF_BIRTH="19970209", FAMILY_AFFILIATION=1, - REC_AID_TOTALLY_DISABLED=2, + REC_AID_TOTALLY_DISABLED=0, REC_SSI=2 ), T5Factory.create( @@ -1049,7 +1049,7 @@ def test_section2_aabd_ssi_validator_fail_territory_adult_aabd(self, small_corre CASE_NUMBER='123', DATE_OF_BIRTH="19970209", FAMILY_AFFILIATION=2, - REC_AID_TOTALLY_DISABLED=2, + REC_AID_TOTALLY_DISABLED=0, REC_SSI=2 ), ] From c3b9c332752b2107541bb634c7139da3881d9fba Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 19 Apr 2024 11:43:50 -0400 Subject: [PATCH 14/24] lint --- tdrs-backend/tdpservice/parsers/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-backend/tdpservice/parsers/util.py b/tdrs-backend/tdpservice/parsers/util.py index 70fb75b66..287c58cff 100644 --- a/tdrs-backend/tdpservice/parsers/util.py +++ b/tdrs-backend/tdpservice/parsers/util.py @@ -170,7 +170,7 @@ def get_quarter_from_month(month): def get_years_apart(rpt_month_year_date, date): - """Return the number of years (double) between rpt_month_year_date and the target date - both should be `datetime`s.""" + """Return the number of years (double) between rpt_month_year_date and the target date - both `datetime`s.""" delta = rpt_month_year_date - date age = delta.days/365.25 return age From 82f79eb54c72736e96e59412d1b0345918504813 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Mon, 13 May 2024 10:37:41 -0400 Subject: [PATCH 15/24] add missing program_type to tests --- .../tdpservice/parsers/test/test_case_consistency.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index 654d2a0c3..91076ea07 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -469,6 +469,7 @@ def test_section1_records_are_related_validator_fail_no_family_affiliation( case_consistency_validator = CaseConsistencyValidator( header, + header['program_type'], stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -553,6 +554,7 @@ def test_section2_validator_pass(self, small_correct_file, header, T4Stuff, T5St case_consistency_validator = CaseConsistencyValidator( header, + header['program_type'], stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -698,6 +700,7 @@ def test_section2_validator_fail_case_closure_employment( case_consistency_validator = CaseConsistencyValidator( header, + header['program_type'], stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -766,6 +769,7 @@ def test_section2_validator_fail_case_closure_ftl(self, small_correct_file, head case_consistency_validator = CaseConsistencyValidator( header, + header['program_type'], stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -843,6 +847,7 @@ def test_section2_records_are_related_validator_fail_no_t5s( case_consistency_validator = CaseConsistencyValidator( header, + header['program_type'], stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -897,6 +902,7 @@ def test_section2_records_are_related_validator_fail_no_t4s( case_consistency_validator = CaseConsistencyValidator( header, + header['program_type'], stt_type, util.make_generate_parser_error(small_correct_file, None) ) @@ -962,6 +968,7 @@ def test_section2_aabd_ssi_validator_pass_territory_adult_aadb(self, small_corre case_consistency_validator = CaseConsistencyValidator( header, + header['program_type'], STT.EntityType.TERRITORY, util.make_generate_parser_error(small_correct_file, None) ) @@ -1103,6 +1110,7 @@ def test_section2_aabd_ssi_validator_pass_territory_child_aabd(self, small_corre case_consistency_validator = CaseConsistencyValidator( header, + header['program_type'], STT.EntityType.TERRITORY, util.make_generate_parser_error(small_correct_file, None) ) @@ -1169,6 +1177,7 @@ def test_section2_aabd_ssi_validator_fail_state_aabd(self, small_correct_file, h case_consistency_validator = CaseConsistencyValidator( header, + header['program_type'], STT.EntityType.STATE, util.make_generate_parser_error(small_correct_file, None) ) @@ -1318,6 +1327,7 @@ def test_section2_aabd_ssi_validator_fail_state_ssi(self, small_correct_file, he case_consistency_validator = CaseConsistencyValidator( header, + header['program_type'], STT.EntityType.STATE, util.make_generate_parser_error(small_correct_file, None) ) From c1c87d91f4510e7a374f05b8b09d4e70dae47477 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Wed, 15 May 2024 13:33:53 -0400 Subject: [PATCH 16/24] update adult age to 19 --- tdrs-backend/tdpservice/parsers/case_consistency_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index a46a345d8..c5fe90f54 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -415,7 +415,7 @@ def __validate_t5_aabd_and_ssi(self): rpt_month_year_dd = f'{rpt_month_year}01' rpt_date = datetime.strptime(rpt_month_year_dd, '%Y%m%d') dob_date = datetime.strptime(dob, '%Y%m%d') - is_adult = get_years_apart(rpt_date, dob_date) >= 18 + is_adult = get_years_apart(rpt_date, dob_date) >= 19 if is_territory and is_adult and (rec_aabd != 1 and rec_aabd != 2): self.__generate_and_add_error( From f9728eaacf22b16a10b988a6cd4b2bb6758f8498 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Thu, 16 May 2024 13:22:09 -0400 Subject: [PATCH 17/24] for a -> per in cat4 messaging --- tdrs-backend/tdpservice/parsers/case_consistency_validator.py | 4 ++-- tdrs-backend/tdpservice/parsers/test/test_case_consistency.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index c5fe90f54..95cbcdf50 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -199,7 +199,7 @@ def __validate_s1_records_are_related(self): field='RPT_MONTH_YEAR', msg=( f'There should only be one {t1_model_name} record ' - f'for a RPT_MONTH_YEAR and CASE_NUMBER.' + f'per RPT_MONTH_YEAR and CASE_NUMBER.' ) ) num_errors += 1 @@ -345,7 +345,7 @@ def __validate_s2_records_are_related(self): field='RPT_MONTH_YEAR', msg=( f'There should only be one {t4_model_name} record ' - f'for a RPT_MONTH_YEAR and CASE_NUMBER.' + f'per RPT_MONTH_YEAR and CASE_NUMBER.' ) ) num_errors += 1 diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index 91076ea07..f6f10b5cf 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -433,7 +433,7 @@ def test_section1_records_are_related_validator_fail_multiple_t1s( assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( f'There should only be one {t1_model_name} record ' - f'for a RPT_MONTH_YEAR and CASE_NUMBER.' + f'per RPT_MONTH_YEAR and CASE_NUMBER.' ) @pytest.mark.parametrize("header,T1Stuff,T2Stuff,T3Stuff,stt_type", [ @@ -668,7 +668,7 @@ def test_section2_validator_fail_multiple_t4s(self, small_correct_file, header, assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( f'There should only be one {t4_model_name} record ' - f'for a RPT_MONTH_YEAR and CASE_NUMBER.' + f'per RPT_MONTH_YEAR and CASE_NUMBER.' ) @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [ From 89bb216c3319db6aec0fc6b24e1a99fa658ee2ec Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Thu, 16 May 2024 16:39:35 -0400 Subject: [PATCH 18/24] update closure reason logic --- .../tdpservice/parsers/case_consistency_validator.py | 4 ++-- .../tdpservice/parsers/test/test_case_consistency.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 95cbcdf50..7aa5e421e 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -356,9 +356,9 @@ def __validate_s2_records_are_related(self): if closure_reason == '01': num_errors += self.__validate_case_closure_employment(t4, t5s, ( - 'At least one person on the case must have employment status = 1:Yes in the same month.' + 'At least one person on the case must have employment status = 1:Yes in the same RPT_MONTH_YEAR since CLOSURE_REASON = 1:Employment/excess earnings.' )) - elif closure_reason == '99' and not is_ssp: + elif closure_reason == '03' and not is_ssp: num_errors += self.__validate_case_closure_ftl(t4, t5s, ( 'At least one person who is HoH or spouse of HoH on case must have FTL months >=60.' )) diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index f6f10b5cf..91a998e16 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -744,7 +744,8 @@ def test_section2_validator_fail_case_closure_employment( assert num_errors == 1 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( - 'At least one person on the case must have employment status = 1:Yes in the same month.' + 'At least one person on the case must have employment status = 1:Yes' + ' in the same RPT_MONTH_YEAR since CLOSURE_REASON = 1:Employment/excess earnings.' ) @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [ @@ -778,7 +779,7 @@ def test_section2_validator_fail_case_closure_ftl(self, small_correct_file, head T4Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', - CLOSURE_REASON='99' + CLOSURE_REASON='03' ), ] for t4 in t4s: From f6fb71d4ad79200edc9d1c76fc2ace54e81ed313 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 17 May 2024 09:56:36 -0400 Subject: [PATCH 19/24] lint --- tdrs-backend/tdpservice/parsers/case_consistency_validator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 7aa5e421e..b93aeaf2c 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -356,7 +356,8 @@ def __validate_s2_records_are_related(self): if closure_reason == '01': num_errors += self.__validate_case_closure_employment(t4, t5s, ( - 'At least one person on the case must have employment status = 1:Yes in the same RPT_MONTH_YEAR since CLOSURE_REASON = 1:Employment/excess earnings.' + 'At least one person on the case must have employment status = 1:Yes in the ' + 'same RPT_MONTH_YEAR since CLOSURE_REASON = 1:Employment/excess earnings.' )) elif closure_reason == '03' and not is_ssp: num_errors += self.__validate_case_closure_ftl(t4, t5s, ( From fc7e751f29ab6d3bd139757892a8e2d94da4186b Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 17 May 2024 10:51:11 -0400 Subject: [PATCH 20/24] update validator messaging --- .../tdpservice/parsers/case_consistency_validator.py | 4 ++-- .../tdpservice/parsers/test/test_case_consistency.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index b93aeaf2c..770fd4006 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -434,7 +434,7 @@ def __validate_t5_aabd_and_ssi(self): record, field='REC_AID_TOTALLY_DISABLED', msg=( - f'{t5_model_name} People in states shouldn\'t have a value of 1.' + f'{t5_model_name} People in states should not have a value of 1.' ) ) num_errors += 1 @@ -445,7 +445,7 @@ def __validate_t5_aabd_and_ssi(self): record, field='REC_SSI', msg=( - f'{t5_model_name} People in territories must have a valid value for 19E.' + f'{t5_model_name} People in territories must have value = 2:No for 19E.' ) ) num_errors += 1 diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index 91a998e16..7ea6535fd 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -1221,11 +1221,11 @@ def test_section2_aabd_ssi_validator_fail_state_aabd(self, small_correct_file, h assert num_errors == 2 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( - f'{t5_model_name} People in states shouldn\'t have a value of 1.' + f'{t5_model_name} People in states should not have a value of 1.' ) assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[1].error_message == ( - f'{t5_model_name} People in states shouldn\'t have a value of 1.' + f'{t5_model_name} People in states should not have a value of 1.' ) @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ @@ -1296,11 +1296,11 @@ def test_section2_aabd_ssi_validator_fail_territory_ssi(self, small_correct_file assert num_errors == 2 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( - f'{t5_model_name} People in territories must have a valid value for 19E.' + f'{t5_model_name} People in territories must have value = 2:No for 19E.' ) assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[1].error_message == ( - f'{t5_model_name} People in territories must have a valid value for 19E.' + f'{t5_model_name} People in territories must have value = 2:No for 19E.' ) @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ From 8a17eb30fc6204cf5bf4c01f18818b1c57749c67 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 17 May 2024 11:22:21 -0400 Subject: [PATCH 21/24] update states rec_ssi logic --- .../parsers/case_consistency_validator.py | 2 +- .../parsers/test/test_case_consistency.py | 36 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 770fd4006..7b078dddb 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -449,7 +449,7 @@ def __validate_t5_aabd_and_ssi(self): ) ) num_errors += 1 - elif is_state and family_affiliation == 1 and rec_ssi != 1: + elif is_state and family_affiliation == 1: self.__generate_and_add_error( schema, record, diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index 7ea6535fd..c6c0a69a4 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -572,16 +572,16 @@ def test_section2_validator_pass(self, small_correct_file, header, T4Stuff, T5St T5Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', - FAMILY_AFFILIATION=1, + FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=2, - REC_SSI=1 + REC_SSI=2 ), T5Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=2, - REC_SSI=1 + REC_SSI=2 ), ] for t5 in t5s: @@ -644,16 +644,16 @@ def test_section2_validator_fail_multiple_t4s(self, small_correct_file, header, T5Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', - FAMILY_AFFILIATION=1, + FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=2, - REC_SSI=1 + REC_SSI=2 ), T5Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=2, - REC_SSI=1 + REC_SSI=2 ), ] for t5 in t5s: @@ -719,9 +719,9 @@ def test_section2_validator_fail_case_closure_employment( T5Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', - FAMILY_AFFILIATION=1, + FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=2, - REC_SSI=1, + REC_SSI=2, EMPLOYMENT_STATUS=3, ), T5Factory.create( @@ -729,7 +729,7 @@ def test_section2_validator_fail_case_closure_employment( CASE_NUMBER='123', FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=2, - REC_SSI=1, + REC_SSI=2, EMPLOYMENT_STATUS=2, ), ] @@ -789,9 +789,9 @@ def test_section2_validator_fail_case_closure_ftl(self, small_correct_file, head T5Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', - FAMILY_AFFILIATION=1, + FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=2, - REC_SSI=1, + REC_SSI=2, RELATIONSHIP_HOH='10', COUNTABLE_MONTH_FED_TIME='059', ), @@ -800,7 +800,7 @@ def test_section2_validator_fail_case_closure_ftl(self, small_correct_file, head CASE_NUMBER='123', FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=2, - REC_SSI=1, + REC_SSI=2, RELATIONSHIP_HOH='03', COUNTABLE_MONTH_FED_TIME='001', ), @@ -912,16 +912,16 @@ def test_section2_records_are_related_validator_fail_no_t4s( T5Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', - FAMILY_AFFILIATION=1, + FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=2, - REC_SSI=1 + REC_SSI=2 ), T5Factory.create( RPT_MONTH_YEAR=202010, CASE_NUMBER='123', FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=2, - REC_SSI=1 + REC_SSI=2 ), ] for t5 in t5s: @@ -1197,9 +1197,9 @@ def test_section2_aabd_ssi_validator_fail_state_aabd(self, small_correct_file, h RPT_MONTH_YEAR=202010, CASE_NUMBER='123', DATE_OF_BIRTH="19970209", - FAMILY_AFFILIATION=1, + FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=1, - REC_SSI=1 + REC_SSI=2 ), T5Factory.create( RPT_MONTH_YEAR=202010, @@ -1207,7 +1207,7 @@ def test_section2_aabd_ssi_validator_fail_state_aabd(self, small_correct_file, h DATE_OF_BIRTH="20170209", FAMILY_AFFILIATION=2, REC_AID_TOTALLY_DISABLED=1, - REC_SSI=1 + REC_SSI=2 ), ] for t5 in t5s: From 9ce7ea1a94325d90b4bf569454bbf256d2dbc823 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 17 May 2024 13:42:13 -0400 Subject: [PATCH 22/24] add field name to error messages --- .../parsers/case_consistency_validator.py | 8 ++++---- .../parsers/test/test_case_consistency.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 7b078dddb..948d0c95b 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -424,7 +424,7 @@ def __validate_t5_aabd_and_ssi(self): record, field='REC_AID_TOTALLY_DISABLED', msg=( - f'{t5_model_name} Adults in territories must have a valid value for 19C.' + f'{t5_model_name} Adults in territories must have a valid value for REC_AID_TOTALLY_DISABLED.' ) ) num_errors += 1 @@ -434,7 +434,7 @@ def __validate_t5_aabd_and_ssi(self): record, field='REC_AID_TOTALLY_DISABLED', msg=( - f'{t5_model_name} People in states should not have a value of 1.' + f'{t5_model_name} People in states should not have a value of 1 for REC_AID_TOTALLY_DISABLED.' ) ) num_errors += 1 @@ -445,7 +445,7 @@ def __validate_t5_aabd_and_ssi(self): record, field='REC_SSI', msg=( - f'{t5_model_name} People in territories must have value = 2:No for 19E.' + f'{t5_model_name} People in territories must have value = 2:No for REC_SSI.' ) ) num_errors += 1 @@ -455,7 +455,7 @@ def __validate_t5_aabd_and_ssi(self): record, field='REC_SSI', msg=( - f'{t5_model_name} People in states must have a valid value.' + f'{t5_model_name} People in states must have a valid value for REC_SSI.' ) ) num_errors += 1 diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index c6c0a69a4..cdeecc381 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -1079,11 +1079,11 @@ def test_section2_aabd_ssi_validator_fail_territory_adult_aabd(self, small_corre assert num_errors == 2 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( - f'{t5_model_name} Adults in territories must have a valid value for 19C.' + f'{t5_model_name} Adults in territories must have a valid value for REC_AID_TOTALLY_DISABLED.' ) assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[1].error_message == ( - f'{t5_model_name} Adults in territories must have a valid value for 19C.' + f'{t5_model_name} Adults in territories must have a valid value for REC_AID_TOTALLY_DISABLED.' ) @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ @@ -1221,11 +1221,11 @@ def test_section2_aabd_ssi_validator_fail_state_aabd(self, small_correct_file, h assert num_errors == 2 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( - f'{t5_model_name} People in states should not have a value of 1.' + f'{t5_model_name} People in states should not have a value of 1 for REC_AID_TOTALLY_DISABLED.' ) assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[1].error_message == ( - f'{t5_model_name} People in states should not have a value of 1.' + f'{t5_model_name} People in states should not have a value of 1 for REC_AID_TOTALLY_DISABLED.' ) @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ @@ -1296,11 +1296,11 @@ def test_section2_aabd_ssi_validator_fail_territory_ssi(self, small_correct_file assert num_errors == 2 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( - f'{t5_model_name} People in territories must have value = 2:No for 19E.' + f'{t5_model_name} People in territories must have value = 2:No for REC_SSI.' ) assert errors[1].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[1].error_message == ( - f'{t5_model_name} People in territories must have value = 2:No for 19E.' + f'{t5_model_name} People in territories must have value = 2:No for REC_SSI.' ) @pytest.mark.parametrize("header,T4Stuff,T5Stuff", [ @@ -1371,5 +1371,5 @@ def test_section2_aabd_ssi_validator_fail_state_ssi(self, small_correct_file, he assert num_errors == 1 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( - f'{t5_model_name} People in states must have a valid value.' + f'{t5_model_name} People in states must have a valid value for REC_SSI.' ) From 3ad8be07149a9fa7b79f6cf2dd05f4e0d1ea76d6 Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 17 May 2024 13:53:12 -0400 Subject: [PATCH 23/24] lint --- .../tdpservice/parsers/case_consistency_validator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 948d0c95b..833a66540 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -424,7 +424,8 @@ def __validate_t5_aabd_and_ssi(self): record, field='REC_AID_TOTALLY_DISABLED', msg=( - f'{t5_model_name} Adults in territories must have a valid value for REC_AID_TOTALLY_DISABLED.' + f'{t5_model_name} Adults in territories must have a valid ' + 'value for REC_AID_TOTALLY_DISABLED.' ) ) num_errors += 1 @@ -434,7 +435,8 @@ def __validate_t5_aabd_and_ssi(self): record, field='REC_AID_TOTALLY_DISABLED', msg=( - f'{t5_model_name} People in states should not have a value of 1 for REC_AID_TOTALLY_DISABLED.' + f'{t5_model_name} People in states should not have a value ' + 'of 1 for REC_AID_TOTALLY_DISABLED.' ) ) num_errors += 1 From 5e9897110bb0a1a4c22897bff337aea73871f25a Mon Sep 17 00:00:00 2001 From: Jan Timpe Date: Fri, 17 May 2024 15:03:55 -0400 Subject: [PATCH 24/24] update ftl error language --- tdrs-backend/tdpservice/parsers/case_consistency_validator.py | 4 +++- tdrs-backend/tdpservice/parsers/test/test_case_consistency.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py index 833a66540..db90a99e8 100644 --- a/tdrs-backend/tdpservice/parsers/case_consistency_validator.py +++ b/tdrs-backend/tdpservice/parsers/case_consistency_validator.py @@ -361,7 +361,9 @@ def __validate_s2_records_are_related(self): )) elif closure_reason == '03' and not is_ssp: num_errors += self.__validate_case_closure_ftl(t4, t5s, ( - 'At least one person who is HoH or spouse of HoH on case must have FTL months >=60.' + 'At least one person who is head-of-household or spouse of head-of-household ' + 'on case must have countable months toward time limit >= 60 since ' + 'CLOSURE_REASON = 03: federal 5 year time limit.' )) if len(t5s) == 0: for record, schema in t4s: diff --git a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py index cdeecc381..f18eb7e3e 100644 --- a/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py +++ b/tdrs-backend/tdpservice/parsers/test/test_case_consistency.py @@ -816,7 +816,9 @@ def test_section2_validator_fail_case_closure_ftl(self, small_correct_file, head assert num_errors == 1 assert errors[0].error_type == ParserErrorCategoryChoices.CASE_CONSISTENCY assert errors[0].error_message == ( - 'At least one person who is HoH or spouse of HoH on case must have FTL months >=60.' + 'At least one person who is head-of-household or spouse of head-of-household ' + 'on case must have countable months toward time limit >= 60 since ' + 'CLOSURE_REASON = 03: federal 5 year time limit.' ) @pytest.mark.parametrize("header,T4Stuff,T5Stuff,stt_type", [