Skip to content

Commit b6c36f5

Browse files
vahid-ahmadiclaudeMaxGhenis
authored
Add marginal_tax_rate_wrt_employer_cost (#45) (#1641)
* Add marginal_tax_rate_wrt_employer_cost (#45) Mirror the existing `marginal_tax_rate` variable but divide the change in household net income by the change in the employer's cost of employment rather than the change in gross pay. The denominator is `employer_cost` (gross pay plus employer NI, employer pension contributions, and statutory sick/maternity/paternity pay), so a pound of employer-side NI is no longer invisible to the MTR. Computation uses the same simulation-branching pattern as `marginal_tax_rate` — for each adult, branch the simulation, bump `employment_income` by the configured delta, recompute, and read both `household_net_income` and `employer_cost` in the branch. Division-by- zero is guarded for people whose employer cost happens not to move. No change to the existing `marginal_tax_rate` variable. Downstream surfaces (API, household calculator) would need a separate change to expose the new variable to end users. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Test employer-cost marginal tax rate --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Max Ghenis <mghenis@gmail.com>
1 parent 4accd4b commit b6c36f5

3 files changed

Lines changed: 70 additions & 0 deletions

File tree

changelog.d/45.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add `marginal_tax_rate_wrt_employer_cost`, a person-level variable that reports the share of a marginal increase in the employer's cost of employment (gross pay plus employer NI, employer pension contributions and statutory sick/maternity/paternity pay) that does not flow to household net income. Computed via the same simulation-branching pattern as `marginal_tax_rate`. Useful for analysing reforms that shift incidence between worker-side and employer-side charges.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
- name: Single employee marginal tax rate with respect to employer cost
2+
period: 2024
3+
absolute_error_margin: 0.001
4+
input:
5+
people:
6+
adult:
7+
age: 40
8+
employment_income: 50_000
9+
benunits:
10+
benunit:
11+
members: [adult]
12+
households:
13+
household:
14+
members: [adult]
15+
output:
16+
marginal_tax_rate_wrt_employer_cost: 0.457
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from policyengine_uk.model_api import *
2+
from policyengine_core.variables import Variable
3+
4+
5+
class marginal_tax_rate_wrt_employer_cost(Variable):
6+
label = "Marginal tax rate with respect to employer cost"
7+
documentation = (
8+
"Percent of a marginal increase in the employer's cost of employment "
9+
"(gross pay plus employer NI, employer pension contributions and "
10+
"statutory sick/maternity/paternity pay) that does not flow to "
11+
"household net income."
12+
)
13+
entity = Person
14+
definition_period = YEAR
15+
value_type = float
16+
unit = "/1"
17+
18+
def formula(person, period, parameters):
19+
p = parameters(period).gov.simulation
20+
mtr_values = np.zeros(person.count, dtype=np.float32)
21+
simulation = person.simulation
22+
adult_index_values = person("adult_index", period)
23+
delta = p.marginal_tax_rate_delta
24+
adult_count = p.marginal_tax_rate_adults
25+
baseline_employer_cost = person("employer_cost", period)
26+
baseline_net = person.household("household_net_income", period)
27+
for adult_index in range(1, 1 + adult_count):
28+
alt_simulation = simulation.get_branch(
29+
f"adult_{adult_index}_employer_cost_mtr"
30+
)
31+
mask = adult_index_values == adult_index
32+
for variable in simulation.tax_benefit_system.variables:
33+
variable_data = simulation.tax_benefit_system.variables[variable]
34+
if (
35+
variable not in simulation.input_variables
36+
and not variable_data.is_input_variable()
37+
):
38+
alt_simulation.delete_arrays(variable)
39+
alt_simulation.set_input(
40+
"employment_income",
41+
period,
42+
person("employment_income", period) + mask * delta,
43+
)
44+
alt_person = alt_simulation.person
45+
new_net = alt_person.household("household_net_income", period)
46+
new_employer_cost = alt_person("employer_cost", period)
47+
net_increase = new_net - baseline_net
48+
employer_cost_increase = new_employer_cost - baseline_employer_cost
49+
safe_denominator = where(
50+
employer_cost_increase != 0, employer_cost_increase, 1
51+
)
52+
mtr_values += where(mask, 1 - net_increase / safe_denominator, 0)
53+
return mtr_values

0 commit comments

Comments
 (0)