Skip to content

Commit b8d1104

Browse files
authored
Preserve zero-valued SLC targets (#336)
1 parent e207496 commit b8d1104

3 files changed

Lines changed: 51 additions & 2 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Preserve zero-valued SLC borrower targets so calibration can enforce explicit zero years.

policyengine_uk_data/targets/sources/slc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
},
5252
"plan_5": {
5353
"above_threshold": {
54+
2025: 0,
5455
2026: 35_000,
5556
2027: 145_000,
5657
2028: 390_000,
@@ -145,7 +146,7 @@ def parse_values(row, start_index, years):
145146
if cell_idx >= len(row):
146147
continue
147148
value_text = row[cell_idx].get("text", "")
148-
if value_text and value_text not in ("no data", "0"):
149+
if value_text and value_text != "no data":
149150
data[year] = int(value_text.replace(",", ""))
150151
return data
151152

policyengine_uk_data/tests/test_student_loan_targets.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_slc_snapshot_values_match_higher_education_total_rows():
2828
assert targets["slc/plan_2_borrowers_liable"].values[2025] == 8_940_000
2929
assert targets["slc/plan_2_borrowers_liable"].values[2030] == 10_525_000
3030

31-
assert 2025 not in targets["slc/plan_5_borrowers_above_threshold"].values
31+
assert targets["slc/plan_5_borrowers_above_threshold"].values[2025] == 0
3232
assert targets["slc/plan_5_borrowers_above_threshold"].values[2026] == 35_000
3333
assert targets["slc/plan_5_borrowers_above_threshold"].values[2030] == 1_235_000
3434
assert targets["slc/plan_5_borrowers_liable"].values[2025] == 10_000
@@ -120,6 +120,53 @@ def raise_for_status():
120120
slc._fetch_slc_data.cache_clear()
121121

122122

123+
def test_slc_parser_preserves_zero_value_years(monkeypatch):
124+
"""A literal zero should remain a real target year, not be dropped."""
125+
from policyengine_uk_data.targets.sources import slc
126+
127+
table_json = {
128+
"thead": [
129+
[],
130+
[{"text": "2024-25"}] * 6 + [{"text": "2024-25"}] * 6,
131+
],
132+
"tbody": [
133+
[{"text": "Higher education total"}, {"text": "liable"}]
134+
+ [{"text": "8,940,000"}] * 6
135+
+ [{"text": "10,000"}] * 6,
136+
[
137+
{
138+
"text": "Number of borrowers liable to repay and earning above repayment threshold"
139+
}
140+
]
141+
+ [{"text": "3,985,000"}] * 6
142+
+ [{"text": "0"}] * 6,
143+
],
144+
}
145+
html = (
146+
'<script id="__NEXT_DATA__" type="application/json">'
147+
+ json.dumps(
148+
{"props": {"pageProps": {"data": {"table": {"json": table_json}}}}}
149+
)
150+
+ "</script>"
151+
)
152+
153+
class DummyResponse:
154+
text = html
155+
156+
@staticmethod
157+
def raise_for_status():
158+
return None
159+
160+
slc._fetch_slc_data.cache_clear()
161+
monkeypatch.delenv("TESTING", raising=False)
162+
monkeypatch.setattr(slc.requests, "get", lambda *args, **kwargs: DummyResponse())
163+
164+
data = slc._fetch_slc_data()
165+
assert data["plan_5"]["above_threshold"][2025] == 0
166+
167+
slc._fetch_slc_data.cache_clear()
168+
169+
123170
def test_student_loan_target_compute_distinguishes_liable_from_repaying():
124171
"""Above-threshold counts should require repayments, while liable counts should not."""
125172
from policyengine_uk_data.targets.compute.other import (

0 commit comments

Comments
 (0)