Skip to content

Commit 1b0a4fd

Browse files
MaxGhenisclaude
andcommitted
Add test coverage and enhance documentation for salary sacrifice pensions
- Add comprehensive smoke test for pension_contributions_via_salary_sacrifice - Validates non-negative values - Checks for presence of data - Ensures reasonableness against total employment income - Enhance inline documentation with detailed explanation: - What SPNAMT represents - Relationship to other pension variables - Rationale for not using outlier clipping (follows job table pattern) - Source attribution and data transformation notes Addresses review feedback on PR #214 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ab9b3f0 commit 1b0a4fd

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

policyengine_uk_data/datasets/frs.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,12 +631,20 @@ def determine_education_level(fted_val, typeed2_val, age_val):
631631
pe_person["employer_pension_contributions"] = (
632632
pe_person["employee_pension_contributions"] * 3
633633
) # Rough estimate based on aggregates.
634+
# Salary sacrifice pension contributions from FRS Job table (SPNAMT field)
635+
# SPNAMT represents employer pension contributions made via salary sacrifice
636+
# arrangements where employees forego salary in exchange for increased pension
637+
# contributions. This is separate from regular employee pension contributions
638+
# (deduc1) and provides tax advantages for both employer and employee.
639+
# Uses same pattern as employee_pension_contributions (deduc1) without outlier
640+
# clipping, as job-level data is generally cleaner than pension provider data.
641+
# Source: https://datacatalogue.ukdataservice.ac.uk/datasets/dataset/630d4a8d-ba6a-82b3-f33d-c713c66efcb3
642+
# Note: Values are annualized from weekly amounts reported in the survey.
634643
pe_person["pension_contributions_via_salary_sacrifice"] = np.maximum(
635644
0,
636645
sum_to_entity(job.spnamt.fillna(0), job.person_id, person.person_id)
637646
* WEEKS_IN_YEAR,
638647
)
639-
# link to the variable: https://datacatalogue.ukdataservice.ac.uk/datasets/dataset/630d4a8d-ba6a-82b3-f33d-c713c66efcb3
640648

641649
pe_household["housing_service_charges"] = (
642650
pd.DataFrame(
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
def test_pension_contributions_via_salary_sacrifice(baseline):
2+
"""Test that pension_contributions_via_salary_sacrifice loads and has reasonable values."""
3+
values = baseline.calculate(
4+
"pension_contributions_via_salary_sacrifice", period=2025
5+
)
6+
7+
# Basic validation: all values should be non-negative
8+
assert (
9+
values >= 0
10+
).all(), "Salary sacrifice pension contributions must be non-negative"
11+
12+
# Should have some non-zero values (not everyone uses salary sacrifice, but some do)
13+
total = values.sum()
14+
assert (
15+
total > 0
16+
), f"Expected some salary sacrifice contributions, got {total}"
17+
18+
# Reasonableness check: total should be less than total employment income
19+
# This is a very loose check just to catch major issues
20+
employment_income = baseline.calculate("employment_income", period=2025)
21+
total_employment = employment_income.sum()
22+
assert (
23+
total < total_employment
24+
), f"Salary sacrifice contributions ({total/1e9:.1f}B) cannot exceed total employment income ({total_employment/1e9:.1f}B)"

0 commit comments

Comments
 (0)