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,619 @@
1
+ # Standard library imports
2
+ from typing import Dict, Optional, Union, Type, List
3
+
4
+ # Third-party imports
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ # PolicyEngine core imports
9
+ from policyengine_core.data import Dataset
10
+ from policyengine_core.periods import period as period_
11
+ from policyengine_core.parameters import Parameter
12
+ from policyengine_core.reforms import Reform
13
+ from policyengine_core.simulations import Simulation as CoreSimulation
14
+ from policyengine_core.tools.hugging_face import download_huggingface_dataset
15
+ from policyengine_core.tracers import FullTracer, SimpleTracer
16
+
17
+ # PolicyEngine UK imports
18
+ from policyengine_uk.data.dataset_schema import (
19
+ UKMultiYearDataset,
20
+ UKSingleYearDataset,
21
+ )
22
+ from policyengine_uk.utils.scenario import Scenario
23
+ from policyengine_uk.data.economic_assumptions import (
24
+ extend_single_year_dataset,
25
+ )
26
+ from policyengine_uk.utils.dependencies import get_variable_dependencies
27
+
28
+ from .tax_benefit_system import CountryTaxBenefitSystem
29
+
30
+ from microdf import MicroDataFrame
31
+
32
+
33
+ class Simulation(CoreSimulation):
34
+ """UK-specific simulation class for calculating tax and benefit outcomes.
35
+
36
+ Extends the core simulation functionality with UK-specific features
37
+ and data handling capabilities.
38
+ """
39
+
40
+ default_input_period: int = 2025
41
+ default_calculation_period: int = 2025
42
+ baseline: "Simulation"
43
+
44
+ calculated_periods: List[str] = []
45
+ _variable_dependencies: Dict[str, List[str]] = None
46
+ dataset = None
47
+
48
+ def __init__(
49
+ self,
50
+ scenario: Optional[Scenario] = None,
51
+ situation: Optional[Dict] = None,
52
+ dataset: Optional[
53
+ Union[pd.DataFrame, str, UKSingleYearDataset, UKMultiYearDataset]
54
+ ] = None,
55
+ trace: bool = False,
56
+ reform: Dict | Type[Reform] = None,
57
+ ):
58
+ """Initialize a UK simulation.
59
+
60
+ Args:
61
+ scenario: A Scenario object defining a modification to the simulation
62
+ situation: A dictionary describing the situation to simulate
63
+ dataset: Data source - can be DataFrame, URL string, or Dataset object
64
+ trace: Whether to enable detailed tracing of calculations
65
+ """
66
+ # Initialize tax-benefit rules
67
+ self.tax_benefit_system = CountryTaxBenefitSystem()
68
+
69
+ # Migrate Reform to Scenario
70
+
71
+ if reform is not None:
72
+ scenario = Scenario.from_reform(reform)
73
+
74
+ self.branch_name = "default"
75
+ self.invalidated_caches = set()
76
+ self.debug: bool = False
77
+ self.trace: bool = trace
78
+ self.tracer: SimpleTracer = (
79
+ SimpleTracer() if not trace else FullTracer()
80
+ )
81
+ self.opt_out_cache: bool = False
82
+ self.max_spiral_loops: int = 10
83
+ self.memory_config = None
84
+ self._data_storage_dir: Optional[str] = None
85
+
86
+ self.branches: Dict[str, Simulation] = {}
87
+
88
+ if scenario is not None and scenario.applied_before_data_load:
89
+ if scenario.simulation_modifier is not None:
90
+ scenario.simulation_modifier(self)
91
+
92
+ if scenario.parameter_changes is not None:
93
+ self.apply_parameter_changes(scenario.parameter_changes)
94
+ # Build simulation from appropriate source
95
+ if situation is not None:
96
+ self.build_from_situation(situation)
97
+ elif isinstance(dataset, str):
98
+ self.build_from_url(dataset)
99
+ elif isinstance(dataset, pd.DataFrame):
100
+ self.build_from_dataframe(dataset)
101
+ elif isinstance(dataset, Dataset):
102
+ self.build_from_dataset(dataset)
103
+ elif isinstance(dataset, UKSingleYearDataset):
104
+ self.build_from_single_year_dataset(dataset)
105
+ elif isinstance(dataset, UKMultiYearDataset):
106
+ self.build_from_multi_year_dataset(dataset)
107
+ elif dataset is None:
108
+ self.build_from_url(
109
+ "hf://policyengine/policyengine-uk-data/enhanced_frs_2023_24.h5"
110
+ )
111
+ else:
112
+ raise ValueError(f"Unsupported dataset type: {dataset.__class__}")
113
+
114
+ # Universal Credit reform (July 2025). Needs closer integration in the baseline,
115
+ # but adding here for ease of toggling on/off via the 'active' parameter.
116
+ from policyengine_uk.scenarios import universal_credit_july_2025_reform
117
+
118
+ universal_credit_july_2025_reform.simulation_modifier(self)
119
+
120
+ # Apply structural modifiers
121
+
122
+ self.tax_benefit_system.reset_parameter_caches()
123
+
124
+ self.move_values("capital_gains", "capital_gains_before_response")
125
+ self.move_values("employment_income", "employment_income_before_lsr")
126
+ self.move_values(
127
+ "employee_pension_contributions",
128
+ "employee_pension_contributions_reported",
129
+ )
130
+
131
+ self.input_variables = self.get_known_variables()
132
+
133
+ if scenario is not None:
134
+ self.baseline = Simulation(
135
+ scenario=None,
136
+ situation=situation,
137
+ dataset=dataset,
138
+ trace=trace,
139
+ )
140
+ else:
141
+ self.baseline = self.clone()
142
+
143
+ self.calculated_periods = []
144
+
145
+ if scenario is not None and not scenario.applied_before_data_load:
146
+ if scenario.simulation_modifier is not None:
147
+ scenario.simulation_modifier(self)
148
+ if scenario.parameter_changes is not None:
149
+ self.apply_parameter_changes(scenario.parameter_changes)
150
+
151
+ def reset_calculations(self):
152
+ for variable in self.tax_benefit_system.variables:
153
+ if variable not in self.input_variables:
154
+ self.delete_arrays(variable)
155
+
156
+ def get_known_variables(self):
157
+ variables = []
158
+ for variable in self.tax_benefit_system.variables:
159
+ if len(self.get_holder(variable).get_known_periods()) > 0:
160
+ variables.append(variable)
161
+ return variables
162
+
163
+ def apply_parameter_changes(self, changes: dict):
164
+ self.tax_benefit_system.reset_parameters()
165
+
166
+ for parameter in changes:
167
+ p: Parameter = self.tax_benefit_system.parameters.get_child(
168
+ parameter
169
+ )
170
+ if isinstance(changes[parameter], dict):
171
+ # Time-period specific changes
172
+ for time_period in changes[parameter]:
173
+ p.update(
174
+ period=time_period,
175
+ value=changes[parameter][time_period],
176
+ )
177
+ else:
178
+ p.update(period="year:2000:100", value=changes[parameter])
179
+
180
+ self.tax_benefit_system.process_parameters()
181
+
182
+ def build_from_situation(self, situation: Dict) -> None:
183
+ """Build simulation from a situation dictionary.
184
+
185
+ Args:
186
+ situation: Dictionary describing household composition and characteristics
187
+ """
188
+ self.build_from_populations(
189
+ self.tax_benefit_system.instantiate_entities()
190
+ )
191
+ from policyengine_core.simulations.simulation_builder import (
192
+ SimulationBuilder,
193
+ ) # Import here to avoid circular dependency
194
+
195
+ builder = SimulationBuilder()
196
+ builder.default_period = self.default_input_period
197
+ builder.build_from_dict(self.tax_benefit_system, situation, self)
198
+ self.has_axes = builder.has_axes
199
+
200
+ self.dataset = UKSingleYearDataset.from_simulation(
201
+ self, fiscal_year=self.default_input_period
202
+ )
203
+
204
+ def build_from_url(self, url: str) -> None:
205
+ """Build simulation from a HuggingFace dataset URL.
206
+
207
+ Args:
208
+ url: HuggingFace URL in format "hf://owner/repo/filename"
209
+
210
+ Raises:
211
+ ValueError: If URL is not a HuggingFace URL
212
+ """
213
+ if "hf://" not in url:
214
+ raise ValueError(
215
+ f"Non-HuggingFace URLs are currently not supported."
216
+ )
217
+
218
+ # Parse HuggingFace URL components
219
+ owner, repo, filename = url.split("/")[-3:]
220
+ if "@" in filename:
221
+ version = filename.split("@")[-1]
222
+ filename = filename.split("@")[0]
223
+ else:
224
+ version = None
225
+
226
+ # Download dataset from HuggingFace
227
+ dataset = download_huggingface_dataset(
228
+ repo=f"{owner}/{repo}",
229
+ repo_filename=filename,
230
+ version=version,
231
+ )
232
+
233
+ # Determine dataset type and build accordingly
234
+ if UKMultiYearDataset.validate_file_path(dataset, False):
235
+ self.build_from_multi_year_dataset(UKMultiYearDataset(dataset))
236
+ elif UKSingleYearDataset.validate_file_path(dataset, False):
237
+ self.build_from_single_year_dataset(UKSingleYearDataset(dataset))
238
+ else:
239
+ dataset = Dataset.from_file(dataset, self.default_input_period)
240
+ self.build_from_dataset(dataset)
241
+
242
+ def build_from_dataframe(self, df: pd.DataFrame) -> None:
243
+ """Build simulation from a pandas DataFrame.
244
+
245
+ Args:
246
+ df: DataFrame with columns in format "variable_name__time_period"
247
+ """
248
+
249
+ def get_first_array(variable_name: str) -> pd.Series:
250
+ """Extract the first array for a given variable name pattern."""
251
+ columns = df.columns[df.columns.str.contains(variable_name + "__")]
252
+ return df[columns[0]]
253
+
254
+ # Extract ID columns
255
+ (
256
+ person_id,
257
+ person_benunit_id,
258
+ person_household_id,
259
+ benunit_id,
260
+ household_id,
261
+ ) = map(
262
+ get_first_array,
263
+ [
264
+ "person_id",
265
+ "person_benunit_id",
266
+ "person_household_id",
267
+ "benunit_id",
268
+ "household_id",
269
+ ],
270
+ )
271
+
272
+ # Build entity structure
273
+ self.build_from_ids(
274
+ person_id,
275
+ person_benunit_id,
276
+ person_household_id,
277
+ benunit_id,
278
+ household_id,
279
+ )
280
+
281
+ # Set input values for each variable and time period
282
+ for column in df:
283
+ variable, time_period = column.split("__")
284
+ if variable not in self.tax_benefit_system.variables:
285
+ continue
286
+ self.set_input(variable, time_period, df[column])
287
+
288
+ def build_from_dataset(self, dataset: Dataset) -> None:
289
+ """Build simulation from a Dataset object.
290
+
291
+ Args:
292
+ dataset: PolicyEngine Dataset object containing simulation data
293
+ """
294
+ data: Dict[str, Dict[str, Union[float, int, str]]] = (
295
+ dataset.load_dataset()
296
+ )
297
+
298
+ first_variable = data[list(data.keys())[0]]
299
+ first_time_period = list(first_variable.keys())[0]
300
+
301
+ def get_first_array(variable_name: str) -> np.ndarray:
302
+ """Get the first time period's values for a variable."""
303
+ time_period_values = data[variable_name]
304
+ return time_period_values[first_time_period]
305
+
306
+ # Build entity structure from IDs
307
+ self.build_from_ids(
308
+ *map(
309
+ get_first_array,
310
+ [
311
+ "person_id",
312
+ "person_benunit_id",
313
+ "person_household_id",
314
+ "benunit_id",
315
+ "household_id",
316
+ ],
317
+ )
318
+ )
319
+
320
+ # Load all variable values
321
+ for variable in data:
322
+ for time_period in data[variable]:
323
+ if variable not in self.tax_benefit_system.variables:
324
+ continue
325
+ self.set_input(
326
+ variable, time_period, data[variable][time_period]
327
+ )
328
+
329
+ # Now convert to the new UKSingleYearDataset
330
+ self.input_variables = self.get_known_variables()
331
+ self.dataset = dataset
332
+ dataset = UKSingleYearDataset.from_simulation(
333
+ self, fiscal_year=first_time_period
334
+ )
335
+ multi_year_dataset = extend_single_year_dataset(
336
+ dataset, self.tax_benefit_system.parameters
337
+ )
338
+
339
+ self.build_from_multi_year_dataset(multi_year_dataset)
340
+ self.dataset = multi_year_dataset
341
+
342
+ def build_from_single_year_dataset(
343
+ self, dataset: UKSingleYearDataset
344
+ ) -> None:
345
+ """Build simulation from a single-year UK dataset.
346
+
347
+ Args:
348
+ dataset: UKSingleYearDataset containing one year of data
349
+ """
350
+
351
+ dataset = extend_single_year_dataset(
352
+ dataset, self.tax_benefit_system.parameters
353
+ )
354
+ self.build_from_multi_year_dataset(dataset)
355
+ self.dataset = dataset
356
+
357
+ def build_from_multi_year_dataset(
358
+ self, dataset: UKMultiYearDataset
359
+ ) -> None:
360
+ """Build simulation from a multi-year UK dataset.
361
+
362
+ Args:
363
+ dataset: UKMultiYearDataset containing multiple years of data
364
+ """
365
+ # Use first year to establish entity structure
366
+ first_year = dataset[dataset.years[0]]
367
+ self.build_from_ids(
368
+ first_year.person.person_id,
369
+ first_year.person.person_benunit_id,
370
+ first_year.person.person_household_id,
371
+ first_year.benunit.benunit_id,
372
+ first_year.household.household_id,
373
+ )
374
+
375
+ # Load variable values for all years
376
+ for year in dataset.years:
377
+ for table in dataset[year].tables:
378
+ for variable in table.columns:
379
+ if variable not in self.tax_benefit_system.variables:
380
+ continue
381
+ self.set_input(variable, year, table[variable])
382
+
383
+ self.dataset = dataset
384
+
385
+ def build_from_ids(
386
+ self,
387
+ person_id: np.ndarray,
388
+ person_benunit_id: np.ndarray,
389
+ person_household_id: np.ndarray,
390
+ benunit_id: np.ndarray,
391
+ household_id: np.ndarray,
392
+ ) -> None:
393
+ """Build simulation entities from ID arrays.
394
+
395
+ Args:
396
+ person_id: Array of person IDs
397
+ person_benunit_id: Array mapping persons to benefit units
398
+ person_household_id: Array mapping persons to households
399
+ benunit_id: Array of benefit unit IDs
400
+ household_id: Array of household IDs
401
+ """
402
+ from policyengine_core.simulations.simulation_builder import (
403
+ SimulationBuilder,
404
+ ) # Import here to avoid circular dependency
405
+
406
+ builder = SimulationBuilder()
407
+ builder.populations = self.tax_benefit_system.instantiate_entities()
408
+
409
+ # Declare entities
410
+ builder.declare_person_entity("person", person_id)
411
+ builder.declare_entity("benunit", np.unique(benunit_id))
412
+ builder.declare_entity("household", np.unique(household_id))
413
+
414
+ # Link persons to benefit units and households
415
+ builder.join_with_persons(
416
+ builder.populations["benunit"],
417
+ person_benunit_id,
418
+ np.array(["member"] * len(person_benunit_id)),
419
+ )
420
+ builder.join_with_persons(
421
+ builder.populations["household"],
422
+ person_household_id,
423
+ np.array(["member"] * len(person_household_id)),
424
+ )
425
+
426
+ self.build_from_populations(builder.populations)
427
+
428
+ def move_values(self, variable_donor: str, variable_target: str) -> None:
429
+ """Move values from one variable to another across all branches.
430
+
431
+ Used for behavioral response modeling where original values need
432
+ to be preserved.
433
+
434
+ Args:
435
+ variable_donor: Variable to move values from
436
+ variable_target: Variable to move values to
437
+ """
438
+ for simulation in list(self.branches.values()) + [self]:
439
+ holder = simulation.get_holder(variable_donor)
440
+ for known_period in holder.get_known_periods():
441
+ array = holder.get_array(known_period)
442
+ simulation.set_input(variable_target, known_period, array)
443
+ holder.delete_arrays(known_period)
444
+
445
+ def calculate_all(self, year: int) -> None:
446
+ person, benunit, household = (
447
+ pd.DataFrame(),
448
+ pd.DataFrame(),
449
+ pd.DataFrame(),
450
+ )
451
+ period = str(year)
452
+ for variable in self.tax_benefit_system.variables.values():
453
+ entity = variable.entity.key
454
+ result = self.calculate(variable.name, period).values
455
+ if entity == "person":
456
+ person[variable.name] = result
457
+ elif entity == "benunit":
458
+ benunit[variable.name] = result
459
+ elif entity == "household":
460
+ household[variable.name] = result
461
+
462
+ person = MicroDataFrame(person, weights="person_weight")
463
+ benunit = MicroDataFrame(benunit, weights="benunit_weight")
464
+ household = MicroDataFrame(household, weights="household_weight")
465
+
466
+ return UKSingleYearDataset(
467
+ person=person,
468
+ benunit=benunit,
469
+ household=household,
470
+ fiscal_year=year,
471
+ )
472
+
473
+ def calculate(
474
+ self,
475
+ variable_name: str = None,
476
+ period: str = None,
477
+ map_to: str = None,
478
+ decode_enums: bool = False,
479
+ ):
480
+ if variable_name is None:
481
+ return self.calculate_all()
482
+ tracer: SimpleTracer = self.tracer
483
+ if len(tracer.stack) == 0:
484
+ # Only decode enums to string values when we're not within
485
+ # the simulation tree.
486
+ decode_enums = True
487
+
488
+ if period is None:
489
+ period = self.default_calculation_period
490
+
491
+ period = period_(period)
492
+
493
+ return super().calculate(
494
+ variable_name, period, map_to=map_to, decode_enums=decode_enums
495
+ )
496
+
497
+ def apply_dynamics(self, year: int):
498
+ """Apply dynamics to the simulation for a specific year.
499
+
500
+ Args:
501
+ year: Year to apply dynamics for
502
+ """
503
+ # Apply OBR labour supply dynamics if enabled
504
+ from policyengine_uk.dynamics.labour_supply import (
505
+ apply_labour_supply_responses,
506
+ )
507
+
508
+ return apply_labour_supply_responses(self, year=str(year))
509
+
510
+ def get_variable_dependencies(self, variable_name: str, depth: int = 1):
511
+ if self._variable_dependencies is None:
512
+ self._variable_dependencies = {}
513
+ self._example_simulation = Simulation(
514
+ situation={
515
+ "age": 30,
516
+ },
517
+ trace=True,
518
+ )
519
+ self._example_simulation.calculate("hbai_household_net_income")
520
+ for (
521
+ variable
522
+ ) in self._example_simulation.tax_benefit_system.variables:
523
+ try:
524
+ dependencies = get_variable_dependencies(
525
+ variable, self._example_simulation
526
+ )
527
+ self._variable_dependencies[variable] = dependencies
528
+ except Exception as e:
529
+ self._variable_dependencies[variable] = []
530
+
531
+ if variable_name not in self._variable_dependencies:
532
+ raise ValueError(
533
+ f"Variable '{variable_name}' not found in simulation dependencies."
534
+ )
535
+ dependencies = self._variable_dependencies[variable_name]
536
+
537
+ if depth <= 1:
538
+ return dependencies
539
+
540
+ all_dependencies = set(dependencies)
541
+ for dep in dependencies:
542
+ try:
543
+ sub_dependencies = self.get_variable_dependencies(
544
+ dep, depth - 1
545
+ )
546
+ all_dependencies.update(sub_dependencies)
547
+ except ValueError:
548
+ continue
549
+ return list(all_dependencies)
550
+
551
+ def compare(
552
+ self,
553
+ other: "Simulation",
554
+ variables: list[str] = None,
555
+ period: str = None,
556
+ change_only: bool = False,
557
+ ):
558
+ """Compare two simulations for a specific variable list.
559
+
560
+ Args:
561
+ other: Another Simulation instance to compare against
562
+ variables: List of variable names to compare. If None, compares all variables.
563
+
564
+ Returns:
565
+ DataFrame with comparison results
566
+ """
567
+
568
+ if variables is None:
569
+ variables = self.input_variables + [
570
+ "hbai_household_net_income",
571
+ ]
572
+
573
+ df_self = self.calculate_dataframe(variables, period=period)
574
+ df_other = other.calculate_dataframe(variables, period=period)
575
+
576
+ df_combined = pd.concat(
577
+ [df_self, df_other], axis=1, keys=["self", "other"]
578
+ )
579
+ # Reset columns, then order by variable name first then self/other
580
+ df_combined.columns = [
581
+ f"{var}_{entity}" for entity, var in df_combined.columns
582
+ ]
583
+ # Add change where applicable (numeric only)
584
+ for variable in variables:
585
+ self_col = f"{variable}_self"
586
+ other_col = f"{variable}_other"
587
+ try:
588
+ df_combined[f"{variable}_change"] = (
589
+ df_combined[other_col] - df_combined[self_col]
590
+ )
591
+ except:
592
+ # For other data types, use XOR
593
+ df_combined[f"{variable}_change"] = (
594
+ df_combined[other_col] != df_combined[self_col]
595
+ )
596
+ columns = []
597
+
598
+ for variable in variables:
599
+ columns.extend(
600
+ [
601
+ f"{variable}_self",
602
+ f"{variable}_other",
603
+ f"{variable}_change",
604
+ ]
605
+ )
606
+
607
+ if change_only:
608
+ variables_to_include = []
609
+ for variable in variables:
610
+ if df_combined[f"{variable}_change"].mean() > 0:
611
+ variables_to_include.extend(
612
+ [
613
+ f"{variable}_self",
614
+ f"{variable}_other",
615
+ f"{variable}_change",
616
+ ]
617
+ )
618
+ columns = variables_to_include
619
+ return df_combined[columns]