diff --git a/changelog.d/codex-local-employee-taxes.changed.md b/changelog.d/codex-local-employee-taxes.changed.md new file mode 100644 index 00000000000..6c469f2c0e3 --- /dev/null +++ b/changelog.d/codex-local-employee-taxes.changed.md @@ -0,0 +1 @@ +Add employee-side local income and occupational taxes for Philadelphia, Kansas City, St. Louis, and Colorado occupational privilege jurisdictions, wiring them into household net income. diff --git a/policyengine_us/parameters/gov/local/co/denver/tax/payroll/occupational_privilege/employee_amount.yaml b/policyengine_us/parameters/gov/local/co/denver/tax/payroll/occupational_privilege/employee_amount.yaml new file mode 100644 index 00000000000..4274b0d0be7 --- /dev/null +++ b/policyengine_us/parameters/gov/local/co/denver/tax/payroll/occupational_privilege/employee_amount.yaml @@ -0,0 +1,11 @@ +description: Denver employee occupational privilege tax amount per taxable month. +values: + 0000-01-01: 5.75 + +metadata: + period: month + unit: currency-USD + label: Denver employee occupational privilege tax monthly amount + reference: + - title: Denver business tax FAQ + href: https://www.denvergov.org/Government/Agencies-Departments-Offices/Agencies-Departments-Offices-Directory/Department-of-Finance/Our-Divisions/Treasury/Business-Tax-Information/Business-Tax-FAQ diff --git a/policyengine_us/parameters/gov/local/co/glendale/tax/payroll/occupational_privilege/employee_amount.yaml b/policyengine_us/parameters/gov/local/co/glendale/tax/payroll/occupational_privilege/employee_amount.yaml new file mode 100644 index 00000000000..17ee7f6eb47 --- /dev/null +++ b/policyengine_us/parameters/gov/local/co/glendale/tax/payroll/occupational_privilege/employee_amount.yaml @@ -0,0 +1,11 @@ +description: Glendale employee occupational privilege tax amount per taxable month. +values: + 0000-01-01: 5 + +metadata: + period: month + unit: currency-USD + label: Glendale employee occupational privilege tax monthly amount + reference: + - title: Glendale occupational privilege tax page + href: https://www.glendale.co.us/355/Occupational-Privilege-Tax diff --git a/policyengine_us/parameters/gov/local/co/greenwood_village/tax/payroll/occupational_privilege/employee_amount.yaml b/policyengine_us/parameters/gov/local/co/greenwood_village/tax/payroll/occupational_privilege/employee_amount.yaml new file mode 100644 index 00000000000..3923e8426e9 --- /dev/null +++ b/policyengine_us/parameters/gov/local/co/greenwood_village/tax/payroll/occupational_privilege/employee_amount.yaml @@ -0,0 +1,11 @@ +description: Greenwood Village employee occupational privilege tax amount per taxable month. +values: + 0000-01-01: 2 + +metadata: + period: month + unit: currency-USD + label: Greenwood Village employee occupational privilege tax monthly amount + reference: + - title: Greenwood Village occupational privilege tax newsletter + href: https://greenwoodvillage.com/files/newsletter2010/April2010/TakeNote.pdf#page=18 diff --git a/policyengine_us/parameters/gov/local/co/sheridan/tax/payroll/occupational_privilege/employee_amount.yaml b/policyengine_us/parameters/gov/local/co/sheridan/tax/payroll/occupational_privilege/employee_amount.yaml new file mode 100644 index 00000000000..2bffd814879 --- /dev/null +++ b/policyengine_us/parameters/gov/local/co/sheridan/tax/payroll/occupational_privilege/employee_amount.yaml @@ -0,0 +1,11 @@ +description: Sheridan employee occupational privilege tax amount per taxable month. +values: + 0000-01-01: 3 + +metadata: + period: month + unit: currency-USD + label: Sheridan employee occupational privilege tax monthly amount + reference: + - title: Sheridan occupational privilege tax page + href: https://www.ci.sheridan.co.us/288/Occupational-Privilege-Tax diff --git a/policyengine_us/parameters/gov/local/mo/kansas_city/tax/income/rate.yaml b/policyengine_us/parameters/gov/local/mo/kansas_city/tax/income/rate.yaml new file mode 100644 index 00000000000..fa14d194a69 --- /dev/null +++ b/policyengine_us/parameters/gov/local/mo/kansas_city/tax/income/rate.yaml @@ -0,0 +1,10 @@ +description: Kansas City earnings tax rate. +values: + 0000-01-01: 0.01 + +metadata: + unit: /1 + label: Kansas City earnings tax rate + reference: + - title: Kansas City earnings tax page + href: https://www.kcmo.gov/city-hall/departments/finance/earnings-tax diff --git a/policyengine_us/parameters/gov/local/mo/st_louis/tax/income/rate.yaml b/policyengine_us/parameters/gov/local/mo/st_louis/tax/income/rate.yaml new file mode 100644 index 00000000000..308d6e0cc40 --- /dev/null +++ b/policyengine_us/parameters/gov/local/mo/st_louis/tax/income/rate.yaml @@ -0,0 +1,10 @@ +description: St. Louis earnings tax rate. +values: + 0000-01-01: 0.01 + +metadata: + unit: /1 + label: St. Louis earnings tax rate + reference: + - title: St. Louis individual earnings tax page + href: https://www.stlouis-mo.gov/government/departments/collector/earnings-tax/file-earnings-tax.cfm diff --git a/policyengine_us/parameters/gov/local/pa/philadelphia/tax/income/nonresident_rate.yaml b/policyengine_us/parameters/gov/local/pa/philadelphia/tax/income/nonresident_rate.yaml new file mode 100644 index 00000000000..0d03bbfa746 --- /dev/null +++ b/policyengine_us/parameters/gov/local/pa/philadelphia/tax/income/nonresident_rate.yaml @@ -0,0 +1,13 @@ +description: Philadelphia nonresident wage tax rate. +values: + 2022-07-01: 0.0344 + 2023-07-01: 0.0344 + 2024-07-01: 0.0344 + 2025-07-01: 0.0343 + +metadata: + unit: /1 + label: Philadelphia nonresident wage tax rate + reference: + - title: Philadelphia Wage Tax employers page + href: https://www.phila.gov/services/payments-assistance-taxes/taxes/business-taxes/business-taxes-by-type/wage-tax-employers/ diff --git a/policyengine_us/parameters/gov/local/pa/philadelphia/tax/income/reduced_rate.yaml b/policyengine_us/parameters/gov/local/pa/philadelphia/tax/income/reduced_rate.yaml new file mode 100644 index 00000000000..600e0895a32 --- /dev/null +++ b/policyengine_us/parameters/gov/local/pa/philadelphia/tax/income/reduced_rate.yaml @@ -0,0 +1,10 @@ +description: Philadelphia reduced income-based wage tax rate. +values: + 0000-01-01: 0.015 + +metadata: + unit: /1 + label: Philadelphia reduced income-based wage tax rate + reference: + - title: Philadelphia Wage Tax employers page + href: https://www.phila.gov/services/payments-assistance-taxes/taxes/business-taxes/business-taxes-by-type/wage-tax-employers/ diff --git a/policyengine_us/parameters/gov/local/pa/philadelphia/tax/income/resident_rate.yaml b/policyengine_us/parameters/gov/local/pa/philadelphia/tax/income/resident_rate.yaml new file mode 100644 index 00000000000..d43d5c98d3b --- /dev/null +++ b/policyengine_us/parameters/gov/local/pa/philadelphia/tax/income/resident_rate.yaml @@ -0,0 +1,13 @@ +description: Philadelphia resident wage tax rate. +values: + 2022-07-01: 0.0379 + 2023-07-01: 0.0375 + 2024-07-01: 0.0375 + 2025-07-01: 0.0374 + +metadata: + unit: /1 + label: Philadelphia resident wage tax rate + reference: + - title: Philadelphia Wage Tax employers page + href: https://www.phila.gov/services/payments-assistance-taxes/taxes/business-taxes/business-taxes-by-type/wage-tax-employers/ diff --git a/policyengine_us/tests/core/test_local_employee_taxes.py b/policyengine_us/tests/core/test_local_employee_taxes.py new file mode 100644 index 00000000000..1a3d20fb3b9 --- /dev/null +++ b/policyengine_us/tests/core/test_local_employee_taxes.py @@ -0,0 +1,299 @@ +import pytest + +from policyengine_us import CountryTaxBenefitSystem, Simulation + + +SYSTEM = CountryTaxBenefitSystem() +PERIOD = "2026" +WAGES = 100_000 + + +def make_local_tax_simulation( + state_code: str, + period: str = PERIOD, + *, + pa_philadelphia_wage_tax_taxable_wages: float = 0, + pa_philadelphia_wage_tax_resident: bool = False, + pa_philadelphia_wage_tax_reduced_rate_eligible: bool = False, + mo_kansas_city_earnings_tax_taxable_earnings: float = 0, + mo_st_louis_earnings_tax_taxable_earnings: float = 0, + mo_st_louis_earnings_tax_credit: float = 0, + co_denver_employee_occupational_privilege_tax_months: int = 0, + co_glendale_employee_occupational_privilege_tax_months: int = 0, + co_greenwood_village_employee_occupational_privilege_tax_months: int = 0, + co_sheridan_employee_occupational_privilege_tax_months: int = 0, +) -> Simulation: + return Simulation( + tax_benefit_system=SYSTEM, + situation={ + "people": { + "person": { + "age": {period: 30}, + "employment_income": {period: WAGES}, + "pa_philadelphia_wage_tax_taxable_wages": { + period: pa_philadelphia_wage_tax_taxable_wages + }, + "pa_philadelphia_wage_tax_resident": { + period: pa_philadelphia_wage_tax_resident + }, + "pa_philadelphia_wage_tax_reduced_rate_eligible": { + period: pa_philadelphia_wage_tax_reduced_rate_eligible + }, + "mo_kansas_city_earnings_tax_taxable_earnings": { + period: mo_kansas_city_earnings_tax_taxable_earnings + }, + "mo_st_louis_earnings_tax_taxable_earnings": { + period: mo_st_louis_earnings_tax_taxable_earnings + }, + "mo_st_louis_earnings_tax_credit": { + period: mo_st_louis_earnings_tax_credit + }, + "co_denver_employee_occupational_privilege_tax_months": { + period: co_denver_employee_occupational_privilege_tax_months + }, + "co_glendale_employee_occupational_privilege_tax_months": { + period: co_glendale_employee_occupational_privilege_tax_months + }, + "co_greenwood_village_employee_occupational_privilege_tax_months": { + period: co_greenwood_village_employee_occupational_privilege_tax_months + }, + "co_sheridan_employee_occupational_privilege_tax_months": { + period: co_sheridan_employee_occupational_privilege_tax_months + }, + } + }, + "households": { + "household": { + "members": ["person"], + "state_code": {period: state_code}, + } + }, + "tax_units": {"tax_unit": {"members": ["person"]}}, + "spm_units": {"spm_unit": {"members": ["person"]}}, + "families": {"family": {"members": ["person"]}}, + "marital_units": {"marital_unit": {"members": ["person"]}}, + }, + ) + + +def calculate(sim: Simulation, variable: str, period: str = PERIOD) -> float: + return sim.calculate(variable, period)[0] + + +def make_nyc_credit_simulation( + *, + period: str = PERIOD, + nyc_income_tax_before_refundable_credits: float = 0, + nyc_refundable_credits: float = 0, +) -> Simulation: + return Simulation( + tax_benefit_system=SYSTEM, + situation={ + "people": {"person": {"age": {period: 30}}}, + "households": { + "household": { + "members": ["person"], + "state_code": {period: "NY"}, + "in_nyc": {period: True}, + } + }, + "tax_units": { + "tax_unit": { + "members": ["person"], + "nyc_income_tax_before_refundable_credits": { + period: nyc_income_tax_before_refundable_credits + }, + "nyc_refundable_credits": {period: nyc_refundable_credits}, + } + }, + "spm_units": {"spm_unit": {"members": ["person"]}}, + "families": {"family": {"members": ["person"]}}, + "marital_units": {"marital_unit": {"members": ["person"]}}, + }, + ) + + +@pytest.mark.parametrize( + ("label", "state_code", "inputs", "variable", "expected"), + [ + ( + "philadelphia_resident", + "PA", + { + "pa_philadelphia_wage_tax_taxable_wages": WAGES, + "pa_philadelphia_wage_tax_resident": True, + }, + "pa_philadelphia_wage_tax", + 3_740, + ), + ( + "philadelphia_nonresident", + "NJ", + { + "pa_philadelphia_wage_tax_taxable_wages": WAGES, + }, + "pa_philadelphia_wage_tax", + 3_430, + ), + ( + "philadelphia_reduced_rate", + "NJ", + { + "pa_philadelphia_wage_tax_taxable_wages": WAGES, + "pa_philadelphia_wage_tax_reduced_rate_eligible": True, + }, + "pa_philadelphia_wage_tax", + 1_500, + ), + ( + "kansas_city_nonresident_worker", + "KS", + { + "mo_kansas_city_earnings_tax_taxable_earnings": WAGES, + }, + "mo_kansas_city_earnings_tax", + 1_000, + ), + ( + "st_louis_credit", + "IL", + { + "mo_st_louis_earnings_tax_taxable_earnings": WAGES, + "mo_st_louis_earnings_tax_credit": 200, + }, + "mo_st_louis_earnings_tax", + 800, + ), + ( + "st_louis_credit_floor", + "IL", + { + "mo_st_louis_earnings_tax_taxable_earnings": WAGES, + "mo_st_louis_earnings_tax_credit": 2_000, + }, + "mo_st_louis_earnings_tax", + 0, + ), + ], + ids=lambda item: item if isinstance(item, str) else None, +) +def test_local_income_tax_components( + label: str, + state_code: str, + inputs: dict[str, float | bool], + variable: str, + expected: float, +): + sim = make_local_tax_simulation(state_code, **inputs) + assert calculate(sim, variable) == pytest.approx(expected, abs=0.01) + + +def test_philadelphia_wage_tax_uses_historical_rate_schedule(): + sim = make_local_tax_simulation( + "PA", + period="2024", + pa_philadelphia_wage_tax_taxable_wages=WAGES, + pa_philadelphia_wage_tax_resident=True, + ) + + assert calculate(sim, "pa_philadelphia_wage_tax", "2024") == pytest.approx( + 3_750, abs=0.01 + ) + + +def test_local_occupational_tax_components(): + sim = make_local_tax_simulation( + "WY", + co_denver_employee_occupational_privilege_tax_months=12, + co_glendale_employee_occupational_privilege_tax_months=12, + co_greenwood_village_employee_occupational_privilege_tax_months=12, + co_sheridan_employee_occupational_privilege_tax_months=12, + ) + + assert calculate(sim, "co_denver_employee_occupational_privilege_tax") == 69 + assert calculate(sim, "co_glendale_employee_occupational_privilege_tax") == 60 + assert ( + calculate(sim, "co_greenwood_village_employee_occupational_privilege_tax") == 24 + ) + assert calculate(sim, "co_sheridan_employee_occupational_privilege_tax") == 36 + assert calculate(sim, "local_occupational_tax") == 189 + + +def test_local_income_tax_before_refundable_credits_keeps_nyc_pre_credit_amount(): + baseline = make_nyc_credit_simulation() + reformed = make_nyc_credit_simulation( + nyc_income_tax_before_refundable_credits=500, + nyc_refundable_credits=200, + ) + + assert calculate(reformed, "local_income_tax") - calculate( + baseline, "local_income_tax" + ) == pytest.approx(300, abs=0.01) + assert calculate( + reformed, "household_state_tax_before_refundable_credits" + ) - calculate( + baseline, "household_state_tax_before_refundable_credits" + ) == pytest.approx(500, abs=0.01) + assert calculate(reformed, "household_tax_before_refundable_credits") - calculate( + baseline, "household_tax_before_refundable_credits" + ) == pytest.approx(500, abs=0.01) + + +def test_st_louis_credit_does_not_pool_across_people(): + sim = Simulation( + tax_benefit_system=SYSTEM, + situation={ + "people": { + "person1": { + "age": {PERIOD: 30}, + "is_tax_unit_head": {PERIOD: True}, + "mo_st_louis_earnings_tax_taxable_earnings": {PERIOD: 10_000}, + "mo_st_louis_earnings_tax_credit": {PERIOD: 200}, + }, + "person2": { + "age": {PERIOD: 30}, + "is_tax_unit_spouse": {PERIOD: True}, + "mo_st_louis_earnings_tax_taxable_earnings": {PERIOD: 10_000}, + "mo_st_louis_earnings_tax_credit": {PERIOD: 0}, + }, + }, + "households": { + "household": { + "members": ["person1", "person2"], + "state_code": {PERIOD: "MO"}, + } + }, + "tax_units": {"tax_unit": {"members": ["person1", "person2"]}}, + "spm_units": {"spm_unit": {"members": ["person1", "person2"]}}, + "families": {"family": {"members": ["person1", "person2"]}}, + "marital_units": {"marital_unit": {"members": ["person1", "person2"]}}, + }, + ) + + assert calculate(sim, "mo_st_louis_earnings_tax_before_credit") == pytest.approx( + 200, abs=0.01 + ) + assert calculate(sim, "mo_st_louis_earnings_tax") == pytest.approx(100, abs=0.01) + + +def test_local_taxes_feed_household_net_income(): + baseline = make_local_tax_simulation("PA") + reformed = make_local_tax_simulation( + "PA", + pa_philadelphia_wage_tax_taxable_wages=WAGES, + pa_philadelphia_wage_tax_resident=True, + co_denver_employee_occupational_privilege_tax_months=12, + ) + + local_income_tax = calculate(reformed, "local_income_tax") + local_occupational_tax = calculate(reformed, "local_occupational_tax") + total_local_tax = local_income_tax + local_occupational_tax + + assert local_income_tax == pytest.approx(3_740, abs=0.01) + assert local_occupational_tax == pytest.approx(69, abs=0.01) + assert calculate(reformed, "household_tax_before_refundable_credits") - calculate( + baseline, "household_tax_before_refundable_credits" + ) == pytest.approx(total_local_tax, abs=0.01) + assert calculate(baseline, "household_net_income") - calculate( + reformed, "household_net_income" + ) == pytest.approx(total_local_tax, abs=0.01) diff --git a/policyengine_us/variables/gov/local/co/denver/tax/payroll/occupational_privilege/co_denver_employee_occupational_privilege_tax.py b/policyengine_us/variables/gov/local/co/denver/tax/payroll/occupational_privilege/co_denver_employee_occupational_privilege_tax.py new file mode 100644 index 00000000000..f9184c2a57a --- /dev/null +++ b/policyengine_us/variables/gov/local/co/denver/tax/payroll/occupational_privilege/co_denver_employee_occupational_privilege_tax.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class co_denver_employee_occupational_privilege_tax(Variable): + value_type = float + entity = TaxUnit + label = "Denver employee occupational privilege tax" + documentation = ( + "Employee-side Denver occupational privilege tax from explicit " + "taxable-month inputs." + ) + definition_period = YEAR + unit = USD + reference = ( + "https://www.denvergov.org/Government/Agencies-Departments-Offices/" + "Agencies-Departments-Offices-Directory/Department-of-Finance/" + "Our-Divisions/Treasury/Business-Tax-Information/Business-Tax-FAQ" + ) + + def formula(tax_unit, period, parameters): + taxable_months = tax_unit.sum( + tax_unit.members( + "co_denver_employee_occupational_privilege_tax_months", period + ) + ) + p = parameters(period).gov.local.co.denver.tax.payroll.occupational_privilege + return taxable_months * p.employee_amount diff --git a/policyengine_us/variables/gov/local/co/glendale/tax/payroll/occupational_privilege/co_glendale_employee_occupational_privilege_tax.py b/policyengine_us/variables/gov/local/co/glendale/tax/payroll/occupational_privilege/co_glendale_employee_occupational_privilege_tax.py new file mode 100644 index 00000000000..43297871603 --- /dev/null +++ b/policyengine_us/variables/gov/local/co/glendale/tax/payroll/occupational_privilege/co_glendale_employee_occupational_privilege_tax.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class co_glendale_employee_occupational_privilege_tax(Variable): + value_type = float + entity = TaxUnit + label = "Glendale employee occupational privilege tax" + documentation = ( + "Employee-side Glendale occupational privilege tax from explicit " + "taxable-month inputs." + ) + definition_period = YEAR + unit = USD + reference = "https://www.glendale.co.us/355/Occupational-Privilege-Tax" + + def formula(tax_unit, period, parameters): + taxable_months = tax_unit.sum( + tax_unit.members( + "co_glendale_employee_occupational_privilege_tax_months", period + ) + ) + p = parameters(period).gov.local.co.glendale.tax.payroll.occupational_privilege + return taxable_months * p.employee_amount diff --git a/policyengine_us/variables/gov/local/co/greenwood_village/tax/payroll/occupational_privilege/co_greenwood_village_employee_occupational_privilege_tax.py b/policyengine_us/variables/gov/local/co/greenwood_village/tax/payroll/occupational_privilege/co_greenwood_village_employee_occupational_privilege_tax.py new file mode 100644 index 00000000000..7c402a80ed9 --- /dev/null +++ b/policyengine_us/variables/gov/local/co/greenwood_village/tax/payroll/occupational_privilege/co_greenwood_village_employee_occupational_privilege_tax.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class co_greenwood_village_employee_occupational_privilege_tax(Variable): + value_type = float + entity = TaxUnit + label = "Greenwood Village employee occupational privilege tax" + documentation = ( + "Employee-side Greenwood Village occupational privilege tax from " + "explicit taxable-month inputs." + ) + definition_period = YEAR + unit = USD + reference = "https://greenwoodvillage.com/files/newsletter2010/April2010/TakeNote.pdf#page=18" + + def formula(tax_unit, period, parameters): + taxable_months = tax_unit.sum( + tax_unit.members( + "co_greenwood_village_employee_occupational_privilege_tax_months", + period, + ) + ) + p = parameters( + period + ).gov.local.co.greenwood_village.tax.payroll.occupational_privilege + return taxable_months * p.employee_amount diff --git a/policyengine_us/variables/gov/local/co/sheridan/tax/payroll/occupational_privilege/co_sheridan_employee_occupational_privilege_tax.py b/policyengine_us/variables/gov/local/co/sheridan/tax/payroll/occupational_privilege/co_sheridan_employee_occupational_privilege_tax.py new file mode 100644 index 00000000000..ad31f33a2f8 --- /dev/null +++ b/policyengine_us/variables/gov/local/co/sheridan/tax/payroll/occupational_privilege/co_sheridan_employee_occupational_privilege_tax.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class co_sheridan_employee_occupational_privilege_tax(Variable): + value_type = float + entity = TaxUnit + label = "Sheridan employee occupational privilege tax" + documentation = ( + "Employee-side Sheridan occupational privilege tax from explicit " + "taxable-month inputs." + ) + definition_period = YEAR + unit = USD + reference = "https://www.ci.sheridan.co.us/288/Occupational-Privilege-Tax" + + def formula(tax_unit, period, parameters): + taxable_months = tax_unit.sum( + tax_unit.members( + "co_sheridan_employee_occupational_privilege_tax_months", period + ) + ) + p = parameters(period).gov.local.co.sheridan.tax.payroll.occupational_privilege + return taxable_months * p.employee_amount diff --git a/policyengine_us/variables/gov/local/mo/kansas_city/tax/income/mo_kansas_city_earnings_tax.py b/policyengine_us/variables/gov/local/mo/kansas_city/tax/income/mo_kansas_city_earnings_tax.py new file mode 100644 index 00000000000..220f8254fd2 --- /dev/null +++ b/policyengine_us/variables/gov/local/mo/kansas_city/tax/income/mo_kansas_city_earnings_tax.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class mo_kansas_city_earnings_tax(Variable): + value_type = float + entity = TaxUnit + label = "Kansas City earnings tax" + documentation = ( + "Kansas City earnings tax based on explicit taxable earnings inputs." + ) + definition_period = YEAR + unit = USD + reference = "https://www.kcmo.gov/city-hall/departments/finance/earnings-tax" + + def formula(tax_unit, period, parameters): + person = tax_unit.members + p = parameters(period).gov.local.mo.kansas_city.tax.income + taxable_earnings = person( + "mo_kansas_city_earnings_tax_taxable_earnings", period + ) + return tax_unit.sum(taxable_earnings * p.rate) diff --git a/policyengine_us/variables/gov/local/mo/st_louis/tax/income/mo_st_louis_earnings_tax.py b/policyengine_us/variables/gov/local/mo/st_louis/tax/income/mo_st_louis_earnings_tax.py new file mode 100644 index 00000000000..1c31987db47 --- /dev/null +++ b/policyengine_us/variables/gov/local/mo/st_louis/tax/income/mo_st_louis_earnings_tax.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class mo_st_louis_earnings_tax(Variable): + value_type = float + entity = TaxUnit + label = "St. Louis earnings tax" + documentation = ( + "Net St. Louis earnings tax after optional credits supplied as inputs." + ) + definition_period = YEAR + unit = USD + reference = ( + "https://www.stlouis-mo.gov/government/departments/collector/" + "earnings-tax/file-earnings-tax.cfm" + ) + + def formula(tax_unit, period, parameters): + person = tax_unit.members + p = parameters(period).gov.local.mo.st_louis.tax.income + taxable_earnings = person("mo_st_louis_earnings_tax_taxable_earnings", period) + credits = person("mo_st_louis_earnings_tax_credit", period) + person_tax = taxable_earnings * p.rate + return tax_unit.sum(max_(0, person_tax - credits)) diff --git a/policyengine_us/variables/gov/local/mo/st_louis/tax/income/mo_st_louis_earnings_tax_before_credit.py b/policyengine_us/variables/gov/local/mo/st_louis/tax/income/mo_st_louis_earnings_tax_before_credit.py new file mode 100644 index 00000000000..faeae879c4e --- /dev/null +++ b/policyengine_us/variables/gov/local/mo/st_louis/tax/income/mo_st_louis_earnings_tax_before_credit.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class mo_st_louis_earnings_tax_before_credit(Variable): + value_type = float + entity = TaxUnit + label = "St. Louis earnings tax before credit" + documentation = ( + "St. Louis earnings tax before credits for taxes paid to another " + "state or political subdivision." + ) + definition_period = YEAR + unit = USD + reference = ( + "https://www.stlouis-mo.gov/government/departments/collector/" + "earnings-tax/file-earnings-tax.cfm" + ) + + def formula(tax_unit, period, parameters): + person = tax_unit.members + p = parameters(period).gov.local.mo.st_louis.tax.income + taxable_earnings = person("mo_st_louis_earnings_tax_taxable_earnings", period) + return tax_unit.sum(taxable_earnings * p.rate) diff --git a/policyengine_us/variables/gov/local/pa/philadelphia/tax/income/pa_philadelphia_wage_tax.py b/policyengine_us/variables/gov/local/pa/philadelphia/tax/income/pa_philadelphia_wage_tax.py new file mode 100644 index 00000000000..62e37e476a6 --- /dev/null +++ b/policyengine_us/variables/gov/local/pa/philadelphia/tax/income/pa_philadelphia_wage_tax.py @@ -0,0 +1,32 @@ +from policyengine_us.model_api import * + + +class pa_philadelphia_wage_tax(Variable): + value_type = float + entity = TaxUnit + label = "Philadelphia wage tax" + documentation = ( + "Philadelphia employee wage or earnings tax based on explicit taxable " + "wages and resident-status inputs." + ) + definition_period = YEAR + unit = USD + reference = ( + "https://www.phila.gov/services/payments-assistance-taxes/taxes/" + "business-taxes/business-taxes-by-type/wage-tax-employers/" + ) + + def formula(tax_unit, period, parameters): + person = tax_unit.members + taxable_wages = person("pa_philadelphia_wage_tax_taxable_wages", period) + if not np.any(taxable_wages): + return 0 + + p = parameters(period).gov.local.pa.philadelphia.tax.income + resident = person("pa_philadelphia_wage_tax_resident", period) + reduced_rate_eligible = person( + "pa_philadelphia_wage_tax_reduced_rate_eligible", period + ) + standard_rate = where(resident, p.resident_rate, p.nonresident_rate) + rate = where(reduced_rate_eligible, p.reduced_rate, standard_rate) + return tax_unit.sum(taxable_wages * rate) diff --git a/policyengine_us/variables/gov/local/tax/income/local_income_tax.py b/policyengine_us/variables/gov/local/tax/income/local_income_tax.py index e1d37dc80c0..ab2ced34021 100644 --- a/policyengine_us/variables/gov/local/tax/income/local_income_tax.py +++ b/policyengine_us/variables/gov/local/tax/income/local_income_tax.py @@ -6,6 +6,12 @@ class local_income_tax(Variable): entity = TaxUnit definition_period = YEAR label = "Local income tax" + documentation = "Local income, wage, and earnings taxes." unit = USD - adds = ["nyc_income_tax"] + adds = [ + "nyc_income_tax", + "pa_philadelphia_wage_tax", + "mo_kansas_city_earnings_tax", + "mo_st_louis_earnings_tax", + ] diff --git a/policyengine_us/variables/gov/local/tax/income/local_income_tax_before_refundable_credits.py b/policyengine_us/variables/gov/local/tax/income/local_income_tax_before_refundable_credits.py new file mode 100644 index 00000000000..38fbbf891f5 --- /dev/null +++ b/policyengine_us/variables/gov/local/tax/income/local_income_tax_before_refundable_credits.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class local_income_tax_before_refundable_credits(Variable): + value_type = float + entity = TaxUnit + definition_period = YEAR + label = "Local income tax before refundable credits" + documentation = ( + "Local income, wage, and earnings taxes not already included in the " + "state-income-tax-before-refunds aggregate." + ) + unit = USD + + adds = [ + "pa_philadelphia_wage_tax", + "mo_kansas_city_earnings_tax", + "mo_st_louis_earnings_tax", + ] diff --git a/policyengine_us/variables/gov/local/tax/occupational/local_occupational_tax.py b/policyengine_us/variables/gov/local/tax/occupational/local_occupational_tax.py new file mode 100644 index 00000000000..10843fcd838 --- /dev/null +++ b/policyengine_us/variables/gov/local/tax/occupational/local_occupational_tax.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class local_occupational_tax(Variable): + value_type = float + entity = TaxUnit + definition_period = YEAR + label = "Local occupational tax" + documentation = ( + "Local employee occupational and head taxes, aggregated from " + "jurisdiction-specific rules." + ) + unit = USD + + adds = [ + "co_denver_employee_occupational_privilege_tax", + "co_glendale_employee_occupational_privilege_tax", + "co_greenwood_village_employee_occupational_privilege_tax", + "co_sheridan_employee_occupational_privilege_tax", + ] diff --git a/policyengine_us/variables/household/income/household/household_tax_before_refundable_credits.py b/policyengine_us/variables/household/income/household/household_tax_before_refundable_credits.py index ca2f8a90bac..643ab81307c 100644 --- a/policyengine_us/variables/household/income/household/household_tax_before_refundable_credits.py +++ b/policyengine_us/variables/household/income/household/household_tax_before_refundable_credits.py @@ -14,4 +14,6 @@ class household_tax_before_refundable_credits(Variable): "income_tax_before_refundable_credits", "flat_tax", "household_state_tax_before_refundable_credits", + "local_income_tax_before_refundable_credits", + "local_occupational_tax", ] diff --git a/policyengine_us/variables/input/co_denver_employee_occupational_privilege_tax_months.py b/policyengine_us/variables/input/co_denver_employee_occupational_privilege_tax_months.py new file mode 100644 index 00000000000..53c9bdbc03e --- /dev/null +++ b/policyengine_us/variables/input/co_denver_employee_occupational_privilege_tax_months.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class co_denver_employee_occupational_privilege_tax_months(Variable): + value_type = int + entity = Person + label = "Denver employee occupational privilege taxable months" + documentation = ( + "Number of months this person is subject to Denver's employee " + "occupational privilege tax." + ) + definition_period = YEAR + default_value = 0 diff --git a/policyengine_us/variables/input/co_glendale_employee_occupational_privilege_tax_months.py b/policyengine_us/variables/input/co_glendale_employee_occupational_privilege_tax_months.py new file mode 100644 index 00000000000..a18de16275f --- /dev/null +++ b/policyengine_us/variables/input/co_glendale_employee_occupational_privilege_tax_months.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class co_glendale_employee_occupational_privilege_tax_months(Variable): + value_type = int + entity = Person + label = "Glendale employee occupational privilege taxable months" + documentation = ( + "Number of months this person is subject to Glendale's employee " + "occupational privilege tax." + ) + definition_period = YEAR + default_value = 0 diff --git a/policyengine_us/variables/input/co_greenwood_village_employee_occupational_privilege_tax_months.py b/policyengine_us/variables/input/co_greenwood_village_employee_occupational_privilege_tax_months.py new file mode 100644 index 00000000000..8e696beae5b --- /dev/null +++ b/policyengine_us/variables/input/co_greenwood_village_employee_occupational_privilege_tax_months.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class co_greenwood_village_employee_occupational_privilege_tax_months(Variable): + value_type = int + entity = Person + label = "Greenwood Village employee occupational privilege taxable months" + documentation = ( + "Number of months this person is subject to Greenwood Village's " + "employee occupational privilege tax." + ) + definition_period = YEAR + default_value = 0 diff --git a/policyengine_us/variables/input/co_sheridan_employee_occupational_privilege_tax_months.py b/policyengine_us/variables/input/co_sheridan_employee_occupational_privilege_tax_months.py new file mode 100644 index 00000000000..0603c872616 --- /dev/null +++ b/policyengine_us/variables/input/co_sheridan_employee_occupational_privilege_tax_months.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class co_sheridan_employee_occupational_privilege_tax_months(Variable): + value_type = int + entity = Person + label = "Sheridan employee occupational privilege taxable months" + documentation = ( + "Number of months this person is subject to Sheridan's employee " + "occupational privilege tax." + ) + definition_period = YEAR + default_value = 0 diff --git a/policyengine_us/variables/input/mo_kansas_city_earnings_tax_taxable_earnings.py b/policyengine_us/variables/input/mo_kansas_city_earnings_tax_taxable_earnings.py new file mode 100644 index 00000000000..5911614c6d6 --- /dev/null +++ b/policyengine_us/variables/input/mo_kansas_city_earnings_tax_taxable_earnings.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class mo_kansas_city_earnings_tax_taxable_earnings(Variable): + value_type = float + entity = Person + label = "Kansas City earnings tax taxable earnings" + documentation = "Earnings subject to Kansas City's 1% earnings tax for this person." + unit = USD + definition_period = YEAR + default_value = 0 diff --git a/policyengine_us/variables/input/mo_st_louis_earnings_tax_credit.py b/policyengine_us/variables/input/mo_st_louis_earnings_tax_credit.py new file mode 100644 index 00000000000..ae345e465e4 --- /dev/null +++ b/policyengine_us/variables/input/mo_st_louis_earnings_tax_credit.py @@ -0,0 +1,14 @@ +from policyengine_us.model_api import * + + +class mo_st_louis_earnings_tax_credit(Variable): + value_type = float + entity = Person + label = "St. Louis earnings tax credit" + documentation = ( + "Optional credit against St. Louis earnings tax, such as for taxes " + "paid to another state or political subdivision." + ) + unit = USD + definition_period = YEAR + default_value = 0 diff --git a/policyengine_us/variables/input/mo_st_louis_earnings_tax_taxable_earnings.py b/policyengine_us/variables/input/mo_st_louis_earnings_tax_taxable_earnings.py new file mode 100644 index 00000000000..4ce6b3ab6e5 --- /dev/null +++ b/policyengine_us/variables/input/mo_st_louis_earnings_tax_taxable_earnings.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class mo_st_louis_earnings_tax_taxable_earnings(Variable): + value_type = float + entity = Person + label = "St. Louis earnings tax taxable earnings" + documentation = ( + "Earnings subject to the City of St. Louis earnings tax for this person." + ) + unit = USD + definition_period = YEAR + default_value = 0 diff --git a/policyengine_us/variables/input/pa_philadelphia_wage_tax_reduced_rate_eligible.py b/policyengine_us/variables/input/pa_philadelphia_wage_tax_reduced_rate_eligible.py new file mode 100644 index 00000000000..3c5614e5b5a --- /dev/null +++ b/policyengine_us/variables/input/pa_philadelphia_wage_tax_reduced_rate_eligible.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class pa_philadelphia_wage_tax_reduced_rate_eligible(Variable): + value_type = bool + entity = Person + label = "Eligible for Philadelphia reduced wage tax rate" + documentation = ( + "Whether this person qualifies for Philadelphia's reduced 1.5% " + "income-based wage tax rate." + ) + definition_period = YEAR + default_value = False diff --git a/policyengine_us/variables/input/pa_philadelphia_wage_tax_resident.py b/policyengine_us/variables/input/pa_philadelphia_wage_tax_resident.py new file mode 100644 index 00000000000..57fd4d56170 --- /dev/null +++ b/policyengine_us/variables/input/pa_philadelphia_wage_tax_resident.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class pa_philadelphia_wage_tax_resident(Variable): + value_type = bool + entity = Person + label = "Philadelphia wage tax resident status" + documentation = ( + "Whether this person is treated as a Philadelphia resident for wage " + "tax purposes." + ) + definition_period = YEAR + default_value = False diff --git a/policyengine_us/variables/input/pa_philadelphia_wage_tax_taxable_wages.py b/policyengine_us/variables/input/pa_philadelphia_wage_tax_taxable_wages.py new file mode 100644 index 00000000000..97fa7b51ea8 --- /dev/null +++ b/policyengine_us/variables/input/pa_philadelphia_wage_tax_taxable_wages.py @@ -0,0 +1,14 @@ +from policyengine_us.model_api import * + + +class pa_philadelphia_wage_tax_taxable_wages(Variable): + value_type = float + entity = Person + label = "Philadelphia wage tax taxable wages" + documentation = ( + "Compensation subject to Philadelphia's wage tax or employee earnings " + "tax for this person." + ) + unit = USD + definition_period = YEAR + default_value = 0