policyengine-uk 2.41.0__py3-none-any.whl → 2.41.2__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.
@@ -16,7 +16,7 @@ reforms:
16
16
  parameters:
17
17
  gov.hmrc.child_benefit.amount.additional: 25
18
18
  - name: Reduce Universal Credit taper rate to 20%
19
- expected_impact: -36.6
19
+ expected_impact: -36.7
20
20
  parameters:
21
21
  gov.dwp.universal_credit.means_test.reduction_rate: 0.2
22
22
  - name: Raise Class 1 main employee NICs rate to 10%
@@ -24,7 +24,7 @@ reforms:
24
24
  parameters:
25
25
  gov.hmrc.national_insurance.class_1.rates.employee.main: 0.1
26
26
  - name: Raise VAT standard rate by 2pp
27
- expected_impact: 18.8
27
+ expected_impact: 18.9
28
28
  parameters:
29
29
  gov.hmrc.vat.standard_rate: 0.22
30
30
  - name: Raise additional rate by 3pp
@@ -0,0 +1,263 @@
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 = 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
+
198
+ # PolicyEngine style (roboto mono for numbers, roboto serif for text), dark grey for negative, blue for positive, spacing, etc.
199
+
200
+ # Set layout properties for a clean, professional look
201
+ fig.update_layout(
202
+ plot_bgcolor="white",
203
+ paper_bgcolor="white",
204
+ font_family="Roboto Serif",
205
+ font_size=12,
206
+ margin=dict(l=60, r=60, t=80, b=60),
207
+ title_font_size=16,
208
+ title_font_family="Roboto Serif",
209
+ title_x=0.5,
210
+ title_xanchor="center",
211
+ )
212
+
213
+ # Update axes
214
+ fig.update_xaxes(
215
+ title_font_family="Roboto Serif",
216
+ tickfont_family="Roboto Mono",
217
+ showline=True,
218
+ linewidth=1,
219
+ linecolor="lightgray",
220
+ )
221
+
222
+ fig.update_yaxes(
223
+ title_font_family="Roboto Serif",
224
+ tickfont_family="Roboto Mono",
225
+ tickformat=",.0f",
226
+ tickprefix="£",
227
+ gridcolor="lightgray",
228
+ showline=True,
229
+ linewidth=1,
230
+ linecolor="lightgray",
231
+ )
232
+
233
+ # Update waterfall specific styles
234
+ fig.update_traces(
235
+ increasing=dict(marker=dict(color="#2C6496")),
236
+ decreasing=dict(marker=dict(color="#555555")),
237
+ connector=dict(line=dict(color="rgb(63, 63, 63)", width=1)),
238
+ )
239
+
240
+ # Height/width adjustments for better visibility
241
+ fig.update_layout(
242
+ height=600,
243
+ width=800,
244
+ # set font to black
245
+ font_color="black",
246
+ )
247
+
248
+ # Add rounded numbers on bars
249
+ for trace in fig.data:
250
+ if isinstance(trace, go.Waterfall):
251
+ trace.texttemplate = "£%{y:,.0f}"
252
+ trace.textposition = "outside"
253
+ trace.hovertemplate = "%{x}: £%{y:,.0f}<extra></extra>"
254
+ # mono font for numbers
255
+ trace.textfont = dict(family="Roboto Mono", size=12, color="black")
256
+
257
+ # Add padding round the edges
258
+ fig.update_layout(margin=dict(l=100, r=100, t=100, b=100))
259
+
260
+ return fig
261
+
262
+
263
+ add_fonts()
@@ -24,10 +24,10 @@ class BRMA_LHA_rate(Variable):
24
24
  )
25
25
  category = benunit("LHA_category", period).decode_to_str()
26
26
 
27
- from policyengine_uk.parameters.gov.dwp.LHA import lha_list_of_rents
27
+ from policyengine_uk.parameters.gov.dwp.lha import lha_list_of_rents
28
28
 
29
29
  parameters = benunit.simulation.tax_benefit_system.parameters
30
- lha = parameters.gov.dwp.LHA
30
+ lha = parameters.gov.dwp.lha
31
31
 
32
32
  # We first need to know what time period to collect rents from. If LHA is frozen, we need to look earlier
33
33
  # than the current time period.
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.41.2] - 2025-07-24 10:30:43
9
+
10
+ ### Fixed
11
+
12
+ - debug LHA lowercase
13
+
14
+ ## [2.41.1] - 2025-07-22 20:55:35
15
+
16
+ ### Changed
17
+
18
+ - Update microdf_python dependency to >=1.0.0.
19
+
8
20
  ## [2.41.0] - 2025-07-22 19:49:35
9
21
 
10
22
  ### Changed
@@ -1992,6 +2004,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1992
2004
 
1993
2005
 
1994
2006
 
2007
+ [2.41.2]: https://github.com/PolicyEngine/openfisca-uk/compare/2.41.1...2.41.2
2008
+ [2.41.1]: https://github.com/PolicyEngine/openfisca-uk/compare/2.41.0...2.41.1
1995
2009
  [2.41.0]: https://github.com/PolicyEngine/openfisca-uk/compare/2.40.2...2.41.0
1996
2010
  [2.40.2]: https://github.com/PolicyEngine/openfisca-uk/compare/2.40.1...2.40.2
1997
2011
  [2.40.1]: https://github.com/PolicyEngine/openfisca-uk/compare/2.40.0...2.40.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine-uk
3
- Version: 2.41.0
3
+ Version: 2.41.2
4
4
  Summary: PolicyEngine tax and benefit system for the UK
5
5
  Project-URL: Homepage, https://github.com/PolicyEngine/policyengine-uk
6
6
  Project-URL: Repository, https://github.com/PolicyEngine/policyengine-uk
@@ -528,7 +528,7 @@ policyengine_uk/reforms/policyengine/disable_simulated_benefits.py,sha256=siEs1E
528
528
  policyengine_uk/tests/test_parameter_metadata.py,sha256=_2w2dSokAf5Jskye_KIL8eh80N7yIrUszlmqnZtwQws,450
529
529
  policyengine_uk/tests/code_health/test_variables.py,sha256=9Y-KpmzhyRGy9eEqocK9z91NXHX5QIF3mDMNGvegb7Q,1398
530
530
  policyengine_uk/tests/microsimulation/README.md,sha256=1toB1Z06ynlUielTrsAaeo9Vb-c3ZrB3tbbR4E1xUGk,3924
531
- policyengine_uk/tests/microsimulation/reforms_config.yaml,sha256=xM7x1KxctM0equ4z4kd32bfcMgQC87pNFhF6n0hlYkQ,1086
531
+ policyengine_uk/tests/microsimulation/reforms_config.yaml,sha256=azRPWlthHO8JEHTUq7qcReKqb4cgm2oFwi-eK6TFImY,1086
532
532
  policyengine_uk/tests/microsimulation/test_reform_impacts.py,sha256=xM3M2pclEhA9JIFpnuiPMy1fEBFOKcSzroRPk73FPls,2635
533
533
  policyengine_uk/tests/microsimulation/test_validity.py,sha256=RWhbSKrnrZCNQRVmGYlM8hnpe1_Blo5_xP8vr8u3kV0,694
534
534
  policyengine_uk/tests/microsimulation/update_reform_impacts.py,sha256=2pxp2RNLWxV4CesGKKHmg3qBs79Jq2Jcq3GJIBk4euU,4824
@@ -685,6 +685,7 @@ policyengine_uk/tests/policy/reforms/parametric/two_child_limit/ctc_age_exemptio
685
685
  policyengine_uk/tests/policy/reforms/parametric/winter_fuel_allowance/taxable_income_test.yaml,sha256=QY3LYJ92VzYH9mXU54XD_6PhefwHNK80kGYQ0R5I0SM,6289
686
686
  policyengine_uk/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
687
687
  policyengine_uk/utils/create_triple_lock.py,sha256=E8qR51cq5jPL6EOXFoPi1qJhrcUBXg3dfWTvdqWN2Bo,948
688
+ policyengine_uk/utils/dependencies.py,sha256=6mLDZr-lypI6RZMeb0rcWoJy5Ig7QT18kOuAMUgI7vg,8198
688
689
  policyengine_uk/utils/parameters.py,sha256=OQTzTkHMdwbphCo0mV7_n_FJT0rdwIKNFTsk_lsdETE,1301
689
690
  policyengine_uk/utils/solve_private_school_attendance_factor.py,sha256=LUZCgHKAQTY5qHlGJutn7pOFUWT0AP16YcJy-YUjQ3Q,1609
690
691
  policyengine_uk/utils/water/README.md,sha256=sdBI-JZ-jcRoSUfwNx5wjv5Ig_nM8OPvvjSsSMs_Wh8,443
@@ -760,7 +761,7 @@ policyengine_uk/variables/gov/dhsc/admitted_patient/admitted_patient_visits.py,s
760
761
  policyengine_uk/variables/gov/dhsc/admitted_patient/nhs_admitted_patient_spending.py,sha256=imlt4k8AdKwH8EiyIild3E6NdUaI8TJ5U57qbHwDyIQ,338
761
762
  policyengine_uk/variables/gov/dhsc/outpatient/nhs_outpatient_spending.py,sha256=q1FL7JPTAGIb0uJbNcCp9nJTPPpta8LJ5wuzcAK09wg,320
762
763
  policyengine_uk/variables/gov/dhsc/outpatient/outpatient_visits.py,sha256=KdTZwq8JumDHoxq9oK-VAibexxEjXQ6KxfLC6BPxmqM,262
763
- policyengine_uk/variables/gov/dwp/BRMA_LHA_rate.py,sha256=korWE8r3J_tiYyGc7nbWarqO_jy8W1L22zMLnXhl09U,2069
764
+ policyengine_uk/variables/gov/dwp/BRMA_LHA_rate.py,sha256=p_JATS7ygCtceJ0qFwC93JWmSUWETEEXnYFWdRPdmmk,2069
764
765
  policyengine_uk/variables/gov/dwp/CTC_child_element.py,sha256=6m9Mpql9FDSWQndgB0IPS2TVbXF8aLxdrMQ7Dr6cLrY,1000
765
766
  policyengine_uk/variables/gov/dwp/CTC_disabled_child_element.py,sha256=st4fgA-0fwLxhFxd3xgkxDCvUSfSfNMO8ihCh-5W31c,825
766
767
  policyengine_uk/variables/gov/dwp/CTC_family_element.py,sha256=p0hpeOSXftML95tfKGLWRUgYEuCfqDqt705tuO_kab8,462
@@ -1367,10 +1368,10 @@ policyengine_uk/variables/misc/spi_imputed.py,sha256=iPVlBF_TisM0rtKvO-3-PQ2UYCe
1367
1368
  policyengine_uk/variables/misc/uc_migrated.py,sha256=zFNcUJaO8gwmbL1iY9GKgUt3G6J9yrCraqBV_5dCvlM,306
1368
1369
  policyengine_uk/variables/misc/categories/lower_middle_or_higher.py,sha256=C54tHYz2DmOyvQYCC1bF8RJwRZinhAq_e3aYC-9F5fM,157
1369
1370
  policyengine_uk/variables/misc/categories/lower_or_higher.py,sha256=81NIbLLabRr9NwjpUZDuV8IV8_mqmp5NM-CZvt55TwE,129
1370
- policyengine_uk-2.41.0.data/data/share/openfisca/openfisca-country-template/CHANGELOG.md,sha256=tLSGM5YR9A2xEsrX_eRD0aYCYVBdw9sMs2KkwhQONSo,57566
1371
- policyengine_uk-2.41.0.data/data/share/openfisca/openfisca-country-template/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
1372
- policyengine_uk-2.41.0.data/data/share/openfisca/openfisca-country-template/README.md,sha256=PCy7LRLdUDQS8U4PaeHeBVnyBZAqHv1dAVDDvEcom20,1976
1373
- policyengine_uk-2.41.0.dist-info/METADATA,sha256=E3QHz9dDVOs9KWSOdWR_3DLZIUI0x-3F0x1fjw_kdKg,3502
1374
- policyengine_uk-2.41.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1375
- policyengine_uk-2.41.0.dist-info/licenses/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
1376
- policyengine_uk-2.41.0.dist-info/RECORD,,
1371
+ policyengine_uk-2.41.2.data/data/share/openfisca/openfisca-country-template/CHANGELOG.md,sha256=gJepvHXWbQC1OC578qzj9RhZf9wpXtJd0tvCv_EIA20,57889
1372
+ policyengine_uk-2.41.2.data/data/share/openfisca/openfisca-country-template/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
1373
+ policyengine_uk-2.41.2.data/data/share/openfisca/openfisca-country-template/README.md,sha256=PCy7LRLdUDQS8U4PaeHeBVnyBZAqHv1dAVDDvEcom20,1976
1374
+ policyengine_uk-2.41.2.dist-info/METADATA,sha256=LqKRdlEza3q5VBPwbhfvVKl0pPrPZY0qD0dc1Zw02rY,3502
1375
+ policyengine_uk-2.41.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1376
+ policyengine_uk-2.41.2.dist-info/licenses/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
1377
+ policyengine_uk-2.41.2.dist-info/RECORD,,