Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- bump: patch
changes:
fixed:
- Cap all state TANF benefit formulas to prevent negative countable income from inflating benefits above the payment standard. Fixes NC household size .sum() bug and adds min_() caps to 38 state programs (AL, AK, AR, AZ, CA, CO, CT, DC, FL, HI, IA, IL, IN, KS, LA, MA, MD, MI, MN, MO, MS, MT, NC, ND, NE, NH, NJ, NM, NV, NY, OH, OK, OR, PA, RI, SC, SD, TX, UT, VT, WV, WY). Previously, negative countable income could produce benefits exceeding $1M per household, inflating total TANF microsimulation from $9B target to $17.9T.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Tests that TANF benefits are capped at the maximum payment amount,
# even when countable income is negative.
# This prevents the bug where negative income inflates benefits
# beyond the payment standard (e.g., benefit = max - (-5M) = 5M+).

- name: CA TANF benefit capped at max payment when countable income is negative.
period: 2023
input:
ca_tanf_eligible: true
ca_tanf_maximum_payment: 1_000
ca_tanf_countable_income_recipient: -5_000
output:
ca_tanf: 1_000

- name: PA TANF benefit capped at max when countable income is negative.
period: 2025-01
input:
pa_tanf_eligible: true
pa_tanf_maximum_benefit: 500
pa_tanf_countable_income: -10_000
output:
pa_tanf: 500

- name: NY TANF benefit capped at need standard when income is negative.
period: 2025-01
input:
ny_tanf_eligible: true
ny_tanf_need_standard: 800
ny_tanf_countable_income: -1_000
output:
ny_tanf: 800

- name: FL TCA benefit capped at payment standard when income is negative.
period: 2025-01
input:
fl_tca_eligible: true
fl_tca_payment_standard: 300
fl_tca_countable_income: -50_000
output:
fl_tca: 300

- name: NC TANF reduced need standard capped at need standard when income is negative.
period: 2025
input:
people:
person1:
age: 30
households:
household1:
members: [person1]
state_code: NC
spm_units:
spm_unit:
members: [person1]
nc_tanf_need_standard: 500
nc_tanf_countable_earned_income: -10_000
nc_tanf_countable_gross_unearned_income: 0
output:
nc_tanf_reduced_need_standard: 500

- name: MN MFIP benefit capped at transitional standard when unearned is negative.
period: 2025-01
input:
mn_mfip_eligible: true
mn_mfip_transitional_standard: 600
mn_mfip_family_wage_level: 800
mn_mfip_countable_earned_income: 0
mn_mfip_countable_unearned_income: -100_000
output:
mn_mfip: 600

- name: OH OWF benefit capped at payment standard when income is negative.
period: 2025-01
input:
oh_owf_eligible: true
oh_owf_payment_standard: 400
oh_owf_countable_income: -5_000
output:
oh_owf: 400
5 changes: 4 additions & 1 deletion policyengine_us/variables/gov/states/ak/dpa/atap/ak_atap.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ def formula(spm_unit, period, parameters):
# multiplied by (size 2 max payment / size 2 need standard)
# The ratio uses fixed size-2 values, not the family's actual size
income_deficit = max_(need_standard - countable_income, 0)
# Cap deficit at need standard to prevent negative income
# from inflating benefits above the maximum.
capped_deficit = min_(income_deficit, need_standard)
size_2_max = p.payment.base
# Per 7 AAC 45.525: ratable reduction formula always uses size-2 as base
size_2_need = p.need_standard.amount["2"]
ratable_reduction = size_2_max / size_2_need
return income_deficit * ratable_reduction
return capped_deficit * ratable_reduction
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/al/dhs/tanf/al_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ class al_tanf(Variable):
def formula(spm_unit, period, parameters):
payment_standard = spm_unit("al_tanf_payment_standard", period)
countable_income = spm_unit("al_tanf_countable_income", period)
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
return min_(benefit, payment_standard)
4 changes: 3 additions & 1 deletion policyengine_us/variables/gov/states/ar/dhs/tea/ar_tea.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ def formula(spm_unit, period, parameters):

# When gross income >= trigger: payment is 50% of max (no subtraction)
# When gross income < trigger: payment is max - countable income
below_trigger_benefit = max_(maximum_benefit - countable_income, 0)
capped_benefit = min_(below_trigger_benefit, maximum_benefit)
return where(
above_trigger,
reduced_payment,
max_(maximum_benefit - countable_income, 0),
capped_benefit,
)
5 changes: 4 additions & 1 deletion policyengine_us/variables/gov/states/az/hhs/tanf/az_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ class az_tanf(Variable):
def formula(spm_unit, period, parameters):
payment_standard = spm_unit("az_tanf_payment_standard", period)
countable_income = spm_unit("az_tanf_countable_income", period)
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
# Cap benefit at payment standard to prevent negative income
# from inflating benefits above the maximum.
return min_(benefit, payment_standard)
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ def formula(spm_unit, period, parameters):
eligible_people_based_on_immigration_status[mask]
/ spm_unit_size[mask]
)
return prorated_fraction * max_(maximum_payment - countable_income, 0)
benefit = max_(maximum_payment - countable_income, 0)
# Cap benefit at maximum payment to prevent negative income
# from inflating benefits above the maximum.
capped_benefit = min_(benefit, maximum_payment)
return prorated_fraction * capped_benefit
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/co/cdhs/tanf/co_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ def formula(spm_unit, period, parameters):
"co_tanf_countable_gross_unearned_income",
],
)
return max_(grant_standard - income, 0)
benefit = max_(grant_standard - income, 0)
return min_(benefit, grant_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/ct/dss/tfa/ct_tfa.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ def formula(spm_unit, period, parameters):
countable_unearned_income = spm_unit(
"ct_tfa_countable_unearned_income", period
)
benefit_amount = max_(payment_standard - countable_unearned_income, 0)
raw_benefit = max_(payment_standard - countable_unearned_income, 0)
benefit_amount = min_(raw_benefit, payment_standard)
# When gross earnings are >= 171% of FPG, reduce the benefit by 20%
gross_earnings = add(spm_unit, period, ["tanf_gross_earned_income"])
fpg = spm_unit("tanf_fpg", period)
Expand Down
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/dc/dhs/tanf/dc_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ class dc_tanf(Variable):
def formula(spm_unit, period, parameters):
standard_payment = spm_unit("dc_tanf_standard_payment", period)
countable_income = spm_unit("dc_tanf_countable_income", period)
return max_(standard_payment - countable_income, 0)
benefit = max_(standard_payment - countable_income, 0)
return min_(benefit, standard_payment)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/fl/dcf/tanf/fl_tca.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ def formula(spm_unit, period, parameters):
countable_income = spm_unit("fl_tca_countable_income", period)

# Benefit = Payment Standard - Countable Income
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
return min_(benefit, payment_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/hi/dhs/tanf/hi_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ def formula(spm_unit, period, parameters):
countable_income = spm_unit("hi_tanf_countable_income", period)

# Benefit = SOA - countable income, floored at 0
return max_(maximum_benefit - countable_income, 0)
benefit = max_(maximum_benefit - countable_income, 0)
return min_(benefit, maximum_benefit)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/ia/dhs/fip/ia_fip.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ class ia_fip(Variable):
def formula(spm_unit, period, parameters):
payment_standard = spm_unit("ia_fip_payment_standard", period)
countable_income = spm_unit("ia_fip_countable_income", period)
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
return min_(benefit, payment_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/il/dhs/tanf/il_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ def formula(spm_unit, period, parameters):
countable_income = spm_unit(
"il_tanf_countable_income_for_grant_calculation", period
)
return max_(payment_level - countable_income, 0)
benefit = max_(payment_level - countable_income, 0)
return min_(benefit, payment_level)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/in/fssa/tanf/in_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ def formula(spm_unit, period, parameters):
countable_income = spm_unit(
"in_tanf_countable_income_for_payment", period
)
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
return min_(benefit, payment_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/ks/dcf/tanf/ks_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ def formula(spm_unit, period, parameters):
# Benefit = Payment Standard - Countable Income
maximum_benefit = spm_unit("ks_tanf_maximum_benefit", period)
countable_income = spm_unit("ks_tanf_countable_income", period)
return max_(maximum_benefit - countable_income, 0)
benefit = max_(maximum_benefit - countable_income, 0)
return min_(benefit, maximum_benefit)
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ def formula(spm_unit, period, parameters):
countable_income = spm_unit("la_fitap_countable_income", period)

# Benefit = flat grant minus countable income
return max_(flat_grant - countable_income, 0)
benefit = max_(flat_grant - countable_income, 0)
return min_(benefit, flat_grant)
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ def formula(spm_unit, period, parameters):
countable_income = spm_unit(
"ma_tafdc_applicable_income_grant_amount", period
)
return max_(0, payment_standard - countable_income)
benefit = max_(0, payment_standard - countable_income)
return min_(benefit, payment_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/md/tca/md_tca.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ class md_tca(Variable):
def formula(spm_unit, period, parameters):
grant_standard = spm_unit("md_tca_maximum_benefit", period)
countable_income = spm_unit("md_tca_countable_income", period)
return max_(grant_standard - countable_income, 0)
benefit = max_(grant_standard - countable_income, 0)
return min_(benefit, grant_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/mi/mdhhs/fip/mi_fip.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ def formula(spm_unit, period, parameters):

# BEM 518: Minimum benefit requirement is $10 deficit
# Negative benefits are set to 0
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
return min_(benefit, payment_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/mn/dcyf/mfip/mn_mfip.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ def formula(spm_unit, period, parameters):
# Step 2: Cap at transitional standard
earned_based_benefit = min_(fwl_minus_earned, transitional_standard)
# Step 3: Subtract countable unearned income, floor at zero
return max_(earned_based_benefit - countable_unearned, 0)
benefit = max_(earned_based_benefit - countable_unearned, 0)
return min_(benefit, transitional_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/mo/dss/tanf/mo_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ def formula(spm_unit, period, parameters):
maximum_benefit = spm_unit("mo_tanf_maximum_benefit", period)
countable_income = spm_unit("mo_tanf_countable_income", period)
benefit = max_(maximum_benefit - countable_income, 0)
capped_benefit = min_(benefit, maximum_benefit)
p = parameters(period).gov.states.mo.dss.tanf
return where(benefit >= p.minimum_payment, benefit, 0)
return where(capped_benefit >= p.minimum_payment, capped_benefit, 0)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/ms/dhs/tanf/ms_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ class ms_tanf(Variable):
def formula(spm_unit, period, parameters):
maximum_benefit = spm_unit("ms_tanf_maximum_benefit", period)
countable_income = spm_unit("ms_tanf_countable_income", period)
return max_(maximum_benefit - countable_income, 0)
benefit = max_(maximum_benefit - countable_income, 0)
return min_(benefit, maximum_benefit)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/mt/dhs/tanf/mt_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ class mt_tanf(Variable):
def formula(spm_unit, period, parameters):
standard_payment = spm_unit("mt_tanf_payment_standard", period)
countable_income = spm_unit("mt_tanf_countable_income", period)
return max_(standard_payment - countable_income, 0)
benefit = max_(standard_payment - countable_income, 0)
return min_(benefit, standard_payment)
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ def formula(spm_unit, period, parameters):
# Eligible members are those with no SSI income
eligible_members = ssi_income <= 0

return eligible_members.sum()
return spm_unit.sum(eligible_members)
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ def formula(spm_unit, period, parameters):
)
need_standard = spm_unit("nc_tanf_need_standard", period)

return max_(need_standard - income, 0)
reduced = max_(need_standard - income, 0)
return min_(reduced, need_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/nd/dhs/tanf/nd_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ class nd_tanf(Variable):
def formula(spm_unit, period, parameters):
standard_of_need = spm_unit("nd_tanf_standard_of_need", period)
countable_income = spm_unit("nd_tanf_countable_income", period)
return max_(standard_of_need - countable_income, 0)
benefit = max_(standard_of_need - countable_income, 0)
return min_(benefit, standard_of_need)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/ne/dhhs/adc/ne_adc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ class ne_adc(Variable):
def formula(spm_unit, period, parameters):
maximum_benefit = spm_unit("ne_adc_maximum_benefit", period)
unearned = add(spm_unit, period, ["tanf_gross_unearned_income"])
return max_(maximum_benefit - unearned, 0)
benefit = max_(maximum_benefit - unearned, 0)
return min_(benefit, maximum_benefit)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/nh/dhhs/fanf/nh_fanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ class nh_fanf(Variable):
def formula(spm_unit, period, parameters):
payment_standard = spm_unit("nh_fanf_payment_standard", period)
countable_income = spm_unit("nh_fanf_countable_income", period)
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
return min_(benefit, payment_standard)
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ class nj_wfnj(Variable):
def formula(spm_unit, period, parameters):
payment_levels = spm_unit("nj_wfnj_payment_levels", period)
countable_income = spm_unit("nj_wfnj_countable_income", period)
return max_(payment_levels - countable_income, 0)
benefit = max_(payment_levels - countable_income, 0)
return min_(benefit, payment_levels)
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ def formula(spm_unit, period, parameters):
# Benefit = Payment Standard - Net Countable Income
maximum_benefit = spm_unit("nm_works_maximum_benefit", period)
countable_income = spm_unit("nm_works_countable_income", period)
return max_(maximum_benefit - countable_income, 0)
benefit = max_(maximum_benefit - countable_income, 0)
return min_(benefit, maximum_benefit)
5 changes: 4 additions & 1 deletion policyengine_us/variables/gov/states/nv/dwss/tanf/nv_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ def formula(spm_unit, period, parameters):
payment_standard = spm_unit("nv_tanf_payment_standard", period)
countable_income = spm_unit("nv_tanf_countable_income", period)
# Benefit is payment standard minus countable income
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
# Cap benefit at payment standard to prevent negative income
# from inflating benefits above the maximum.
return min_(benefit, payment_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/ny/otda/tanf/ny_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ class ny_tanf(Variable):
def formula(spm_unit, period, parameters):
need_standard = spm_unit("ny_tanf_need_standard", period)
income = spm_unit("ny_tanf_countable_income", period)
return max_(need_standard - income, 0)
benefit = max_(need_standard - income, 0)
return min_(benefit, need_standard)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/oh/odjfs/owf/oh_owf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ def formula(spm_unit, period, parameters):
countable_income = spm_unit("oh_owf_countable_income", period)

# Per OAC 5101:1-23-40: benefit = payment standard - countable income
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
return min_(benefit, payment_standard)
5 changes: 4 additions & 1 deletion policyengine_us/variables/gov/states/ok/dhs/tanf/ok_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def formula(spm_unit, period, parameters):

# Per OAC 340:10-3-59: Benefit = Payment Standard - Countable Income
benefit = max_(payment_standard - countable_income, 0)
capped_benefit = min_(benefit, payment_standard)

# Per OAC 340:10-3-59: Minimum benefit is $10; if less, no payment issues
return where(benefit >= p.benefit.minimum_benefit, benefit, 0)
return where(
capped_benefit >= p.benefit.minimum_benefit, capped_benefit, 0
)
5 changes: 3 additions & 2 deletions policyengine_us/variables/gov/states/or/odhs/tanf/or_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ def formula(spm_unit, period, parameters):
payment_standard = spm_unit("or_tanf_payment_standard", period)
adjusted_income = spm_unit("or_tanf_adjusted_income", period)
calculated_benefit = max_(payment_standard - adjusted_income, 0)
capped_benefit = min_(calculated_benefit, payment_standard)
return where(
calculated_benefit >= p.minimum_benefit,
calculated_benefit,
capped_benefit >= p.minimum_benefit,
capped_benefit,
0,
)
5 changes: 4 additions & 1 deletion policyengine_us/variables/gov/states/pa/dhs/tanf/pa_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ def formula(spm_unit, period, parameters):
maximum_benefit = spm_unit("pa_tanf_maximum_benefit", period)
countable_income = spm_unit("pa_tanf_countable_income", period)

return max_(maximum_benefit - countable_income, 0)
benefit = max_(maximum_benefit - countable_income, 0)
# Cap benefit at maximum to prevent negative income
# from inflating benefits above the maximum.
return min_(benefit, maximum_benefit)
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ class ri_works(Variable):
def formula(spm_unit, period, parameters):
payment_standard = spm_unit("ri_works_payment_standard", period)
countable_income = spm_unit("ri_works_countable_income", period)
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
return min_(benefit, payment_standard)
7 changes: 5 additions & 2 deletions policyengine_us/variables/gov/states/sc/tanf/sc_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ def formula(spm_unit, period, parameters):
fpg = spm_unit("tanf_fpg", period)
need_standard = fpg * p.income.need_standard.rate
countable_income = spm_unit("sc_tanf_countable_income", period)
excess_income = need_standard - countable_income
return excess_income * p.payment.rate
excess_income = max_(need_standard - countable_income, 0)
benefit = excess_income * p.payment.rate
# Cap benefit at need standard to prevent negative income
# from inflating benefits above the maximum.
return min_(benefit, need_standard * p.payment.rate)
3 changes: 2 additions & 1 deletion policyengine_us/variables/gov/states/sd/dss/tanf/sd_tanf.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ class sd_tanf(Variable):
def formula(spm_unit, period, parameters):
payment_standard = spm_unit("sd_tanf_payment_standard", period)
countable_income = spm_unit("sd_tanf_countable_income", period)
return max_(payment_standard - countable_income, 0)
benefit = max_(payment_standard - countable_income, 0)
return min_(benefit, payment_standard)
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def formula(spm_unit, period, parameters):

# Calculate benefit as payment standard minus countable income
calculated_benefit = max_(payment_standard - countable_income, 0)
capped_benefit = min_(calculated_benefit, payment_standard)

# Apply minimum grant rule
return max_(calculated_benefit, p.minimum_grant)
return max_(capped_benefit, p.minimum_grant)
Loading
Loading