Skip to content

Commit 54ee554

Browse files
authored
Raise clear error when uprating year is outside table range (#352)
* Raise clear error when uprating year is outside table range * Apply ruff format
1 parent a87047d commit 54ee554

3 files changed

Lines changed: 119 additions & 2 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Raise `UpratingYearOutOfRangeError` with a clear message when `uprate_values` or `uprate_dataset` is called with a year outside the `[START_YEAR, END_YEAR]` range of the uprating factor table, instead of surfacing a pandas `KeyError` or silently returning wrong values.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Tests for year-range validation in `policyengine_uk_data.utils.uprating`.
2+
3+
The uprating factor table covers ``[START_YEAR, END_YEAR]`` (2020–2034 on
4+
main). Callers that request a year outside this range previously got a
5+
raw pandas ``KeyError`` or a silently wrong value. After the fix in
6+
finding U5 they must get ``UpratingYearOutOfRangeError`` with a clear
7+
message.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import importlib.util
13+
14+
import pandas as pd
15+
import pytest
16+
17+
if importlib.util.find_spec("policyengine_uk") is None:
18+
pytest.skip(
19+
"policyengine_uk not available in test environment",
20+
allow_module_level=True,
21+
)
22+
23+
24+
def _seed_uprating_table(tmp_path, start=2020, end=2034):
25+
"""Write a minimal uprating_factors.csv covering a known year range."""
26+
27+
storage = tmp_path / "storage"
28+
storage.mkdir()
29+
years = list(range(start, end + 1))
30+
table = pd.DataFrame(
31+
{str(y): [1.0 + 0.02 * (y - start)] for y in years},
32+
index=pd.Index(["employment_income"], name="Variable"),
33+
)
34+
table.to_csv(storage / "uprating_factors.csv")
35+
return storage
36+
37+
38+
def test_uprate_dataset_rejects_year_above_end(tmp_path, monkeypatch):
39+
from policyengine_uk_data.utils import uprating as uprating_module
40+
from policyengine_uk_data.utils.uprating import (
41+
UpratingYearOutOfRangeError,
42+
uprate_dataset,
43+
)
44+
45+
storage = _seed_uprating_table(tmp_path)
46+
monkeypatch.setattr(uprating_module, "STORAGE_FOLDER", storage)
47+
48+
# target_year=2035 is above END_YEAR=2034 → must raise with clear message.
49+
class _Dataset:
50+
time_period = 2023
51+
tables = []
52+
53+
def copy(self):
54+
return self
55+
56+
with pytest.raises(UpratingYearOutOfRangeError, match="target_year=2035"):
57+
uprate_dataset(_Dataset(), target_year=2035)
58+
59+
60+
def test_uprate_values_rejects_years_outside_range(tmp_path, monkeypatch):
61+
from policyengine_uk_data.utils import uprating as uprating_module
62+
from policyengine_uk_data.utils.uprating import (
63+
UpratingYearOutOfRangeError,
64+
uprate_values,
65+
)
66+
67+
storage = _seed_uprating_table(tmp_path)
68+
monkeypatch.setattr(uprating_module, "STORAGE_FOLDER", storage)
69+
70+
with pytest.raises(UpratingYearOutOfRangeError, match="end_year=2099"):
71+
uprate_values(100.0, "employment_income", start_year=2020, end_year=2099)
72+
73+
with pytest.raises(UpratingYearOutOfRangeError, match="start_year=1999"):
74+
uprate_values(100.0, "employment_income", start_year=1999, end_year=2034)
75+
76+
77+
def test_uprate_values_accepts_supported_range(tmp_path, monkeypatch):
78+
from policyengine_uk_data.utils import uprating as uprating_module
79+
from policyengine_uk_data.utils.uprating import uprate_values
80+
81+
storage = _seed_uprating_table(tmp_path)
82+
monkeypatch.setattr(uprating_module, "STORAGE_FOLDER", storage)
83+
84+
# A supported year range still works and returns a sensible factor.
85+
result = uprate_values(100.0, "employment_income", start_year=2020, end_year=2034)
86+
# Seed table has linear growth 0.02 per year; 2020→2034 = 14 years ×
87+
# 0.02 = 1.28 multiplier off a 1.0 base.
88+
assert result == pytest.approx(128.0)

policyengine_uk_data/utils/uprating.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,28 @@
66
END_YEAR = 2034
77

88

9+
class UpratingYearOutOfRangeError(ValueError):
10+
"""Raised when a caller asks for an uprating factor outside the table range.
11+
12+
The uprating factor table is written by `create_policyengine_uprating_factors_table()`
13+
for years in ``[START_YEAR, END_YEAR]`` and does not include columns for years
14+
beyond ``END_YEAR``. Silently returning a KeyError (or a stale last-year factor)
15+
would produce wrong values; raising a specific, actionable error instead tells
16+
the caller to either regenerate the table with a later ``END_YEAR`` or pick a
17+
supported target year.
18+
"""
19+
20+
21+
def _check_year_in_range(year: int, *, kind: str) -> None:
22+
if year < START_YEAR or year > END_YEAR:
23+
raise UpratingYearOutOfRangeError(
24+
f"{kind}={year} is outside the uprating table range "
25+
f"[{START_YEAR}, {END_YEAR}]. Regenerate the table via "
26+
f"`create_policyengine_uprating_factors_table()` with a later "
27+
f"END_YEAR, or pick a supported year."
28+
)
29+
30+
931
def create_policyengine_uprating_factors_table():
1032
from policyengine_uk.system import system
1133

@@ -45,7 +67,10 @@ def create_policyengine_uprating_factors_table():
4567
return df
4668

4769

48-
def uprate_values(values, variable_name, start_year=2020, end_year=2034):
70+
def uprate_values(values, variable_name, start_year=START_YEAR, end_year=END_YEAR):
71+
_check_year_in_range(start_year, kind="start_year")
72+
_check_year_in_range(end_year, kind="end_year")
73+
4974
uprating_factors = pd.read_csv(STORAGE_FOLDER / "uprating_factors.csv")
5075
uprating_factors = uprating_factors.set_index("Variable")
5176
uprating_factors = uprating_factors.loc[variable_name]
@@ -57,11 +82,14 @@ def uprate_values(values, variable_name, start_year=2020, end_year=2034):
5782
return values * relative_change
5883

5984

60-
def uprate_dataset(dataset: UKSingleYearDataset, target_year=2034):
85+
def uprate_dataset(dataset: UKSingleYearDataset, target_year: int = END_YEAR):
86+
_check_year_in_range(target_year, kind="target_year")
87+
6188
dataset = dataset.copy()
6289
uprating_factors = pd.read_csv(STORAGE_FOLDER / "uprating_factors.csv")
6390
uprating_factors = uprating_factors.set_index("Variable")
6491
start_year = dataset.time_period
92+
_check_year_in_range(int(start_year), kind="dataset.time_period")
6593

6694
for table in dataset.tables:
6795
for variable in table.columns:

0 commit comments

Comments
 (0)