diff --git a/changelog.d/codex-id-primitives.fixed.md b/changelog.d/codex-id-primitives.fixed.md new file mode 100644 index 00000000000..694e1019aa6 --- /dev/null +++ b/changelog.d/codex-id-primitives.fixed.md @@ -0,0 +1 @@ +Added statute-facing taxpayer ID primitives, including `taxpayer_id_type` and `has_valid_ssn`, updated federal SSN- and TIN-gated logic to consume them, and kept `has_itin` as a migration alias for `has_tin`. diff --git a/policyengine_us/reforms/reconciliation/reconciled_ssn_for_llc_and_aoc.py b/policyengine_us/reforms/reconciliation/reconciled_ssn_for_llc_and_aoc.py index 86740f80b31..6fbc18dbe55 100644 --- a/policyengine_us/reforms/reconciliation/reconciled_ssn_for_llc_and_aoc.py +++ b/policyengine_us/reforms/reconciliation/reconciled_ssn_for_llc_and_aoc.py @@ -67,13 +67,15 @@ class filer_meets_llc_and_aoc_identification_requirements(Variable): label = "Filer meets LLC and AOC identification requirements" def formula(tax_unit, period, parameters): - # Both head and spouse in the tax unit must have valid SSN card type to be eligible for the CTC + # Both head and spouse in the tax unit must have valid SSNs. person = tax_unit.members is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) - eligible_ssn_card_type = person( + meets_identification_requirements = person( "meets_llc_and_aoc_identification_requirements", period ) - ineligible_head_or_spouse = is_head_or_spouse & ~eligible_ssn_card_type + ineligible_head_or_spouse = ( + is_head_or_spouse & ~meets_identification_requirements + ) return tax_unit.sum(ineligible_head_or_spouse) == 0 class meets_llc_and_aoc_identification_requirements(Variable): @@ -84,13 +86,7 @@ class meets_llc_and_aoc_identification_requirements(Variable): reference = "https://docs.house.gov/meetings/WM/WM00/20250513/118260/BILLS-119CommitteePrintih.pdf#page=4" def formula(person, period, parameters): - ssn_card_type = person("ssn_card_type", period) - ssn_card_types = ssn_card_type.possible_values - citizen = ssn_card_type == ssn_card_types.CITIZEN - non_citizen_valid_ead = ( - ssn_card_type == ssn_card_types.NON_CITIZEN_VALID_EAD - ) - return citizen | non_citizen_valid_ead + return person("has_valid_ssn", period) class reform(Reform): def apply(self): diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/integration.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/integration.yaml index 181319ef027..cea68cccddb 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/integration.yaml @@ -305,7 +305,7 @@ ctc: 500 ctc_adult_individual_maximum: [0, 500] # ODC for 17-year-old -- name: Child without the required SSN gets ODC instead of child CTC in 2024 +- name: Child with ITIN but without the required SSN gets ODC instead of child CTC in 2024 period: 2024 input: people: @@ -315,6 +315,7 @@ age: 10 is_tax_unit_dependent: true ssn_card_type: OTHER_NON_CITIZEN + has_tin: true tax_units: tax_unit: members: [parent, child] @@ -327,6 +328,28 @@ non_refundable_ctc: 500 ctc: 500 +- name: Child with explicit valid SSN gets child CTC instead of ODC in 2024 + period: 2024 + input: + people: + parent: + age: 40 + ssn_card_type: CITIZEN + child: + age: 10 + is_tax_unit_dependent: true + ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true + tax_units: + tax_unit: + members: [parent, child] + income_tax_before_credits: 2_000 + output: + ctc_qualifying_child: [false, true] + ctc_child_individual_maximum: [0, 2_000] + ctc_adult_individual_maximum: [0, 0] + ctc: 2_000 + - name: ITIN-only filer can still claim ODC for an adult dependent in 2026 period: 2026 input: diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/meets_ctc_child_identification_requirements.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/meets_ctc_child_identification_requirements.yaml index 73a47ef58a6..a2a8bff6ab8 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/meets_ctc_child_identification_requirements.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/meets_ctc_child_identification_requirements.yaml @@ -14,6 +14,7 @@ period: 2023 input: ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true output: meets_ctc_child_identification_requirements: true diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/filer_meets_eitc_identification_requirements.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/filer_meets_eitc_identification_requirements.yaml index c9fd65af002..2d3381e5402 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/filer_meets_eitc_identification_requirements.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/filer_meets_eitc_identification_requirements.yaml @@ -13,6 +13,7 @@ person2: is_tax_unit_head_or_spouse: true ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true tax_units: tax_unit: members: [person1, person2] @@ -26,9 +27,11 @@ person1: is_tax_unit_head_or_spouse: true ssn_card_type: OTHER_NON_CITIZEN + has_valid_ssn: false person2: is_tax_unit_head_or_spouse: true ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true tax_units: tax_unit: members: [person1, person2] @@ -42,9 +45,11 @@ person1: is_tax_unit_head_or_spouse: false ssn_card_type: OTHER_NON_CITIZEN + has_valid_ssn: false person2: is_tax_unit_head_or_spouse: true ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true tax_units: tax_unit: members: [person1, person2] diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/meets_eitc_identification_requirements.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/meets_eitc_identification_requirements.yaml index fbcf111a96d..2cdf11c8a1e 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/meets_eitc_identification_requirements.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/earned_income/meets_eitc_identification_requirements.yaml @@ -14,5 +14,6 @@ period: 2023 input: ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true output: meets_eitc_identification_requirements: true diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/senior_deduction/meets_additional_senior_deduction_identification_requirements.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/senior_deduction/meets_additional_senior_deduction_identification_requirements.yaml index f73222bde6c..1b5f26fc5e8 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/senior_deduction/meets_additional_senior_deduction_identification_requirements.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/deductions/senior_deduction/meets_additional_senior_deduction_identification_requirements.yaml @@ -10,6 +10,7 @@ age: 65 is_tax_unit_head_or_spouse: true ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true tax_units: tax_unit: members: [person1, person2] @@ -28,6 +29,7 @@ age: 64 is_tax_unit_head_or_spouse: true ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true tax_units: tax_unit: members: [person1, person2] @@ -46,6 +48,7 @@ age: 65 is_tax_unit_head_or_spouse: false ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true tax_units: tax_unit: members: [person1, person2] diff --git a/policyengine_us/tests/policy/baseline/household/demographic/person/has_tin.yaml b/policyengine_us/tests/policy/baseline/household/demographic/person/has_tin.yaml index 363a9b836bd..95d2f2fc15a 100644 --- a/policyengine_us/tests/policy/baseline/household/demographic/person/has_tin.yaml +++ b/policyengine_us/tests/policy/baseline/household/demographic/person/has_tin.yaml @@ -5,6 +5,27 @@ output: has_tin: false +- name: has_tin derives true from taxpayer_id_type other TIN + period: 2024 + input: + taxpayer_id_type: OTHER_TIN + output: + has_tin: true + +- name: has_tin derives false from taxpayer_id_type none + period: 2024 + input: + taxpayer_id_type: NONE + output: + has_tin: false + +- name: has_tin does not infer TIN from other non-citizen SSN card type + period: 2024 + input: + ssn_card_type: OTHER_NON_CITIZEN + output: + has_tin: false + - name: has_tin stays true when canonical has_tin input is true period: 2024 input: diff --git a/policyengine_us/tests/policy/baseline/household/demographic/person/has_valid_ssn.yaml b/policyengine_us/tests/policy/baseline/household/demographic/person/has_valid_ssn.yaml new file mode 100644 index 00000000000..b1f5a65dc3f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/demographic/person/has_valid_ssn.yaml @@ -0,0 +1,41 @@ +- name: has_valid_ssn follows canonical input + period: 2024 + input: + has_valid_ssn: false + output: + has_valid_ssn: false + +- name: has_valid_ssn derives true from citizen SSN card type + period: 2024 + input: + ssn_card_type: CITIZEN + output: + has_valid_ssn: true + +- name: has_valid_ssn does not infer true from non-citizen EAD proxy alone + period: 2024 + input: + ssn_card_type: NON_CITIZEN_VALID_EAD + output: + has_valid_ssn: false + +- name: has_valid_ssn derives false from explicit other TIN taxpayer ID type + period: 2024 + input: + taxpayer_id_type: OTHER_TIN + output: + has_valid_ssn: false + +- name: has_valid_ssn derives false when no SSN card type is available + period: 2024 + input: + ssn_card_type: NONE + output: + has_valid_ssn: false + +- name: has_valid_ssn derives false from other non-citizen SSN card type + period: 2024 + input: + ssn_card_type: OTHER_NON_CITIZEN + output: + has_valid_ssn: false diff --git a/policyengine_us/tests/policy/baseline/household/demographic/person/taxpayer_id_type.yaml b/policyengine_us/tests/policy/baseline/household/demographic/person/taxpayer_id_type.yaml new file mode 100644 index 00000000000..fae579ff5de --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/demographic/person/taxpayer_id_type.yaml @@ -0,0 +1,50 @@ +- name: taxpayer_id_type follows canonical input + period: 2024 + input: + taxpayer_id_type: OTHER_TIN + output: + taxpayer_id_type: OTHER_TIN + +- name: taxpayer_id_type derives valid SSN from citizen SSN card type + period: 2024 + input: + ssn_card_type: CITIZEN + output: + taxpayer_id_type: VALID_SSN + +- name: taxpayer_id_type does not infer valid SSN from non-citizen EAD proxy alone + period: 2024 + input: + ssn_card_type: NON_CITIZEN_VALID_EAD + output: + taxpayer_id_type: NONE + +- name: taxpayer_id_type derives valid SSN from explicit valid SSN input + period: 2024 + input: + ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true + output: + taxpayer_id_type: VALID_SSN + +- name: taxpayer_id_type does not infer other TIN from other non-citizen SSN card type + period: 2024 + input: + ssn_card_type: OTHER_NON_CITIZEN + output: + taxpayer_id_type: NONE + +- name: taxpayer_id_type derives other TIN from explicit TIN input + period: 2024 + input: + ssn_card_type: OTHER_NON_CITIZEN + has_tin: true + output: + taxpayer_id_type: OTHER_TIN + +- name: taxpayer_id_type derives none when no SSN card type is available + period: 2024 + input: + ssn_card_type: NONE + output: + taxpayer_id_type: NONE diff --git a/policyengine_us/tests/policy/contrib/reconciliation/reconciled_ssn_for_llc_and_aoc.yaml b/policyengine_us/tests/policy/contrib/reconciliation/reconciled_ssn_for_llc_and_aoc.yaml index 8a61f5667d5..3d8132702a7 100644 --- a/policyengine_us/tests/policy/contrib/reconciliation/reconciled_ssn_for_llc_and_aoc.yaml +++ b/policyengine_us/tests/policy/contrib/reconciliation/reconciled_ssn_for_llc_and_aoc.yaml @@ -65,6 +65,7 @@ is_eligible_for_american_opportunity_credit: true person2: ssn_card_type: NON_CITIZEN_VALID_EAD + has_valid_ssn: true qualified_tuition_expenses: 1_000 is_eligible_for_american_opportunity_credit: true tax_units: diff --git a/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/meets_ctc_child_identification_requirements.py b/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/meets_ctc_child_identification_requirements.py index 0ffc8504445..88ffb56072b 100644 --- a/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/meets_ctc_child_identification_requirements.py +++ b/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/meets_ctc_child_identification_requirements.py @@ -11,8 +11,5 @@ class meets_ctc_child_identification_requirements(Variable): def formula(person, period, parameters): p = parameters(period).gov.irs.credits.ctc if p.child_ssn_requirement_applies: - ssn_card_type = person("ssn_card_type", period) - ssn_card_str = ssn_card_type.decode_to_str() - p = parameters(period).gov.irs.credits.ctc - return np.isin(ssn_card_str, p.eligible_ssn_card_type) + return person("has_valid_ssn", period) return True diff --git a/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/meets_ctc_identification_requirements.py b/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/meets_ctc_identification_requirements.py index ef2c95fa55a..bd0970c2451 100644 --- a/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/meets_ctc_identification_requirements.py +++ b/policyengine_us/variables/gov/irs/credits/ctc/maximum/individual/meets_ctc_identification_requirements.py @@ -9,7 +9,4 @@ class meets_ctc_identification_requirements(Variable): reference = "https://www.congress.gov/bill/119th-congress/house-bill/1/text" def formula(person, period, parameters): - ssn_card_type = person("ssn_card_type", period) - ssn_card_str = ssn_card_type.decode_to_str() - p = parameters(period).gov.irs.credits.ctc - return np.isin(ssn_card_str, p.eligible_ssn_card_type) + return person("has_valid_ssn", period) diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/filer_meets_eitc_identification_requirements.py b/policyengine_us/variables/gov/irs/credits/earned_income/filer_meets_eitc_identification_requirements.py index 9c9b2ec7abe..cd6d40de050 100644 --- a/policyengine_us/variables/gov/irs/credits/earned_income/filer_meets_eitc_identification_requirements.py +++ b/policyengine_us/variables/gov/irs/credits/earned_income/filer_meets_eitc_identification_requirements.py @@ -12,11 +12,13 @@ class filer_meets_eitc_identification_requirements(Variable): ) def formula(tax_unit, period, parameters): - # Both head and spouse in the tax unit must have valid SSN card type to be eligible for the EITC + # Both head and spouse in the tax unit must have valid SSNs to be eligible for the EITC. person = tax_unit.members is_head_or_spouse = person("is_tax_unit_head_or_spouse", period) - eligible_ssn_card_type = person( + meets_identification_requirements = person( "meets_eitc_identification_requirements", period ) - ineligible_head_or_spouse = is_head_or_spouse & ~eligible_ssn_card_type + ineligible_head_or_spouse = ( + is_head_or_spouse & ~meets_identification_requirements + ) return tax_unit.sum(ineligible_head_or_spouse) == 0 diff --git a/policyengine_us/variables/gov/irs/credits/earned_income/meets_eitc_identification_requirements.py b/policyengine_us/variables/gov/irs/credits/earned_income/meets_eitc_identification_requirements.py index d32e994548c..13883f4bea2 100644 --- a/policyengine_us/variables/gov/irs/credits/earned_income/meets_eitc_identification_requirements.py +++ b/policyengine_us/variables/gov/irs/credits/earned_income/meets_eitc_identification_requirements.py @@ -19,8 +19,4 @@ class meets_eitc_identification_requirements(Variable): ) def formula(person, period, parameters): - ssn_card_type = person("ssn_card_type", period) - ssn_card_types = ssn_card_type.possible_values - citizen = ssn_card_type == ssn_card_types.CITIZEN - non_citizen_valid_ead = ssn_card_type == ssn_card_types.NON_CITIZEN_VALID_EAD - return citizen | non_citizen_valid_ead + return person("has_valid_ssn", period) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/overtime_income/overtime_income_deduction_ssn_requirement_met.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/overtime_income/overtime_income_deduction_ssn_requirement_met.py index a2429af10cf..be2f9cb31b8 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/overtime_income/overtime_income_deduction_ssn_requirement_met.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/overtime_income/overtime_income_deduction_ssn_requirement_met.py @@ -9,13 +9,9 @@ class overtime_income_deduction_ssn_requirement_met(Variable): def formula(tax_unit, period, parameters): person = tax_unit.members - ssn_card_type = person("ssn_card_type", period) - ssn_card_str = ssn_card_type.decode_to_str() - p = parameters(period).gov.irs.deductions.overtime_income - eligible_ssn_card_type = np.isin(ssn_card_str, p.eligible_ssn_card_type) joint = tax_unit("tax_unit_is_joint", period) head_or_spouse = person("is_tax_unit_head_or_spouse", period) - eligible_head_or_spouse = eligible_ssn_card_type & head_or_spouse + eligible_head_or_spouse = person("has_valid_ssn", period) & head_or_spouse return where( joint, tax_unit.sum(eligible_head_or_spouse) == 2, diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/senior_deduction/additional_senior_deduction_eligible_person.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/senior_deduction/additional_senior_deduction_eligible_person.py index 669a06e65b9..2fe9227e75e 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/senior_deduction/additional_senior_deduction_eligible_person.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/senior_deduction/additional_senior_deduction_eligible_person.py @@ -9,12 +9,8 @@ class additional_senior_deduction_eligible_person(Variable): reference = "https://www.congress.gov/bill/119th-congress/house-bill/1/text" def formula(person, period, parameters): - ssn_card_type = person("ssn_card_type", period) - ssn_card_str = ssn_card_type.decode_to_str() - p = parameters(period).gov.irs.deductions.senior_deduction - eligible_ssn_card_type = np.isin(ssn_card_str, p.eligible_ssn_card_type) head_or_spouse = person("is_tax_unit_head_or_spouse", period) age = person("age", period) p_irs = parameters(period).gov.irs.deductions.standard.aged_or_blind aged = age >= p_irs.age_threshold - return eligible_ssn_card_type & head_or_spouse & aged + return person("has_valid_ssn", period) & head_or_spouse & aged diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tip_income/tip_income_deduction_ssn_requirement_met.py b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tip_income/tip_income_deduction_ssn_requirement_met.py index 6e06600c6cf..681d7985ac5 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tip_income/tip_income_deduction_ssn_requirement_met.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/deductions/tip_income/tip_income_deduction_ssn_requirement_met.py @@ -9,13 +9,9 @@ class tip_income_deduction_ssn_requirement_met(Variable): def formula(tax_unit, period, parameters): person = tax_unit.members - ssn_card_type = person("ssn_card_type", period) - ssn_card_str = ssn_card_type.decode_to_str() - p = parameters(period).gov.irs.deductions.tip_income - eligible_ssn_card_type = np.isin(ssn_card_str, p.eligible_ssn_card_type) joint = tax_unit("tax_unit_is_joint", period) head_or_spouse = person("is_tax_unit_head_or_spouse", period) - eligible_head_or_spouse = eligible_ssn_card_type & head_or_spouse + eligible_head_or_spouse = person("has_valid_ssn", period) & head_or_spouse return where( joint, tax_unit.sum(eligible_head_or_spouse) == 2, diff --git a/policyengine_us/variables/household/demographic/person/has_tin.py b/policyengine_us/variables/household/demographic/person/has_tin.py index 2dd744b99d8..bf9e7a6ef2e 100644 --- a/policyengine_us/variables/household/demographic/person/has_tin.py +++ b/policyengine_us/variables/household/demographic/person/has_tin.py @@ -4,14 +4,14 @@ class has_tin(Variable): value_type = bool entity = Person - label = "Has TIN (ITIN or SSN)" + label = "Has taxpayer identification number (TIN)" definition_period = YEAR default_value = True def formula(person, period, parameters): simulation = person.simulation - # Canonical path: allow direct `has_tin` inputs to override the formula. + # Allow direct `has_tin` inputs to override the derived value. holder = simulation.get_holder("has_tin") if period in holder.get_known_periods(): array = holder.get_array(period) @@ -25,4 +25,5 @@ def formula(person, period, parameters): if array is not None: return array - return np.full(person.count, True) + taxpayer_id_type = person("taxpayer_id_type", period) + return taxpayer_id_type != taxpayer_id_type.possible_values.NONE diff --git a/policyengine_us/variables/household/demographic/person/has_valid_ssn.py b/policyengine_us/variables/household/demographic/person/has_valid_ssn.py new file mode 100644 index 00000000000..9c56fe184d7 --- /dev/null +++ b/policyengine_us/variables/household/demographic/person/has_valid_ssn.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class has_valid_ssn(Variable): + value_type = bool + entity = Person + label = "Has a valid SSN for SSN-gated federal tax rules" + definition_period = YEAR + default_value = True + + def formula(person, period, parameters): + id_holder = person.simulation.get_holder("taxpayer_id_type") + if period in id_holder.get_known_periods(): + taxpayer_id_type = person("taxpayer_id_type", period) + return taxpayer_id_type == taxpayer_id_type.possible_values.VALID_SSN + + ssn_card_type = person("ssn_card_type", period) + ssn_card_types = ssn_card_type.possible_values + return ssn_card_type == ssn_card_types.CITIZEN diff --git a/policyengine_us/variables/household/demographic/person/taxpayer_id_type.py b/policyengine_us/variables/household/demographic/person/taxpayer_id_type.py new file mode 100644 index 00000000000..e725836be6e --- /dev/null +++ b/policyengine_us/variables/household/demographic/person/taxpayer_id_type.py @@ -0,0 +1,35 @@ +from policyengine_us.model_api import * + + +class TaxpayerIDType(Enum): + VALID_SSN = "Valid SSN" + OTHER_TIN = "Other TIN" + NONE = "None" + + +class taxpayer_id_type(Variable): + value_type = Enum + entity = Person + possible_values = TaxpayerIDType + default_value = TaxpayerIDType.VALID_SSN + definition_period = YEAR + label = "Taxpayer ID type for federal tax identification rules" + + def formula(person, period, parameters): + has_valid_ssn = person("has_valid_ssn", period) + + simulation = person.simulation + tin_holder = simulation.get_holder("has_tin") + if period in tin_holder.get_known_periods(): + has_tin = tin_holder.get_array(period) + else: + legacy_holder = simulation.get_holder("has_itin") + if period in legacy_holder.get_known_periods(): + has_tin = legacy_holder.get_array(period) + else: + has_tin = has_valid_ssn + + derived = np.full(person.count, TaxpayerIDType.NONE.name, dtype=object) + derived[has_tin] = TaxpayerIDType.OTHER_TIN.name + derived[has_valid_ssn] = TaxpayerIDType.VALID_SSN.name + return TaxpayerIDType.encode(derived)