diff --git a/src/microplex_us/policyengine/target_profiles.py b/src/microplex_us/policyengine/target_profiles.py index dfa176a..c5cdd23 100644 --- a/src/microplex_us/policyengine/target_profiles.py +++ b/src/microplex_us/policyengine/target_profiles.py @@ -770,15 +770,6 @@ def _target_cell_key(cell: PolicyEngineUSTargetCell) -> PolicyEngineUSTargetCell "Loaded SOI state AGI sources provide return counts and AGI amounts, " "not filer-person counts by AGI band." ), - ( - "alimony_expense", - "national", - None, - None, - ): ( - "No accepted primary source mapping is encoded for this " - "survey/model-input expense variable." - ), ( "child_support_expense", "national", diff --git a/src/microplex_us/targets/arch.py b/src/microplex_us/targets/arch.py index 6f2edbe..f17a699 100644 --- a/src/microplex_us/targets/arch.py +++ b/src/microplex_us/targets/arch.py @@ -92,6 +92,7 @@ "taxable_interest_amount": "taxable_interest_income", "tax_exempt_interest_amount": "tax_exempt_interest_income", "alimony_received_amount": "alimony_income", + "alimony_paid_amount": "alimony_expense", "personal_dividend_income_amount": "dividend_income", "ordinary_dividends_amount": "dividend_income", "qualified_dividends_amount": "qualified_dividend_income", @@ -275,6 +276,16 @@ ), "tanf_family_count": ("spm_unit_count", EntityType.SPM_UNIT, "tanf"), "tanf_recipient_count": ("person_count", EntityType.PERSON, "tanf"), + "alimony_received_returns": ( + "tax_unit_count", + EntityType.TAX_UNIT, + "alimony_income", + ), + "alimony_paid_returns": ( + "tax_unit_count", + EntityType.TAX_UNIT, + "alimony_expense", + ), } ARCH_FACT_CONCEPT_TO_TARGET = { @@ -300,6 +311,16 @@ "taxable_social_security_returns", "COUNT", ), + "irs_soi.returns_with_alimony_received": ( + "alimony_received_returns", + "COUNT", + ), + "irs_soi.alimony_received": ("alimony_received_amount", "AMOUNT"), + "irs_soi.returns_with_alimony_paid": ( + "alimony_paid_returns", + "COUNT", + ), + "irs_soi.alimony_paid": ("alimony_paid_amount", "AMOUNT"), "irs_soi.returns_with_income_tax_after_credits": ( "income_tax_liability_returns", "COUNT", @@ -1067,7 +1088,6 @@ ARCH_DEPRIORITIZED_SURVEY_OR_MODEL_GAP_VARIABLES = frozenset( { - "alimony_expense", "child_support_expense", "child_support_received", "health_insurance_premiums_without_medicare_part_b", diff --git a/tests/policyengine/test_target_profiles.py b/tests/policyengine/test_target_profiles.py index e1d1529..15bedb5 100644 --- a/tests/policyengine/test_target_profiles.py +++ b/tests/policyengine/test_target_profiles.py @@ -370,10 +370,10 @@ def test_source_backed_profile_excludes_only_documented_non_source_cells() -> No } assert len(broad_cells) == 189 - assert len(exclusion_reasons) == 23 + assert len(exclusion_reasons) == 22 assert all(reason for reason in exclusion_reasons.values()) assert set(exclusion_reasons) <= broad_cells - assert len(source_backed_cells) == 166 + assert len(source_backed_cells) == 167 assert source_backed_cells == broad_cells - set(exclusion_reasons) assert ( "childcare_expenses", @@ -417,6 +417,18 @@ def test_source_backed_profile_excludes_only_documented_non_source_cells() -> No None, None, ) in source_backed_cells + assert ( + "alimony_expense", + "national", + None, + None, + ) in source_backed_cells + assert ( + "alimony_income", + "national", + None, + None, + ) in source_backed_cells assert ( "medicare_part_b_premiums", "national", diff --git a/tests/targets/test_arch.py b/tests/targets/test_arch.py index 3068b9d..b34e6dc 100644 --- a/tests/targets/test_arch.py +++ b/tests/targets/test_arch.py @@ -907,6 +907,19 @@ def test_arch_provider_matches_current_profile_aliases(tmp_path): ( 9, 2, + "alimony_paid_amount", + 2023, + 25.0, + "AMOUNT", + None, + "IRS_SOI", + "SOI", + None, + None, + ), + ( + 10, + 2, "schedule_c_income_amount", 2023, 30.0, @@ -918,7 +931,7 @@ def test_arch_provider_matches_current_profile_aliases(tmp_path): None, ), ( - 10, + 11, 1, "medicaid_total_enrollment", 2024, @@ -931,7 +944,7 @@ def test_arch_provider_matches_current_profile_aliases(tmp_path): None, ), ( - 11, + 12, 2, "wages_salaries_amount", 2023, @@ -944,7 +957,7 @@ def test_arch_provider_matches_current_profile_aliases(tmp_path): None, ), ( - 12, + 13, 2, "wages_salaries_returns", 2023, @@ -957,7 +970,7 @@ def test_arch_provider_matches_current_profile_aliases(tmp_path): None, ), ( - 13, + 14, 2, "schedule_c_income_returns", 2023, @@ -985,6 +998,11 @@ def test_arch_provider_matches_current_profile_aliases(tmp_path): "geo_level": "national", "domain_variable": None, }, + { + "variable": "alimony_expense", + "geo_level": "national", + "domain_variable": None, + }, { "variable": "self_employment_income", "geo_level": "national", @@ -1027,6 +1045,7 @@ def test_arch_provider_matches_current_profile_aliases(tmp_path): 11, 12, 13, + 14, } variables_by_id = { target.metadata["target_id"]: target.metadata["variable"] @@ -1034,11 +1053,12 @@ def test_arch_provider_matches_current_profile_aliases(tmp_path): } assert variables_by_id == { 8: "alimony_income", - 9: "self_employment_income", - 10: "person_count", - 11: "employment_income", + 9: "alimony_expense", + 10: "self_employment_income", + 11: "person_count", 12: "employment_income", - 13: "self_employment_income", + 13: "employment_income", + 14: "self_employment_income", } diff --git a/tests/targets/test_arch_facts.py b/tests/targets/test_arch_facts.py index 0958b7c..21dc75e 100644 --- a/tests/targets/test_arch_facts.py +++ b/tests/targets/test_arch_facts.py @@ -1712,6 +1712,82 @@ def test_arch_consumer_fact_jsonl_provider_maps_state_broad_soi_concepts( ) +def test_arch_consumer_fact_jsonl_provider_maps_soi_alimony_concepts( + tmp_path: Path, +) -> None: + consumer_jsonl = tmp_path / "consumer_facts.jsonl" + rows = [ + _consumer_fact( + "soi-alimony-received-returns", + concept="irs_soi.returns_with_alimony_received", + domain="all_individual_income_tax_returns", + source_name="irs_soi", + source_table="Publication 1304 Table 1.4", + period={"type": "tax_year", "value": 2023}, + value=183_582, + ), + _consumer_fact( + "soi-alimony-received-amount", + concept="irs_soi.alimony_received", + domain="all_individual_income_tax_returns", + source_name="irs_soi", + source_table="Publication 1304 Table 1.4", + period={"type": "tax_year", "value": 2023}, + value=6_686_429_000, + unit="usd", + ), + _consumer_fact( + "soi-alimony-paid-returns", + concept="irs_soi.returns_with_alimony_paid", + domain="all_individual_income_tax_returns", + source_name="irs_soi", + source_table="Publication 1304 Table 1.4", + period={"type": "tax_year", "value": 2023}, + value=278_541, + ), + _consumer_fact( + "soi-alimony-paid-amount", + concept="irs_soi.alimony_paid", + domain="all_individual_income_tax_returns", + source_name="irs_soi", + source_table="Publication 1304 Table 1.4", + period={"type": "tax_year", "value": 2023}, + value=7_497_135_000, + unit="usd", + ), + ] + consumer_jsonl.write_text( + "\n".join(json.dumps(row, sort_keys=True) for row in rows) + "\n" + ) + + target_set = ArchConsumerFactJSONLTargetProvider(consumer_jsonl).load_target_set( + TargetQuery(period=2023) + ) + targets_by_arch_variable = { + target.metadata["arch_variable"]: target for target in target_set.targets + } + + received_amount = targets_by_arch_variable["alimony_received_amount"] + assert received_amount.metadata["variable"] == "alimony_income" + assert received_amount.measure == "alimony_income" + + received_returns = targets_by_arch_variable["alimony_received_returns"] + assert received_returns.metadata["variable"] == "tax_unit_count" + assert received_returns.aggregation.value == "count" + assert ("alimony_income", ">", "0") in _target_filter_tuples( + received_returns + ) + + paid_amount = targets_by_arch_variable["alimony_paid_amount"] + assert paid_amount.metadata["variable"] == "alimony_expense" + assert paid_amount.measure == "alimony_expense" + + paid_returns = targets_by_arch_variable["alimony_paid_returns"] + assert paid_returns.metadata["variable"] == "tax_unit_count" + assert paid_returns.aggregation.value == "count" + assert ("alimony_expense", ">", "0") in _target_filter_tuples(paid_returns) + + def test_arch_consumer_fact_jsonl_provider_maps_eitc_by_agi_and_children( tmp_path: Path, ) -> None: