policyengine-uk 2.41.1__py3-none-any.whl → 2.41.3__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()
@@ -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.3] - 2025-07-24 13:53:51
9
+
10
+ ### Fixed
11
+
12
+ - change LHA param name
13
+
14
+ ## [2.41.2] - 2025-07-24 10:30:43
15
+
16
+ ### Fixed
17
+
18
+ - debug LHA lowercase
19
+
8
20
  ## [2.41.1] - 2025-07-22 20:55:35
9
21
 
10
22
  ### Changed
@@ -1998,6 +2010,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1998
2010
 
1999
2011
 
2000
2012
 
2013
+ [2.41.3]: https://github.com/PolicyEngine/openfisca-uk/compare/2.41.2...2.41.3
2014
+ [2.41.2]: https://github.com/PolicyEngine/openfisca-uk/compare/2.41.1...2.41.2
2001
2015
  [2.41.1]: https://github.com/PolicyEngine/openfisca-uk/compare/2.41.0...2.41.1
2002
2016
  [2.41.0]: https://github.com/PolicyEngine/openfisca-uk/compare/2.40.2...2.41.0
2003
2017
  [2.40.2]: https://github.com/PolicyEngine/openfisca-uk/compare/2.40.1...2.40.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine-uk
3
- Version: 2.41.1
3
+ Version: 2.41.3
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
@@ -15,7 +15,7 @@ Classifier: Operating System :: POSIX
15
15
  Classifier: Programming Language :: Python
16
16
  Classifier: Topic :: Scientific/Engineering :: Information Analysis
17
17
  Requires-Python: >=3.10
18
- Requires-Dist: microdf-python>=1.0.0
18
+ Requires-Dist: microdf-python==0.4.4
19
19
  Requires-Dist: policyengine-core>=3.6.4
20
20
  Provides-Extra: dev
21
21
  Requires-Dist: black; extra == 'dev'
@@ -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
@@ -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.1.data/data/share/openfisca/openfisca-country-template/CHANGELOG.md,sha256=Vafga2umO63HhXBWycXl8sI97DnU2c8qfe4D_7c5Ehw,57741
1371
- policyengine_uk-2.41.1.data/data/share/openfisca/openfisca-country-template/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
1372
- policyengine_uk-2.41.1.data/data/share/openfisca/openfisca-country-template/README.md,sha256=PCy7LRLdUDQS8U4PaeHeBVnyBZAqHv1dAVDDvEcom20,1976
1373
- policyengine_uk-2.41.1.dist-info/METADATA,sha256=rDGFAdUNwJKnOYL3BaQzbvfHDW9AYsZdjNxm06iPMtg,3502
1374
- policyengine_uk-2.41.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1375
- policyengine_uk-2.41.1.dist-info/licenses/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
1376
- policyengine_uk-2.41.1.dist-info/RECORD,,
1371
+ policyengine_uk-2.41.3.data/data/share/openfisca/openfisca-country-template/CHANGELOG.md,sha256=WUPxoboZaT8uQ8KNRlu20cdu1sMPjxkZKiON2Ki0chs,58039
1372
+ policyengine_uk-2.41.3.data/data/share/openfisca/openfisca-country-template/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
1373
+ policyengine_uk-2.41.3.data/data/share/openfisca/openfisca-country-template/README.md,sha256=PCy7LRLdUDQS8U4PaeHeBVnyBZAqHv1dAVDDvEcom20,1976
1374
+ policyengine_uk-2.41.3.dist-info/METADATA,sha256=1sHSTWMHikxKfzxzYenbsJe3HVxJv1qfZxd1ZmA4SBk,3502
1375
+ policyengine_uk-2.41.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1376
+ policyengine_uk-2.41.3.dist-info/licenses/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
1377
+ policyengine_uk-2.41.3.dist-info/RECORD,,