diff --git a/changelog.d/ak-pfd-receipt.added.md b/changelog.d/ak-pfd-receipt.added.md new file mode 100644 index 00000000000..c8382c693ae --- /dev/null +++ b/changelog.d/ak-pfd-receipt.added.md @@ -0,0 +1 @@ +Limit the Alaska Permanent Fund Dividend and energy relief to the immigration statuses in AS 43.23.005(a)(5), and add a default-true take-up input for residents Alaska does not pay. diff --git a/docs/validation/alaska-pfd.md b/docs/validation/alaska-pfd.md index d2b8982deee..190960c268e 100644 --- a/docs/validation/alaska-pfd.md +++ b/docs/validation/alaska-pfd.md @@ -1,11 +1,16 @@ # Alaska Permanent Fund Dividend in model comparisons PolicyEngine automatically calculates `ak_permanent_fund_dividend` and -`ak_energy_relief` for each person whose household is in Alaska. These amounts -are calculated benefits, rather than imputed income or fixed default inputs. -Current Alaska residence is an eligibility proxy: the model does not check an -application, prior-year residence, or actual receipt. Supplying wages and other -known income does not disable either calculation. +`ak_energy_relief` for each person who lives in Alaska, holds one of the +immigration statuses in the dividend statute, and takes up the dividend. These +amounts are calculated benefits, rather than imputed income or fixed default +inputs. PolicyEngine cannot resolve the statute's other tests from its inputs: +an application, residence on the application date and during all of the prior +calendar year, 72 consecutive hours of presence in the prior two years, +allowable absences, Selective Service compliance, or a felony sentence or +conviction-based incarceration during that year. `is_incarcerated` records no +conviction and does not tie to that year, so it does not affect the dividend. +Supplying wages and other known income does not disable either calculation. The regular PFD is included in federal gross income for nondependent people. A dependent child's PFD is not automatically added to the parent's adjusted @@ -14,13 +19,106 @@ status; supply explicit roles or dependent status when constructing a household. The regular dividend appears on [2022 Schedule 1, line 8g](https://www.irs.gov/pub/irs-prior/f1040s1--2022.pdf#page=1). +## Who receives the dividend + +`ak_permanent_fund_dividend_eligible` applies the status test in [AS +43.23.005(a)(5)](https://www.akleg.gov/basis/statutes.asp#43.23.005): a citizen, +an alien lawfully admitted for permanent residence, a refugee, or an asylee. + +In *State v. Andrade*, 23 P.3d 58 (Alaska 2001), the Alaska Supreme Court held +that the state phrase does not incorporate the federal definition: an alien must +be legally present and able to form the intent to remain in Alaska. In [15 AAC +23.154(a)](https://www.akleg.gov/basis/aac.asp#15.23.154), the Department of +Revenue applies that reading to a status under 8 U.S.C. 1101 to 1189 that lets +the alien adopt the United States as their domicile. The department lists +immigrants, nonimmigrants whose status requires no foreign residence, people on +indefinite parole, and conditional permanent residents. PolicyEngine has no +status for the last three, so code them as `LEGAL_PERMANENT_RESIDENT`. Under 15 +AAC 23.154(b) and (d), a person whose status requires a foreign residence fails, +and a limited-stay nonimmigrant fails the residence test until they take a +significant step toward a permanent or indefinite status. On its [immigration +page](https://pfd.alaska.gov/eligibility/immigration), the department's +Permanent Fund Dividend Division also describes paths for US nationals and +Compact of Free Association migrants. PolicyEngine has no status for those +either, so code a person the Division accepts as `LEGAL_PERMANENT_RESIDENT`. + +PolicyEngine leaves the other values of `immigration_status` out: + +- `PAROLED_ONE_YEAR` does not say a parole runs indefinitely. In [OAH No. + 25-3180-PFD](https://aws.state.ak.us/OAH/Decision/Display?rec=7350), an + administrative law judge upheld the Division's denial of a 2025 dividend to a + fixed-term parolee. +- `TPS` (8 U.S.C. 1254a) and `DEPORTATION_WITHHELD` (8 U.S.C. 1231(b)(3)) fall + outside 8 U.S.C. 1101 to 1189, and a person coded `DACA` holds no status under + those sections. A TPS holder might meet *Andrade*'s test alone, but the court + did not address TPS, and PolicyEngine follows the regulation's range. +- A person coded `UNDOCUMENTED` lacks the legal presence the court required in + *Andrade*. +- `CUBAN_HAITIAN_ENTRANT` and `CONDITIONAL_ENTRANT` do not say whether the person + may adopt the United States as their domicile. + +Alaska tests the status a person holds during the qualifying year and on the +application date (15 AAC 23.154(e)), and under 15 AAC 23.154(c) an alien's +qualifying year starts on January 1 after the person gains the status. +PolicyEngine has no status history, so it reads the status for the dividend year. + +`takes_up_ak_permanent_fund_dividend_if_eligible` covers the statute's other +tests and anyone who does not apply. It defaults to true, so a household +calculation pays every eligible Alaska resident unless an input says otherwise. +Set it to false for a person Alaska did not pay. Both variables gate +`ak_energy_relief` as well, because Alaska pays energy relief as part of the +dividend ([sec. 13, ch. 34, SLA +2026](https://www.akleg.gov/PDF/34/Bills/HB0263Z.PDF#page=84)). + +### Children under one + +A child born during the dividend year cannot receive that year's dividend. The +child fails AS 43.23.005(a)(3), and under (c) only children born in the two +calendar years before the dividend year qualify. The Division [takes +applications](https://pfd.alaska.gov/application/applying-for-a-child) for a child +"born on or before December 31st of the qualifying year." A child born during the +qualifying year qualifies only under (c), and only through a parent eligible for +that dividend. `ak_permanent_fund_dividend_eligible` tests the child's own +status, not a parent's eligibility. + +PolicyEngine cannot identify a child born during the dividend year from `age`, +because a child aged 0 may have a birth date in either calendar year. Populace +takes `age` from the CPS ASEC, which the Census Bureau [conducts in February, +March and +April](https://www.census.gov/topics/income-poverty/poverty/guidance/surveys-programs.html), +recording each person's age [as of the survey +date](https://www.census.gov/programs-surveys/cps/technical-documentation/subject-definitions.html). +In each survey, the Bureau asks about income in the year before, which Populace +records as `source_year`. Of the people aged 0 in Populace's 2024 Alaska records, +74% come from the 2023 survey. If each record keeps its own survey date, 92% to +96% of them have birth dates in 2022 or 2023 and can qualify under (c). If every +survey fell in the dividend year instead, 71% to 87% could. The ranges run from +mid-February to mid-April interviews. `birth_year`, which subtracts `age` from +the year, would put all of them in the dividend year. So the model pays a +resident aged 0 unless the take-up input says otherwise. Set that input to false +for a child born during the dividend year, or for a child born during the +qualifying year who has no eligible parent. + +### Recipients in microsimulation + +Populace does not set `takes_up_ak_permanent_fund_dividend_if_eligible` yet, so +the default microsimulation pays everyone who passes the status test: 730,150 +people for 2024, against the [624,489 applications Alaska +paid](https://pfd.alaska.gov/division-info/summary-of-dividend-applications-payments) +for that dividend year. The Microcosm data pipeline can close that gap by setting +the flag from reported receipt and Alaska's paid count; see +[microcosm issue 1551](https://github.com/PolicyEngine/microcosm/issues/1551). +The microsimulation has no inputs before its 2024 base year, so it cannot report +recipients for earlier years. + ## Override calculated payments Supply either variable as an input on the person to replace its calculated -amount. To exclude both transfers from a controlled comparison, set both to zero -for **every person**. Overriding only `ak_permanent_fund_dividend` leaves energy -relief in place. Overriding only tax-unit AGI leaves the underlying person income -and benefit amounts unchanged. +amount. Overriding only `ak_permanent_fund_dividend` leaves energy relief in +place. To exclude both transfers from a controlled comparison, set +`takes_up_ak_permanent_fund_dividend_if_eligible` to false or set both payments +to zero, for **every person**. Overriding only tax-unit AGI leaves the underlying +person income and benefit amounts unchanged. This complete situation retains Alaska residence while excluding both payments: diff --git a/policyengine_us/parameters/gov/states/ak/dor/permanent_fund_dividend_eligible_immigration_statuses.yaml b/policyengine_us/parameters/gov/states/ak/dor/permanent_fund_dividend_eligible_immigration_statuses.yaml new file mode 100644 index 00000000000..9c21d9de366 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ak/dor/permanent_fund_dividend_eligible_immigration_statuses.yaml @@ -0,0 +1,42 @@ +description: Alaska limits the Permanent Fund Dividend to people with these immigration statuses. + +values: + # In State v. Andrade, 23 P.3d 58 (Alaska 2001), the Alaska Supreme Court + # held that "lawfully admitted for permanent residence" in AS + # 43.23.005(a)(5)(B) does not incorporate the federal definition: an alien + # must be legally present and able to form the intent to remain in Alaska. + # In 15 AAC 23.154(a), the Department of Revenue applies that reading to a + # status under 8 U.S.C. 1101 to 1189 that lets the alien adopt the United + # States as their domicile: an immigrant, a nonimmigrant whose status + # requires no foreign residence, a person on indefinite parole, or a + # conditional permanent resident. PolicyEngine has no status for the last + # three, so code them as LEGAL_PERMANENT_RESIDENT. + # + # PolicyEngine leaves the other statuses out. PAROLED_ONE_YEAR does not say a + # parole runs indefinitely, and in OAH No. 25-3180-PFD an administrative law + # judge upheld the Permanent Fund Dividend Division's denial of a fixed-term + # parolee's dividend. TPS (8 U.S.C. 1254a) and DEPORTATION_WITHHELD (8 U.S.C. + # 1231(b)(3)) fall outside 8 U.S.C. 1101 to 1189, and a person coded DACA + # holds no status under those sections. A person coded UNDOCUMENTED lacks the + # legal presence the court required in Andrade. CUBAN_HAITIAN_ENTRANT and + # CONDITIONAL_ENTRANT do not say whether the person may adopt the United + # States as their domicile. + 2015-01-01: + - CITIZEN + - LEGAL_PERMANENT_RESIDENT + - REFUGEE + - ASYLEE + +metadata: + unit: list + period: year + label: Alaska Permanent Fund Dividend eligible immigration statuses + reference: + - title: AS 43.23.005(a)(5), Eligibility + href: https://www.akleg.gov/basis/statutes.asp#43.23.005 + - title: 15 AAC 23.154, Eligibility of aliens + href: https://www.akleg.gov/basis/aac.asp#15.23.154 + - title: Alaska Permanent Fund Dividend Division, Immigration - Alien Status and Eligibility for the PFD + href: https://pfd.alaska.gov/eligibility/immigration + - title: Alaska Office of Administrative Hearings, OAH No. 25-3180-PFD (adopted March 10, 2026) + href: https://aws.state.ak.us/OAH/Decision/Display?rec=7350 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_energy_relief.yaml b/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_energy_relief.yaml index 15b1baed973..e372c665507 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_energy_relief.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_energy_relief.yaml @@ -82,3 +82,73 @@ state_code: AK output: ak_energy_relief: 0 + +- name: Case 13, a resident with no status input gets the 2026 energy relief, since the status defaults to citizen and take-up to true. + period: 2026 + input: + state_code: AK + output: + ak_energy_relief: 200 + +- name: Case 14, an undocumented Alaska resident gets no energy relief, because Alaska pays it as part of the dividend. + period: 2026 + input: + state_code: AK + immigration_status: UNDOCUMENTED + output: + ak_energy_relief: 0 + +- name: Case 15, a resident who does not take up the dividend gets no energy relief. + period: 2026 + input: + state_code: AK + takes_up_ak_permanent_fund_dividend_if_eligible: false + output: + ak_energy_relief: 0 + +- name: Case 16, each person in one household gets energy relief on their own status and take-up. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + citizen: + age: 40 + undocumented_parent: + age: 36 + immigration_status: UNDOCUMENTED + non_applicant: + age: 15 + takes_up_ak_permanent_fund_dividend_if_eligible: false + asylee: + age: 22 + immigration_status: ASYLEE + households: + household: + members: [citizen, undocumented_parent, non_applicant, asylee] + state_code: AK + output: + ak_energy_relief: [298.17, 0, 0, 298.17] + +- name: Case 17, a supplied zero dividend leaves energy relief in place, since each payment has its own override. + period: 2026 + input: + state_code: AK + ak_permanent_fund_dividend: 0 + output: + ak_energy_relief: 200 + +- name: Case 18, an undocumented child gets no energy relief though a citizen parent does. + period: 2026 + input: + people: + parent: + age: 35 + child: + age: 6 + immigration_status: UNDOCUMENTED + households: + household: + members: [parent, child] + state_code: AK + output: + ak_energy_relief: [200, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_permanent_fund_dividend.yaml b/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_permanent_fund_dividend.yaml index c27d49c6925..dd9baa1eac4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_permanent_fund_dividend.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_permanent_fund_dividend.yaml @@ -122,3 +122,142 @@ ak_energy_relief: 0 irs_gross_income: 51_000 adjusted_gross_income: 51_000 + +- name: Case 15, a resident with no status input gets the dividend, since the status defaults to citizen and take-up to true. + period: 2026 + input: + state_code: AK + output: + ak_permanent_fund_dividend_eligible: true + takes_up_ak_permanent_fund_dividend_if_eligible: true + ak_permanent_fund_dividend: 1_000 + +- name: Case 16, a lawful permanent resident gets the dividend under AS 43.23.005(a)(5)(B). + period: 2026 + input: + state_code: AK + immigration_status: LEGAL_PERMANENT_RESIDENT + output: + ak_permanent_fund_dividend: 1_000 + +- name: Case 17, a refugee gets the dividend under AS 43.23.005(a)(5)(C). + period: 2026 + input: + state_code: AK + immigration_status: REFUGEE + output: + ak_permanent_fund_dividend: 1_000 + +- name: Case 18, an asylee gets the dividend under AS 43.23.005(a)(5)(D). + period: 2026 + input: + state_code: AK + immigration_status: ASYLEE + output: + ak_permanent_fund_dividend: 1_000 + +- name: Case 19, an undocumented Alaska resident gets no dividend. + period: 2026 + input: + state_code: AK + immigration_status: UNDOCUMENTED + output: + ak_permanent_fund_dividend_eligible: false + ak_permanent_fund_dividend: 0 + +- name: Case 20, a resident who does not take up the dividend gets none, though eligible. + period: 2026 + input: + state_code: AK + takes_up_ak_permanent_fund_dividend_if_eligible: false + output: + ak_permanent_fund_dividend_eligible: true + ak_permanent_fund_dividend: 0 + +- name: Case 21, each person in one household gets the dividend on their own status and take-up. + period: 2026 + input: + people: + citizen: + age: 40 + permanent_resident: + age: 38 + immigration_status: LEGAL_PERMANENT_RESIDENT + undocumented_parent: + age: 36 + immigration_status: UNDOCUMENTED + non_applicant: + age: 15 + takes_up_ak_permanent_fund_dividend_if_eligible: false + child: + age: 5 + households: + household: + members: [citizen, permanent_resident, undocumented_parent, non_applicant, child] + state_code: AK + output: + ak_permanent_fund_dividend: [1_000, 1_000, 0, 0, 1_000] + +- name: Case 22, a filer who does not take up the dividend has no dividend in federal gross income or AGI. + period: 2026 + input: + people: + person: + age: 40 + employment_income: 50_000 + takes_up_ak_permanent_fund_dividend_if_eligible: false + tax_units: + tax_unit: + members: [person] + households: + household: + members: [person] + state_code: AK + output: + ak_permanent_fund_dividend: 0 + ak_energy_relief: 0 + irs_gross_income: 50_000 + adjusted_gross_income: 50_000 + +- name: Case 23, a supplied dividend overrides the calculation for an ineligible person. + period: 2026 + input: + state_code: AK + immigration_status: UNDOCUMENTED + ak_permanent_fund_dividend: 1_000 + output: + ak_permanent_fund_dividend: 1_000 + +- name: Case 24, an undocumented child gets no dividend though a citizen parent does, since under AS 43.23.005(c) a child still needs a status in (a)(5). + period: 2026 + input: + people: + parent: + age: 35 + child: + age: 6 + immigration_status: UNDOCUMENTED + households: + household: + members: [parent, child] + state_code: AK + output: + ak_permanent_fund_dividend: [1_000, 0] + +- name: Case 25, a resident child under one gets the dividend unless an input says otherwise, since age 0 does not show a birth during the dividend year. + period: 2026 + input: + people: + infant: + age: 0 + infant_born_during_the_dividend_year: + age: 0 + takes_up_ak_permanent_fund_dividend_if_eligible: false + one_year_old: + age: 1 + households: + household: + members: [infant, infant_born_during_the_dividend_year, one_year_old] + state_code: AK + output: + ak_permanent_fund_dividend: [1_000, 0, 1_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_permanent_fund_dividend_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_permanent_fund_dividend_eligible.yaml new file mode 100644 index 00000000000..08e7ff9736d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ak/dor/ak_permanent_fund_dividend_eligible.yaml @@ -0,0 +1,81 @@ +- name: Case 1, a citizen in Alaska qualifies under AS 43.23.005(a)(5)(A). + period: 2026 + input: + state_code: AK + immigration_status: CITIZEN + output: + ak_permanent_fund_dividend_eligible: true + +- name: Case 2, a lawful permanent resident in Alaska qualifies under AS 43.23.005(a)(5)(B). + period: 2026 + input: + state_code: AK + immigration_status: LEGAL_PERMANENT_RESIDENT + output: + ak_permanent_fund_dividend_eligible: true + +- name: Case 3, a refugee in Alaska qualifies under AS 43.23.005(a)(5)(C). + period: 2026 + input: + state_code: AK + immigration_status: REFUGEE + output: + ak_permanent_fund_dividend_eligible: true + +- name: Case 4, an asylee in Alaska qualifies under AS 43.23.005(a)(5)(D). + period: 2026 + input: + state_code: AK + immigration_status: ASYLEE + output: + ak_permanent_fund_dividend_eligible: true + +- name: Case 5, people coded with statuses that PolicyEngine does not map to AS 43.23.005(a)(5) or 15 AAC 23.154(a) do not qualify, including PAROLED_ONE_YEAR, which does not say a parole runs indefinitely. + period: 2026 + input: + people: + undocumented: + immigration_status: UNDOCUMENTED + daca: + immigration_status: DACA + tps: + immigration_status: TPS + paroled: + immigration_status: PAROLED_ONE_YEAR + deportation_withheld: + immigration_status: DEPORTATION_WITHHELD + cuban_haitian_entrant: + immigration_status: CUBAN_HAITIAN_ENTRANT + conditional_entrant: + immigration_status: CONDITIONAL_ENTRANT + households: + household: + members: + [undocumented, daca, tps, paroled, deportation_withheld, cuban_haitian_entrant, conditional_entrant] + state_code: AK + output: + ak_permanent_fund_dividend_eligible: [false, false, false, false, false, false, false] + +- name: Case 6, a citizen outside Alaska does not qualify. + period: 2026 + input: + state_code: WA + immigration_status: CITIZEN + output: + ak_permanent_fund_dividend_eligible: false + +- name: Case 7, PolicyEngine applies the status test in 2015, its first modeled dividend year. + period: 2015 + input: + people: + citizen: + immigration_status: CITIZEN + undocumented: + immigration_status: UNDOCUMENTED + households: + household: + members: [citizen, undocumented] + state_code: AK + output: + ak_permanent_fund_dividend_eligible: [true, false] + ak_permanent_fund_dividend: [2_072, 0] diff --git a/policyengine_us/tests/test_ak_permanent_fund_dividend_receipt.py b/policyengine_us/tests/test_ak_permanent_fund_dividend_receipt.py new file mode 100644 index 00000000000..d5baa19c739 --- /dev/null +++ b/policyengine_us/tests/test_ak_permanent_fund_dividend_receipt.py @@ -0,0 +1,170 @@ +"""Invariants for who receives the Alaska Permanent Fund Dividend and energy relief. + +Under AS 43.23.005(a)(5), Alaska limits the dividend to citizens, lawful +permanent residents, refugees and asylees, and it pays energy relief as part of +the dividend (sec. 13, ch. 34, SLA 2026). PolicyEngine also lets an input say a +person does not take up the dividend, to cover the statute's tests that +PolicyEngine cannot resolve from its other inputs. These properties hold for +every modeled year, every mix of people in a household and every age, unless an +input overrides the eligibility variable or a payment: + +1. A person's dividend and energy relief each equal zero or the full amount + for the year. +2. A person gets the dividend exactly when they live in Alaska, hold one of the + statute's statuses, and take it up. An unset status counts as citizen and an + unset take-up flag as true, so a household calculation pays a resident + unless an input says otherwise. Age plays no part: age 0 does not show a + birth during the dividend year. +3. Energy relief passes through the same gate as the dividend. +4. Nobody outside Alaska gets either payment. +5. Turning one person's take-up off lowers that person's payment to zero and + changes nobody else's. + +ELIGIBLE_STATUSES restates AS 43.23.005(a)(5), so these tests fail if someone +changes the parameter's statuses in a year these tests cover without changing +this file. The YAML tests pin examples. These tests run the real model on every +combination of state, status, take-up and representative age, and on drawn +years, states, ages and household mixes. They share one tax-benefit system, so +they build no extra model and load no dataset. +""" + +from collections import defaultdict, namedtuple +from itertools import product + +import numpy as np +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation +from policyengine_us.system import system +from policyengine_us.tools.parameters import FIRST_MODELED_YEAR +from policyengine_us.variables.household.demographic.geographic.state_code import ( + StateCode, +) +from policyengine_us.variables.household.demographic.person.immigration_status import ( + ImmigrationStatus, +) + +# AS 43.23.005(a)(5)(A) to (D). +ELIGIBLE_STATUSES = {"CITIZEN", "LEGAL_PERMANENT_RESIDENT", "REFUGEE", "ASYLEE"} +TAKE_UP = "takes_up_ak_permanent_fund_dividend_if_eligible" +STATUSES = tuple(status.name for status in ImmigrationStatus) +# None leaves the input unset, so the model's default applies. +STATUS_INPUTS = (None, *STATUSES) +TAKE_UP_INPUTS = (None, True, False) +# None leaves age at the model's default of 40. The rest straddle the ages where +# a mistaken child or newborn rule would change a payment. +AGE_INPUTS = (None, 0, 1, 17, 18, 65) +EXHAUSTIVE_STATES = ("AK", "WA") +ALL_STATES = tuple(state.name for state in StateCode) +LAST_YEAR = 2100 +# 2015 starts the modeled years. Alaska paid energy relief in 2022 and 2026 and +# has appropriated none for 2027. +EXHAUSTIVE_YEARS = (2015, 2022, 2026, 2027) + +Person = namedtuple("Person", ["state", "status", "take_up", "age"]) +PERSON = st.builds( + Person, + # Half the draws land in Alaska, so the gate's positive cases stay common. + st.one_of(st.just("AK"), st.sampled_from(ALL_STATES)), + st.sampled_from(STATUS_INPUTS), + st.sampled_from(TAKE_UP_INPUTS), + st.one_of(st.sampled_from(AGE_INPUTS), st.integers(min_value=0, max_value=120)), +) +PEOPLE = st.lists(PERSON, min_size=1, max_size=10) +YEARS = st.integers(min_value=FIRST_MODELED_YEAR, max_value=LAST_YEAR) + + +def amounts(year): + dor = system.parameters.gov.states.ak.dor + instant = f"{year}-01-01" + return float(dor.permanent_fund_dividend(instant)), float( + dor.energy_relief(instant) + ) + + +def calculate(year, people): + """Run the model with each state's people in one household.""" + situation_people = {} + members = defaultdict(list) + for index, person in enumerate(people): + name = f"person_{index}" + inputs = {} + if person.status is not None: + inputs["immigration_status"] = {year: person.status} + if person.take_up is not None: + inputs[TAKE_UP] = {year: person.take_up} + if person.age is not None: + inputs["age"] = {year: person.age} + situation_people[name] = inputs + members[person.state].append(name) + situation = { + "people": situation_people, + "households": { + state: {"members": names, "state_code": {year: state}} + for state, names in members.items() + }, + } + simulation = Simulation(situation=situation) + # The results follow the order of situation_people, whatever the households. + assert list(simulation.persons.ids) == list(situation_people) + return ( + np.asarray(simulation.calculate("ak_permanent_fund_dividend", year)), + np.asarray(simulation.calculate("ak_energy_relief", year)), + ) + + +def receives(person): + return ( + person.state == "AK" + and (person.status is None or person.status in ELIGIBLE_STATUSES) + and person.take_up is not False + ) + + +def assert_gate_holds(year, people, dividend, energy_relief): + full_dividend, full_energy_relief = amounts(year) + for person, paid, relief in zip(people, dividend, energy_relief): + expected = receives(person) + assert paid == pytest.approx(full_dividend if expected else 0, abs=0.005) + assert relief == pytest.approx(full_energy_relief if expected else 0, abs=0.005) + + +def test_statuses_named_in_the_statute_exist_in_the_model(): + assert ELIGIBLE_STATUSES <= set(STATUSES) + + +@pytest.mark.parametrize("year", EXHAUSTIVE_YEARS) +def test_every_combination_of_state_status_take_up_and_age(year): + people = [ + Person(*combination) + for combination in product( + EXHAUSTIVE_STATES, STATUS_INPUTS, TAKE_UP_INPUTS, AGE_INPUTS + ) + ] + assert_gate_holds(year, people, *calculate(year, people)) + + +@settings(max_examples=25, deadline=None) +@given(YEARS, PEOPLE) +def test_payments_follow_the_gate_for_any_year_and_household_mix(year, people): + assert_gate_holds(year, people, *calculate(year, people)) + + +@settings(max_examples=15, deadline=None) +@given(YEARS, PEOPLE, st.integers(min_value=0)) +def test_turning_take_up_off_only_removes_that_persons_payment(year, people, draw): + target = draw % len(people) + declined = list(people) + declined[target] = people[target]._replace(take_up=False) + dividend, energy_relief = calculate(year, people) + new_dividend, new_energy_relief = calculate(year, declined) + + assert new_dividend[target] == 0 + assert new_energy_relief[target] == 0 + others = np.arange(len(people)) != target + np.testing.assert_allclose(new_dividend[others], dividend[others], atol=0.005) + np.testing.assert_allclose( + new_energy_relief[others], energy_relief[others], atol=0.005 + ) diff --git a/policyengine_us/variables/gov/states/ak/dor/ak_energy_relief.py b/policyengine_us/variables/gov/states/ak/dor/ak_energy_relief.py index 845c9a3efc3..def672e485f 100644 --- a/policyengine_us/variables/gov/states/ak/dor/ak_energy_relief.py +++ b/policyengine_us/variables/gov/states/ak/dor/ak_energy_relief.py @@ -6,12 +6,16 @@ class ak_energy_relief(Variable): entity = Person label = "Alaska One Time Energy Relief" documentation = ( - "Energy relief calculated automatically for each Alaska resident, " - "using current residence as an eligibility proxy without checking an " - "application, prior-year residence, or actual receipt. Supplying " + "PolicyEngine calculates energy relief automatically for each Alaska " + "resident who passes ak_permanent_fund_dividend_eligible and takes up " + "the dividend (takes_up_ak_permanent_fund_dividend_if_eligible, true " + "by default), since Alaska pays it as part of the dividend. The model " + "does not otherwise check an application, prior-year residence, or " + "actual receipt. Supplying " "other income does not disable the calculation. Override this " - "person-level variable directly; excluding both Alaska transfers " - "requires also overriding ak_permanent_fund_dividend for each person. " + "person-level variable directly, which leaves " + "ak_permanent_fund_dividend in place. To remove both Alaska payments " + "for a person, set the take-up input to false. " "The rounded 2022 energy payment is $662, separate from the $2,622 " "regular PFD. Its exclusion from federal gross income follows the " "IRS's 2022 nonchallenge convention, which does not establish a " @@ -21,6 +25,9 @@ class ak_energy_relief(Variable): unit = USD definition_period = YEAR reference = "https://pfd.alaska.gov" - defined_for = StateCode.AK + defined_for = "ak_permanent_fund_dividend_eligible" - adds = ["gov.states.ak.dor.energy_relief"] + def formula(person, period, parameters): + p = parameters(period).gov.states.ak.dor + takes_up = person("takes_up_ak_permanent_fund_dividend_if_eligible", period) + return p.energy_relief * takes_up diff --git a/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend.py b/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend.py index b07a5fa303d..58b700b892a 100644 --- a/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend.py +++ b/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend.py @@ -6,12 +6,16 @@ class ak_permanent_fund_dividend(Variable): entity = Person label = "Alaska Permanent Fund Dividend" documentation = ( - "Regular PFD calculated automatically for each Alaska resident, using " - "current residence as an eligibility proxy without checking an " - "application, prior-year residence, or actual receipt. Supplying wages " - "or other income does not disable this calculation. Supply this " - "person-level variable to override it; to exclude both Alaska " - "transfers, also supply ak_energy_relief as zero for every person. " + "PolicyEngine calculates the regular PFD automatically for each Alaska " + "resident who passes ak_permanent_fund_dividend_eligible and takes up " + "the dividend (takes_up_ak_permanent_fund_dividend_if_eligible, true " + "by default). " + "The model does not otherwise check an application, prior-year " + "residence, or actual receipt. Supplying wages or other income does " + "not disable this calculation. Supply this " + "person-level variable to override it, which leaves ak_energy_relief " + "in place. To remove both Alaska payments for a person, set the " + "take-up input to false. " "A dependent child's PFD is not automatically included in a parent's " "AGI. In 2022 this variable is the rounded $2,622 taxable regular " "dividend; the separate $662 energy payment follows the IRS's 2022 " @@ -20,6 +24,9 @@ class ak_permanent_fund_dividend(Variable): unit = USD definition_period = YEAR reference = "https://pfd.alaska.gov" - defined_for = StateCode.AK + defined_for = "ak_permanent_fund_dividend_eligible" - adds = ["gov.states.ak.dor.permanent_fund_dividend"] + def formula(person, period, parameters): + p = parameters(period).gov.states.ak.dor + takes_up = person("takes_up_ak_permanent_fund_dividend_if_eligible", period) + return p.permanent_fund_dividend * takes_up diff --git a/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend_eligible.py b/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend_eligible.py new file mode 100644 index 00000000000..ba3f8cc705a --- /dev/null +++ b/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend_eligible.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class ak_permanent_fund_dividend_eligible(Variable): + value_type = bool + entity = Person + label = "Alaska Permanent Fund Dividend eligible" + documentation = ( + "Applies the immigration status test in AS 43.23.005(a)(5). " + "PolicyEngine cannot resolve the statute's other tests from its inputs: an " + "application, residence on the application date and during all of " + "the qualifying year, 72 consecutive hours of presence in the prior " + "two years, allowable absences, Selective Service compliance, a " + "felony sentence or conviction-based incarceration during that " + "year, a child's birth during the dividend year, or, for a child " + "born during the qualifying year, a parent's eligibility (AS " + "43.23.005(c)). To model a person who fails one of those, set " + "takes_up_ak_permanent_fund_dividend_if_eligible to false. See " + "docs/validation/alaska-pfd.md." + ) + definition_period = YEAR + reference = "https://www.akleg.gov/basis/statutes.asp#43.23.005" + defined_for = StateCode.AK + + def formula(person, period, parameters): + p = parameters(period).gov.states.ak.dor + immigration_status = person("immigration_status", period) + return np.isin( + immigration_status.decode_to_str(), + p.permanent_fund_dividend_eligible_immigration_statuses, + ) diff --git a/policyengine_us/variables/gov/states/ak/dor/takes_up_ak_permanent_fund_dividend_if_eligible.py b/policyengine_us/variables/gov/states/ak/dor/takes_up_ak_permanent_fund_dividend_if_eligible.py new file mode 100644 index 00000000000..cd1a042dcc6 --- /dev/null +++ b/policyengine_us/variables/gov/states/ak/dor/takes_up_ak_permanent_fund_dividend_if_eligible.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class takes_up_ak_permanent_fund_dividend_if_eligible(Variable): + value_type = bool + entity = Person + label = "Takes up the Alaska Permanent Fund Dividend if eligible" + documentation = ( + "Whether Alaska pays this person the Permanent Fund Dividend and any " + "energy relief when they pass ak_permanent_fund_dividend_eligible. Set " + "this to false for someone who did not apply or who fails a test " + "PolicyEngine cannot resolve from its inputs, such as residence during all " + "of the prior calendar year or a child's birth during the dividend " + "year. It defaults to true, so a household calculation pays every " + "eligible Alaska resident unless an input says otherwise. " + "See docs/validation/alaska-pfd.md." + ) + definition_period = YEAR + default_value = True