policyengine-uk 2.42.0__py3-none-any.whl → 2.43.1__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.
@@ -14,11 +14,17 @@ class UKSingleYearDataset:
14
14
  household: pd.DataFrame
15
15
 
16
16
  @staticmethod
17
- def validate_file_path(file_path: str):
17
+ def validate_file_path(file_path: str, raise_exception: bool = True):
18
18
  if not file_path.endswith(".h5"):
19
- raise ValueError("File path must end with '.h5' for UKDataset.")
19
+ if raise_exception:
20
+ raise ValueError(
21
+ "File path must end with '.h5' for UKDataset."
22
+ )
23
+ return False
20
24
  if not Path(file_path).exists():
21
- raise FileNotFoundError(f"File not found: {file_path}")
25
+ if raise_exception:
26
+ raise FileNotFoundError(f"File not found: {file_path}")
27
+ return False
22
28
 
23
29
  # Check if the file contains time_period, person, benunit, and household datasets
24
30
  with h5py.File(file_path, "r") as f:
@@ -30,9 +36,14 @@ class UKSingleYearDataset:
30
36
  ]
31
37
  for dataset in required_datasets:
32
38
  if dataset not in f:
33
- raise ValueError(
34
- f"Dataset '{dataset}' not found in the file: {file_path}"
35
- )
39
+ if raise_exception:
40
+ raise ValueError(
41
+ f"Dataset '{dataset}' not found in the file: {file_path}"
42
+ )
43
+ else:
44
+ return False
45
+
46
+ return True
36
47
 
37
48
  def __init__(
38
49
  self,
@@ -137,7 +148,7 @@ class UKMultiYearDataset:
137
148
  self.datasets[year] = dataset
138
149
 
139
150
  if file_path is not None:
140
- UKSingleYearDataset.validate_file_path(file_path)
151
+ UKMultiYearDataset.validate_file_path(file_path)
141
152
  with pd.HDFStore(file_path) as f:
142
153
  self.datasets = {}
143
154
  for year in f.keys():
@@ -162,6 +173,10 @@ class UKMultiYearDataset:
162
173
  else:
163
174
  raise ValueError(f"No dataset found for year {fiscal_year}.")
164
175
 
176
+ @property
177
+ def years(self):
178
+ return list(self.datasets.keys())
179
+
165
180
  def __getitem__(self, fiscal_year: int):
166
181
  return self.get_year(fiscal_year)
167
182
 
@@ -203,21 +218,49 @@ class UKMultiYearDataset:
203
218
  return UKMultiYearDataset(datasets=list(new_datasets.values()))
204
219
 
205
220
  @staticmethod
206
- def validate_file_path(file_path: str):
221
+ def validate_file_path(file_path: str, raise_exception: bool = False):
207
222
  if not file_path.endswith(".h5"):
208
- raise ValueError(
209
- "File path must end with '.h5' for UKMultiYearDataset."
210
- )
223
+ if raise_exception:
224
+ raise ValueError(
225
+ "File path must end with '.h5' for UKMultiYearDataset."
226
+ )
227
+ else:
228
+ return False
211
229
  if not Path(file_path).exists():
212
- raise FileNotFoundError(f"File not found: {file_path}")
230
+ if raise_exception:
231
+ raise FileNotFoundError(f"File not found: {file_path}")
232
+ else:
233
+ return False
213
234
 
214
235
  # Check if the file contains datasets for multiple years
215
236
  with h5py.File(file_path, "r") as f:
216
237
  for required_dataset in ["person", "benunit", "household"]:
217
238
  if not any(f"{required_dataset}" in key for key in f.keys()):
218
- raise ValueError(
219
- f"Dataset '{required_dataset}' not found in the file: {file_path}"
220
- )
239
+ if raise_exception:
240
+ raise ValueError(
241
+ f"Dataset '{required_dataset}' not found in the file: {file_path}"
242
+ )
243
+ else:
244
+ return False
245
+
246
+ # Check that there is at least one dataset year in the folder (keys include e.g. person/2025)
247
+
248
+ # Check that there is at least one dataset year in the folder
249
+ years_found = False
250
+ for key in f.keys():
251
+ parts = key.split("/")
252
+ if len(parts) >= 2 and required_dataset == parts[0]:
253
+ years_found = True
254
+ break
255
+
256
+ if not years_found:
257
+ if raise_exception:
258
+ raise ValueError(
259
+ f"No yearly data found for '{required_dataset}' in file: {file_path}"
260
+ )
261
+ else:
262
+ return False
263
+ return True
221
264
 
222
265
  def load(self):
223
266
  data = {}
@@ -228,3 +271,8 @@ class UKMultiYearDataset:
228
271
  data[col] = {}
229
272
  data[col][year] = df[col].values
230
273
  return data
274
+
275
+ def reset_uprating(self):
276
+ from policyengine_uk.data.economic_assumptions import reset_uprating
277
+
278
+ reset_uprating(self)
@@ -1,14 +1,31 @@
1
1
  from policyengine_uk.data import UKMultiYearDataset, UKSingleYearDataset
2
- from policyengine_uk.system import system
2
+
3
3
  import yaml
4
4
  from policyengine_core.parameters import ParameterNode
5
5
  from pathlib import Path
6
6
  import numpy as np
7
7
 
8
8
 
9
+ def extend_single_year_dataset(
10
+ dataset: UKSingleYearDataset,
11
+ end_year: int = 2029,
12
+ ) -> UKMultiYearDataset:
13
+ # Extend years and uprate
14
+ start_year = int(dataset.time_period)
15
+ datasets = [dataset]
16
+ for year in range(start_year, end_year + 1):
17
+ next_year = dataset.copy()
18
+ next_year.time_period = str(year)
19
+ datasets.append(next_year)
20
+ multi_year_dataset = UKMultiYearDataset(datasets=datasets)
21
+ return apply_uprating(multi_year_dataset)
22
+
23
+
9
24
  def apply_uprating(
10
25
  dataset: UKMultiYearDataset,
11
26
  ):
27
+ from policyengine_uk.system import system
28
+
12
29
  # Apply uprating to the dataset.
13
30
  dataset = dataset.copy()
14
31
 
@@ -2,5 +2,6 @@ from policyengine_core.model_api import *
2
2
  from policyengine_uk.entities import *
3
3
  from policyengine_core import periods
4
4
  from microdf import MicroSeries, MicroDataFrame
5
+ from policyengine_uk.utils.scenario import Scenario
5
6
 
6
7
  GBP = "currency-GBP"
@@ -108,12 +108,10 @@ obr:
108
108
  reference:
109
109
  - title: OBR EFO March 2025
110
110
  href: https://obr.uk/efo/economic-and-fiscal-outlook-march-2025/
111
- reference:
112
111
  - title: ONS CPI weights
113
112
  href: https://www.ons.gov.uk/economy/inflationandpriceindices/datasets/consumerpriceinflationupdatingweightsannexatablesw1tow3
114
- reference:
115
113
  - title: ONS CPI series excluding housing costs
116
- - href: https://www.ons.gov.uk/economy/inflationandpriceindices/adhocs/2863consumerpriceindicesseriesexcludingrentsmaintenancerepairsandwaterchargesfortheperiodjanuary1996toapril2025
114
+ href: https://www.ons.gov.uk/economy/inflationandpriceindices/adhocs/2863consumerpriceindicesseriesexcludingrentsmaintenancerepairsandwaterchargesfortheperiodjanuary1996toapril2025
117
115
 
118
116
  non_labour_income:
119
117
  description: Non-labour income year-on-year growth.
@@ -0,0 +1,3 @@
1
+ from .pip_reform import reform_pip_phase_in
2
+ from .reindex_benefit_cap import reindex_benefit_cap
3
+ from .repeal_two_child_limit import repeal_two_child_limit
@@ -0,0 +1,23 @@
1
+ from policyengine_uk.model_api import Scenario
2
+ from policyengine_uk import Simulation
3
+ import numpy as np
4
+
5
+
6
+ def modify_simulation(sim: Simulation):
7
+ np.random.seed(42) # For reproducibility
8
+ pip_seed = np.random.random(len(sim.calculate("person_id")))
9
+
10
+ start_year = 2025
11
+ end_year = 2029
12
+
13
+ for year in range(start_year, end_year + 1):
14
+ current_pip = sim.calculate("pip", year)
15
+ percent_along_phase_in = (year - start_year) / (end_year - start_year)
16
+ current_pip[pip_seed < 0.25 * percent_along_phase_in] = 0
17
+ sim.set_input("pip", year, current_pip)
18
+ return sim
19
+
20
+
21
+ reform_pip_phase_in = Scenario(
22
+ simulation_modifier=modify_simulation,
23
+ )
@@ -0,0 +1,32 @@
1
+ from policyengine_uk.model_api import Scenario
2
+ from policyengine_uk import Simulation
3
+ from policyengine_core.parameters import Parameter
4
+
5
+
6
+ def index_benefit_cap(simulation: Simulation):
7
+ # Identify benefit cap parameters
8
+ simulation.tax_benefit_system.reset_parameters()
9
+
10
+ params = (
11
+ simulation.tax_benefit_system.parameters.gov.dwp.benefit_cap.get_descendants()
12
+ )
13
+ # We just want the leaf nodes of the parameter tree
14
+ # TODO: add a get-descendants-leaf-nodes-only function
15
+
16
+ params = [param for param in params if isinstance(param, Parameter)]
17
+
18
+ for parameter in params:
19
+ parameter: Parameter # Type annotation
20
+ # Delete all values after 2025
21
+ parameter.values_list = [
22
+ entry
23
+ for entry in parameter.values_list
24
+ if entry.instant_str < "2026-01-01"
25
+ ]
26
+ parameter.metadata.update(
27
+ uprating="gov.benefit_uprating_cpi",
28
+ )
29
+ simulation.tax_benefit_system.process_parameters()
30
+
31
+
32
+ reindex_benefit_cap = Scenario(simulation_modifier=index_benefit_cap)
@@ -0,0 +1,10 @@
1
+ from policyengine_uk.model_api import Scenario
2
+ import numpy as np
3
+
4
+ repeal_two_child_limit = Scenario(
5
+ parameter_changes={
6
+ "gov.dwp.universal_credit.elements.child.limit.child_count": {
7
+ "year:2026:10": np.inf
8
+ }
9
+ }
10
+ )