diff --git a/changelog.d/fix-2016-hhs-poverty-guidelines.fixed.md b/changelog.d/fix-2016-hhs-poverty-guidelines.fixed.md new file mode 100644 index 00000000000..4b5aebf6543 --- /dev/null +++ b/changelog.d/fix-2016-hhs-poverty-guidelines.fixed.md @@ -0,0 +1 @@ +Use the published 2016 HHS household-size poverty guideline tables, including the guidelines used for 2017 ACA income eligibility. diff --git a/policyengine_us/parameters/gov/hhs/fpg_table.yaml b/policyengine_us/parameters/gov/hhs/fpg_table.yaml new file mode 100644 index 00000000000..3546c33bac5 --- /dev/null +++ b/policyengine_us/parameters/gov/hhs/fpg_table.yaml @@ -0,0 +1,98 @@ +description: HHS publishes these nonuniform annual poverty guidelines for 2016. +metadata: + propagate_metadata_to_children: true + reference: + - title: 'Annual Update of the HHS Poverty Guidelines, 81 FR 4036-4037 #page 1-2' + href: https://www.govinfo.gov/content/pkg/FR-2016-01-25/pdf/2016-01450.pdf#page=1 +in_effect: + description: The model uses the literal HHS household-size table for this guideline year. + # The annual model identifies each guideline year from January 1. The + # notice took effect January 25, 2016, unless a program chose another date. + # These bounds select the table override, not legal adoption or repeal. + values: + 0000-01-01: false + 2016-01-01: true + 2017-01-01: false + metadata: + unit: bool + period: year + label: HHS literal poverty guideline table in effect +min_household_size: + description: HHS starts its published poverty guideline table at this household size. + values: + 2016-01-01: 1 + metadata: + unit: person + period: year + label: HHS poverty guideline minimum listed household size +max_household_size: + description: HHS lists poverty guidelines through this household size before applying its additional-person amount. + values: + 2016-01-01: 8 + metadata: + unit: person + period: year + label: HHS poverty guideline maximum listed household size +amount: + description: HHS sets the annual poverty guideline at this amount for each geography and listed household size. + metadata: + unit: currency-USD + period: year + label: HHS literal annual poverty guideline + breakdown: + - [CONTIGUOUS_US, AK, HI] + - range(1, 9) + breakdown_label: + - Guideline geography + - Household size + CONTIGUOUS_US: + 1: + 2016-01-01: 11_880 + 2: + 2016-01-01: 16_020 + 3: + 2016-01-01: 20_160 + 4: + 2016-01-01: 24_300 + 5: + 2016-01-01: 28_440 + 6: + 2016-01-01: 32_580 + 7: + 2016-01-01: 36_730 + 8: + 2016-01-01: 40_890 + AK: + 1: + 2016-01-01: 14_840 + 2: + 2016-01-01: 20_020 + 3: + 2016-01-01: 25_200 + 4: + 2016-01-01: 30_380 + 5: + 2016-01-01: 35_560 + 6: + 2016-01-01: 40_740 + 7: + 2016-01-01: 45_920 + 8: + 2016-01-01: 51_120 + HI: + 1: + 2016-01-01: 13_670 + 2: + 2016-01-01: 18_430 + 3: + 2016-01-01: 23_190 + 4: + 2016-01-01: 27_950 + 5: + 2016-01-01: 32_710 + 6: + 2016-01-01: 37_470 + 7: + 2016-01-01: 42_230 + 8: + 2016-01-01: 47_010 diff --git a/policyengine_us/tests/core/test_spm_simulation_isolation.py b/policyengine_us/tests/core/test_spm_simulation_isolation.py index 1914fd50c39..bfc5770dc9a 100644 --- a/policyengine_us/tests/core/test_spm_simulation_isolation.py +++ b/policyengine_us/tests/core/test_spm_simulation_isolation.py @@ -255,6 +255,7 @@ def test_traced_simulations_record_their_own_parameter_accesses(period): "gov.abolitions.spm_unit_size", "gov.hhs.fpg.additional_person", "gov.hhs.fpg.first_person", + "gov.hhs.fpg_table.in_effect", } assert set(first_accesses) == expected assert set(parameter_accesses(second.tracer)) == expected diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi_fraction.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi_fraction.yaml index 0be43407ad3..dd50585a284 100644 --- a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi_fraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi_fraction.yaml @@ -41,6 +41,185 @@ output: aca_magi_fraction: 2.54 +# IRS 2017 Form 8962 instructions, line 4 and Tables 1-1 to 1-3 use 2016. +# https://www.irs.gov/pub/irs-prior/i8962--2017.pdf#page=6 +# Stable memberships and residence isolate the guideline table from PRs 10413/10860. +# Real wages supply MAGI. No eligibility or income-fraction inputs are supplied. +- name: A stable Texas family of three at the 2016 guideline has the correct 2017 ACA income + floor. + period: 2017 + input: + people: + parent: + age: + 2016: 40 + 2017: 41 + employment_income: + 2016: 20160 + 2017: 20160 + immigration_status: + 2016: CITIZEN + 2017: CITIZEN + has_tin: + 2016: true + 2017: true + spouse: + age: + 2016: 39 + 2017: 40 + immigration_status: + 2016: CITIZEN + 2017: CITIZEN + has_tin: + 2016: true + 2017: true + child: + age: + 2016: 9 + 2017: 10 + is_tax_unit_dependent: + 2016: true + 2017: true + immigration_status: + 2016: CITIZEN + 2017: CITIZEN + tax_units: + tax_unit: + members: + - parent + - spouse + - child + marital_units: + couple: + members: + - parent + - spouse + child: + members: + - child + spm_units: + spm_unit: + members: + - parent + - spouse + - child + households: + household: + members: + - parent + - spouse + - child + state_code: + 2016: TX + 2017: TX + output: + tax_unit_size: + 2016: 3 + 2017: 3 + tax_unit_fpg: + 2016: 20160 + 2017: 20420 + aca_magi: 20160 + aca_magi_fraction: 1 + people: + parent: + is_medicaid_eligible: false + medicaid_enrolled: false + pays_aca_premium: true + is_aca_ptc_eligible: true + spouse: + is_medicaid_eligible: false + medicaid_enrolled: false + pays_aca_premium: true + is_aca_ptc_eligible: true +- name: A stable Texas family of three one dollar below the 2016 guideline has the correct + 2017 ACA income floor. + period: 2017 + input: + people: + parent: + age: + 2016: 40 + 2017: 41 + employment_income: + 2016: 20159 + 2017: 20159 + immigration_status: + 2016: CITIZEN + 2017: CITIZEN + has_tin: + 2016: true + 2017: true + spouse: + age: + 2016: 39 + 2017: 40 + immigration_status: + 2016: CITIZEN + 2017: CITIZEN + has_tin: + 2016: true + 2017: true + child: + age: + 2016: 9 + 2017: 10 + is_tax_unit_dependent: + 2016: true + 2017: true + immigration_status: + 2016: CITIZEN + 2017: CITIZEN + tax_units: + tax_unit: + members: + - parent + - spouse + - child + marital_units: + couple: + members: + - parent + - spouse + child: + members: + - child + spm_units: + spm_unit: + members: + - parent + - spouse + - child + households: + household: + members: + - parent + - spouse + - child + state_code: + 2016: TX + 2017: TX + output: + tax_unit_size: + 2016: 3 + 2017: 3 + tax_unit_fpg: + 2016: 20160 + 2017: 20420 + aca_magi: 20159 + aca_magi_fraction: 0.99 + people: + parent: + is_medicaid_eligible: false + medicaid_enrolled: false + pays_aca_premium: false + is_aca_ptc_eligible: false + spouse: + is_medicaid_eligible: false + medicaid_enrolled: false + pays_aca_premium: false + is_aca_ptc_eligible: false + - name: 2025 joint return with one claimable spouse measures income against the one-person line (the claimable spouse is the head) period: 2025 input: diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/spm_unit_fpg.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/spm_unit_fpg.yaml index e76feb6f1fe..bbfd7703ef4 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/spm_unit_fpg.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/spm_unit_fpg.yaml @@ -48,7 +48,7 @@ spm_unit_size: 5 state_group_str: AK output: - spm_unit_fpg: 14_840 + 4 * 5_200 + spm_unit_fpg: 35_560 # HHS 2016 table, size 5. - name: Three-person SPM unit in 2016 in Hawaii period: 2016 @@ -56,4 +56,1759 @@ spm_unit_size: 3 state_group_str: HI output: - spm_unit_fpg: 13_670 + 2 * 4_780 + spm_unit_fpg: 23_190 # HHS 2016 table, size 3. + +# Component sizes are explicit; these are not 30 real families of the stated size. +# HHS 81 FR 4036-4037, PDF pages 1-2; IRS 2017 Form 8962 instructions pages 6-7. +# https://www.govinfo.gov/content/pkg/FR-2016-01-25/pdf/2016-01450.pdf#page=1 +- name: The literal 2016 HHS tables and size-eight extensions agree for mixed tax and SPM + units. + period: 2016 + input: + people: + component1: + age: + 2016: 40 + component2: + age: + 2016: 40 + component3: + age: + 2016: 40 + component4: + age: + 2016: 40 + component5: + age: + 2016: 40 + component6: + age: + 2016: 40 + component7: + age: + 2016: 40 + component8: + age: + 2016: 40 + component9: + age: + 2016: 40 + component10: + age: + 2016: 40 + component11: + age: + 2016: 40 + component12: + age: + 2016: 40 + component13: + age: + 2016: 40 + component14: + age: + 2016: 40 + component15: + age: + 2016: 40 + component16: + age: + 2016: 40 + component17: + age: + 2016: 40 + component18: + age: + 2016: 40 + component19: + age: + 2016: 40 + component20: + age: + 2016: 40 + component21: + age: + 2016: 40 + component22: + age: + 2016: 40 + component23: + age: + 2016: 40 + component24: + age: + 2016: 40 + component25: + age: + 2016: 40 + component26: + age: + 2016: 40 + component27: + age: + 2016: 40 + component28: + age: + 2016: 40 + component29: + age: + 2016: 40 + component30: + age: + 2016: 40 + tax_units: + tax_unit1: + members: + - component1 + tax_unit_size: + 2016: 1 + tax_unit2: + members: + - component2 + tax_unit_size: + 2016: 2 + tax_unit3: + members: + - component3 + tax_unit_size: + 2016: 3 + tax_unit4: + members: + - component4 + tax_unit_size: + 2016: 4 + tax_unit5: + members: + - component5 + tax_unit_size: + 2016: 5 + tax_unit6: + members: + - component6 + tax_unit_size: + 2016: 6 + tax_unit7: + members: + - component7 + tax_unit_size: + 2016: 7 + tax_unit8: + members: + - component8 + tax_unit_size: + 2016: 8 + tax_unit9: + members: + - component9 + tax_unit_size: + 2016: 9 + tax_unit10: + members: + - component10 + tax_unit_size: + 2016: 11 + tax_unit11: + members: + - component11 + tax_unit_size: + 2016: 1 + tax_unit12: + members: + - component12 + tax_unit_size: + 2016: 2 + tax_unit13: + members: + - component13 + tax_unit_size: + 2016: 3 + tax_unit14: + members: + - component14 + tax_unit_size: + 2016: 4 + tax_unit15: + members: + - component15 + tax_unit_size: + 2016: 5 + tax_unit16: + members: + - component16 + tax_unit_size: + 2016: 6 + tax_unit17: + members: + - component17 + tax_unit_size: + 2016: 7 + tax_unit18: + members: + - component18 + tax_unit_size: + 2016: 8 + tax_unit19: + members: + - component19 + tax_unit_size: + 2016: 9 + tax_unit20: + members: + - component20 + tax_unit_size: + 2016: 11 + tax_unit21: + members: + - component21 + tax_unit_size: + 2016: 1 + tax_unit22: + members: + - component22 + tax_unit_size: + 2016: 2 + tax_unit23: + members: + - component23 + tax_unit_size: + 2016: 3 + tax_unit24: + members: + - component24 + tax_unit_size: + 2016: 4 + tax_unit25: + members: + - component25 + tax_unit_size: + 2016: 5 + tax_unit26: + members: + - component26 + tax_unit_size: + 2016: 6 + tax_unit27: + members: + - component27 + tax_unit_size: + 2016: 7 + tax_unit28: + members: + - component28 + tax_unit_size: + 2016: 8 + tax_unit29: + members: + - component29 + tax_unit_size: + 2016: 9 + tax_unit30: + members: + - component30 + tax_unit_size: + 2016: 11 + spm_units: + spm_unit1: + members: + - component1 + spm_unit_size: + 2016: 1 + spm_unit2: + members: + - component2 + spm_unit_size: + 2016: 2 + spm_unit3: + members: + - component3 + spm_unit_size: + 2016: 3 + spm_unit4: + members: + - component4 + spm_unit_size: + 2016: 4 + spm_unit5: + members: + - component5 + spm_unit_size: + 2016: 5 + spm_unit6: + members: + - component6 + spm_unit_size: + 2016: 6 + spm_unit7: + members: + - component7 + spm_unit_size: + 2016: 7 + spm_unit8: + members: + - component8 + spm_unit_size: + 2016: 8 + spm_unit9: + members: + - component9 + spm_unit_size: + 2016: 9 + spm_unit10: + members: + - component10 + spm_unit_size: + 2016: 11 + spm_unit11: + members: + - component11 + spm_unit_size: + 2016: 1 + spm_unit12: + members: + - component12 + spm_unit_size: + 2016: 2 + spm_unit13: + members: + - component13 + spm_unit_size: + 2016: 3 + spm_unit14: + members: + - component14 + spm_unit_size: + 2016: 4 + spm_unit15: + members: + - component15 + spm_unit_size: + 2016: 5 + spm_unit16: + members: + - component16 + spm_unit_size: + 2016: 6 + spm_unit17: + members: + - component17 + spm_unit_size: + 2016: 7 + spm_unit18: + members: + - component18 + spm_unit_size: + 2016: 8 + spm_unit19: + members: + - component19 + spm_unit_size: + 2016: 9 + spm_unit20: + members: + - component20 + spm_unit_size: + 2016: 11 + spm_unit21: + members: + - component21 + spm_unit_size: + 2016: 1 + spm_unit22: + members: + - component22 + spm_unit_size: + 2016: 2 + spm_unit23: + members: + - component23 + spm_unit_size: + 2016: 3 + spm_unit24: + members: + - component24 + spm_unit_size: + 2016: 4 + spm_unit25: + members: + - component25 + spm_unit_size: + 2016: 5 + spm_unit26: + members: + - component26 + spm_unit_size: + 2016: 6 + spm_unit27: + members: + - component27 + spm_unit_size: + 2016: 7 + spm_unit28: + members: + - component28 + spm_unit_size: + 2016: 8 + spm_unit29: + members: + - component29 + spm_unit_size: + 2016: 9 + spm_unit30: + members: + - component30 + spm_unit_size: + 2016: 11 + households: + household1: + members: + - component1 + state_code: + 2016: TX + household2: + members: + - component2 + state_code: + 2016: TX + household3: + members: + - component3 + state_code: + 2016: TX + household4: + members: + - component4 + state_code: + 2016: TX + household5: + members: + - component5 + state_code: + 2016: TX + household6: + members: + - component6 + state_code: + 2016: TX + household7: + members: + - component7 + state_code: + 2016: TX + household8: + members: + - component8 + state_code: + 2016: TX + household9: + members: + - component9 + state_code: + 2016: TX + household10: + members: + - component10 + state_code: + 2016: TX + household11: + members: + - component11 + state_code: + 2016: AK + household12: + members: + - component12 + state_code: + 2016: AK + household13: + members: + - component13 + state_code: + 2016: AK + household14: + members: + - component14 + state_code: + 2016: AK + household15: + members: + - component15 + state_code: + 2016: AK + household16: + members: + - component16 + state_code: + 2016: AK + household17: + members: + - component17 + state_code: + 2016: AK + household18: + members: + - component18 + state_code: + 2016: AK + household19: + members: + - component19 + state_code: + 2016: AK + household20: + members: + - component20 + state_code: + 2016: AK + household21: + members: + - component21 + state_code: + 2016: HI + household22: + members: + - component22 + state_code: + 2016: HI + household23: + members: + - component23 + state_code: + 2016: HI + household24: + members: + - component24 + state_code: + 2016: HI + household25: + members: + - component25 + state_code: + 2016: HI + household26: + members: + - component26 + state_code: + 2016: HI + household27: + members: + - component27 + state_code: + 2016: HI + household28: + members: + - component28 + state_code: + 2016: HI + household29: + members: + - component29 + state_code: + 2016: HI + household30: + members: + - component30 + state_code: + 2016: HI + output: + tax_unit_fpg: + - 11880 + - 16020 + - 20160 + - 24300 + - 28440 + - 32580 + - 36730 + - 40890 + - 45050 + - 53370 + - 14840 + - 20020 + - 25200 + - 30380 + - 35560 + - 40740 + - 45920 + - 51120 + - 56320 + - 66720 + - 13670 + - 18430 + - 23190 + - 27950 + - 32710 + - 37470 + - 42230 + - 47010 + - 51790 + - 61350 + spm_unit_fpg: + - 11880 + - 16020 + - 20160 + - 24300 + - 28440 + - 32580 + - 36730 + - 40890 + - 45050 + - 53370 + - 14840 + - 20020 + - 25200 + - 30380 + - 35560 + - 40740 + - 45920 + - 51120 + - 56320 + - 66720 + - 13670 + - 18430 + - 23190 + - 27950 + - 32710 + - 37470 + - 42230 + - 47010 + - 51790 + - 61350 + +# These controls preserve canonical model outputs outside the 2016 override. +# They do not independently certify those years, publication dates or agency adoption. +- name: The exact-table override leaves 2015 and consecutive 2017 through 2026 guidelines + unchanged. + period: 2015 + input: + people: + component1: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component2: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component3: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component4: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component5: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component6: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component7: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component8: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component9: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component10: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component11: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + component12: + age: + 2015: 40 + 2017: 40 + 2018: 40 + 2019: 40 + 2020: 40 + 2021: 40 + 2022: 40 + 2023: 40 + 2024: 40 + 2025: 40 + 2026: 40 + tax_units: + tax_unit1: + members: + - component1 + tax_unit_size: + 2015: 1 + 2017: 1 + 2018: 1 + 2019: 1 + 2020: 1 + 2021: 1 + 2022: 1 + 2023: 1 + 2024: 1 + 2025: 1 + 2026: 1 + tax_unit2: + members: + - component2 + tax_unit_size: + 2015: 2 + 2017: 2 + 2018: 2 + 2019: 2 + 2020: 2 + 2021: 2 + 2022: 2 + 2023: 2 + 2024: 2 + 2025: 2 + 2026: 2 + tax_unit3: + members: + - component3 + tax_unit_size: + 2015: 8 + 2017: 8 + 2018: 8 + 2019: 8 + 2020: 8 + 2021: 8 + 2022: 8 + 2023: 8 + 2024: 8 + 2025: 8 + 2026: 8 + tax_unit4: + members: + - component4 + tax_unit_size: + 2015: 11 + 2017: 11 + 2018: 11 + 2019: 11 + 2020: 11 + 2021: 11 + 2022: 11 + 2023: 11 + 2024: 11 + 2025: 11 + 2026: 11 + tax_unit5: + members: + - component5 + tax_unit_size: + 2015: 1 + 2017: 1 + 2018: 1 + 2019: 1 + 2020: 1 + 2021: 1 + 2022: 1 + 2023: 1 + 2024: 1 + 2025: 1 + 2026: 1 + tax_unit6: + members: + - component6 + tax_unit_size: + 2015: 2 + 2017: 2 + 2018: 2 + 2019: 2 + 2020: 2 + 2021: 2 + 2022: 2 + 2023: 2 + 2024: 2 + 2025: 2 + 2026: 2 + tax_unit7: + members: + - component7 + tax_unit_size: + 2015: 8 + 2017: 8 + 2018: 8 + 2019: 8 + 2020: 8 + 2021: 8 + 2022: 8 + 2023: 8 + 2024: 8 + 2025: 8 + 2026: 8 + tax_unit8: + members: + - component8 + tax_unit_size: + 2015: 11 + 2017: 11 + 2018: 11 + 2019: 11 + 2020: 11 + 2021: 11 + 2022: 11 + 2023: 11 + 2024: 11 + 2025: 11 + 2026: 11 + tax_unit9: + members: + - component9 + tax_unit_size: + 2015: 1 + 2017: 1 + 2018: 1 + 2019: 1 + 2020: 1 + 2021: 1 + 2022: 1 + 2023: 1 + 2024: 1 + 2025: 1 + 2026: 1 + tax_unit10: + members: + - component10 + tax_unit_size: + 2015: 2 + 2017: 2 + 2018: 2 + 2019: 2 + 2020: 2 + 2021: 2 + 2022: 2 + 2023: 2 + 2024: 2 + 2025: 2 + 2026: 2 + tax_unit11: + members: + - component11 + tax_unit_size: + 2015: 8 + 2017: 8 + 2018: 8 + 2019: 8 + 2020: 8 + 2021: 8 + 2022: 8 + 2023: 8 + 2024: 8 + 2025: 8 + 2026: 8 + tax_unit12: + members: + - component12 + tax_unit_size: + 2015: 11 + 2017: 11 + 2018: 11 + 2019: 11 + 2020: 11 + 2021: 11 + 2022: 11 + 2023: 11 + 2024: 11 + 2025: 11 + 2026: 11 + spm_units: + spm_unit1: + members: + - component1 + spm_unit_size: + 2015: 1 + 2017: 1 + 2018: 1 + 2019: 1 + 2020: 1 + 2021: 1 + 2022: 1 + 2023: 1 + 2024: 1 + 2025: 1 + 2026: 1 + spm_unit2: + members: + - component2 + spm_unit_size: + 2015: 2 + 2017: 2 + 2018: 2 + 2019: 2 + 2020: 2 + 2021: 2 + 2022: 2 + 2023: 2 + 2024: 2 + 2025: 2 + 2026: 2 + spm_unit3: + members: + - component3 + spm_unit_size: + 2015: 8 + 2017: 8 + 2018: 8 + 2019: 8 + 2020: 8 + 2021: 8 + 2022: 8 + 2023: 8 + 2024: 8 + 2025: 8 + 2026: 8 + spm_unit4: + members: + - component4 + spm_unit_size: + 2015: 11 + 2017: 11 + 2018: 11 + 2019: 11 + 2020: 11 + 2021: 11 + 2022: 11 + 2023: 11 + 2024: 11 + 2025: 11 + 2026: 11 + spm_unit5: + members: + - component5 + spm_unit_size: + 2015: 1 + 2017: 1 + 2018: 1 + 2019: 1 + 2020: 1 + 2021: 1 + 2022: 1 + 2023: 1 + 2024: 1 + 2025: 1 + 2026: 1 + spm_unit6: + members: + - component6 + spm_unit_size: + 2015: 2 + 2017: 2 + 2018: 2 + 2019: 2 + 2020: 2 + 2021: 2 + 2022: 2 + 2023: 2 + 2024: 2 + 2025: 2 + 2026: 2 + spm_unit7: + members: + - component7 + spm_unit_size: + 2015: 8 + 2017: 8 + 2018: 8 + 2019: 8 + 2020: 8 + 2021: 8 + 2022: 8 + 2023: 8 + 2024: 8 + 2025: 8 + 2026: 8 + spm_unit8: + members: + - component8 + spm_unit_size: + 2015: 11 + 2017: 11 + 2018: 11 + 2019: 11 + 2020: 11 + 2021: 11 + 2022: 11 + 2023: 11 + 2024: 11 + 2025: 11 + 2026: 11 + spm_unit9: + members: + - component9 + spm_unit_size: + 2015: 1 + 2017: 1 + 2018: 1 + 2019: 1 + 2020: 1 + 2021: 1 + 2022: 1 + 2023: 1 + 2024: 1 + 2025: 1 + 2026: 1 + spm_unit10: + members: + - component10 + spm_unit_size: + 2015: 2 + 2017: 2 + 2018: 2 + 2019: 2 + 2020: 2 + 2021: 2 + 2022: 2 + 2023: 2 + 2024: 2 + 2025: 2 + 2026: 2 + spm_unit11: + members: + - component11 + spm_unit_size: + 2015: 8 + 2017: 8 + 2018: 8 + 2019: 8 + 2020: 8 + 2021: 8 + 2022: 8 + 2023: 8 + 2024: 8 + 2025: 8 + 2026: 8 + spm_unit12: + members: + - component12 + spm_unit_size: + 2015: 11 + 2017: 11 + 2018: 11 + 2019: 11 + 2020: 11 + 2021: 11 + 2022: 11 + 2023: 11 + 2024: 11 + 2025: 11 + 2026: 11 + households: + household1: + members: + - component1 + state_code: + 2015: TX + 2017: TX + 2018: TX + 2019: TX + 2020: TX + 2021: TX + 2022: TX + 2023: TX + 2024: TX + 2025: TX + 2026: TX + household2: + members: + - component2 + state_code: + 2015: TX + 2017: TX + 2018: TX + 2019: TX + 2020: TX + 2021: TX + 2022: TX + 2023: TX + 2024: TX + 2025: TX + 2026: TX + household3: + members: + - component3 + state_code: + 2015: TX + 2017: TX + 2018: TX + 2019: TX + 2020: TX + 2021: TX + 2022: TX + 2023: TX + 2024: TX + 2025: TX + 2026: TX + household4: + members: + - component4 + state_code: + 2015: TX + 2017: TX + 2018: TX + 2019: TX + 2020: TX + 2021: TX + 2022: TX + 2023: TX + 2024: TX + 2025: TX + 2026: TX + household5: + members: + - component5 + state_code: + 2015: AK + 2017: AK + 2018: AK + 2019: AK + 2020: AK + 2021: AK + 2022: AK + 2023: AK + 2024: AK + 2025: AK + 2026: AK + household6: + members: + - component6 + state_code: + 2015: AK + 2017: AK + 2018: AK + 2019: AK + 2020: AK + 2021: AK + 2022: AK + 2023: AK + 2024: AK + 2025: AK + 2026: AK + household7: + members: + - component7 + state_code: + 2015: AK + 2017: AK + 2018: AK + 2019: AK + 2020: AK + 2021: AK + 2022: AK + 2023: AK + 2024: AK + 2025: AK + 2026: AK + household8: + members: + - component8 + state_code: + 2015: AK + 2017: AK + 2018: AK + 2019: AK + 2020: AK + 2021: AK + 2022: AK + 2023: AK + 2024: AK + 2025: AK + 2026: AK + household9: + members: + - component9 + state_code: + 2015: HI + 2017: HI + 2018: HI + 2019: HI + 2020: HI + 2021: HI + 2022: HI + 2023: HI + 2024: HI + 2025: HI + 2026: HI + household10: + members: + - component10 + state_code: + 2015: HI + 2017: HI + 2018: HI + 2019: HI + 2020: HI + 2021: HI + 2022: HI + 2023: HI + 2024: HI + 2025: HI + 2026: HI + household11: + members: + - component11 + state_code: + 2015: HI + 2017: HI + 2018: HI + 2019: HI + 2020: HI + 2021: HI + 2022: HI + 2023: HI + 2024: HI + 2025: HI + 2026: HI + household12: + members: + - component12 + state_code: + 2015: HI + 2017: HI + 2018: HI + 2019: HI + 2020: HI + 2021: HI + 2022: HI + 2023: HI + 2024: HI + 2025: HI + 2026: HI + output: + tax_unit_fpg: + 2015: + - 11770 + - 15930 + - 40890 + - 53370 + - 14720 + - 19920 + - 51120 + - 66720 + - 13550 + - 18330 + - 47010 + - 61350 + 2017: + - 12060 + - 16240 + - 41320 + - 53860 + - 15060 + - 20290 + - 51670 + - 67360 + - 13860 + - 18670 + - 47530 + - 61960 + 2018: + - 12140 + - 16460 + - 42380 + - 55340 + - 15180 + - 20580 + - 52980 + - 69180 + - 13960 + - 18770 + - 47630 + - 62060 + 2019: + - 12490 + - 16910 + - 43430 + - 56690 + - 15600 + - 21130 + - 54310 + - 70900 + - 14380 + - 19460 + - 49940 + - 65180 + 2020: + - 12760 + - 17240 + - 44120 + - 57560 + - 15950 + - 21550 + - 55150 + - 71950 + - 14680 + - 19830 + - 50730 + - 66180 + 2021: + - 12880 + - 17420 + - 44660 + - 58280 + - 16090 + - 21770 + - 55850 + - 72890 + - 14820 + - 20040 + - 51360 + - 67020 + 2022: + - 13590 + - 18310 + - 46630 + - 60790 + - 16990 + - 22890 + - 58290 + - 75990 + - 15630 + - 21060 + - 53640 + - 69930 + 2023: + - 14580 + - 19720 + - 50560 + - 65980 + - 18210 + - 24640 + - 63220 + - 82510 + - 16770 + - 22680 + - 58140 + - 75870 + 2024: + - 15060 + - 20440 + - 52720 + - 68860 + - 18810 + - 25540 + - 65920 + - 86110 + - 17310 + - 23500 + - 60640 + - 79210 + 2025: + - 15650 + - 21150 + - 54150 + - 70650 + - 19550 + - 26430 + - 67710 + - 88350 + - 17990 + - 24320 + - 62300 + - 81290 + 2026: + - 15960 + - 21640 + - 55720 + - 72760 + - 19950 + - 27050 + - 69650 + - 90950 + - 18360 + - 24890 + - 64070 + - 83660 + spm_unit_fpg: + 2015: + - 11770 + - 15930 + - 40890 + - 53370 + - 14720 + - 19920 + - 51120 + - 66720 + - 13550 + - 18330 + - 47010 + - 61350 + 2017: + - 12060 + - 16240 + - 41320 + - 53860 + - 15060 + - 20290 + - 51670 + - 67360 + - 13860 + - 18670 + - 47530 + - 61960 + 2018: + - 12140 + - 16460 + - 42380 + - 55340 + - 15180 + - 20580 + - 52980 + - 69180 + - 13960 + - 18770 + - 47630 + - 62060 + 2019: + - 12490 + - 16910 + - 43430 + - 56690 + - 15600 + - 21130 + - 54310 + - 70900 + - 14380 + - 19460 + - 49940 + - 65180 + 2020: + - 12760 + - 17240 + - 44120 + - 57560 + - 15950 + - 21550 + - 55150 + - 71950 + - 14680 + - 19830 + - 50730 + - 66180 + 2021: + - 12880 + - 17420 + - 44660 + - 58280 + - 16090 + - 21770 + - 55850 + - 72890 + - 14820 + - 20040 + - 51360 + - 67020 + 2022: + - 13590 + - 18310 + - 46630 + - 60790 + - 16990 + - 22890 + - 58290 + - 75990 + - 15630 + - 21060 + - 53640 + - 69930 + 2023: + - 14580 + - 19720 + - 50560 + - 65980 + - 18210 + - 24640 + - 63220 + - 82510 + - 16770 + - 22680 + - 58140 + - 75870 + 2024: + - 15060 + - 20440 + - 52720 + - 68860 + - 18810 + - 25540 + - 65920 + - 86110 + - 17310 + - 23500 + - 60640 + - 79210 + 2025: + - 15650 + - 21150 + - 54150 + - 70650 + - 19550 + - 26430 + - 67710 + - 88350 + - 17990 + - 24320 + - 62300 + - 81290 + 2026: + - 15960 + - 21640 + - 55720 + - 72760 + - 19950 + - 27050 + - 69650 + - 90950 + - 18360 + - 24890 + - 64070 + - 83660 + +# Invalid-size inputs retain their legacy result; these are not HHS guidelines. +- name: Nonpositive component sizes retain the legacy calculation. + period: 2016 + input: + people: + component1: + age: + 2016: 40 + component2: + age: + 2016: 40 + component3: + age: + 2016: 40 + component4: + age: + 2016: 40 + component5: + age: + 2016: 40 + component6: + age: + 2016: 40 + tax_units: + tax_unit1: + members: + - component1 + tax_unit_size: + 2016: 0 + tax_unit2: + members: + - component2 + tax_unit_size: + 2016: -1 + tax_unit3: + members: + - component3 + tax_unit_size: + 2016: 0 + tax_unit4: + members: + - component4 + tax_unit_size: + 2016: -1 + tax_unit5: + members: + - component5 + tax_unit_size: + 2016: 0 + tax_unit6: + members: + - component6 + tax_unit_size: + 2016: -1 + spm_units: + spm_unit1: + members: + - component1 + spm_unit_size: + 2016: 0 + spm_unit2: + members: + - component2 + spm_unit_size: + 2016: -1 + spm_unit3: + members: + - component3 + spm_unit_size: + 2016: 0 + spm_unit4: + members: + - component4 + spm_unit_size: + 2016: -1 + spm_unit5: + members: + - component5 + spm_unit_size: + 2016: 0 + spm_unit6: + members: + - component6 + spm_unit_size: + 2016: -1 + households: + household1: + members: + - component1 + state_code: + 2016: TX + household2: + members: + - component2 + state_code: + 2016: TX + household3: + members: + - component3 + state_code: + 2016: AK + household4: + members: + - component4 + state_code: + 2016: AK + household5: + members: + - component5 + state_code: + 2016: HI + household6: + members: + - component6 + state_code: + 2016: HI + output: + tax_unit_fpg: + - 7720 + - 3560 + - 9640 + - 4440 + - 8890 + - 4110 + spm_unit_fpg: + - 7720 + - 3560 + - 9640 + - 4440 + - 8890 + - 4110 + +# Compatibility control for existing territory placeholders, not a claim that +# HHS publishes zero-dollar guidelines for territories. +- name: Mixed source geographies and territories preserve legacy placeholders. + period: 2016 + input: + state_code: [TX, AK, HI, PR, GU, VI] + tax_unit_size: [3, 3, 3, 3, 3, 3] + spm_unit_size: [3, 3, 3, 3, 3, 3] + output: + tax_unit_fpg: [20160, 25200, 23190, 0, 0, 0] + spm_unit_fpg: [20160, 25200, 23190, 0, 0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/il/hfs/fpp/il_fpp_income_level.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/hfs/fpp/il_fpp_income_level.yaml index 629a8a38f1c..c5de1bbcf92 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/il/hfs/fpp/il_fpp_income_level.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/il/hfs/fpp/il_fpp_income_level.yaml @@ -49,3 +49,16 @@ # Before: [27_000, 17_000]. adjusted_gross_income_person: [24_000, 20_000] il_fpp_income_level: [1.1742, 0.9785] + +# This component exercises the shared FPG helper's scalar-size call through +# an existing consumer. It does not assert that IL FPP operated in 2016. +# HHS 81 FR 4036: the contiguous two-person guideline is $16,020. +- name: A scalar household-size parameter uses the literal 2016 guideline. + period: 2016 + input: + state_code: IL + age: 40 + employment_income: 16_020 + output: + adjusted_gross_income_person: 16_020 + il_fpp_income_level: 1.0 diff --git a/policyengine_us/variables/gov/hhs/spm_unit_fpg.py b/policyengine_us/variables/gov/hhs/spm_unit_fpg.py index 248fcf225ff..f9298f72fa9 100644 --- a/policyengine_us/variables/gov/hhs/spm_unit_fpg.py +++ b/policyengine_us/variables/gov/hhs/spm_unit_fpg.py @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg class spm_unit_fpg(Variable): @@ -11,7 +12,4 @@ class spm_unit_fpg(Variable): def formula(spm_unit, period, parameters): n = spm_unit("spm_unit_size", period) state_group = spm_unit.household("state_group_str", period) - p_fpg = parameters(period).gov.hhs.fpg - p1 = p_fpg.first_person[state_group] - pn = p_fpg.additional_person[state_group] - return p1 + pn * (n - 1) + return fpg(n, state_group, period, parameters) diff --git a/policyengine_us/variables/gov/hhs/tax_unit_fpg.py b/policyengine_us/variables/gov/hhs/tax_unit_fpg.py index 51714218342..387397df18d 100644 --- a/policyengine_us/variables/gov/hhs/tax_unit_fpg.py +++ b/policyengine_us/variables/gov/hhs/tax_unit_fpg.py @@ -7,12 +7,46 @@ def fpg(unit_size, state_group, period, parameters, year_lag=0): Lag only the parameter date, preserving its month and day. Callers supply the current household size and apply any caps, policy rates, or rounding. + The 2016 guideline year uses HHS's nonuniform household-size table. """ guideline_date = instant(period).offset(-int(year_lag), "year") p_fpg = parameters(guideline_date).gov.hhs.fpg p1 = p_fpg.first_person[state_group] pn = p_fpg.additional_person[state_group] - return p1 + pn * (unit_size - 1) + linear_guideline = p1 + pn * (unit_size - 1) + table = parameters(guideline_date).gov.hhs.fpg_table + if not table.in_effect: + return linear_guideline + + # Some callers supply a scalar size for a vector of households. Core's + # table lookup needs one size per result, including a one-element vector + # when both the size and geography are scalars. + table_size, table_geography, _ = np.broadcast_arrays( + unit_size, state_group, np.atleast_1d(linear_guideline) + ) + source_geographies = list(table.amount) + has_table = np.isin(table_geography, source_geographies) + safe_geography = where(has_table, table_geography, source_geographies[0]) + # HHS retained some prior-year amounts in 2016, so the published + # additional-person amount applies only beyond the last listed size. + # Keep the legacy result for inputs outside the integer-size domain. + valid_size = ( + np.isfinite(table_size) + & (table_size >= table.min_household_size) + & (table_size == np.floor(table_size)) + ) + lookup_size = min_( + where(valid_size, table_size, table.min_household_size), + table.max_household_size, + ).astype(int) + listed_guideline = table.amount[safe_geography][lookup_size] + extended_guideline = listed_guideline + pn * max_( + table_size - table.max_household_size, 0 + ) + # HHS does not define territory guidelines. Preserve their existing + # placeholders rather than applying one of the three source tables. + result = where(has_table & valid_size, extended_guideline, linear_guideline) + return result.item() if np.ndim(linear_guideline) == 0 else result class tax_unit_fpg(Variable):