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,83 @@
1
+ # Tests for November 2025 Autumn Budget income tax threshold freeze extension
2
+ # The budget extends the freeze on personal allowance, higher rate threshold, and additional rate threshold
3
+ # from April 2028 to April 2031 (covering tax years 2028-29, 2029-30, 2030-31)
4
+
5
+ # Personal allowance freeze tests
6
+ - name: Personal allowance frozen at 12570 in 2028-29
7
+ period: 2028
8
+ input:
9
+ employment_income: 50000
10
+ output:
11
+ personal_allowance: 12570
12
+
13
+ - name: Personal allowance frozen at 12570 in 2029-30
14
+ period: 2029
15
+ input:
16
+ employment_income: 50000
17
+ output:
18
+ personal_allowance: 12570
19
+
20
+ - name: Personal allowance frozen at 12570 in 2030-31
21
+ period: 2030
22
+ input:
23
+ employment_income: 50000
24
+ output:
25
+ personal_allowance: 12570
26
+
27
+ # Higher rate threshold tests (PA + basic rate band = 12570 + 37700 = 50270)
28
+ - name: Income at 50270 pays basic rate only in 2028-29
29
+ period: 2028
30
+ input:
31
+ employment_income: 50270
32
+ output:
33
+ higher_rate_earned_income: 0
34
+
35
+ - name: Income at 50271 pays higher rate in 2028-29
36
+ period: 2028
37
+ input:
38
+ employment_income: 50271
39
+ output:
40
+ higher_rate_earned_income: 1
41
+
42
+ - name: Income at 50270 pays basic rate only in 2030-31
43
+ period: 2030
44
+ input:
45
+ employment_income: 50270
46
+ output:
47
+ higher_rate_earned_income: 0
48
+
49
+ - name: Income at 50271 pays higher rate in 2030-31
50
+ period: 2030
51
+ input:
52
+ employment_income: 50271
53
+ output:
54
+ higher_rate_earned_income: 1
55
+
56
+ # Additional rate threshold tests (125140)
57
+ - name: Income at 125140 pays higher rate only in 2028-29
58
+ period: 2028
59
+ input:
60
+ employment_income: 125140
61
+ output:
62
+ add_rate_earned_income: 0
63
+
64
+ - name: Income at 125141 pays additional rate in 2028-29
65
+ period: 2028
66
+ input:
67
+ employment_income: 125141
68
+ output:
69
+ add_rate_earned_income: 1
70
+
71
+ - name: Income at 125140 pays higher rate only in 2030-31
72
+ period: 2030
73
+ input:
74
+ employment_income: 125140
75
+ output:
76
+ add_rate_earned_income: 0
77
+
78
+ - name: Income at 125141 pays additional rate in 2030-31
79
+ period: 2030
80
+ input:
81
+ employment_income: 125141
82
+ output:
83
+ add_rate_earned_income: 1
@@ -21,6 +21,7 @@
21
21
  benunits:
22
22
  benunit:
23
23
  members: [working_age, senior, child]
24
+ would_claim_child_benefit: true
24
25
  output:
25
26
  basic_income: [52, 104, 156]
26
27
  child_benefit: 1820
@@ -0,0 +1,215 @@
1
+ """
2
+ Tests for behavioral labor supply responses.
3
+
4
+ This test module validates that the behavioral response system works correctly
5
+ and that all the critical fixes are functioning:
6
+ - No more simulation state corruption from sim.reset_calculations()
7
+ - Proper NaN handling prevents calculation errors
8
+ - Income changes are calculated before any state modifications
9
+ - The system correctly returns appropriate FTE responses
10
+ """
11
+
12
+ import pytest
13
+ import yaml
14
+ from pathlib import Path
15
+ from policyengine_uk import Microsimulation
16
+ from policyengine_uk.model_api import Scenario
17
+
18
+
19
+ # Load YAML test cases
20
+ yaml_file = (
21
+ Path(__file__).parent
22
+ / "behavioral_responses"
23
+ / "test_labor_supply_responses.yaml"
24
+ )
25
+ with open(yaml_file, "r") as f:
26
+ yaml_content = f.read()
27
+ test_cases = yaml.safe_load(yaml_content)
28
+
29
+
30
+ class TestBehavioralResponses:
31
+ """Test behavioral labor supply responses functionality"""
32
+
33
+ def test_yaml_file_structure(self):
34
+ """Test that YAML file loads correctly and has expected structure"""
35
+ assert (
36
+ len(test_cases) == 6
37
+ ), f"Expected 6 test cases, got {len(test_cases)}"
38
+
39
+ for i, test_case in enumerate(test_cases):
40
+ assert "name" in test_case, f"Test case {i+1} missing 'name'"
41
+ assert "period" in test_case, f"Test case {i+1} missing 'period'"
42
+ assert "input" in test_case, f"Test case {i+1} missing 'input'"
43
+ assert "reforms" in test_case, f"Test case {i+1} missing 'reforms'"
44
+ assert "output" in test_case, f"Test case {i+1} missing 'output'"
45
+
46
+ def test_obr_parameter_functionality(self):
47
+ """Test that OBR parameter can be enabled and disabled"""
48
+ # Test enabling OBR
49
+ scenario_on = Scenario(
50
+ parameter_changes={
51
+ "gov.dynamic.obr_labour_supply_assumptions": {"2025": True}
52
+ }
53
+ )
54
+ sim_on = Microsimulation(scenario=scenario_on)
55
+ obr_on = sim_on.tax_benefit_system.parameters.gov.dynamic.obr_labour_supply_assumptions(
56
+ "2025"
57
+ )
58
+
59
+ # Test disabling OBR
60
+ scenario_off = Scenario(
61
+ parameter_changes={
62
+ "gov.dynamic.obr_labour_supply_assumptions": {"2025": False}
63
+ }
64
+ )
65
+ sim_off = Microsimulation(scenario=scenario_off)
66
+ obr_off = sim_off.tax_benefit_system.parameters.gov.dynamic.obr_labour_supply_assumptions(
67
+ "2025"
68
+ )
69
+
70
+ assert (
71
+ obr_on == True
72
+ ), "OBR parameter should be enabled when set to True"
73
+ assert (
74
+ obr_off == False
75
+ ), "OBR parameter should be disabled when set to False"
76
+
77
+ def test_dynamics_no_crash_simple(self):
78
+ """Test that dynamics application doesn't crash with simple scenarios"""
79
+ situation = {
80
+ "people": {"person": {"age": 30, "employment_income": 25_000}},
81
+ "benunits": {"benunit": {"members": ["person"]}},
82
+ "households": {"household": {"members": ["person"]}},
83
+ }
84
+
85
+ baseline = Microsimulation(situation=situation)
86
+
87
+ scenario = Scenario(
88
+ parameter_changes={
89
+ "gov.dynamic.obr_labour_supply_assumptions": {"2025": True}
90
+ }
91
+ )
92
+ reformed = Microsimulation(situation=situation, scenario=scenario)
93
+ reformed.baseline = baseline
94
+
95
+ # Test dynamics application - may fail with bin edge error on single person
96
+ # This is expected behavior with minimal dataset, so we catch the specific error
97
+ try:
98
+ dynamics = reformed.apply_dynamics(2025)
99
+ # If successful, dynamics may be None if no income change
100
+ if dynamics is not None:
101
+ assert hasattr(
102
+ dynamics, "fte_impacts"
103
+ ), "Dynamics should have fte_impacts attribute"
104
+ except ValueError as e:
105
+ if (
106
+ "Bin labels must be one fewer than the number of bin edges"
107
+ in str(e)
108
+ ):
109
+ # This is expected with single-person scenarios due to insufficient data for binning
110
+ # The important thing is that our NaN handling and state corruption fixes work
111
+ pass
112
+ else:
113
+ # Re-raise other ValueError exceptions
114
+ raise
115
+
116
+ def test_basic_behavioral_response_enabled(self):
117
+ """Test basic behavioral response mechanism with OBR enabled"""
118
+ test_case = test_cases[0] # First test case
119
+
120
+ situation = test_case["input"]
121
+ reforms = test_case["reforms"]
122
+
123
+ scenario = Scenario(parameter_changes=reforms)
124
+ baseline = Microsimulation(situation=situation)
125
+ reformed = Microsimulation(situation=situation, scenario=scenario)
126
+ reformed.baseline = baseline
127
+
128
+ # Verify OBR is enabled
129
+ obr_enabled = reformed.tax_benefit_system.parameters.gov.dynamic.obr_labour_supply_assumptions(
130
+ "2025"
131
+ )
132
+ assert obr_enabled == True, "OBR should be enabled for this test"
133
+
134
+ # Apply dynamics - should not crash
135
+ dynamics = reformed.apply_dynamics(2025)
136
+ # Test passes if no exception is raised
137
+
138
+ def test_behavioral_response_disabled(self):
139
+ """Test behavioral response with OBR disabled"""
140
+ test_case = test_cases[3] # OBR disabled test case
141
+
142
+ situation = test_case["input"]
143
+ reforms = test_case["reforms"]
144
+
145
+ scenario = Scenario(parameter_changes=reforms)
146
+ reformed = Microsimulation(situation=situation, scenario=scenario)
147
+
148
+ # Verify OBR is disabled
149
+ obr_enabled = reformed.tax_benefit_system.parameters.gov.dynamic.obr_labour_supply_assumptions(
150
+ "2025"
151
+ )
152
+ assert obr_enabled == False, "OBR should be disabled for this test"
153
+
154
+ # With baseline linked
155
+ baseline = Microsimulation(situation=situation)
156
+ reformed.baseline = baseline
157
+
158
+ # Apply dynamics - should return None when disabled
159
+ dynamics = reformed.apply_dynamics(2025)
160
+ assert dynamics is None, "Dynamics should be None when OBR is disabled"
161
+
162
+ def test_zero_income_handling(self):
163
+ """Test that zero income cases don't cause NaN errors"""
164
+ test_case = test_cases[5] # Zero income test case
165
+
166
+ situation = test_case["input"]
167
+ reforms = test_case["reforms"]
168
+
169
+ scenario = Scenario(parameter_changes=reforms)
170
+ baseline = Microsimulation(situation=situation)
171
+ reformed = Microsimulation(situation=situation, scenario=scenario)
172
+ reformed.baseline = baseline
173
+
174
+ # This should not crash even with zero income
175
+ try:
176
+ dynamics = reformed.apply_dynamics(2025)
177
+ # Test passes if no NaN-related exceptions are raised
178
+ except ValueError as e:
179
+ if "NaN" in str(e) or "inf" in str(e):
180
+ pytest.fail(f"NaN/inf error in zero income handling: {e}")
181
+ else:
182
+ # Other ValueError might be expected
183
+ pass
184
+
185
+ @pytest.mark.parametrize("test_case", test_cases)
186
+ def test_all_yaml_cases_structure(self, test_case):
187
+ """Test that all YAML test cases have valid structure and can create simulations"""
188
+ situation = test_case["input"]
189
+ reforms = test_case["reforms"]
190
+
191
+ # Should be able to create simulations without errors
192
+ baseline = Microsimulation(situation=situation)
193
+
194
+ if reforms:
195
+ scenario = Scenario(parameter_changes=reforms)
196
+ reformed = Microsimulation(situation=situation, scenario=scenario)
197
+ else:
198
+ reformed = Microsimulation(situation=situation)
199
+
200
+ # Basic validation - should have people
201
+ assert (
202
+ len(situation["people"]) > 0
203
+ ), f"Test case '{test_case['name']}' should have people"
204
+
205
+ # Should be able to calculate basic variables
206
+ employment_income = reformed.calculate(
207
+ "employment_income", test_case["period"]
208
+ )
209
+ assert (
210
+ employment_income is not None
211
+ ), f"Should be able to calculate employment_income for '{test_case['name']}'"
212
+
213
+
214
+ if __name__ == "__main__":
215
+ pytest.main([__file__])
@@ -0,0 +1,131 @@
1
+ """
2
+ Tests for UK fiscal year parameter handling.
3
+
4
+ The UK fiscal year runs April 6 to April 5. PolicyEngine UK converts
5
+ parameters to use fiscal year values by sampling at April 30 of each year.
6
+
7
+ This test suite verifies that annual period queries (e.g., param("2026"))
8
+ return the correct fiscal year values, especially for policy changes
9
+ that take effect on April 6.
10
+
11
+ Key policy example:
12
+ - Two-child limit repeal: limit=2 until April 5, 2026, then infinity
13
+ from April 6, 2026
14
+ """
15
+
16
+ import pytest
17
+ from policyengine_uk import CountryTaxBenefitSystem
18
+
19
+
20
+ @pytest.fixture(scope="module")
21
+ def uk_system():
22
+ """Create a UK tax-benefit system for testing."""
23
+ return CountryTaxBenefitSystem()
24
+
25
+
26
+ class TestTwoChildLimitRepeal:
27
+ """Tests for the two-child limit repeal on April 6, 2026."""
28
+
29
+ def test_two_child_limit_fiscal_year_2025(self, uk_system):
30
+ """Test that the two-child limit is 2 for fiscal year 2025/26."""
31
+ params = uk_system.get_parameters_at_instant("2025")
32
+ child_limit = (
33
+ params.gov.dwp.universal_credit.elements.child.limit.child_count
34
+ )
35
+ assert child_limit == 2
36
+
37
+ def test_two_child_limit_fiscal_year_2026(self, uk_system):
38
+ """Test that the two-child limit is infinity for fiscal year 2026/27.
39
+
40
+ This is the key test case - the repeal happens on April 6, 2026.
41
+ When querying for "2026", the system should use April 30, 2026
42
+ as the reference date, which is AFTER the repeal date.
43
+ """
44
+ params = uk_system.get_parameters_at_instant("2026")
45
+ child_limit = (
46
+ params.gov.dwp.universal_credit.elements.child.limit.child_count
47
+ )
48
+ assert child_limit == float("inf"), (
49
+ f"Expected infinity for 2026 fiscal year, got {child_limit}. "
50
+ "The fiscal year conversion may not be covering 2026."
51
+ )
52
+
53
+ def test_annual_query_reflects_fiscal_year(self, uk_system):
54
+ """
55
+ Test that annual queries return fiscal year values.
56
+
57
+ For year 2026, the fiscal year is April 6, 2026 to April 5, 2027.
58
+ The two-child limit is repealed on April 6, 2026, so:
59
+ - 2025 should be 2 (fiscal year 2025/26)
60
+ - 2026 should be infinity (fiscal year 2026/27)
61
+ - 2027 should be infinity (fiscal year 2027/28)
62
+ """
63
+ params_2025 = uk_system.get_parameters_at_instant("2025")
64
+ params_2026 = uk_system.get_parameters_at_instant("2026")
65
+ params_2027 = uk_system.get_parameters_at_instant("2027")
66
+
67
+ limit_2025 = (
68
+ params_2025.gov.dwp.universal_credit.elements.child.limit.child_count
69
+ )
70
+ limit_2026 = (
71
+ params_2026.gov.dwp.universal_credit.elements.child.limit.child_count
72
+ )
73
+ limit_2027 = (
74
+ params_2027.gov.dwp.universal_credit.elements.child.limit.child_count
75
+ )
76
+
77
+ assert limit_2025 == 2
78
+ assert limit_2026 == float("inf")
79
+ assert limit_2027 == float("inf")
80
+
81
+
82
+ class TestFiscalYearBoundary:
83
+ """Tests for fiscal year boundary behavior.
84
+
85
+ Note: After fiscal year conversion, the entire year is set to the
86
+ April 30 value. This means January-March dates return the fiscal
87
+ year value for that calendar year, not the previous fiscal year.
88
+ """
89
+
90
+ def test_january_2026_returns_fiscal_year_2026_value(self, uk_system):
91
+ """January 2026 should return fiscal year 2026/27 value.
92
+
93
+ After fiscal year conversion, the year 2026 is set to the
94
+ April 30, 2026 value (infinity, post-repeal).
95
+ """
96
+ params = uk_system.get_parameters_at_instant("2026-01-15")
97
+ child_limit = (
98
+ params.gov.dwp.universal_credit.elements.child.limit.child_count
99
+ )
100
+ # This returns inf because the whole year 2026 is set to fiscal year value
101
+ assert child_limit == float("inf")
102
+
103
+ def test_december_2025_returns_fiscal_year_2025_value(self, uk_system):
104
+ """December 2025 should return fiscal year 2025/26 value."""
105
+ params = uk_system.get_parameters_at_instant("2025-12-15")
106
+ child_limit = (
107
+ params.gov.dwp.universal_credit.elements.child.limit.child_count
108
+ )
109
+ # The year 2025 is set to April 30, 2025 value (2, pre-repeal)
110
+ assert child_limit == 2
111
+
112
+
113
+ class TestFiscalYearCoverage:
114
+ """Tests to verify fiscal year conversion covers all needed years."""
115
+
116
+ @pytest.mark.parametrize(
117
+ "year", [2025, 2026, 2027, 2028, 2029, 2030, 2040]
118
+ )
119
+ def test_year_in_conversion_range(self, uk_system, year):
120
+ """
121
+ Test that years from 2025-2040 can be queried.
122
+
123
+ This verifies that the fiscal year conversion range covers
124
+ all years needed for long-term projections.
125
+ """
126
+ params = uk_system.get_parameters_at_instant(str(year))
127
+ assert params is not None
128
+ # Verify we can access a parameter
129
+ pa = params.gov.hmrc.income_tax.allowances.personal_allowance.amount
130
+ assert pa is not None
131
+ assert pa > 0
@@ -0,0 +1 @@
1
+ from .compare import compare_simulations
@@ -0,0 +1,28 @@
1
+ import pandas as pd
2
+ from typing import TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from policyengine_uk.simulation import Microsimulation
6
+
7
+
8
+ def compare_simulations(
9
+ simulations: list["Microsimulation"],
10
+ names: list[str],
11
+ year: int,
12
+ variables: list[str],
13
+ ):
14
+ dfs = [
15
+ sim.calculate_dataframe(variables, year).rename(
16
+ columns=lambda x: f"{x}_{name}"
17
+ )
18
+ for sim, name in zip(simulations, names)
19
+ ]
20
+
21
+ df = pd.concat(dfs, axis=1).reset_index().rename(columns={"index": "id"})
22
+
23
+ # Sort columns by variable
24
+ columns = []
25
+ for var in variables:
26
+ columns.extend([col for col in df.columns if col.startswith(var)])
27
+
28
+ return df[columns]
@@ -0,0 +1,169 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+ from policyengine_uk.system import system
5
+
6
+ # Source: ONS series ref 2863
7
+ historical_modified_cpi = pd.DataFrame(
8
+ {
9
+ "period": ["2022-01-01", "2023-01-01", "2024-01-01"],
10
+ "modified_cpi_index": [126.05, 133.22, 135.68],
11
+ "modified_cpi_yoy": [0.107, 0.057, 0.018],
12
+ }
13
+ )
14
+
15
+ # Source: ONS Consumer price inflation, updating weights: Annex A, Tables W1 to W3
16
+ initial_weights = {
17
+ "w_water": 9.7399 / 1000,
18
+ "w_rent": 81.4551 / 1000,
19
+ "w_repair": 2.9902 / 1000,
20
+ }
21
+
22
+ cpi = (
23
+ system.parameters.gov.economic_assumptions.indices.obr.consumer_price_index
24
+ )
25
+ water_index = (
26
+ system.parameters.gov.economic_assumptions.indices.ofwat.water_bills
27
+ )
28
+ rent_index = system.parameters.gov.economic_assumptions.indices.obr.rent
29
+
30
+
31
+ def get_parameter_values(param, start_year, end_year):
32
+ values = {}
33
+ for year in range(start_year, end_year + 1):
34
+ instant = f"{year}-01-01"
35
+ value = param(instant)
36
+ if value is not None:
37
+ values[year] = value
38
+ return values
39
+
40
+
41
+ def create_modified_cpi_forecast(
42
+ cpi_parameter,
43
+ water_index_parameter,
44
+ rent_index_parameter,
45
+ historical_modified_cpi,
46
+ initial_weights,
47
+ forecast_start_year=2025,
48
+ forecast_end_year=2029,
49
+ ):
50
+ cpi_values = get_parameter_values(
51
+ cpi_parameter, forecast_start_year, forecast_end_year
52
+ )
53
+ water_values = get_parameter_values(
54
+ water_index_parameter, forecast_start_year, forecast_end_year
55
+ )
56
+ rent_values = get_parameter_values(
57
+ rent_index_parameter, forecast_start_year, forecast_end_year
58
+ )
59
+
60
+ last_historical_year = forecast_start_year - 1
61
+ last_cpi = cpi_parameter(f"{last_historical_year}-01-01")
62
+ last_water = water_index_parameter(f"{last_historical_year}-01-01")
63
+ last_rent = rent_index_parameter(f"{last_historical_year}-01-01")
64
+
65
+ results = []
66
+ for _, row in historical_modified_cpi.iterrows():
67
+ results.append(
68
+ {
69
+ "period": row["period"],
70
+ "modified_cpi_index": row["modified_cpi_index"],
71
+ "modified_cpi_yoy": row["modified_cpi_yoy"],
72
+ "data_type": "historical",
73
+ }
74
+ )
75
+
76
+ last_historical = historical_modified_cpi.iloc[-1]
77
+ last_modified_index = last_historical["modified_cpi_index"]
78
+
79
+ current_weights = initial_weights.copy()
80
+ current_weights["w_other"] = 1 - (
81
+ current_weights["w_water"]
82
+ + current_weights["w_repair"]
83
+ + current_weights["w_rent"]
84
+ )
85
+
86
+ forecast_years = sorted(cpi_values.keys())
87
+ prev_cpi = last_cpi
88
+ prev_water_index = last_water
89
+ prev_rent_index = last_rent
90
+
91
+ for year in forecast_years:
92
+ current_cpi = cpi_values[year]
93
+ current_water_index = water_values.get(year)
94
+ current_rent_index = rent_values.get(year)
95
+
96
+ pi_total = (current_cpi / prev_cpi) - 1
97
+ pi_repair = pi_total # Use CPI for maintenance and repair
98
+ pi_water = (current_water_index / prev_water_index) - 1
99
+ pi_rent = (current_rent_index / prev_rent_index) - 1
100
+
101
+ housing_contribution = (
102
+ current_weights["w_water"] * pi_water
103
+ + current_weights["w_repair"] * pi_repair
104
+ + current_weights["w_rent"] * pi_rent
105
+ )
106
+
107
+ pi_other = (pi_total - housing_contribution) / current_weights[
108
+ "w_other"
109
+ ]
110
+
111
+ new_modified_index = last_modified_index * (1 + pi_other)
112
+
113
+ if len(results) >= 1:
114
+ prev_year_value = results[-1]["modified_cpi_index"]
115
+ modified_cpi_yoy = (new_modified_index / prev_year_value) - 1
116
+ else:
117
+ modified_cpi_yoy = np.nan
118
+
119
+ results.append(
120
+ {
121
+ "period": f"{year}-01-01",
122
+ "year": int(year),
123
+ "modified_cpi_index": new_modified_index,
124
+ "modified_cpi_yoy": modified_cpi_yoy,
125
+ "original_cpi_yoy": pi_total,
126
+ "data_type": "forecast",
127
+ "pi_other": pi_other,
128
+ "pi_total": pi_total,
129
+ "pi_rent": pi_rent,
130
+ "pi_water": pi_water,
131
+ "pi_repair": pi_repair,
132
+ }
133
+ )
134
+
135
+ total_weighted_growth = (
136
+ current_weights["w_water"] * (1 + pi_water)
137
+ + current_weights["w_rent"] * (1 + pi_rent)
138
+ + current_weights["w_repair"] * (1 + pi_repair)
139
+ + current_weights["w_other"] * (1 + pi_other)
140
+ )
141
+
142
+ current_weights["w_water"] = (
143
+ current_weights["w_water"] * (1 + pi_water) / total_weighted_growth
144
+ )
145
+ current_weights["w_rent"] = (
146
+ current_weights["w_rent"] * (1 + pi_rent) / total_weighted_growth
147
+ )
148
+ current_weights["w_repair"] = (
149
+ current_weights["w_repair"]
150
+ * (1 + pi_repair)
151
+ / total_weighted_growth
152
+ )
153
+ current_weights["w_other"] = (
154
+ current_weights["w_other"] * (1 + pi_other) / total_weighted_growth
155
+ )
156
+
157
+ last_modified_index = new_modified_index
158
+ prev_cpi = current_cpi
159
+ prev_water_index = current_water_index
160
+ prev_rent_index = current_rent_index
161
+
162
+ return pd.DataFrame(results)
163
+
164
+
165
+ if __name__ == "__main__":
166
+ modified_cpi_forecast = create_modified_cpi_forecast(
167
+ cpi, water_index, rent_index, historical_modified_cpi, initial_weights
168
+ )
169
+ print(modified_cpi_forecast.to_markdown(index=False))
@@ -20,5 +20,5 @@ for year in range(START_YEAR, 2029):
20
20
  triple_lock_increase = max(earnings_increase, cpi_increase, 1.025)
21
21
  lock_value *= triple_lock_increase
22
22
  print(
23
- f" {year}-01-01: {lock_value:.3f} # Earnings increase FY{year - 1}/{year - 2} = {earnings_increase-1:.1%}, CPI increase FY{year - 1}/{year - 2} = {cpi_increase-1:.1%}"
23
+ f" {year}-01-01: {lock_value:.3f} # Earnings increase FY{year - 1}/{year - 2} = {earnings_increase - 1:.1%}, CPI increase FY{year - 1}/{year - 2} = {cpi_increase - 1:.1%}"
24
24
  )