Skip to content

Commit 01b4fe7

Browse files
Merge pull request #131 from PolicyEngine/nikhilwoodruff/issue130
Enable cliff impacts
2 parents cf7bdb4 + 72a63ea commit 01b4fe7

File tree

4 files changed

+96
-43
lines changed

4 files changed

+96
-43
lines changed

changelog_entry.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- bump: patch
2+
changes:
3+
fixed:
4+
- Added cliff impacts.

policyengine/outputs/macro/comparison/calculate_economy_comparison.py

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,16 @@ def uk_constituency_breakdown(
775775
return UKConstituencyBreakdownWithValues(**output)
776776

777777

778+
class CliffImpactInSimulation(BaseModel):
779+
cliff_gap: float
780+
cliff_share: float
781+
782+
783+
class CliffImpact(BaseModel):
784+
baseline: CliffImpactInSimulation
785+
reform: CliffImpactInSimulation
786+
787+
778788
class EconomyComparison(BaseModel):
779789
country_package_version: str
780790
budget: BudgetaryImpact
@@ -789,6 +799,7 @@ class EconomyComparison(BaseModel):
789799
intra_wealth_decile: IntraWealthDecileImpact
790800
labor_supply_response: LaborSupplyResponse
791801
constituency_impact: UKConstituencyBreakdown
802+
cliff_impact: CliffImpact | None
792803

793804

794805
def calculate_economy_comparison(
@@ -802,51 +813,54 @@ def calculate_economy_comparison(
802813
reform: SingleEconomy = simulation.calculate_single_economy(reform=True)
803814
options = simulation.options
804815
country_id = options.country
805-
if baseline.type == "general":
806-
budgetary_impact_data = budgetary_impact(baseline, reform)
807-
detailed_budgetary_impact_data = detailed_budgetary_impact(
808-
baseline, reform, country_id
809-
)
810-
decile_impact_data = decile_impact(baseline, reform)
811-
inequality_impact_data = inequality_impact(baseline, reform)
812-
poverty_impact_data = poverty_impact(baseline, reform)
813-
poverty_by_gender_data = poverty_gender_breakdown(baseline, reform)
814-
poverty_by_race_data = poverty_racial_breakdown(baseline, reform)
815-
intra_decile_impact_data = intra_decile_impact(baseline, reform)
816-
labor_supply_response_data = labor_supply_response(baseline, reform)
817-
constituency_impact_data: UKConstituencyBreakdown = (
818-
uk_constituency_breakdown(baseline, reform, country_id)
819-
)
820-
wealth_decile_impact_data = wealth_decile_impact(
821-
baseline, reform, country_id
822-
)
823-
intra_wealth_decile_impact_data = intra_wealth_decile_impact(
824-
baseline, reform, country_id
825-
)
826-
827-
return EconomyComparison(
828-
country_package_version=get_country_package_version(country_id),
829-
budget=budgetary_impact_data,
830-
detailed_budget=detailed_budgetary_impact_data,
831-
decile=decile_impact_data,
832-
inequality=inequality_impact_data,
833-
poverty=poverty_impact_data,
834-
poverty_by_gender=poverty_by_gender_data,
835-
poverty_by_race=poverty_by_race_data,
836-
intra_decile=intra_decile_impact_data,
837-
wealth_decile=wealth_decile_impact_data,
838-
intra_wealth_decile=intra_wealth_decile_impact_data,
839-
labor_supply_response=labor_supply_response_data,
840-
constituency_impact=constituency_impact_data,
841-
)
842-
elif baseline.type == "cliff":
843-
return dict(
844-
baseline=dict(
816+
budgetary_impact_data = budgetary_impact(baseline, reform)
817+
detailed_budgetary_impact_data = detailed_budgetary_impact(
818+
baseline, reform, country_id
819+
)
820+
decile_impact_data = decile_impact(baseline, reform)
821+
inequality_impact_data = inequality_impact(baseline, reform)
822+
poverty_impact_data = poverty_impact(baseline, reform)
823+
poverty_by_gender_data = poverty_gender_breakdown(baseline, reform)
824+
poverty_by_race_data = poverty_racial_breakdown(baseline, reform)
825+
intra_decile_impact_data = intra_decile_impact(baseline, reform)
826+
labor_supply_response_data = labor_supply_response(baseline, reform)
827+
constituency_impact_data: UKConstituencyBreakdown = (
828+
uk_constituency_breakdown(baseline, reform, country_id)
829+
)
830+
wealth_decile_impact_data = wealth_decile_impact(
831+
baseline, reform, country_id
832+
)
833+
intra_wealth_decile_impact_data = intra_wealth_decile_impact(
834+
baseline, reform, country_id
835+
)
836+
837+
if simulation.options.include_cliffs:
838+
cliff_impact = CliffImpact(
839+
baseline=CliffImpactInSimulation(
845840
cliff_gap=baseline.cliff_gap,
846841
cliff_share=baseline.cliff_share,
847842
),
848-
reform=dict(
843+
reform=CliffImpactInSimulation(
849844
cliff_gap=reform.cliff_gap,
850845
cliff_share=reform.cliff_share,
851846
),
852847
)
848+
else:
849+
cliff_impact = None
850+
851+
return EconomyComparison(
852+
country_package_version=get_country_package_version(country_id),
853+
budget=budgetary_impact_data,
854+
detailed_budget=detailed_budgetary_impact_data,
855+
decile=decile_impact_data,
856+
inequality=inequality_impact_data,
857+
poverty=poverty_impact_data,
858+
poverty_by_gender=poverty_by_gender_data,
859+
poverty_by_race=poverty_by_race_data,
860+
intra_decile=intra_decile_impact_data,
861+
wealth_decile=wealth_decile_impact_data,
862+
intra_wealth_decile=intra_wealth_decile_impact_data,
863+
labor_supply_response=labor_supply_response_data,
864+
constituency_impact=constituency_impact_data,
865+
cliff_impact=cliff_impact,
866+
)

policyengine/outputs/macro/single/calculate_single_economy.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from policyengine_core.simulations import Microsimulation
1111
from typing import Dict
1212
from dataclasses import dataclass
13+
from typing import Literal
14+
from microdf import MicroSeries
1315

1416

1517
class SingleEconomy(BaseModel):
@@ -47,8 +49,10 @@ class SingleEconomy(BaseModel):
4749
weekly_hours: float | None
4850
weekly_hours_income_effect: float | None
4951
weekly_hours_substitution_effect: float | None
50-
type: str
52+
type: Literal["general", "cliff"]
5153
programs: Dict[str, float] | None
54+
cliff_gap: float | None = None
55+
cliff_share: float | None = None
5256

5357

5458
@dataclass
@@ -327,10 +331,27 @@ def calculate_uk_programs(self) -> Dict[str, float]:
327331
for program in UKPrograms.PROGRAMS
328332
}
329333

334+
def calculate_cliffs(self):
335+
cliff_gap: MicroSeries = self.simulation.calculate("cliff_gap")
336+
is_on_cliff: MicroSeries = self.simulation.calculate("is_on_cliff")
337+
total_cliff_gap: float = cliff_gap.sum()
338+
total_adults: float = self.simulation.calculate("is_adult").sum()
339+
cliff_share: float = is_on_cliff.sum() / total_adults
340+
return CliffImpactInSimulation(
341+
cliff_gap=total_cliff_gap,
342+
cliff_share=cliff_share,
343+
)
344+
345+
346+
class CliffImpactInSimulation(BaseModel):
347+
cliff_gap: float
348+
cliff_share: float
349+
330350

331351
def calculate_single_economy(
332352
simulation: Simulation, reform: bool = False
333353
) -> Dict:
354+
include_cliffs = simulation.options.include_cliffs
334355
task_manager = GeneralEconomyTask(
335356
(
336357
simulation.baseline_simulation
@@ -382,6 +403,14 @@ def calculate_single_economy(
382403
except:
383404
total_state_tax = 0
384405

406+
if include_cliffs:
407+
cliffs = task_manager.calculate_cliffs()
408+
cliff_gap = cliffs.cliff_gap
409+
cliff_share = cliffs.cliff_share
410+
else:
411+
cliff_gap = None
412+
cliff_share = None
413+
385414
return SingleEconomy(
386415
**{
387416
"total_net_income": total_net_income,
@@ -414,7 +443,9 @@ def calculate_single_economy(
414443
"age": age,
415444
**labor_supply_responses,
416445
**lsr_working_hours,
417-
"type": "general",
446+
"type": "general" if not include_cliffs else "cliff",
418447
"programs": uk_programs,
448+
"cliff_gap": cliff_gap if include_cliffs else None,
449+
"cliff_share": cliff_share if include_cliffs else None,
419450
}
420451
)

policyengine/simulation.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ class SimulationOptions(BaseModel):
5858
"[Analysis title]",
5959
description="The title of the analysis (for charts). If not provided, a default title will be generated.",
6060
)
61+
include_cliffs: bool | None = Field(
62+
False,
63+
description="Whether to include tax-benefit cliffs in the simulation analyses. If True, cliffs will be included.",
64+
)
6165

6266

6367
class Simulation:

0 commit comments

Comments
 (0)