policyengine-uk 2.40.1__py3-none-any.whl → 2.65.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- policyengine_uk/__init__.py +5 -3
- policyengine_uk/data/__init__.py +1 -0
- policyengine_uk/data/dataset_schema.py +70 -18
- policyengine_uk/data/economic_assumptions.py +36 -10
- policyengine_uk/data/filter_dataset.py +52 -0
- policyengine_uk/dynamics/labour_supply.py +343 -0
- policyengine_uk/dynamics/participation.py +629 -0
- policyengine_uk/dynamics/progression.py +384 -0
- policyengine_uk/microsimulation.py +105 -0
- policyengine_uk/model_api.py +1 -0
- policyengine_uk/parameters/gov/boe/base_rate.yaml +34 -0
- policyengine_uk/parameters/gov/boe/index.yaml +2 -0
- policyengine_uk/parameters/gov/contrib/behavioral_responses/employee_salary_sacrifice_reduction_rate.yaml +14 -0
- policyengine_uk/parameters/gov/contrib/behavioral_responses/salary_sacrifice_broad_base_haircut_rate.yaml +22 -0
- policyengine_uk/parameters/gov/contrib/cec/state_pension_increase.yaml +1 -1
- policyengine_uk/parameters/gov/contrib/ubi_center/carbon_tax.yaml +2 -2
- policyengine_uk/parameters/gov/contrib/ubi_center/land_value_tax.yaml +3 -3
- policyengine_uk/parameters/gov/dcms/bbc/tv_licence/colour.yaml +5 -5
- policyengine_uk/parameters/gov/dfe/education_spending.yaml +1 -1
- policyengine_uk/parameters/gov/dft/rail/fare_index.yaml +32 -0
- policyengine_uk/parameters/gov/dft/rail/prior_law_fare_index.yaml +32 -0
- policyengine_uk/parameters/gov/dft/rail/ridership_index.yaml +30 -0
- policyengine_uk/parameters/gov/dft/spending.yaml +2 -2
- policyengine_uk/parameters/gov/dwp/ESA/income/earn_disregard.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/ESA/income/income_disregard_couple.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/ESA/income/income_disregard_lone_parent.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/ESA/income/income_disregard_single.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/ESA/income/pension_disregard.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/IIDB/maximum.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/JSA/contrib/amount_over_25.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/JSA/contrib/earn_disregard.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/JSA/contrib/pension_disregard.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/JSA/income/amount_18_24.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/JSA/income/amount_over_25.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/JSA/income/income_disregard_couple.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/JSA/income/income_disregard_lone_parent.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/JSA/income/income_disregard_single.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/LHA/shared_accommodation_age_threshold.yaml +12 -0
- policyengine_uk/parameters/gov/dwp/attendance_allowance/higher.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/attendance_allowance/lower.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/benefit_cap.yaml +3 -3
- policyengine_uk/parameters/gov/dwp/carer_premium/couple.yaml +2 -2
- policyengine_uk/parameters/gov/dwp/carer_premium/single.yaml +6 -6
- policyengine_uk/parameters/gov/dwp/carers_allowance/rate.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/disability_premia/disability_couple.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/disability_premia/enhanced_couple.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/disability_premia/enhanced_single.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/disability_premia/severe_couple.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/dla/mobility/higher.yaml +4 -4
- policyengine_uk/parameters/gov/dwp/dla/mobility/lower.yaml +8 -8
- policyengine_uk/parameters/gov/dwp/dla/self_care/higher.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/dla/self_care/lower.yaml +8 -8
- policyengine_uk/parameters/gov/dwp/dla/self_care/middle.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/older.yaml +3 -3
- policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/older.yaml +3 -3
- policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/amount.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/housing_benefit/takeup.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/income_support/amounts/amount_16_24.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/income_support/amounts/amount_couples_over_18.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/income_support/means_test/earn_disregard.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/income_support/means_test/income_disregard_couple.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/income_support/means_test/income_disregard_lone_parent.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/income_support/means_test/income_disregard_single.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/income_support/means_test/pension_disregard.yaml +1 -1
- policyengine_uk/parameters/gov/dwp/income_support/takeup.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/carer/addition.yaml +4 -4
- policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee.yaml +9 -9
- policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/severe_disability/addition.yaml +3 -3
- policyengine_uk/parameters/gov/dwp/pension_credit/savings_credit/threshold.yaml +5 -5
- policyengine_uk/parameters/gov/dwp/pip/daily_living/enhanced.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/pip/daily_living/standard.yaml +8 -8
- policyengine_uk/parameters/gov/dwp/pip/mobility/enhanced.yaml +4 -4
- policyengine_uk/parameters/gov/dwp/pip/mobility/standard.yaml +9 -9
- policyengine_uk/parameters/gov/dwp/sda/maximum.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/state_pension/basic_state_pension/amount.yaml +11 -11
- policyengine_uk/parameters/gov/dwp/state_pension/new_state_pension/amount.yaml +4 -4
- policyengine_uk/parameters/gov/dwp/tax_credits/child_tax_credit/limit/child_count.yaml +10 -1
- policyengine_uk/parameters/gov/dwp/tax_credits/child_tax_credit/takeup.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/tax_credits/working_tax_credit/takeup.yaml +7 -7
- policyengine_uk/parameters/gov/dwp/universal_credit/elements/carer/amount.yaml +3 -3
- policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/amount.yaml +2 -4
- policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/disabled/amount.yaml +2 -4
- policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/first/higher_amount.yaml +6 -8
- policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/limit/child_count.yaml +6 -1
- policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/severely_disabled/amount.yaml +3 -5
- policyengine_uk/parameters/gov/dwp/universal_credit/elements/childcare/cap.yaml +2 -6
- policyengine_uk/parameters/gov/dwp/universal_credit/elements/disabled/amount.yaml +4 -6
- policyengine_uk/parameters/gov/dwp/universal_credit/elements/housing/non_dep_deduction/amount.yaml +4 -1
- policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/active.yaml +9 -0
- policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/new_claimant_health_element.yaml +9 -0
- policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/standard_allowance_uplift.yaml +13 -0
- policyengine_uk/parameters/gov/dwp/universal_credit/standard_allowance/amount.yaml +5 -5
- policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/maximum_taxable_income.yaml +2 -1
- policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/use_maximum_taxable_income.yaml +1 -0
- policyengine_uk/parameters/gov/dynamic/obr_labour_supply_assumptions.yaml +9 -0
- policyengine_uk/parameters/gov/economic_assumptions/create_economic_assumption_indices.py +1 -1
- policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml +522 -153
- policyengine_uk/parameters/gov/hmrc/cgt/additional_rate.yaml +5 -0
- policyengine_uk/parameters/gov/hmrc/cgt/basic_rate.yaml +5 -0
- policyengine_uk/parameters/gov/hmrc/cgt/higher_rate.yaml +4 -0
- policyengine_uk/parameters/gov/hmrc/child_benefit/amount/additional.yaml +6 -6
- policyengine_uk/parameters/gov/hmrc/child_benefit/amount/eldest.yaml +8 -8
- policyengine_uk/parameters/gov/hmrc/child_benefit/takeup/by_age.yaml +1 -1
- policyengine_uk/parameters/gov/hmrc/fuel_duty/calculate_fuel_duty_rates.py +464 -0
- policyengine_uk/parameters/gov/hmrc/fuel_duty/petrol_and_diesel.yaml +86 -10
- policyengine_uk/parameters/gov/hmrc/income_tax/allowances/personal_allowance/amount.yaml +6 -0
- policyengine_uk/parameters/gov/hmrc/income_tax/earned_taxable_income_exclusions.yaml +2 -1
- policyengine_uk/parameters/gov/hmrc/income_tax/income_tax_additions.yaml +1 -0
- policyengine_uk/parameters/gov/hmrc/income_tax/rates/dividends.yaml +12 -0
- policyengine_uk/parameters/gov/hmrc/income_tax/rates/property.yaml +46 -0
- policyengine_uk/parameters/gov/hmrc/income_tax/rates/savings.yaml +46 -0
- policyengine_uk/parameters/gov/hmrc/income_tax/rates/scotland/rates.yaml +2 -2
- policyengine_uk/parameters/gov/hmrc/income_tax/rates/uk.yaml +14 -2
- policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employee/additional.yaml +4 -6
- policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employer.yaml +3 -3
- policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/thresholds/secondary_threshold.yaml +14 -4
- policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/flat_rate.yaml +2 -2
- policyengine_uk/parameters/gov/hmrc/national_insurance/salary_sacrifice_pension_cap.yaml +16 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/index.yaml +12 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_1/boe_margin.yaml +11 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/additional_rate.yaml +27 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/index.yaml +16 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/upper_threshold.yaml +48 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/postgraduate_additional_rate.yaml +11 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/postgraduate_repayment_rate.yaml +9 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/repayment_rate.yaml +9 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/thresholds/plan_1.yaml +25 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/thresholds/plan_2.yaml +58 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/thresholds/plan_4.yaml +19 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/thresholds/plan_5.yaml +16 -0
- policyengine_uk/parameters/gov/hmrc/student_loans/thresholds/postgraduate.yaml +21 -0
- policyengine_uk/parameters/gov/hmrc/vat/reduced_rate_share.yaml +3 -3
- policyengine_uk/parameters/gov/indices/private_rent_index.yaml +9 -9
- policyengine_uk/parameters/gov/revenue_scotland/lbtt/non_residential.yaml +2 -2
- policyengine_uk/parameters/gov/revenue_scotland/lbtt/rent.yaml +2 -2
- policyengine_uk/parameters/gov/revenue_scotland/lbtt/residential/first_time_buyer_rate.yaml +2 -2
- policyengine_uk/parameters/gov/revenue_scotland/lbtt/residential/rate.yaml +2 -2
- policyengine_uk/parameters/gov/wra/land_transaction_tax/non_residential.yaml +2 -2
- policyengine_uk/parameters/gov/wra/land_transaction_tax/rent.yaml +2 -2
- policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/higher_rate.yaml +1 -1
- policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/primary.yaml +2 -2
- policyengine_uk/parameters/household/consumption/carbon/consumption.yaml +8 -6
- policyengine_uk/parameters/household/consumption/carbon/intensity.yaml +4 -1
- policyengine_uk/parameters/household/consumption/carbon/production.yaml +12 -7
- policyengine_uk/parameters/household/consumption/carbon/production_by_source.yaml +76 -41
- policyengine_uk/parameters/household/consumption/fuel/prices/petrol.yaml +1 -1
- policyengine_uk/parameters/household/poverty/absolute_poverty_threshold_bhc.yaml +1 -1
- policyengine_uk/reforms/policyengine/adjust_budgets.py +0 -1
- policyengine_uk/scenarios/__init__.py +4 -0
- policyengine_uk/scenarios/pip_reform.py +23 -0
- policyengine_uk/scenarios/reindex_benefit_cap.py +32 -0
- policyengine_uk/scenarios/repeal_two_child_limit.py +10 -0
- policyengine_uk/scenarios/uc_reform.py +50 -0
- policyengine_uk/simulation.py +619 -0
- policyengine_uk/system.py +3 -257
- policyengine_uk/tax_benefit_system.py +141 -0
- policyengine_uk/tests/behavioral_responses/test_labor_supply_responses.yaml +183 -0
- policyengine_uk/tests/microsimulation/reforms_config.yaml +8 -8
- policyengine_uk/tests/microsimulation/test_reform_impacts.py +2 -2
- policyengine_uk/tests/microsimulation/test_salary_sacrifice_cap_reform.py +401 -0
- policyengine_uk/tests/microsimulation/test_validity.py +2 -3
- policyengine_uk/tests/microsimulation/update_reform_impacts.py +104 -40
- policyengine_uk/tests/policy/baseline/contrib/policyengine/employer_ni/employer_ni_fixed_employer_cost_change.yaml +105 -0
- policyengine_uk/tests/policy/baseline/finance/benefit/family/child_benefit.yaml +2 -0
- policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support.yaml +0 -23
- policyengine_uk/tests/policy/baseline/gov/dcms/bbc/tv-licence/tv_licence.yaml +3 -0
- policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement.yaml +17 -17
- policyengine_uk/tests/policy/baseline/gov/dwp/basic_state_pension.yaml +44 -0
- policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/gift_aid.yaml +71 -0
- policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/personal_allowance.yaml +161 -0
- policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.yaml +107 -0
- policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.yaml +95 -0
- policyengine_uk/tests/policy/baseline/gov/hmrc/student_loans/student_loan_interest_rate.yaml +153 -0
- policyengine_uk/tests/policy/baseline/gov/hmrc/student_loans/student_loan_repayment.yaml +130 -0
- policyengine_uk/tests/policy/baseline/household/wealth/vehicle.yaml +27 -0
- policyengine_uk/tests/policy/reforms/nov_2025_budget/income_source_tax_rates.yaml +235 -0
- policyengine_uk/tests/policy/reforms/nov_2025_budget/income_tax_freeze.yaml +83 -0
- policyengine_uk/tests/policy/reforms/parametric/basic_income/basic_income.yaml +1 -0
- policyengine_uk/tests/test_behavioral_responses.py +215 -0
- policyengine_uk/tests/test_fiscal_year_parameters.py +131 -0
- policyengine_uk/utils/__init__.py +1 -0
- policyengine_uk/utils/compare.py +28 -0
- policyengine_uk/utils/create_ahc_deflator.py +169 -0
- policyengine_uk/utils/create_triple_lock.py +1 -1
- policyengine_uk/utils/dependencies.py +259 -0
- policyengine_uk/utils/parameters.py +12 -1
- policyengine_uk/utils/scenario.py +225 -0
- policyengine_uk/utils/solve_private_school_attendance_factor.py +4 -6
- policyengine_uk/variables/contrib/policyengine/education_budget_change.py +0 -1
- policyengine_uk/variables/contrib/policyengine/employer_ni/baseline_employer_cost.py +5 -1
- policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_fixed_employer_cost_change.py +23 -23
- policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_response_capital_incidence.py +1 -1
- policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_response_consumer_incidence.py +1 -1
- policyengine_uk/variables/contrib/policyengine/other_public_spending_budget_change.py +0 -1
- policyengine_uk/variables/gov/dfe/targeted_childcare_entitlement/targeted_childcare_entitlement_eligible.py +0 -1
- policyengine_uk/variables/gov/dft/rail_subsidy_spending.py +16 -1
- policyengine_uk/variables/gov/dft/rail_usage.py +16 -0
- policyengine_uk/variables/gov/dwp/BRMA_LHA_rate.py +7 -2
- policyengine_uk/variables/gov/dwp/LHA_category.py +4 -2
- policyengine_uk/variables/gov/dwp/additional_state_pension.py +4 -2
- policyengine_uk/variables/gov/dwp/basic_state_pension.py +26 -8
- policyengine_uk/variables/gov/dwp/is_CTC_eligible.py +1 -1
- policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt.py +9 -13
- policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py +66 -0
- policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py +75 -0
- policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py +66 -0
- policyengine_uk/variables/gov/dwp/winter_fuel_allowance.py +5 -4
- policyengine_uk/variables/gov/gov_tax.py +0 -2
- policyengine_uk/variables/gov/hmrc/household_tax.py +0 -1
- policyengine_uk/variables/gov/hmrc/income_tax/allowances/gift_aid.py +23 -0
- policyengine_uk/variables/gov/hmrc/income_tax/allowances/personal_allowance.py +9 -2
- policyengine_uk/variables/gov/hmrc/income_tax/income_tax_pre_charges.py +1 -0
- policyengine_uk/variables/gov/hmrc/income_tax/liability/property_income_tax.py +75 -0
- policyengine_uk/variables/gov/hmrc/income_tax/liability/savings_income_tax.py +4 -4
- policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_broad_base_haircut.py +43 -0
- policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.py +38 -0
- policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.py +27 -0
- policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_via_salary_sacrifice_adjusted.py +31 -0
- policyengine_uk/variables/gov/hmrc/pensions/salary_sacrifice_returned_to_income.py +41 -0
- policyengine_uk/variables/gov/hmrc/student_loans/__init__.py +2 -0
- policyengine_uk/variables/gov/hmrc/student_loans/plan_1_interest_rate.py +23 -0
- policyengine_uk/variables/gov/hmrc/student_loans/plan_2_interest_rate.py +31 -0
- policyengine_uk/variables/gov/hmrc/student_loans/plan_4_interest_rate.py +22 -0
- policyengine_uk/variables/gov/hmrc/student_loans/plan_5_interest_rate.py +18 -0
- policyengine_uk/variables/gov/hmrc/student_loans/postgraduate_interest_rate.py +23 -0
- policyengine_uk/variables/gov/hmrc/student_loans/student_loan_plan.py +27 -0
- policyengine_uk/variables/gov/hmrc/student_loans/student_loan_repayment.py +91 -0
- policyengine_uk/variables/gov/hmrc/student_loans/student_loan_repayment_rate.py +31 -0
- policyengine_uk/variables/gov/hmrc/would_claim_child_benefit.py +5 -1
- policyengine_uk/variables/household/demographic/benunit/benunit_count_adults.py +11 -0
- policyengine_uk/variables/household/demographic/is_disabled_for_benefits.py +13 -1
- policyengine_uk/variables/household/income/hbai_household_net_income.py +29 -1
- policyengine_uk/variables/household/income/hbai_household_net_income_ahc.py +13 -0
- policyengine_uk/variables/household/income/household_net_income.py +5 -1
- policyengine_uk/variables/household/income/inflation_adjustment.py +24 -0
- policyengine_uk/variables/household/post_tax_income.py +12 -0
- policyengine_uk/variables/household/wealth/num_vehicles.py +9 -0
- policyengine_uk/variables/household/wealth/owns_vehicle.py +17 -0
- policyengine_uk/variables/input/consumption/property/council_tax.py +0 -35
- policyengine_uk/variables/input/consumption/property/employee_pension_contributions.py +8 -1
- policyengine_uk/variables/input/consumption/property/employee_pension_contributions_reported.py +16 -0
- policyengine_uk/variables/input/consumption/property/pension_contributions_via_salary_sacrifice.py +16 -0
- policyengine_uk/variables/input/employment_income.py +2 -0
- policyengine_uk/variables/input/rent.py +0 -40
- policyengine_uk/variables/input/savings_interest_income.py +3 -1
- {policyengine_uk-2.40.1.dist-info → policyengine_uk-2.65.6.dist-info}/METADATA +17 -8
- {policyengine_uk-2.40.1.dist-info → policyengine_uk-2.65.6.dist-info}/RECORD +252 -173
- {policyengine_uk-2.40.1.dist-info → policyengine_uk-2.65.6.dist-info}/WHEEL +1 -1
- policyengine_uk/repo.py +0 -3
- policyengine_uk/tests/policy/baseline/gov/abolitions/abolition_parameters.yaml +0 -250
- policyengine_uk/variables/contrib/policyengine/high_income_incident_tax_change.py +0 -22
- policyengine_uk-2.40.1.data/data/share/openfisca/openfisca-country-template/CHANGELOG.md +0 -2285
- policyengine_uk-2.40.1.data/data/share/openfisca/openfisca-country-template/README.md +0 -37
- policyengine_uk-2.40.1.dist-info/licenses/LICENSE +0 -661
- {policyengine_uk-2.40.1.data/data/share/openfisca/openfisca-country-template → policyengine_uk-2.65.6.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from policyengine_uk.system import system
|
|
3
|
+
import plotly.express as px
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from plotly import graph_objects as go
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def find_variable_in_trees(variable_name, tracer):
|
|
9
|
+
for tree in tracer.trees:
|
|
10
|
+
try:
|
|
11
|
+
node = find_variable_in_tree_recursive(variable_name, tree)
|
|
12
|
+
if node:
|
|
13
|
+
return node
|
|
14
|
+
except ValueError:
|
|
15
|
+
continue # Variable not in this tree, try next
|
|
16
|
+
|
|
17
|
+
raise ValueError(
|
|
18
|
+
f"Variable '{variable_name}' not found in any simulation tree. "
|
|
19
|
+
f"Make sure you've calculated this variable or a parent variable that depends on it."
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def find_variable_in_tree_recursive(variable_name, node):
|
|
24
|
+
if node.name == variable_name:
|
|
25
|
+
return node
|
|
26
|
+
|
|
27
|
+
for child in node.children:
|
|
28
|
+
result = find_variable_in_tree_recursive(variable_name, child)
|
|
29
|
+
if result:
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_variable_dependencies(variable_name, sim):
|
|
36
|
+
node = find_variable_in_trees(variable_name, sim.tracer)
|
|
37
|
+
if not node:
|
|
38
|
+
return []
|
|
39
|
+
return [child.name for child in node.children]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def extract_variables_regex(formula_source):
|
|
43
|
+
# Find all strings in double quotes that look like variable names
|
|
44
|
+
pattern = r'benunit\(\s*["\']([^"\']+)["\']\s*,'
|
|
45
|
+
matches = re.findall(pattern, formula_source)
|
|
46
|
+
return matches
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def calculate_dependency_contributions(
|
|
50
|
+
sim, variable_name, year, top_n=None, filter=None, map_to=None
|
|
51
|
+
):
|
|
52
|
+
original_values = sim.calculate(variable_name, year)
|
|
53
|
+
|
|
54
|
+
if map_to is not None:
|
|
55
|
+
source_entity = sim.tax_benefit_system.get_variable(
|
|
56
|
+
variable_name
|
|
57
|
+
).entity.key
|
|
58
|
+
original_values_mapped = sim.map_result(
|
|
59
|
+
original_values,
|
|
60
|
+
source_entity,
|
|
61
|
+
map_to,
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
original_values_mapped = original_values
|
|
65
|
+
|
|
66
|
+
dependency_contributions = {}
|
|
67
|
+
first_level_dependencies = get_variable_dependencies(variable_name, sim)
|
|
68
|
+
for variable in first_level_dependencies:
|
|
69
|
+
if "weight" in variable:
|
|
70
|
+
continue
|
|
71
|
+
sim.get_holder(variable_name).delete_arrays(year)
|
|
72
|
+
value_type = sim.tax_benefit_system.get_variable(variable).value_type
|
|
73
|
+
current_values = sim.calculate(variable, year)
|
|
74
|
+
if value_type == float:
|
|
75
|
+
sim.set_input(variable, year, (current_values * 0).astype(float))
|
|
76
|
+
|
|
77
|
+
new_values_mapped = sim.calculate(variable_name, year, map_to=map_to)
|
|
78
|
+
if filter is not None:
|
|
79
|
+
contribution = (
|
|
80
|
+
original_values_mapped[filter] - new_values_mapped[filter]
|
|
81
|
+
).mean()
|
|
82
|
+
else:
|
|
83
|
+
contribution = (original_values_mapped - new_values_mapped).mean()
|
|
84
|
+
dependency_contributions[variable] = contribution
|
|
85
|
+
sim.set_input(variable_name, year, original_values)
|
|
86
|
+
sim.set_input(variable, year, current_values)
|
|
87
|
+
|
|
88
|
+
result = pd.Series(dependency_contributions)
|
|
89
|
+
|
|
90
|
+
if top_n is not None:
|
|
91
|
+
# Keep the top N variables by absolute contribution
|
|
92
|
+
result = result.reindex(result.abs().nlargest(top_n).index)
|
|
93
|
+
|
|
94
|
+
return result.sort_values(ascending=False)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def calculate_dependency_contribution_change(
|
|
98
|
+
baseline_sim, reform_sim, variable_name, year, reform_year=None, top_n=5
|
|
99
|
+
):
|
|
100
|
+
baseline_dependency = calculate_dependency_contributions(
|
|
101
|
+
baseline_sim, variable_name, year
|
|
102
|
+
)
|
|
103
|
+
reform_dependency = calculate_dependency_contributions(
|
|
104
|
+
reform_sim, variable_name, reform_year or year
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
df = pd.DataFrame(
|
|
108
|
+
{
|
|
109
|
+
"baseline": pd.Series(baseline_dependency),
|
|
110
|
+
"reform": pd.Series(reform_dependency),
|
|
111
|
+
}
|
|
112
|
+
).fillna(0)
|
|
113
|
+
|
|
114
|
+
df["change"] = df["reform"] - df["baseline"]
|
|
115
|
+
df["relative_change"] = df["change"] / df["baseline"].abs().replace(0, 1)
|
|
116
|
+
|
|
117
|
+
# Keep the top N variables by absolute change
|
|
118
|
+
if top_n is not None:
|
|
119
|
+
df = df.reindex(df["change"].abs().nlargest(top_n).index)
|
|
120
|
+
|
|
121
|
+
return df.sort_values(by="change", ascending=False)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def create_waterfall_chart(sim, variable_name, year, top_n=5):
|
|
125
|
+
if not sim.trace:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
"Simulation must have trace enabled to create a waterfall chart."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
df = calculate_dependency_contributions(
|
|
131
|
+
sim, variable_name, year, top_n=top_n
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# make a waterfall chart
|
|
135
|
+
|
|
136
|
+
fig = go.Figure(
|
|
137
|
+
go.Waterfall(
|
|
138
|
+
name="Waterfall",
|
|
139
|
+
orientation="v",
|
|
140
|
+
measure=["relative"] * len(df),
|
|
141
|
+
x=df.index,
|
|
142
|
+
y=df.values,
|
|
143
|
+
connector={"line": {"color": "rgb(63, 63, 63)", "width": 2}},
|
|
144
|
+
increasing={"marker": {"color": "green"}},
|
|
145
|
+
decreasing={"marker": {"color": "red"}},
|
|
146
|
+
totals={"marker": {"color": "blue"}},
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
return format_fig(fig).update_layout(
|
|
150
|
+
title=f"Dependency contributions for {variable_name} in {year}",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def create_waterfall_change_chart(
|
|
155
|
+
sim_1, sim_2, variable_name, year, sim_2_year=None, top_n=5
|
|
156
|
+
):
|
|
157
|
+
df = calculate_dependency_contribution_change(
|
|
158
|
+
sim_1, sim_2, variable_name, year, reform_year=sim_2_year, top_n=top_n
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# make a waterfall chart
|
|
162
|
+
|
|
163
|
+
fig = go.Figure(
|
|
164
|
+
go.Waterfall(
|
|
165
|
+
name="Waterfall",
|
|
166
|
+
orientation="v",
|
|
167
|
+
measure=["relative"] * len(df),
|
|
168
|
+
x=df.index,
|
|
169
|
+
y=df.change,
|
|
170
|
+
connector={"line": {"color": "rgb(63, 63, 63)", "width": 2}},
|
|
171
|
+
increasing={"marker": {"color": "green"}},
|
|
172
|
+
decreasing={"marker": {"color": "red"}},
|
|
173
|
+
totals={"marker": {"color": "blue"}},
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
return format_fig(fig).update_layout(
|
|
177
|
+
title=f"Change in {variable_name} contributions from reform",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def add_fonts():
|
|
182
|
+
fonts = HTML(
|
|
183
|
+
"""
|
|
184
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
185
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
186
|
+
<link href="https://fonts.googleapis.com/css2?family=Roboto+Serif:ital,opsz,wght@0,8..144,100..900;1,8..144,100..900&display=swap" rel="stylesheet">
|
|
187
|
+
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
|
|
188
|
+
"""
|
|
189
|
+
)
|
|
190
|
+
return display_html(fonts)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
from IPython.core.display import HTML, display_html
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def format_fig(fig):
|
|
197
|
+
# PolicyEngine style (roboto mono for numbers, roboto serif for text), dark grey for negative, blue for positive, spacing, etc.
|
|
198
|
+
|
|
199
|
+
# Set layout properties for a clean, professional look
|
|
200
|
+
fig.update_layout(
|
|
201
|
+
plot_bgcolor="white",
|
|
202
|
+
paper_bgcolor="white",
|
|
203
|
+
font_family="Roboto Serif",
|
|
204
|
+
font_size=12,
|
|
205
|
+
margin=dict(l=60, r=60, t=80, b=60),
|
|
206
|
+
title_font_size=16,
|
|
207
|
+
title_font_family="Roboto Serif",
|
|
208
|
+
title_x=0.5,
|
|
209
|
+
title_xanchor="center",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Update axes
|
|
213
|
+
fig.update_xaxes(
|
|
214
|
+
title_font_family="Roboto Serif",
|
|
215
|
+
tickfont_family="Roboto Mono",
|
|
216
|
+
showline=True,
|
|
217
|
+
linewidth=1,
|
|
218
|
+
linecolor="lightgray",
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
fig.update_yaxes(
|
|
222
|
+
title_font_family="Roboto Serif",
|
|
223
|
+
tickfont_family="Roboto Mono",
|
|
224
|
+
tickformat=",.0f",
|
|
225
|
+
tickprefix="£",
|
|
226
|
+
gridcolor="lightgray",
|
|
227
|
+
showline=True,
|
|
228
|
+
linewidth=1,
|
|
229
|
+
linecolor="lightgray",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Update waterfall specific styles
|
|
233
|
+
fig.update_traces(
|
|
234
|
+
increasing=dict(marker=dict(color="#2C6496")),
|
|
235
|
+
decreasing=dict(marker=dict(color="#555555")),
|
|
236
|
+
connector=dict(line=dict(color="rgb(63, 63, 63)", width=1)),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Height/width adjustments for better visibility
|
|
240
|
+
fig.update_layout(
|
|
241
|
+
height=600,
|
|
242
|
+
width=800,
|
|
243
|
+
# set font to black
|
|
244
|
+
font_color="black",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Add rounded numbers on bars
|
|
248
|
+
for trace in fig.data:
|
|
249
|
+
if isinstance(trace, go.Waterfall):
|
|
250
|
+
trace.texttemplate = "£%{y:,.0f}"
|
|
251
|
+
trace.textposition = "outside"
|
|
252
|
+
trace.hovertemplate = "%{x}: £%{y:,.0f}<extra></extra>"
|
|
253
|
+
# mono font for numbers
|
|
254
|
+
trace.textfont = dict(family="Roboto Mono", size=12, color="black")
|
|
255
|
+
|
|
256
|
+
# Add padding round the edges
|
|
257
|
+
fig.update_layout(margin=dict(l=100, r=100, t=100, b=100))
|
|
258
|
+
|
|
259
|
+
return fig
|
|
@@ -26,7 +26,18 @@ def backdate_parameters(
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def convert_to_fiscal_year_parameters(parameters):
|
|
29
|
-
|
|
29
|
+
"""
|
|
30
|
+
Convert parameters to use UK fiscal year values.
|
|
31
|
+
|
|
32
|
+
The UK fiscal year runs April 6 to April 5. When querying a parameter
|
|
33
|
+
for a year (e.g., param("2026")), we want the value at April 30 of
|
|
34
|
+
that year (which represents the fiscal year starting April 6).
|
|
35
|
+
|
|
36
|
+
This function samples each parameter at April 30 of each year and
|
|
37
|
+
sets that as the value for the entire year period.
|
|
38
|
+
"""
|
|
39
|
+
# Cover years from 2015 through 2040 for long-term projections
|
|
40
|
+
YEARS = list(range(2015, 2041))
|
|
30
41
|
for param in parameters.get_descendants():
|
|
31
42
|
if isinstance(param, Parameter):
|
|
32
43
|
for year in YEARS:
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from typing import Optional, Callable, Dict, Type, Union
|
|
3
|
+
from policyengine_core.simulations import Simulation
|
|
4
|
+
from policyengine_core.reforms import Reform
|
|
5
|
+
from policyengine_core.periods import period, instant
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Scenario(BaseModel):
|
|
9
|
+
"""Represents a scenario configuration for policy simulations.
|
|
10
|
+
|
|
11
|
+
A scenario can include parameter changes and/or simulation modifications
|
|
12
|
+
that are applied before running a simulation. Scenarios can be combined
|
|
13
|
+
using the + operator.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
applied_before_data_load: bool = False
|
|
17
|
+
|
|
18
|
+
parameter_changes: Optional[
|
|
19
|
+
Dict[
|
|
20
|
+
str,
|
|
21
|
+
Union[
|
|
22
|
+
int,
|
|
23
|
+
float,
|
|
24
|
+
bool,
|
|
25
|
+
Dict[Union[str, int], Union[int, float, bool]],
|
|
26
|
+
],
|
|
27
|
+
]
|
|
28
|
+
] = None
|
|
29
|
+
"""A dictionary of parameter changes to apply to the simulation. These are applied *before* parameter operations."""
|
|
30
|
+
|
|
31
|
+
simulation_modifier: Optional[Callable[["Simulation"], None]] = None
|
|
32
|
+
"""A function that modifies the simulation before running it."""
|
|
33
|
+
|
|
34
|
+
class Config:
|
|
35
|
+
"""Pydantic configuration."""
|
|
36
|
+
|
|
37
|
+
arbitrary_types_allowed = True # Allow Callable types
|
|
38
|
+
|
|
39
|
+
def __add__(self, other: "Scenario") -> "Scenario":
|
|
40
|
+
"""Combine two scenarios by merging parameter changes and chaining modifiers.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
other: Another Scenario to combine with this one
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
A new Scenario with merged parameter changes and combined modifiers
|
|
47
|
+
"""
|
|
48
|
+
# Merge parameter changes (other's changes take precedence in conflicts)
|
|
49
|
+
merged_params = {}
|
|
50
|
+
|
|
51
|
+
if self.parameter_changes:
|
|
52
|
+
merged_params.update(self.parameter_changes)
|
|
53
|
+
|
|
54
|
+
if other.parameter_changes:
|
|
55
|
+
for key, value in other.parameter_changes.items():
|
|
56
|
+
if (
|
|
57
|
+
key in merged_params
|
|
58
|
+
and isinstance(merged_params[key], dict)
|
|
59
|
+
and isinstance(value, dict)
|
|
60
|
+
):
|
|
61
|
+
# Deep merge nested dictionaries
|
|
62
|
+
merged_params[key] = {**merged_params[key], **value}
|
|
63
|
+
else:
|
|
64
|
+
# Simple override
|
|
65
|
+
merged_params[key] = value
|
|
66
|
+
|
|
67
|
+
# Chain simulation modifiers
|
|
68
|
+
combined_modifier = None
|
|
69
|
+
|
|
70
|
+
if self.simulation_modifier and other.simulation_modifier:
|
|
71
|
+
# Both have modifiers - chain them
|
|
72
|
+
def combined_modifier(simulation: Simulation) -> None:
|
|
73
|
+
self.simulation_modifier(simulation)
|
|
74
|
+
other.simulation_modifier(simulation)
|
|
75
|
+
|
|
76
|
+
elif self.simulation_modifier:
|
|
77
|
+
combined_modifier = self.simulation_modifier
|
|
78
|
+
elif other.simulation_modifier:
|
|
79
|
+
combined_modifier = other.simulation_modifier
|
|
80
|
+
|
|
81
|
+
# Return new scenario with merged configuration
|
|
82
|
+
return Scenario(
|
|
83
|
+
parameter_changes=merged_params if merged_params else None,
|
|
84
|
+
simulation_modifier=combined_modifier,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def from_reform(
|
|
89
|
+
cls, reform: Union[tuple, dict, Type[Reform]]
|
|
90
|
+
) -> "Scenario":
|
|
91
|
+
"""Create a Scenario from various reform representations.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
reform: Can be:
|
|
95
|
+
- A Reform class type (will be applied via simulation modifier)
|
|
96
|
+
- A dict of parameter changes
|
|
97
|
+
- A tuple (treated as a Reform for backward compatibility)
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
A new Scenario configured with the reform
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
ValueError: If reform type is not supported
|
|
104
|
+
"""
|
|
105
|
+
if isinstance(reform, type) and issubclass(reform, Reform):
|
|
106
|
+
# Reform class - create modifier function
|
|
107
|
+
def modifier(simulation: Simulation) -> None:
|
|
108
|
+
reform_instance = reform()
|
|
109
|
+
reform_instance.apply(simulation.tax_benefit_system)
|
|
110
|
+
|
|
111
|
+
return cls(
|
|
112
|
+
simulation_modifier=modifier,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
elif isinstance(reform, dict):
|
|
116
|
+
# Dictionary of parameter changes
|
|
117
|
+
# Make sure to capture YYYY-MM-DD.YYYY-MM-DD.
|
|
118
|
+
|
|
119
|
+
def modifier(sim: Simulation):
|
|
120
|
+
for parameter in reform:
|
|
121
|
+
if isinstance(reform[parameter], dict):
|
|
122
|
+
for period_str, value in reform[parameter].items():
|
|
123
|
+
if "." in period_str:
|
|
124
|
+
start = instant(period_str.split(".")[0])
|
|
125
|
+
stop = instant(period_str.split(".")[1])
|
|
126
|
+
period_ = None
|
|
127
|
+
else:
|
|
128
|
+
start = None
|
|
129
|
+
stop = None
|
|
130
|
+
period_ = period(period_str)
|
|
131
|
+
sim.tax_benefit_system.parameters.get_child(
|
|
132
|
+
parameter
|
|
133
|
+
).update(
|
|
134
|
+
start=start,
|
|
135
|
+
stop=stop,
|
|
136
|
+
period=period_,
|
|
137
|
+
value=value,
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
start = instant("2023-01-01")
|
|
141
|
+
stop = None
|
|
142
|
+
period_ = None
|
|
143
|
+
|
|
144
|
+
sim.tax_benefit_system.parameters.get_child(
|
|
145
|
+
parameter
|
|
146
|
+
).update(
|
|
147
|
+
start=start,
|
|
148
|
+
stop=stop,
|
|
149
|
+
period=period_,
|
|
150
|
+
value=reform[parameter],
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return Scenario(
|
|
154
|
+
simulation_modifier=modifier,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
elif isinstance(reform, tuple):
|
|
158
|
+
# Tuple format (legacy support) - treat as a Reform class
|
|
159
|
+
# Assuming the tuple contains (reform_class, *args)
|
|
160
|
+
if (
|
|
161
|
+
len(reform) > 0
|
|
162
|
+
and isinstance(reform[0], type)
|
|
163
|
+
and issubclass(reform[0], Reform)
|
|
164
|
+
):
|
|
165
|
+
reform_class = reform[0]
|
|
166
|
+
reform_args = reform[1:] if len(reform) > 1 else ()
|
|
167
|
+
|
|
168
|
+
def modifier(simulation: Simulation) -> None:
|
|
169
|
+
reform_instance = reform_class(*reform_args)
|
|
170
|
+
reform_instance.apply(simulation.tax_benefit_system)
|
|
171
|
+
|
|
172
|
+
return cls(
|
|
173
|
+
simulation_modifier=modifier,
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError(f"Invalid tuple format for reform: {reform}")
|
|
177
|
+
|
|
178
|
+
else:
|
|
179
|
+
raise ValueError(
|
|
180
|
+
f"Unsupported reform type: {type(reform)}. "
|
|
181
|
+
"Expected Reform class, dict, or tuple."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def apply(self, simulation: Simulation) -> None:
|
|
185
|
+
"""Apply this scenario to a simulation.
|
|
186
|
+
|
|
187
|
+
First applies parameter changes, then runs the simulation modifier if present.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
simulation: The simulation to modify
|
|
191
|
+
"""
|
|
192
|
+
# Apply parameter changes first
|
|
193
|
+
if self.parameter_changes:
|
|
194
|
+
for path, value in self.parameter_changes.items():
|
|
195
|
+
if isinstance(value, dict):
|
|
196
|
+
# Handle nested parameter changes
|
|
197
|
+
for sub_path, sub_value in value.items():
|
|
198
|
+
full_path = f"{path}.{sub_path}"
|
|
199
|
+
simulation.tax_benefit_system.parameters.update(
|
|
200
|
+
full_path,
|
|
201
|
+
period=None, # Apply to all periods
|
|
202
|
+
value=sub_value,
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
# Simple parameter change
|
|
206
|
+
simulation.tax_benefit_system.parameters.update(
|
|
207
|
+
path,
|
|
208
|
+
period=None,
|
|
209
|
+
value=value, # Apply to all periods
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Then apply simulation modifier
|
|
213
|
+
if self.simulation_modifier:
|
|
214
|
+
self.simulation_modifier(simulation)
|
|
215
|
+
|
|
216
|
+
def __repr__(self) -> str:
|
|
217
|
+
"""String representation of the Scenario."""
|
|
218
|
+
parts = []
|
|
219
|
+
if self.parameter_changes:
|
|
220
|
+
parts.append(
|
|
221
|
+
f"parameter_changes={len(self.parameter_changes)} items"
|
|
222
|
+
)
|
|
223
|
+
if self.simulation_modifier:
|
|
224
|
+
parts.append("simulation_modifier=<function>")
|
|
225
|
+
return f"Scenario({', '.join(parts)})"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from
|
|
1
|
+
from policyengine_uk import Microsimulation
|
|
2
2
|
from policyengine_core.reforms import Reform
|
|
3
3
|
from tqdm import tqdm
|
|
4
4
|
|
|
@@ -19,11 +19,9 @@ for factor in tqdm([round(x * 0.01, 2) for x in range(70, 91)]):
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
# Run the reformed microsimulation
|
|
22
|
-
reformed =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
country="uk",
|
|
26
|
-
).baseline_simulation
|
|
22
|
+
reformed = Microsimulation(
|
|
23
|
+
reform=reform,
|
|
24
|
+
)
|
|
27
25
|
reformed.baseline_simulation.get_holder(
|
|
28
26
|
"attends_private_school"
|
|
29
27
|
).delete_arrays()
|
|
@@ -35,7 +35,11 @@ class baseline_employer_cost(Variable):
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
# Calculate baseline employer cost
|
|
38
|
-
|
|
38
|
+
if person.simulation.baseline is None:
|
|
39
|
+
return 0
|
|
40
|
+
baseline_parameters = (
|
|
41
|
+
person.simulation.baseline.tax_benefit_system.parameters(period)
|
|
42
|
+
)
|
|
39
43
|
baseline_class_1 = (
|
|
40
44
|
baseline_parameters.gov.hmrc.national_insurance.class_1
|
|
41
45
|
)
|
policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_fixed_employer_cost_change.py
CHANGED
|
@@ -9,14 +9,13 @@ class employer_ni_fixed_employer_cost_change(Variable):
|
|
|
9
9
|
unit = GBP
|
|
10
10
|
|
|
11
11
|
def formula(person, period, parameters):
|
|
12
|
-
return 0
|
|
13
12
|
employee_incidence = parameters(
|
|
14
13
|
period
|
|
15
14
|
).gov.contrib.policyengine.employer_ni.employee_incidence
|
|
16
15
|
if employee_incidence == 0:
|
|
17
16
|
return 0
|
|
18
|
-
# First, calculate baseline and reformed employer NI contributions.
|
|
19
17
|
|
|
18
|
+
# First, calculate baseline and reformed employer NI contributions.
|
|
20
19
|
prior_employment_income = person(
|
|
21
20
|
"employment_income_before_lsr", period
|
|
22
21
|
)
|
|
@@ -43,7 +42,12 @@ class employer_ni_fixed_employer_cost_change(Variable):
|
|
|
43
42
|
)
|
|
44
43
|
|
|
45
44
|
# Calculate baseline employer cost
|
|
46
|
-
|
|
45
|
+
if person.simulation.baseline is None:
|
|
46
|
+
return 0
|
|
47
|
+
baseline_parameters = (
|
|
48
|
+
person.simulation.baseline.tax_benefit_system.parameters(period)
|
|
49
|
+
)
|
|
50
|
+
|
|
47
51
|
baseline_class_1 = (
|
|
48
52
|
baseline_parameters.gov.hmrc.national_insurance.class_1
|
|
49
53
|
)
|
|
@@ -52,19 +56,14 @@ class employer_ni_fixed_employer_cost_change(Variable):
|
|
|
52
56
|
p_b = (
|
|
53
57
|
baseline_parameters.gov.contrib.policyengine.employer_ni.exempt_employer_pension_contributions
|
|
54
58
|
)
|
|
55
|
-
pen_con_subtracted_b = employer_pension_contributions
|
|
56
|
-
if p_b:
|
|
57
|
-
pen_con_subtracted_b = employer_pension_contributions
|
|
58
|
-
else:
|
|
59
|
-
pen_con_subtracted_b = 0
|
|
59
|
+
pen_con_subtracted_b = employer_pension_contributions if p_b else 0
|
|
60
60
|
|
|
61
|
-
baseline_employer_ni = r_b * (
|
|
62
|
-
ni_class_1_income - pen_con_subtracted_b - t_b
|
|
61
|
+
baseline_employer_ni = r_b * max_(
|
|
62
|
+
ni_class_1_income - pen_con_subtracted_b - t_b, 0
|
|
63
63
|
)
|
|
64
64
|
c_b = ni_class_1_income + baseline_employer_ni
|
|
65
65
|
|
|
66
|
-
# Calculate
|
|
67
|
-
|
|
66
|
+
# Calculate reform employer cost
|
|
68
67
|
reform_parameters = parameters(period)
|
|
69
68
|
reform_class_1 = reform_parameters.gov.hmrc.national_insurance.class_1
|
|
70
69
|
r_r = reform_class_1.rates.employer
|
|
@@ -72,32 +71,33 @@ class employer_ni_fixed_employer_cost_change(Variable):
|
|
|
72
71
|
p_r = (
|
|
73
72
|
reform_parameters.gov.contrib.policyengine.employer_ni.exempt_employer_pension_contributions
|
|
74
73
|
)
|
|
75
|
-
if p_r
|
|
76
|
-
pen_con_subtracted_r = employer_pension_contributions
|
|
77
|
-
else:
|
|
78
|
-
pen_con_subtracted_r = 0
|
|
74
|
+
pen_con_subtracted_r = employer_pension_contributions if p_r else 0
|
|
79
75
|
|
|
76
|
+
# Early return if no change in parameters
|
|
77
|
+
if r_b == r_r and t_b == t_r and p_b == p_r:
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
# Calculate new employment income keeping employer cost constant
|
|
81
|
+
# Solve: c_b = new_ni_class_1_income + r_r * max(new_ni_class_1_income - pen_con_subtracted_r - t_r, 0)
|
|
82
|
+
# Rearranging: new_ni_class_1_income = (c_b + r_r * (pen_con_subtracted_r + t_r)) / (1 + r_r)
|
|
80
83
|
new_ni_class_1_income = (c_b + r_r * (pen_con_subtracted_r + t_r)) / (
|
|
81
84
|
1 + r_r
|
|
82
85
|
)
|
|
83
86
|
|
|
84
87
|
# Find difference in employment income
|
|
85
|
-
|
|
86
88
|
previous_employment_income = (
|
|
87
|
-
ni_class_1_income - benefits
|
|
88
|
-
)
|
|
89
|
+
ni_class_1_income - benefits - employer_pension_contributions
|
|
90
|
+
)
|
|
89
91
|
new_employment_income = (
|
|
90
|
-
new_ni_class_1_income - benefits
|
|
91
|
-
)
|
|
92
|
+
new_ni_class_1_income - benefits - employer_pension_contributions
|
|
93
|
+
)
|
|
92
94
|
|
|
93
95
|
pay_change = new_employment_income - previous_employment_income
|
|
94
96
|
|
|
95
97
|
# Apply incidence percentage
|
|
96
|
-
|
|
97
98
|
interpolated_pay_change = pay_change * employee_incidence
|
|
98
99
|
|
|
99
100
|
# Where a person's prior employment income was below the secondary threshold, the formula doesn't hold, so assume no change.
|
|
100
|
-
|
|
101
101
|
below_threshold = previous_employment_income < t_b
|
|
102
102
|
|
|
103
103
|
return where(below_threshold, 0, interpolated_pay_change)
|
|
@@ -9,7 +9,6 @@ class targeted_childcare_entitlement_eligible(Variable):
|
|
|
9
9
|
defined_for = "would_claim_targeted_childcare"
|
|
10
10
|
|
|
11
11
|
def formula(benunit, period, parameters):
|
|
12
|
-
|
|
13
12
|
# Check if household is in England
|
|
14
13
|
country = benunit.household("country", period)
|
|
15
14
|
in_england = country == country.possible_values.ENGLAND
|