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.
Files changed (258) hide show
  1. policyengine_uk/__init__.py +5 -3
  2. policyengine_uk/data/__init__.py +1 -0
  3. policyengine_uk/data/dataset_schema.py +70 -18
  4. policyengine_uk/data/economic_assumptions.py +36 -10
  5. policyengine_uk/data/filter_dataset.py +52 -0
  6. policyengine_uk/dynamics/labour_supply.py +343 -0
  7. policyengine_uk/dynamics/participation.py +629 -0
  8. policyengine_uk/dynamics/progression.py +384 -0
  9. policyengine_uk/microsimulation.py +105 -0
  10. policyengine_uk/model_api.py +1 -0
  11. policyengine_uk/parameters/gov/boe/base_rate.yaml +34 -0
  12. policyengine_uk/parameters/gov/boe/index.yaml +2 -0
  13. policyengine_uk/parameters/gov/contrib/behavioral_responses/employee_salary_sacrifice_reduction_rate.yaml +14 -0
  14. policyengine_uk/parameters/gov/contrib/behavioral_responses/salary_sacrifice_broad_base_haircut_rate.yaml +22 -0
  15. policyengine_uk/parameters/gov/contrib/cec/state_pension_increase.yaml +1 -1
  16. policyengine_uk/parameters/gov/contrib/ubi_center/carbon_tax.yaml +2 -2
  17. policyengine_uk/parameters/gov/contrib/ubi_center/land_value_tax.yaml +3 -3
  18. policyengine_uk/parameters/gov/dcms/bbc/tv_licence/colour.yaml +5 -5
  19. policyengine_uk/parameters/gov/dfe/education_spending.yaml +1 -1
  20. policyengine_uk/parameters/gov/dft/rail/fare_index.yaml +32 -0
  21. policyengine_uk/parameters/gov/dft/rail/prior_law_fare_index.yaml +32 -0
  22. policyengine_uk/parameters/gov/dft/rail/ridership_index.yaml +30 -0
  23. policyengine_uk/parameters/gov/dft/spending.yaml +2 -2
  24. policyengine_uk/parameters/gov/dwp/ESA/income/earn_disregard.yaml +1 -1
  25. policyengine_uk/parameters/gov/dwp/ESA/income/income_disregard_couple.yaml +1 -1
  26. policyengine_uk/parameters/gov/dwp/ESA/income/income_disregard_lone_parent.yaml +1 -1
  27. policyengine_uk/parameters/gov/dwp/ESA/income/income_disregard_single.yaml +1 -1
  28. policyengine_uk/parameters/gov/dwp/ESA/income/pension_disregard.yaml +1 -1
  29. policyengine_uk/parameters/gov/dwp/IIDB/maximum.yaml +1 -1
  30. policyengine_uk/parameters/gov/dwp/JSA/contrib/amount_over_25.yaml +1 -1
  31. policyengine_uk/parameters/gov/dwp/JSA/contrib/earn_disregard.yaml +1 -1
  32. policyengine_uk/parameters/gov/dwp/JSA/contrib/pension_disregard.yaml +1 -1
  33. policyengine_uk/parameters/gov/dwp/JSA/income/amount_18_24.yaml +7 -7
  34. policyengine_uk/parameters/gov/dwp/JSA/income/amount_over_25.yaml +7 -7
  35. policyengine_uk/parameters/gov/dwp/JSA/income/income_disregard_couple.yaml +1 -1
  36. policyengine_uk/parameters/gov/dwp/JSA/income/income_disregard_lone_parent.yaml +1 -1
  37. policyengine_uk/parameters/gov/dwp/JSA/income/income_disregard_single.yaml +1 -1
  38. policyengine_uk/parameters/gov/dwp/LHA/shared_accommodation_age_threshold.yaml +12 -0
  39. policyengine_uk/parameters/gov/dwp/attendance_allowance/higher.yaml +7 -7
  40. policyengine_uk/parameters/gov/dwp/attendance_allowance/lower.yaml +7 -7
  41. policyengine_uk/parameters/gov/dwp/benefit_cap.yaml +3 -3
  42. policyengine_uk/parameters/gov/dwp/carer_premium/couple.yaml +2 -2
  43. policyengine_uk/parameters/gov/dwp/carer_premium/single.yaml +6 -6
  44. policyengine_uk/parameters/gov/dwp/carers_allowance/rate.yaml +7 -7
  45. policyengine_uk/parameters/gov/dwp/disability_premia/disability_couple.yaml +1 -1
  46. policyengine_uk/parameters/gov/dwp/disability_premia/enhanced_couple.yaml +1 -1
  47. policyengine_uk/parameters/gov/dwp/disability_premia/enhanced_single.yaml +1 -1
  48. policyengine_uk/parameters/gov/dwp/disability_premia/severe_couple.yaml +1 -1
  49. policyengine_uk/parameters/gov/dwp/dla/mobility/higher.yaml +4 -4
  50. policyengine_uk/parameters/gov/dwp/dla/mobility/lower.yaml +8 -8
  51. policyengine_uk/parameters/gov/dwp/dla/self_care/higher.yaml +7 -7
  52. policyengine_uk/parameters/gov/dwp/dla/self_care/lower.yaml +8 -8
  53. policyengine_uk/parameters/gov/dwp/dla/self_care/middle.yaml +7 -7
  54. policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged.yaml +1 -1
  55. policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/older.yaml +3 -3
  56. policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged.yaml +1 -1
  57. policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/older.yaml +3 -3
  58. policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker.yaml +1 -1
  59. policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/amount.yaml +1 -1
  60. policyengine_uk/parameters/gov/dwp/housing_benefit/takeup.yaml +7 -7
  61. policyengine_uk/parameters/gov/dwp/income_support/amounts/amount_16_24.yaml +1 -1
  62. policyengine_uk/parameters/gov/dwp/income_support/amounts/amount_couples_over_18.yaml +1 -1
  63. policyengine_uk/parameters/gov/dwp/income_support/means_test/earn_disregard.yaml +1 -1
  64. policyengine_uk/parameters/gov/dwp/income_support/means_test/income_disregard_couple.yaml +1 -1
  65. policyengine_uk/parameters/gov/dwp/income_support/means_test/income_disregard_lone_parent.yaml +1 -1
  66. policyengine_uk/parameters/gov/dwp/income_support/means_test/income_disregard_single.yaml +1 -1
  67. policyengine_uk/parameters/gov/dwp/income_support/means_test/pension_disregard.yaml +1 -1
  68. policyengine_uk/parameters/gov/dwp/income_support/takeup.yaml +7 -7
  69. policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/carer/addition.yaml +4 -4
  70. policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee.yaml +9 -9
  71. policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/severe_disability/addition.yaml +3 -3
  72. policyengine_uk/parameters/gov/dwp/pension_credit/savings_credit/threshold.yaml +5 -5
  73. policyengine_uk/parameters/gov/dwp/pip/daily_living/enhanced.yaml +7 -7
  74. policyengine_uk/parameters/gov/dwp/pip/daily_living/standard.yaml +8 -8
  75. policyengine_uk/parameters/gov/dwp/pip/mobility/enhanced.yaml +4 -4
  76. policyengine_uk/parameters/gov/dwp/pip/mobility/standard.yaml +9 -9
  77. policyengine_uk/parameters/gov/dwp/sda/maximum.yaml +7 -7
  78. policyengine_uk/parameters/gov/dwp/state_pension/basic_state_pension/amount.yaml +11 -11
  79. policyengine_uk/parameters/gov/dwp/state_pension/new_state_pension/amount.yaml +4 -4
  80. policyengine_uk/parameters/gov/dwp/tax_credits/child_tax_credit/limit/child_count.yaml +10 -1
  81. policyengine_uk/parameters/gov/dwp/tax_credits/child_tax_credit/takeup.yaml +7 -7
  82. policyengine_uk/parameters/gov/dwp/tax_credits/working_tax_credit/takeup.yaml +7 -7
  83. policyengine_uk/parameters/gov/dwp/universal_credit/elements/carer/amount.yaml +3 -3
  84. policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/amount.yaml +2 -4
  85. policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/disabled/amount.yaml +2 -4
  86. policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/first/higher_amount.yaml +6 -8
  87. policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/limit/child_count.yaml +6 -1
  88. policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/severely_disabled/amount.yaml +3 -5
  89. policyengine_uk/parameters/gov/dwp/universal_credit/elements/childcare/cap.yaml +2 -6
  90. policyengine_uk/parameters/gov/dwp/universal_credit/elements/disabled/amount.yaml +4 -6
  91. policyengine_uk/parameters/gov/dwp/universal_credit/elements/housing/non_dep_deduction/amount.yaml +4 -1
  92. policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/active.yaml +9 -0
  93. policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/new_claimant_health_element.yaml +9 -0
  94. policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/standard_allowance_uplift.yaml +13 -0
  95. policyengine_uk/parameters/gov/dwp/universal_credit/standard_allowance/amount.yaml +5 -5
  96. policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/maximum_taxable_income.yaml +2 -1
  97. policyengine_uk/parameters/gov/dwp/winter_fuel_payment/eligibility/taxable_income_test/use_maximum_taxable_income.yaml +1 -0
  98. policyengine_uk/parameters/gov/dynamic/obr_labour_supply_assumptions.yaml +9 -0
  99. policyengine_uk/parameters/gov/economic_assumptions/create_economic_assumption_indices.py +1 -1
  100. policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml +522 -153
  101. policyengine_uk/parameters/gov/hmrc/cgt/additional_rate.yaml +5 -0
  102. policyengine_uk/parameters/gov/hmrc/cgt/basic_rate.yaml +5 -0
  103. policyengine_uk/parameters/gov/hmrc/cgt/higher_rate.yaml +4 -0
  104. policyengine_uk/parameters/gov/hmrc/child_benefit/amount/additional.yaml +6 -6
  105. policyengine_uk/parameters/gov/hmrc/child_benefit/amount/eldest.yaml +8 -8
  106. policyengine_uk/parameters/gov/hmrc/child_benefit/takeup/by_age.yaml +1 -1
  107. policyengine_uk/parameters/gov/hmrc/fuel_duty/calculate_fuel_duty_rates.py +464 -0
  108. policyengine_uk/parameters/gov/hmrc/fuel_duty/petrol_and_diesel.yaml +86 -10
  109. policyengine_uk/parameters/gov/hmrc/income_tax/allowances/personal_allowance/amount.yaml +6 -0
  110. policyengine_uk/parameters/gov/hmrc/income_tax/earned_taxable_income_exclusions.yaml +2 -1
  111. policyengine_uk/parameters/gov/hmrc/income_tax/income_tax_additions.yaml +1 -0
  112. policyengine_uk/parameters/gov/hmrc/income_tax/rates/dividends.yaml +12 -0
  113. policyengine_uk/parameters/gov/hmrc/income_tax/rates/property.yaml +46 -0
  114. policyengine_uk/parameters/gov/hmrc/income_tax/rates/savings.yaml +46 -0
  115. policyengine_uk/parameters/gov/hmrc/income_tax/rates/scotland/rates.yaml +2 -2
  116. policyengine_uk/parameters/gov/hmrc/income_tax/rates/uk.yaml +14 -2
  117. policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employee/additional.yaml +4 -6
  118. policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/rates/employer.yaml +3 -3
  119. policyengine_uk/parameters/gov/hmrc/national_insurance/class_1/thresholds/secondary_threshold.yaml +14 -4
  120. policyengine_uk/parameters/gov/hmrc/national_insurance/class_2/flat_rate.yaml +2 -2
  121. policyengine_uk/parameters/gov/hmrc/national_insurance/salary_sacrifice_pension_cap.yaml +16 -0
  122. policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/index.yaml +12 -0
  123. policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_1/boe_margin.yaml +11 -0
  124. policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/additional_rate.yaml +27 -0
  125. policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/index.yaml +16 -0
  126. policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/plan_2/upper_threshold.yaml +48 -0
  127. policyengine_uk/parameters/gov/hmrc/student_loans/interest_rates/postgraduate_additional_rate.yaml +11 -0
  128. policyengine_uk/parameters/gov/hmrc/student_loans/postgraduate_repayment_rate.yaml +9 -0
  129. policyengine_uk/parameters/gov/hmrc/student_loans/repayment_rate.yaml +9 -0
  130. policyengine_uk/parameters/gov/hmrc/student_loans/thresholds/plan_1.yaml +25 -0
  131. policyengine_uk/parameters/gov/hmrc/student_loans/thresholds/plan_2.yaml +58 -0
  132. policyengine_uk/parameters/gov/hmrc/student_loans/thresholds/plan_4.yaml +19 -0
  133. policyengine_uk/parameters/gov/hmrc/student_loans/thresholds/plan_5.yaml +16 -0
  134. policyengine_uk/parameters/gov/hmrc/student_loans/thresholds/postgraduate.yaml +21 -0
  135. policyengine_uk/parameters/gov/hmrc/vat/reduced_rate_share.yaml +3 -3
  136. policyengine_uk/parameters/gov/indices/private_rent_index.yaml +9 -9
  137. policyengine_uk/parameters/gov/revenue_scotland/lbtt/non_residential.yaml +2 -2
  138. policyengine_uk/parameters/gov/revenue_scotland/lbtt/rent.yaml +2 -2
  139. policyengine_uk/parameters/gov/revenue_scotland/lbtt/residential/first_time_buyer_rate.yaml +2 -2
  140. policyengine_uk/parameters/gov/revenue_scotland/lbtt/residential/rate.yaml +2 -2
  141. policyengine_uk/parameters/gov/wra/land_transaction_tax/non_residential.yaml +2 -2
  142. policyengine_uk/parameters/gov/wra/land_transaction_tax/rent.yaml +2 -2
  143. policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/higher_rate.yaml +1 -1
  144. policyengine_uk/parameters/gov/wra/land_transaction_tax/residential/primary.yaml +2 -2
  145. policyengine_uk/parameters/household/consumption/carbon/consumption.yaml +8 -6
  146. policyengine_uk/parameters/household/consumption/carbon/intensity.yaml +4 -1
  147. policyengine_uk/parameters/household/consumption/carbon/production.yaml +12 -7
  148. policyengine_uk/parameters/household/consumption/carbon/production_by_source.yaml +76 -41
  149. policyengine_uk/parameters/household/consumption/fuel/prices/petrol.yaml +1 -1
  150. policyengine_uk/parameters/household/poverty/absolute_poverty_threshold_bhc.yaml +1 -1
  151. policyengine_uk/reforms/policyengine/adjust_budgets.py +0 -1
  152. policyengine_uk/scenarios/__init__.py +4 -0
  153. policyengine_uk/scenarios/pip_reform.py +23 -0
  154. policyengine_uk/scenarios/reindex_benefit_cap.py +32 -0
  155. policyengine_uk/scenarios/repeal_two_child_limit.py +10 -0
  156. policyengine_uk/scenarios/uc_reform.py +50 -0
  157. policyengine_uk/simulation.py +619 -0
  158. policyengine_uk/system.py +3 -257
  159. policyengine_uk/tax_benefit_system.py +141 -0
  160. policyengine_uk/tests/behavioral_responses/test_labor_supply_responses.yaml +183 -0
  161. policyengine_uk/tests/microsimulation/reforms_config.yaml +8 -8
  162. policyengine_uk/tests/microsimulation/test_reform_impacts.py +2 -2
  163. policyengine_uk/tests/microsimulation/test_salary_sacrifice_cap_reform.py +401 -0
  164. policyengine_uk/tests/microsimulation/test_validity.py +2 -3
  165. policyengine_uk/tests/microsimulation/update_reform_impacts.py +104 -40
  166. policyengine_uk/tests/policy/baseline/contrib/policyengine/employer_ni/employer_ni_fixed_employer_cost_change.yaml +105 -0
  167. policyengine_uk/tests/policy/baseline/finance/benefit/family/child_benefit.yaml +2 -0
  168. policyengine_uk/tests/policy/baseline/finance/benefit/family/income_support.yaml +0 -23
  169. policyengine_uk/tests/policy/baseline/gov/dcms/bbc/tv-licence/tv_licence.yaml +3 -0
  170. policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement.yaml +17 -17
  171. policyengine_uk/tests/policy/baseline/gov/dwp/basic_state_pension.yaml +44 -0
  172. policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/gift_aid.yaml +71 -0
  173. policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/personal_allowance.yaml +161 -0
  174. policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.yaml +107 -0
  175. policyengine_uk/tests/policy/baseline/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.yaml +95 -0
  176. policyengine_uk/tests/policy/baseline/gov/hmrc/student_loans/student_loan_interest_rate.yaml +153 -0
  177. policyengine_uk/tests/policy/baseline/gov/hmrc/student_loans/student_loan_repayment.yaml +130 -0
  178. policyengine_uk/tests/policy/baseline/household/wealth/vehicle.yaml +27 -0
  179. policyengine_uk/tests/policy/reforms/nov_2025_budget/income_source_tax_rates.yaml +235 -0
  180. policyengine_uk/tests/policy/reforms/nov_2025_budget/income_tax_freeze.yaml +83 -0
  181. policyengine_uk/tests/policy/reforms/parametric/basic_income/basic_income.yaml +1 -0
  182. policyengine_uk/tests/test_behavioral_responses.py +215 -0
  183. policyengine_uk/tests/test_fiscal_year_parameters.py +131 -0
  184. policyengine_uk/utils/__init__.py +1 -0
  185. policyengine_uk/utils/compare.py +28 -0
  186. policyengine_uk/utils/create_ahc_deflator.py +169 -0
  187. policyengine_uk/utils/create_triple_lock.py +1 -1
  188. policyengine_uk/utils/dependencies.py +259 -0
  189. policyengine_uk/utils/parameters.py +12 -1
  190. policyengine_uk/utils/scenario.py +225 -0
  191. policyengine_uk/utils/solve_private_school_attendance_factor.py +4 -6
  192. policyengine_uk/variables/contrib/policyengine/education_budget_change.py +0 -1
  193. policyengine_uk/variables/contrib/policyengine/employer_ni/baseline_employer_cost.py +5 -1
  194. policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_fixed_employer_cost_change.py +23 -23
  195. policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_response_capital_incidence.py +1 -1
  196. policyengine_uk/variables/contrib/policyengine/employer_ni/employer_ni_response_consumer_incidence.py +1 -1
  197. policyengine_uk/variables/contrib/policyengine/other_public_spending_budget_change.py +0 -1
  198. policyengine_uk/variables/gov/dfe/targeted_childcare_entitlement/targeted_childcare_entitlement_eligible.py +0 -1
  199. policyengine_uk/variables/gov/dft/rail_subsidy_spending.py +16 -1
  200. policyengine_uk/variables/gov/dft/rail_usage.py +16 -0
  201. policyengine_uk/variables/gov/dwp/BRMA_LHA_rate.py +7 -2
  202. policyengine_uk/variables/gov/dwp/LHA_category.py +4 -2
  203. policyengine_uk/variables/gov/dwp/additional_state_pension.py +4 -2
  204. policyengine_uk/variables/gov/dwp/basic_state_pension.py +26 -8
  205. policyengine_uk/variables/gov/dwp/is_CTC_eligible.py +1 -1
  206. policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt.py +9 -13
  207. policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py +66 -0
  208. policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py +75 -0
  209. policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py +66 -0
  210. policyengine_uk/variables/gov/dwp/winter_fuel_allowance.py +5 -4
  211. policyengine_uk/variables/gov/gov_tax.py +0 -2
  212. policyengine_uk/variables/gov/hmrc/household_tax.py +0 -1
  213. policyengine_uk/variables/gov/hmrc/income_tax/allowances/gift_aid.py +23 -0
  214. policyengine_uk/variables/gov/hmrc/income_tax/allowances/personal_allowance.py +9 -2
  215. policyengine_uk/variables/gov/hmrc/income_tax/income_tax_pre_charges.py +1 -0
  216. policyengine_uk/variables/gov/hmrc/income_tax/liability/property_income_tax.py +75 -0
  217. policyengine_uk/variables/gov/hmrc/income_tax/liability/savings_income_tax.py +4 -4
  218. policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_broad_base_haircut.py +43 -0
  219. policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employee.py +38 -0
  220. policyengine_uk/variables/gov/hmrc/national_insurance/salary_sacrifice_pension_ni_employer.py +27 -0
  221. policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_via_salary_sacrifice_adjusted.py +31 -0
  222. policyengine_uk/variables/gov/hmrc/pensions/salary_sacrifice_returned_to_income.py +41 -0
  223. policyengine_uk/variables/gov/hmrc/student_loans/__init__.py +2 -0
  224. policyengine_uk/variables/gov/hmrc/student_loans/plan_1_interest_rate.py +23 -0
  225. policyengine_uk/variables/gov/hmrc/student_loans/plan_2_interest_rate.py +31 -0
  226. policyengine_uk/variables/gov/hmrc/student_loans/plan_4_interest_rate.py +22 -0
  227. policyengine_uk/variables/gov/hmrc/student_loans/plan_5_interest_rate.py +18 -0
  228. policyengine_uk/variables/gov/hmrc/student_loans/postgraduate_interest_rate.py +23 -0
  229. policyengine_uk/variables/gov/hmrc/student_loans/student_loan_plan.py +27 -0
  230. policyengine_uk/variables/gov/hmrc/student_loans/student_loan_repayment.py +91 -0
  231. policyengine_uk/variables/gov/hmrc/student_loans/student_loan_repayment_rate.py +31 -0
  232. policyengine_uk/variables/gov/hmrc/would_claim_child_benefit.py +5 -1
  233. policyengine_uk/variables/household/demographic/benunit/benunit_count_adults.py +11 -0
  234. policyengine_uk/variables/household/demographic/is_disabled_for_benefits.py +13 -1
  235. policyengine_uk/variables/household/income/hbai_household_net_income.py +29 -1
  236. policyengine_uk/variables/household/income/hbai_household_net_income_ahc.py +13 -0
  237. policyengine_uk/variables/household/income/household_net_income.py +5 -1
  238. policyengine_uk/variables/household/income/inflation_adjustment.py +24 -0
  239. policyengine_uk/variables/household/post_tax_income.py +12 -0
  240. policyengine_uk/variables/household/wealth/num_vehicles.py +9 -0
  241. policyengine_uk/variables/household/wealth/owns_vehicle.py +17 -0
  242. policyengine_uk/variables/input/consumption/property/council_tax.py +0 -35
  243. policyengine_uk/variables/input/consumption/property/employee_pension_contributions.py +8 -1
  244. policyengine_uk/variables/input/consumption/property/employee_pension_contributions_reported.py +16 -0
  245. policyengine_uk/variables/input/consumption/property/pension_contributions_via_salary_sacrifice.py +16 -0
  246. policyengine_uk/variables/input/employment_income.py +2 -0
  247. policyengine_uk/variables/input/rent.py +0 -40
  248. policyengine_uk/variables/input/savings_interest_income.py +3 -1
  249. {policyengine_uk-2.40.1.dist-info → policyengine_uk-2.65.6.dist-info}/METADATA +17 -8
  250. {policyengine_uk-2.40.1.dist-info → policyengine_uk-2.65.6.dist-info}/RECORD +252 -173
  251. {policyengine_uk-2.40.1.dist-info → policyengine_uk-2.65.6.dist-info}/WHEEL +1 -1
  252. policyengine_uk/repo.py +0 -3
  253. policyengine_uk/tests/policy/baseline/gov/abolitions/abolition_parameters.yaml +0 -250
  254. policyengine_uk/variables/contrib/policyengine/high_income_incident_tax_change.py +0 -22
  255. policyengine_uk-2.40.1.data/data/share/openfisca/openfisca-country-template/CHANGELOG.md +0 -2285
  256. policyengine_uk-2.40.1.data/data/share/openfisca/openfisca-country-template/README.md +0 -37
  257. policyengine_uk-2.40.1.dist-info/licenses/LICENSE +0 -661
  258. {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
- YEARS = list(range(2015, 2026))
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 policyengine import Simulation
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 = Simulation(
23
- scope="macro",
24
- baseline=reform,
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()
@@ -8,7 +8,6 @@ class education_budget_change(Variable):
8
8
  value_type = float
9
9
 
10
10
  def formula(household, period, parameters):
11
-
12
11
  budget_increase = (
13
12
  parameters(period).gov.contrib.policyengine.budget.education * 1e9
14
13
  )
@@ -35,7 +35,11 @@ class baseline_employer_cost(Variable):
35
35
  )
36
36
 
37
37
  # Calculate baseline employer cost
38
- baseline_parameters = parameters(period).baseline
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
  )
@@ -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
- baseline_parameters = parameters(period).baseline
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 new employment income keeping employer cost constant
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
- ) - employer_pension_contributions
89
+ ni_class_1_income - benefits - employer_pension_contributions
90
+ )
89
91
  new_employment_income = (
90
- new_ni_class_1_income - benefits
91
- ) - employer_pension_contributions
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)
@@ -36,7 +36,7 @@ class employer_ni_response_capital_incidence(Variable):
36
36
  value = (
37
37
  amount_paid_by_employers
38
38
  * share_of_total_wealth
39
- * capital_incidence
39
+ * emp_ni.capital_incidence
40
40
  )
41
41
 
42
42
  if total_wealth == 0:
@@ -36,7 +36,7 @@ class employer_ni_response_consumer_incidence(Variable):
36
36
  value = (
37
37
  amount_paid_by_employers
38
38
  * share_of_total_consumption
39
- * consumer_incidence
39
+ * emp_ni.consumer_incidence
40
40
  )
41
41
 
42
42
  if total_consumption == 0:
@@ -8,7 +8,6 @@ class other_public_spending_budget_change(Variable):
8
8
  value_type = float
9
9
 
10
10
  def formula(household, period, parameters):
11
-
12
11
  budget_increase = (
13
12
  parameters(
14
13
  period
@@ -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