From 5c9652b35fc2a1a0645727d1b9fbeee5abc6d0c4 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 9 Apr 2026 06:59:23 -0400 Subject: [PATCH 1/4] Add Census SPM work expense formulas --- .../parameters/gov/census/index.yaml | 4 ++ .../parameters/gov/census/spm/index.yaml | 4 ++ .../gov/census/spm/work_expense/index.yaml | 4 ++ .../spm/work_expense/weekly_amount.yaml | 31 ++++++++++++++ ...m_unit_capped_work_childcare_expenses.yaml | 42 +++++++++++++++++++ .../childcare/spm_unit_work_expenses.yaml | 22 ++++++++++ .../household/income/person/weeks_worked.yaml | 16 +++++++ policyengine_us/tools/default_uprating.py | 1 - ...spm_unit_capped_work_childcare_expenses.py | 10 ++++- .../spm_unit_head_spouse_earned_cap.py | 21 ++++++++++ .../childcare/spm_unit_work_expenses.py | 21 ++++++++++ .../variables/input/weeks_worked.py | 12 ++++++ 12 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 policyengine_us/parameters/gov/census/index.yaml create mode 100644 policyengine_us/parameters/gov/census/spm/index.yaml create mode 100644 policyengine_us/parameters/gov/census/spm/work_expense/index.yaml create mode 100644 policyengine_us/parameters/gov/census/spm/work_expense/weekly_amount.yaml create mode 100644 policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml create mode 100644 policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_work_expenses.yaml create mode 100644 policyengine_us/tests/policy/baseline/household/income/person/weeks_worked.yaml create mode 100644 policyengine_us/variables/household/expense/childcare/spm_unit_head_spouse_earned_cap.py create mode 100644 policyengine_us/variables/household/expense/childcare/spm_unit_work_expenses.py create mode 100644 policyengine_us/variables/input/weeks_worked.py diff --git a/policyengine_us/parameters/gov/census/index.yaml b/policyengine_us/parameters/gov/census/index.yaml new file mode 100644 index 00000000000..2852e302ad0 --- /dev/null +++ b/policyengine_us/parameters/gov/census/index.yaml @@ -0,0 +1,4 @@ +metadata: + propagate_metadata_to_children: true + economy: false + household: false diff --git a/policyengine_us/parameters/gov/census/spm/index.yaml b/policyengine_us/parameters/gov/census/spm/index.yaml new file mode 100644 index 00000000000..2852e302ad0 --- /dev/null +++ b/policyengine_us/parameters/gov/census/spm/index.yaml @@ -0,0 +1,4 @@ +metadata: + propagate_metadata_to_children: true + economy: false + household: false diff --git a/policyengine_us/parameters/gov/census/spm/work_expense/index.yaml b/policyengine_us/parameters/gov/census/spm/work_expense/index.yaml new file mode 100644 index 00000000000..2852e302ad0 --- /dev/null +++ b/policyengine_us/parameters/gov/census/spm/work_expense/index.yaml @@ -0,0 +1,4 @@ +metadata: + propagate_metadata_to_children: true + economy: false + household: false diff --git a/policyengine_us/parameters/gov/census/spm/work_expense/weekly_amount.yaml b/policyengine_us/parameters/gov/census/spm/work_expense/weekly_amount.yaml new file mode 100644 index 00000000000..e54dbe89dc1 --- /dev/null +++ b/policyengine_us/parameters/gov/census/spm/work_expense/weekly_amount.yaml @@ -0,0 +1,31 @@ +description: Weekly work-expense deduction amount used in Census Supplemental Poverty Measure calculations. +values: + 2009-01-01: 28.0500 + 2010-01-01: 25.5000 + 2011-01-01: 27.1575 + 2012-01-01: 33.0225 + 2013-01-01: 39.3975 + 2014-01-01: 39.2530 + 2015-01-01: 40.0945 + 2016-01-01: 38.4710 + 2017-01-01: 36.3460 + 2018-01-01: 37.1025 + 2019-01-01: 39.7205 + 2020-01-01: 39.6100 + 2021-01-01: 38.0630 + 2022-01-01: 31.0080 + 2023-01-01: 33.4050 + 2024-01-01: 34.9945 + +metadata: + unit: currency-USD + period: year + uprating: gov.bls.cpi.cpi_u + label: Census SPM weekly work-expense deduction amount + reference: + - title: Supplemental Poverty Measure (SPM) Technical Documentation + href: https://www2.census.gov/programs-surveys/supplemental-poverty-measure/technical-documentation/spm_techdoc.pdf + - title: Poverty in the United States 2023 + href: https://www2.census.gov/library/publications/2024/demo/p60-283.pdf + - title: Poverty in the United States 2024 + href: https://www2.census.gov/library/publications/2025/demo/p60-287.pdf diff --git a/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml new file mode 100644 index 00000000000..90c3e794484 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml @@ -0,0 +1,42 @@ +- name: Combined work and childcare expenses are capped at the lower earner + period: 2024 + absolute_error_margin: 0.001 + input: + people: + head: + age: 35 + weeks_worked: 10 + employment_income: 40_000 + is_tax_unit_head: true + spouse: + age: 33 + weeks_worked: 20 + employment_income: 2_000 + is_tax_unit_spouse: true + child: + age: 8 + spm_units: + spm_unit: + members: [head, spouse, child] + spm_unit_pre_subsidy_childcare_expenses: 2_000 + output: + spm_unit_capped_work_childcare_expenses: 2_000 + +- name: Single-head units are capped at the head's earnings + period: 2024 + absolute_error_margin: 0.001 + input: + people: + head: + age: 41 + weeks_worked: 52 + employment_income: 1_500 + is_tax_unit_head: true + child: + age: 5 + spm_units: + spm_unit: + members: [head, child] + spm_unit_pre_subsidy_childcare_expenses: 3_000 + output: + spm_unit_capped_work_childcare_expenses: 1_500 diff --git a/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_work_expenses.yaml b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_work_expenses.yaml new file mode 100644 index 00000000000..262ebd511be --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_work_expenses.yaml @@ -0,0 +1,22 @@ +- name: Work expenses apply only to adult earners in the SPM unit + period: 2024 + absolute_error_margin: 0.001 + input: + people: + adult_worker: + age: 40 + weeks_worked: 10 + employment_income: 20_000 + adult_nonworker: + age: 38 + weeks_worked: 30 + employment_income: 0 + child: + age: 12 + weeks_worked: 52 + employment_income: 500 + spm_units: + spm_unit: + members: [adult_worker, adult_nonworker, child] + output: + spm_unit_work_expenses: 349.945 diff --git a/policyengine_us/tests/policy/baseline/household/income/person/weeks_worked.yaml b/policyengine_us/tests/policy/baseline/household/income/person/weeks_worked.yaml new file mode 100644 index 00000000000..ea615f3cea6 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/income/person/weeks_worked.yaml @@ -0,0 +1,16 @@ +- name: Weeks worked is an input variable + period: 2024 + input: + weeks_worked: 40 + output: + weeks_worked: 40 + +- name: Weeks worked carries forward when future data is missing + period: 2025 + input: + people: + person: + weeks_worked: + 2024: 37 + output: + weeks_worked: 37 diff --git a/policyengine_us/tools/default_uprating.py b/policyengine_us/tools/default_uprating.py index a69922ba8fe..eec084bc4c3 100644 --- a/policyengine_us/tools/default_uprating.py +++ b/policyengine_us/tools/default_uprating.py @@ -60,7 +60,6 @@ "spm_unit_spm_threshold", "non_sch_d_capital_gains", "spm_unit_state_tax_reported", - "spm_unit_capped_work_childcare_expenses", "farm_income", "taxable_403b_distributions", "qualified_tuition_expenses", diff --git a/policyengine_us/variables/household/expense/childcare/spm_unit_capped_work_childcare_expenses.py b/policyengine_us/variables/household/expense/childcare/spm_unit_capped_work_childcare_expenses.py index a1dfaf5d349..3e53113b30f 100644 --- a/policyengine_us/variables/household/expense/childcare/spm_unit_capped_work_childcare_expenses.py +++ b/policyengine_us/variables/household/expense/childcare/spm_unit_capped_work_childcare_expenses.py @@ -7,4 +7,12 @@ class spm_unit_capped_work_childcare_expenses(Variable): label = "SPM unit work and childcare expenses" definition_period = YEAR unit = USD - uprating = "gov.bls.cpi.cpi_u" + + def formula_2024(spm_unit, period, parameters): + work_expenses = spm_unit("spm_unit_work_expenses", period) + childcare_expenses = spm_unit( + "spm_unit_pre_subsidy_childcare_expenses", period + ) + earned_cap = spm_unit("spm_unit_head_spouse_earned_cap", period) + combined_expenses = np.maximum(work_expenses + childcare_expenses, 0) + return min_(combined_expenses, earned_cap) diff --git a/policyengine_us/variables/household/expense/childcare/spm_unit_head_spouse_earned_cap.py b/policyengine_us/variables/household/expense/childcare/spm_unit_head_spouse_earned_cap.py new file mode 100644 index 00000000000..69672e94ec0 --- /dev/null +++ b/policyengine_us/variables/household/expense/childcare/spm_unit_head_spouse_earned_cap.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class spm_unit_head_spouse_earned_cap(Variable): + value_type = float + entity = SPMUnit + label = "SPM unit lower-earner cap for work and childcare expenses" + definition_period = YEAR + unit = USD + + def formula(spm_unit, period, parameters): + person = spm_unit.members + is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) + earned_income = person("earned_income", period) + eligible_earnings = is_head_or_spouse * np.maximum(earned_income, 0) + + count_head_or_spouse = spm_unit.sum(is_head_or_spouse) + total_earned = spm_unit.sum(eligible_earnings) + max_earned = spm_unit.max(eligible_earnings) + + return where(count_head_or_spouse > 1, total_earned - max_earned, total_earned) diff --git a/policyengine_us/variables/household/expense/childcare/spm_unit_work_expenses.py b/policyengine_us/variables/household/expense/childcare/spm_unit_work_expenses.py new file mode 100644 index 00000000000..223d3817742 --- /dev/null +++ b/policyengine_us/variables/household/expense/childcare/spm_unit_work_expenses.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class spm_unit_work_expenses(Variable): + value_type = float + entity = SPMUnit + label = "SPM unit work expenses" + definition_period = YEAR + unit = USD + + def formula(spm_unit, period, parameters): + person = spm_unit.members + weeks_worked = person("weeks_worked", period) + is_adult = person("is_adult", period) + earned_income = person("earned_income", period) + weekly_amount = parameters(period).gov.census.spm.work_expense.weekly_amount + + eligible_weeks = is_adult * (earned_income > 0) * np.clip( + weeks_worked, 0, 52 + ) + return spm_unit.sum(eligible_weeks) * weekly_amount diff --git a/policyengine_us/variables/input/weeks_worked.py b/policyengine_us/variables/input/weeks_worked.py new file mode 100644 index 00000000000..14d40b279bf --- /dev/null +++ b/policyengine_us/variables/input/weeks_worked.py @@ -0,0 +1,12 @@ +from policyengine_us.model_api import * + + +class weeks_worked(Variable): + value_type = int + entity = Person + label = "Weeks worked during the year" + definition_period = YEAR + documentation = "Number of weeks worked during the year." + + def formula_2025(person, period, parameters): + return person("weeks_worked", period.last_year) From 569d7b197db15e17adf369428c51efd7ab8346a6 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 9 Apr 2026 07:02:09 -0400 Subject: [PATCH 2/4] Add changelog entry for SPM work expenses --- changelog_entry.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..a8f81acf597 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,5 @@ +- bump: patch + changes: + added: + - Add Census SPM weekly work-expense parameters and formulas for work and childcare expense caps. + date: 2026-04-09 00:00:00 From 1501189dd1a44388bb6cf57857dc8eb96f3b7fc1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 9 Apr 2026 07:20:38 -0400 Subject: [PATCH 3/4] Fix SPM childcare cap formula --- ...m_unit_capped_work_childcare_expenses.yaml | 26 +++++++++++++++++-- ...spm_unit_capped_work_childcare_expenses.py | 6 +++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml index 90c3e794484..8b24f665610 100644 --- a/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml +++ b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml @@ -22,7 +22,7 @@ output: spm_unit_capped_work_childcare_expenses: 2_000 -- name: Single-head units are capped at the head's earnings +- name: Work expenses are preserved when they already exceed the earnings cap period: 2024 absolute_error_margin: 0.001 input: @@ -39,4 +39,26 @@ members: [head, child] spm_unit_pre_subsidy_childcare_expenses: 3_000 output: - spm_unit_capped_work_childcare_expenses: 1_500 + spm_unit_capped_work_childcare_expenses: 1_819.714 + +- name: Childcare is capped by the remaining lower-earner earnings after work expenses + period: 2024 + absolute_error_margin: 0.001 + input: + people: + head: + age: 46 + weeks_worked: 52 + employment_income: 9_098.57 + is_tax_unit_head: true + spouse: + age: 46 + weeks_worked: 52 + employment_income: 30_000 + is_tax_unit_spouse: true + spm_units: + spm_unit: + members: [head, spouse] + spm_unit_pre_subsidy_childcare_expenses: 21_580 + output: + spm_unit_capped_work_childcare_expenses: 9_098.57 diff --git a/policyengine_us/variables/household/expense/childcare/spm_unit_capped_work_childcare_expenses.py b/policyengine_us/variables/household/expense/childcare/spm_unit_capped_work_childcare_expenses.py index 3e53113b30f..619b7f76673 100644 --- a/policyengine_us/variables/household/expense/childcare/spm_unit_capped_work_childcare_expenses.py +++ b/policyengine_us/variables/household/expense/childcare/spm_unit_capped_work_childcare_expenses.py @@ -14,5 +14,7 @@ def formula_2024(spm_unit, period, parameters): "spm_unit_pre_subsidy_childcare_expenses", period ) earned_cap = spm_unit("spm_unit_head_spouse_earned_cap", period) - combined_expenses = np.maximum(work_expenses + childcare_expenses, 0) - return min_(combined_expenses, earned_cap) + remaining_childcare_cap = np.maximum(earned_cap - work_expenses, 0) + return work_expenses + min_( + np.maximum(childcare_expenses, 0), remaining_childcare_cap + ) From a329d2d0f1132840a8c7b9029c680b1970546a41 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 9 Apr 2026 07:40:25 -0400 Subject: [PATCH 4/4] Catch SPM childcare cap corner cases --- ...m_unit_capped_work_childcare_expenses.yaml | 43 +++++ .../spm_unit_head_spouse_earned_cap.yaml | 163 ++++++++++++++++++ .../childcare/spm_unit_work_expenses.yaml | 19 ++ .../is_unmarried_partner_of_household_head.py | 8 + .../spm_unit_head_spouse_earned_cap.py | 29 +++- .../childcare/spm_unit_work_expenses.py | 2 +- .../childcare/spm_work_childcare_earnings.py | 15 ++ 7 files changed, 274 insertions(+), 5 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_head_spouse_earned_cap.yaml create mode 100644 policyengine_us/variables/household/demographic/person/is_unmarried_partner_of_household_head.py create mode 100644 policyengine_us/variables/household/expense/childcare/spm_work_childcare_earnings.py diff --git a/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml index 8b24f665610..d9bbafe41ea 100644 --- a/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml +++ b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_capped_work_childcare_expenses.yaml @@ -7,6 +7,7 @@ age: 35 weeks_worked: 10 employment_income: 40_000 + is_household_head: true is_tax_unit_head: true spouse: age: 33 @@ -15,6 +16,9 @@ is_tax_unit_spouse: true child: age: 8 + tax_units: + tax_unit: + members: [head, spouse, child] spm_units: spm_unit: members: [head, spouse, child] @@ -31,9 +35,13 @@ age: 41 weeks_worked: 52 employment_income: 1_500 + is_household_head: true is_tax_unit_head: true child: age: 5 + tax_units: + tax_unit: + members: [head, child] spm_units: spm_unit: members: [head, child] @@ -50,15 +58,50 @@ age: 46 weeks_worked: 52 employment_income: 9_098.57 + is_household_head: true is_tax_unit_head: true spouse: age: 46 weeks_worked: 52 employment_income: 30_000 is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [head, spouse] spm_units: spm_unit: members: [head, spouse] spm_unit_pre_subsidy_childcare_expenses: 21_580 output: spm_unit_capped_work_childcare_expenses: 9_098.57 + +- name: Unmarried partner counts toward the reference person's remaining childcare cap + period: 2024 + absolute_error_margin: 0.001 + input: + people: + head: + age: 35 + weeks_worked: 10 + employment_income: 40_000 + is_household_head: true + is_tax_unit_head: true + partner: + age: 33 + weeks_worked: 20 + employment_income: 2_000 + is_unmarried_partner_of_household_head: true + is_tax_unit_head: true + child: + age: 8 + tax_units: + reference_unit: + members: [head, child] + partner_unit: + members: [partner] + spm_units: + spm_unit: + members: [head, partner, child] + spm_unit_pre_subsidy_childcare_expenses: 2_000 + output: + spm_unit_capped_work_childcare_expenses: 2_000 diff --git a/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_head_spouse_earned_cap.yaml b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_head_spouse_earned_cap.yaml new file mode 100644 index 00000000000..966a3e49b63 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_head_spouse_earned_cap.yaml @@ -0,0 +1,163 @@ +- name: Household head and spouse determine the SPM childcare earnings cap + period: 2024 + absolute_error_margin: 0.001 + input: + people: + reference_person: + age: 35 + employment_income: 40_000 + is_household_head: true + is_tax_unit_spouse: true + spouse: + age: 45 + employment_income: 8_000 + is_tax_unit_head: true + child: + age: 8 + tax_units: + tax_unit: + members: [reference_person, spouse, child] + spm_units: + spm_unit: + members: [reference_person, spouse, child] + output: + spm_unit_head_spouse_earned_cap: 8_000 + +- name: Household head and unmarried partner determine the SPM childcare earnings cap + period: 2024 + absolute_error_margin: 0.001 + input: + people: + reference_person: + age: 35 + employment_income: 40_000 + is_household_head: true + is_tax_unit_head: true + partner: + age: 33 + employment_income: 2_500 + is_unmarried_partner_of_household_head: true + is_tax_unit_head: true + child: + age: 8 + tax_units: + reference_unit: + members: [reference_person, child] + partner_unit: + members: [partner] + spm_units: + spm_unit: + members: [reference_person, partner, child] + output: + spm_unit_head_spouse_earned_cap: 2_500 + +- name: Farm income counts toward the Census SPM childcare earnings cap + period: 2024 + absolute_error_margin: 0.001 + input: + people: + reference_person: + age: 35 + employment_income: 40_000 + is_household_head: true + is_tax_unit_head: true + spouse: + age: 33 + farm_income: 3_000 + is_tax_unit_spouse: true + child: + age: 8 + tax_units: + tax_unit: + members: [reference_person, spouse, child] + spm_units: + spm_unit: + members: [reference_person, spouse, child] + output: + spm_unit_head_spouse_earned_cap: 3_000 + +- name: Extra adults in the SPM unit do not expand the reference person's childcare cap + period: 2024 + absolute_error_margin: 0.001 + input: + people: + reference_person: + age: 35 + employment_income: 40_000 + is_household_head: true + is_tax_unit_head: true + partner: + age: 33 + employment_income: 5_000 + is_unmarried_partner_of_household_head: true + is_tax_unit_head: true + unrelated_adult: + age: 55 + employment_income: 1_000 + is_tax_unit_head: true + child: + age: 8 + tax_units: + reference_unit: + members: [reference_person, child] + partner_unit: + members: [partner] + unrelated_unit: + members: [unrelated_adult] + spm_units: + spm_unit: + members: [reference_person, partner, unrelated_adult, child] + output: + spm_unit_head_spouse_earned_cap: 5_000 + +- name: Spouse takes precedence over unmarried partner in the reference person's childcare cap + period: 2024 + absolute_error_margin: 0.001 + input: + people: + reference_person: + age: 35 + employment_income: 40_000 + is_household_head: true + is_tax_unit_head: true + spouse: + age: 33 + employment_income: 6_000 + is_tax_unit_spouse: true + partner: + age: 31 + employment_income: 2_500 + is_unmarried_partner_of_household_head: true + is_tax_unit_head: true + tax_units: + reference_unit: + members: [reference_person, spouse] + partner_unit: + members: [partner] + spm_units: + spm_unit: + members: [reference_person, spouse, partner] + output: + spm_unit_head_spouse_earned_cap: 6_000 + +- name: The childcare cap falls back to tax-unit head/spouse when no reference person is provided + period: 2024 + absolute_error_margin: 0.001 + input: + people: + head: + age: 35 + employment_income: 40_000 + is_tax_unit_head: true + spouse: + age: 33 + employment_income: 4_000 + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [head, spouse] + spm_units: + spm_unit: + members: [head, spouse] + output: + spm_unit_head_spouse_earned_cap: 4_000 diff --git a/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_work_expenses.yaml b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_work_expenses.yaml index 262ebd511be..d3c7c74d672 100644 --- a/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_work_expenses.yaml +++ b/policyengine_us/tests/policy/baseline/household/expense/childcare/spm_unit_work_expenses.yaml @@ -20,3 +20,22 @@ members: [adult_worker, adult_nonworker, child] output: spm_unit_work_expenses: 349.945 + +- name: Farm-income-only adults still incur Census SPM work expenses + period: 2024 + absolute_error_margin: 0.001 + input: + people: + adult_farmer: + age: 40 + weeks_worked: 10 + farm_income: 20_000 + adult_nonworker: + age: 38 + weeks_worked: 30 + farm_income: 0 + spm_units: + spm_unit: + members: [adult_farmer, adult_nonworker] + output: + spm_unit_work_expenses: 349.945 diff --git a/policyengine_us/variables/household/demographic/person/is_unmarried_partner_of_household_head.py b/policyengine_us/variables/household/demographic/person/is_unmarried_partner_of_household_head.py new file mode 100644 index 00000000000..e8d0f44cebc --- /dev/null +++ b/policyengine_us/variables/household/demographic/person/is_unmarried_partner_of_household_head.py @@ -0,0 +1,8 @@ +from policyengine_us.model_api import * + + +class is_unmarried_partner_of_household_head(Variable): + value_type = bool + entity = Person + label = "is unmarried partner of household head" + definition_period = YEAR diff --git a/policyengine_us/variables/household/expense/childcare/spm_unit_head_spouse_earned_cap.py b/policyengine_us/variables/household/expense/childcare/spm_unit_head_spouse_earned_cap.py index 69672e94ec0..3117292a487 100644 --- a/policyengine_us/variables/household/expense/childcare/spm_unit_head_spouse_earned_cap.py +++ b/policyengine_us/variables/household/expense/childcare/spm_unit_head_spouse_earned_cap.py @@ -4,17 +4,38 @@ class spm_unit_head_spouse_earned_cap(Variable): value_type = float entity = SPMUnit - label = "SPM unit lower-earner cap for work and childcare expenses" + label = "SPM unit lower-earner cap for the reference person and spouse/partner" definition_period = YEAR unit = USD def formula(spm_unit, period, parameters): person = spm_unit.members + is_reference_person = person("is_household_head", period) + has_reference_person = spm_unit.any(is_reference_person) is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) - earned_income = person("earned_income", period) - eligible_earnings = is_head_or_spouse * np.maximum(earned_income, 0) + is_reference_person_spouse = ( + person.tax_unit.any(is_reference_person) + & is_head_or_spouse + & ~is_reference_person + ) + is_reference_person_partner = person( + "is_unmarried_partner_of_household_head", period + ) + has_spouse = spm_unit.any(is_reference_person_spouse) + eligible_reference_person_or_partner = ( + is_reference_person + | is_reference_person_spouse + | (~has_spouse & is_reference_person_partner) + ) + eligible_people = where( + has_reference_person, + eligible_reference_person_or_partner, + is_head_or_spouse, + ) + earned_income = person("spm_work_childcare_earnings", period) + eligible_earnings = eligible_people * np.maximum(earned_income, 0) - count_head_or_spouse = spm_unit.sum(is_head_or_spouse) + count_head_or_spouse = spm_unit.sum(eligible_people) total_earned = spm_unit.sum(eligible_earnings) max_earned = spm_unit.max(eligible_earnings) diff --git a/policyengine_us/variables/household/expense/childcare/spm_unit_work_expenses.py b/policyengine_us/variables/household/expense/childcare/spm_unit_work_expenses.py index 223d3817742..6ed82434f9c 100644 --- a/policyengine_us/variables/household/expense/childcare/spm_unit_work_expenses.py +++ b/policyengine_us/variables/household/expense/childcare/spm_unit_work_expenses.py @@ -12,7 +12,7 @@ def formula(spm_unit, period, parameters): person = spm_unit.members weeks_worked = person("weeks_worked", period) is_adult = person("is_adult", period) - earned_income = person("earned_income", period) + earned_income = person("spm_work_childcare_earnings", period) weekly_amount = parameters(period).gov.census.spm.work_expense.weekly_amount eligible_weeks = is_adult * (earned_income > 0) * np.clip( diff --git a/policyengine_us/variables/household/expense/childcare/spm_work_childcare_earnings.py b/policyengine_us/variables/household/expense/childcare/spm_work_childcare_earnings.py new file mode 100644 index 00000000000..382d3cb015d --- /dev/null +++ b/policyengine_us/variables/household/expense/childcare/spm_work_childcare_earnings.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class spm_work_childcare_earnings(Variable): + value_type = float + entity = Person + label = "earnings relevant to Census SPM work and childcare expense caps" + definition_period = YEAR + unit = USD + + adds = [ + "employment_income", + "self_employment_income", + "farm_income", + ]