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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- bump: minor
changes:
added:
- Mamdani Millionaire Income Tax.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
description: Zohran Mamdani's proposed additional NYC income tax on income above a certain threshold is in effect if this is true.
values:
0000-01-01: false
metadata:
unit: bool
period: year
label: Zohran Mamdani NYC additional millionaire income tax in effect
reference:
- title: Mamdani Revenue Proposal
href: https://drive.google.com/file/d/14-aM9DKG337SDMilmfQtLRR-pDwyWSTc/view
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

combine into a scale parameter

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
description: Zohran Mamdani's proposed this NYC income tax rate on income above a certain threshold.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this style for the scale param

Suggested change
description: Zohran Mamdani's proposed this NYC income tax rate on income above a certain threshold.
description: Zohran Mamdani proposed taxing New York City residents' income above a threshold at this rate.

values:
2026-01-01: 0.02
metadata:
unit: /1
period: year
label: Zohran Mamdani NYC high earner income tax rate
reference:
- title: Mamdani Revenue Proposal
href: https://drive.google.com/file/d/14-aM9DKG337SDMilmfQtLRR-pDwyWSTc/view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
description: Zohran Mamdani's proposed NYC income tax applies to income above this threshold.
values:
2026-01-01: 1_000_000
metadata:
unit: currency-USD
period: year
label: Zohran Mamdani NYC income tax threshold
reference:
- title: Mamdani Revenue Proposal
href: https://drive.google.com/file/d/14-aM9DKG337SDMilmfQtLRR-pDwyWSTc/view
4 changes: 4 additions & 0 deletions policyengine_us/reforms/local/ny/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .mamdani_income_tax import (
create_nyc_mamdani_income_tax_reform,
nyc_mamdani_income_tax,
)
88 changes: 88 additions & 0 deletions policyengine_us/reforms/local/ny/mamdani_income_tax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from policyengine_us.model_api import *
from policyengine_core.periods import period as period_


def create_nyc_mamdani_income_tax() -> Reform:
class nyc_mamdani_income_tax(Variable):
value_type = float
entity = Person
label = "Zohran Mamdani NYC income tax"
unit = USD
definition_period = YEAR
defined_for = "in_nyc"

def formula(person, period, parameters):
p = parameters(period).gov.local.ny.mamdani_income_tax
taxable_income = person("nyc_taxable_income", period)
threshold = p.threshold
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

irrelevant if moving to a scale parameter, but call parameters directly

rate = p.rate
# Apply 2% tax only to income above $1 million threshold
excess_income = max_(taxable_income - threshold, 0)
return excess_income * rate

class nyc_income_tax_before_credits(Variable):
value_type = float
entity = TaxUnit
label = "NYC income tax before credits"
unit = USD
definition_period = YEAR
defined_for = "in_nyc"

def formula(tax_unit, period, parameters):
taxable_income = tax_unit("nyc_taxable_income", period)
filing_status = tax_unit("filing_status", period)
filing_statuses = filing_status.possible_values
rates = parameters(period).gov.local.ny.nyc.tax.income.rates
regular_tax = select(
[
filing_status == filing_statuses.SINGLE,
filing_status == filing_statuses.JOINT,
filing_status == filing_statuses.HEAD_OF_HOUSEHOLD,
filing_status == filing_statuses.SURVIVING_SPOUSE,
filing_status == filing_statuses.SEPARATE,
],
[
rates.single.calc(taxable_income),
rates.joint.calc(taxable_income),
rates.head_of_household.calc(taxable_income),
rates.surviving_spouse.calc(taxable_income),
rates.separate.calc(taxable_income),
],
)
mamdani_tax = add(tax_unit, period, ["nyc_mamdani_income_tax"])
return regular_tax + mamdani_tax

class reform(Reform):
def apply(self):
self.update_variable(nyc_mamdani_income_tax)
self.update_variable(nyc_income_tax_before_credits)

return reform


def create_nyc_mamdani_income_tax_reform(
parameters, period, bypass: bool = False
):
if bypass:
return create_nyc_mamdani_income_tax()

p = parameters.gov.local.ny.mamdani_income_tax

reform_active = False
current_period = period_(period)

for i in range(5):
if p(current_period).in_effect:
reform_active = True
break
current_period = current_period.offset(1, "year")

if reform_active:
return create_nyc_mamdani_income_tax()
else:
return None


nyc_mamdani_income_tax = create_nyc_mamdani_income_tax_reform(
None, None, bypass=True
)
7 changes: 7 additions & 0 deletions policyengine_us/reforms/reforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@
from .states.mi.surtax import (
create_mi_surtax_reform,
)
from .local.ny.mamdani_income_tax import (
create_nyc_mamdani_income_tax_reform,
)
from .additional_tax_bracket import (
create_additional_tax_bracket_reform,
)
Expand Down Expand Up @@ -227,6 +230,9 @@ def create_structural_reforms_from_parameters(parameters, period):
parameters, period
)
mi_surtax = create_mi_surtax_reform(parameters, period)
nyc_mamdani_income_tax = create_nyc_mamdani_income_tax_reform(
parameters, period
)

american_worker_rebate_act = create_american_worker_rebate_act_reform(
parameters, period
Expand Down Expand Up @@ -275,6 +281,7 @@ def create_structural_reforms_from_parameters(parameters, period):
reconciled_ssn_for_llc_and_aoc,
ctc_additional_bracket,
mi_surtax,
nyc_mamdani_income_tax,
additional_tax_bracket,
american_worker_rebate_act,
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
- name: Person below threshold in 2026
period: 2026
reforms: policyengine_us.reforms.local.ny.mamdani_income_tax.nyc_mamdani_income_tax
input:
gov.local.ny.mamdani_income_tax.in_effect: True
nyc_taxable_income: 800_000
state_code: NY
output:
nyc_mamdani_income_tax: 0

- name: Person at threshold in 2026
period: 2026
reforms: policyengine_us.reforms.local.ny.mamdani_income_tax.nyc_mamdani_income_tax
input:
gov.local.ny.mamdani_income_tax.in_effect: True
nyc_taxable_income: 1_000_000
state_code: NY
output:
nyc_mamdani_income_tax: 0

- name: Person above threshold in 2026
period: 2026
reforms: policyengine_us.reforms.local.ny.mamdani_income_tax.nyc_mamdani_income_tax
input:
gov.local.ny.mamdani_income_tax.in_effect: True
nyc_taxable_income: 1_200_000
state_code: NY
output:
# (1_200_000 - 1_000_000) * 0.02 = 200_000 * 0.02 = 4_000
nyc_mamdani_income_tax: 4_000

- name: Person well above threshold in 2026
period: 2026
reforms: policyengine_us.reforms.local.ny.mamdani_income_tax.nyc_mamdani_income_tax
input:
gov.local.ny.mamdani_income_tax.in_effect: True
nyc_taxable_income: 2_000_000
state_code: NY
output:
# (2_000_000 - 1_000_000) * 0.02 = 1_000_000 * 0.02 = 20_000
nyc_mamdani_income_tax: 20_000

- name: Pre-enactment test
period: 2025
reforms: policyengine_us.reforms.local.ny.mamdani_income_tax.nyc_mamdani_income_tax
input:
gov.local.ny.mamdani_income_tax.in_effect: False
nyc_taxable_income: 1_500_000
state_code: NY
output:
nyc_mamdani_income_tax: 0

- name: Person not in NYC (no tax)
period: 2026
reforms: policyengine_us.reforms.local.ny.mamdani_income_tax.nyc_mamdani_income_tax
input:
gov.local.ny.mamdani_income_tax.in_effect: True
nyc_taxable_income: 1_500_000
state_code: CA
output:
nyc_mamdani_income_tax: 0