diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb..d6f755478 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - Add pension_contributions_via_salary_sacrifice variable from FRS SPNAMT field diff --git a/policyengine_uk_data/datasets/frs.py b/policyengine_uk_data/datasets/frs.py index 8dd8789c5..56b51042c 100644 --- a/policyengine_uk_data/datasets/frs.py +++ b/policyengine_uk_data/datasets/frs.py @@ -631,6 +631,20 @@ def determine_education_level(fted_val, typeed2_val, age_val): pe_person["employer_pension_contributions"] = ( pe_person["employee_pension_contributions"] * 3 ) # Rough estimate based on aggregates. + # Salary sacrifice pension contributions from FRS Job table (SPNAMT field) + # SPNAMT represents employer pension contributions made via salary sacrifice + # arrangements where employees forego salary in exchange for increased pension + # contributions. This is separate from regular employee pension contributions + # (deduc1) and provides tax advantages for both employer and employee. + # Uses same pattern as employee_pension_contributions (deduc1) without outlier + # clipping, as job-level data is generally cleaner than pension provider data. + # Source: https://datacatalogue.ukdataservice.ac.uk/datasets/dataset/630d4a8d-ba6a-82b3-f33d-c713c66efcb3 + # Note: Values are annualized from weekly amounts reported in the survey. + pe_person["pension_contributions_via_salary_sacrifice"] = np.maximum( + 0, + sum_to_entity(job.spnamt.fillna(0), job.person_id, person.person_id) + * WEEKS_IN_YEAR, + ) pe_household["housing_service_charges"] = ( pd.DataFrame( diff --git a/policyengine_uk_data/storage/uprating_factors.csv b/policyengine_uk_data/storage/uprating_factors.csv index 62ab4dacb..5a7e54df4 100644 --- a/policyengine_uk_data/storage/uprating_factors.csv +++ b/policyengine_uk_data/storage/uprating_factors.csv @@ -21,6 +21,7 @@ domestic_energy_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1 education_consumption,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 employee_pension_contributions,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 employer_pension_contributions,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 +pension_contributions_via_salary_sacrifice,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 employment_income,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 employment_income_before_lsr,1.0,1.059,1.127,1.205,1.261,1.308,1.337,1.365,1.396,1.431,1.431,1.431,1.431,1.431,1.431 esa_contrib_reported,1.0,1.04,1.144,1.209,1.237,1.277,1.301,1.327,1.353,1.38,1.38,1.38,1.38,1.38,1.38 diff --git a/policyengine_uk_data/storage/uprating_growth_factors.csv b/policyengine_uk_data/storage/uprating_growth_factors.csv index eb1bcca2b..eb8b7fb6d 100644 --- a/policyengine_uk_data/storage/uprating_growth_factors.csv +++ b/policyengine_uk_data/storage/uprating_growth_factors.csv @@ -21,6 +21,7 @@ domestic_energy_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0. education_consumption,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 employee_pension_contributions,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 employer_pension_contributions,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 +pension_contributions_via_salary_sacrifice,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 employment_income,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 employment_income_before_lsr,0,0.059,0.064,0.069,0.046,0.037,0.022,0.021,0.023,0.025,0.0,0.0,0.0,0.0,0.0 esa_contrib_reported,0,0.04,0.1,0.057,0.023,0.032,0.019,0.02,0.02,0.02,0.0,0.0,0.0,0.0,0.0 diff --git a/policyengine_uk_data/tests/test_pension_contributions_via_salary_sacrifice.py b/policyengine_uk_data/tests/test_pension_contributions_via_salary_sacrifice.py new file mode 100644 index 000000000..57c5edf89 --- /dev/null +++ b/policyengine_uk_data/tests/test_pension_contributions_via_salary_sacrifice.py @@ -0,0 +1,24 @@ +def test_pension_contributions_via_salary_sacrifice(baseline): + """Test that pension_contributions_via_salary_sacrifice loads and has reasonable values.""" + values = baseline.calculate( + "pension_contributions_via_salary_sacrifice", period=2025 + ) + + # Basic validation: all values should be non-negative + assert ( + values >= 0 + ).all(), "Salary sacrifice pension contributions must be non-negative" + + # Should have some non-zero values (not everyone uses salary sacrifice, but some do) + total = values.sum() + assert ( + total > 0 + ), f"Expected some salary sacrifice contributions, got {total}" + + # Reasonableness check: total should be less than total employment income + # This is a very loose check just to catch major issues + employment_income = baseline.calculate("employment_income", period=2025) + total_employment = employment_income.sum() + assert ( + total < total_employment + ), f"Salary sacrifice contributions ({total/1e9:.1f}B) cannot exceed total employment income ({total_employment/1e9:.1f}B)"