diff --git a/changelog.d/abolish-benefit-cap-scenario.added.md b/changelog.d/abolish-benefit-cap-scenario.added.md new file mode 100644 index 000000000..513380a15 --- /dev/null +++ b/changelog.d/abolish-benefit-cap-scenario.added.md @@ -0,0 +1 @@ +Added an `abolish_benefit_cap` scenario for benefit-cap removal analysis. diff --git a/docs/book/usage/scenarios.md b/docs/book/usage/scenarios.md index a5e757dea..04b71efb9 100644 --- a/docs/book/usage/scenarios.md +++ b/docs/book/usage/scenarios.md @@ -268,6 +268,18 @@ reformed_cap = reformed_sim.calculate("benefit_cap", 2026).mean() print(f"Benefit cap - frozen: £{baseline_cap:.0f}/year, indexed: £{reformed_cap:.0f}/year") ``` +If you want to remove the cap entirely for a poverty-analysis package, PolicyEngine +UK also exposes a reusable scenario: + +```python +from policyengine_uk import Simulation +from policyengine_uk.scenarios import abolish_benefit_cap + +sim = Simulation(situation=benefit_cap_family, scenario=abolish_benefit_cap) +benefit_cap_reduction = sim.calculate("benefit_cap_reduction", 2026).mean() +print(f"Benefit cap reduction after abolition: £{benefit_cap_reduction:.0f}") +``` + ## Advanced scenario techniques ### Time-varying parameters @@ -571,4 +583,4 @@ for name, scenario in scenarios: - Consider unintended interactions between different policy areas ``` -These techniques let you model complex policy changes that go beyond simple parameter adjustments. Simulation modifiers give you complete control over how the tax-benefit system works, allowing you to implement everything from gradual phase-outs to dynamic eligibility changes. The key is to understand the underlying data structures and use them thoughtfully to represent real policy proposals. \ No newline at end of file +These techniques let you model complex policy changes that go beyond simple parameter adjustments. Simulation modifiers give you complete control over how the tax-benefit system works, allowing you to implement everything from gradual phase-outs to dynamic eligibility changes. The key is to understand the underlying data structures and use them thoughtfully to represent real policy proposals. diff --git a/policyengine_uk/scenarios/__init__.py b/policyengine_uk/scenarios/__init__.py index f3c0a1a6e..f6d755250 100644 --- a/policyengine_uk/scenarios/__init__.py +++ b/policyengine_uk/scenarios/__init__.py @@ -1,3 +1,4 @@ +from .abolish_benefit_cap import abolish_benefit_cap from .pip_reform import reform_pip_phase_in from .reindex_benefit_cap import reindex_benefit_cap from .repeal_two_child_limit import repeal_two_child_limit diff --git a/policyengine_uk/scenarios/abolish_benefit_cap.py b/policyengine_uk/scenarios/abolish_benefit_cap.py new file mode 100644 index 000000000..b00c3268c --- /dev/null +++ b/policyengine_uk/scenarios/abolish_benefit_cap.py @@ -0,0 +1,12 @@ +import numpy as np + +from policyengine_uk.model_api import Scenario + +abolish_benefit_cap = Scenario( + parameter_changes={ + "gov.dwp.benefit_cap.single.in_london": {"year:2026:10": np.inf}, + "gov.dwp.benefit_cap.single.outside_london": {"year:2026:10": np.inf}, + "gov.dwp.benefit_cap.non_single.in_london": {"year:2026:10": np.inf}, + "gov.dwp.benefit_cap.non_single.outside_london": {"year:2026:10": np.inf}, + } +) diff --git a/policyengine_uk/tests/test_abolish_benefit_cap_scenario.py b/policyengine_uk/tests/test_abolish_benefit_cap_scenario.py new file mode 100644 index 000000000..f3afea782 --- /dev/null +++ b/policyengine_uk/tests/test_abolish_benefit_cap_scenario.py @@ -0,0 +1,47 @@ +import numpy as np + +from policyengine_uk import Simulation +from policyengine_uk.scenarios import abolish_benefit_cap + +BENEFIT_CAP_FAMILY = { + "people": { + "parent1": {"age": {2026: 30}, "employment_income": {2026: 0}}, + "parent2": {"age": {2026: 28}, "employment_income": {2026: 0}}, + "child1": {"age": {2026: 6}}, + "child2": {"age": {2026: 3}}, + "child3": {"age": {2026: 1}}, + }, + "benunits": { + "family": {"members": ["parent1", "parent2", "child1", "child2", "child3"]} + }, + "households": { + "home": { + "members": ["parent1", "parent2", "child1", "child2", "child3"], + "rent": {2026: 24_000}, + } + }, +} + + +class TestAbolishBenefitCapScenario: + def test_removes_benefit_cap_reduction(self): + baseline = Simulation(situation=BENEFIT_CAP_FAMILY) + reformed = Simulation( + situation=BENEFIT_CAP_FAMILY, scenario=abolish_benefit_cap + ) + + baseline_reduction = baseline.calculate("benefit_cap_reduction", 2026) + baseline_uc = baseline.calculate("universal_credit", 2026) + reformed_cap = reformed.calculate("benefit_cap", 2026) + reformed_reduction = reformed.calculate("benefit_cap_reduction", 2026) + reformed_uc = reformed.calculate("universal_credit", 2026) + reformed_uc_pre_cap = reformed.calculate( + "universal_credit_pre_benefit_cap", 2026 + ) + + assert baseline_reduction[0] > 0 + assert np.isfinite(baseline.calculate("benefit_cap", 2026)[0]) + assert np.isinf(reformed_cap[0]) + assert reformed_reduction.tolist() == [0.0] + assert reformed_uc.tolist() == reformed_uc_pre_cap.tolist() + assert reformed_uc[0] > baseline_uc[0]