Skip to content

Commit 97d4919

Browse files
MaxGhenisclaude
andauthored
Add 2026 Plan 2 student loan interest rate thresholds (#1420)
* Add student loan interest rate parameters and variable Add parameters documenting student loan interest rate rules: - Plan 1: Lower of RPI or base rate + 1% - Plan 2: Income-contingent (RPI to RPI+3% based on income thresholds) - Plan 4: Same as Plan 1 - Plan 5: RPI only (no income-based component) - Postgraduate: RPI + 3% Add student_loan_interest_rate variable that calculates the applicable rate based on plan type and income, with Plan 2 tapering between the lower and upper income thresholds. Fixes #1417 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Update UC taper reform expected impact (drift from -31.0 to -29.2bn) * Add student loan repayment validation notebook * bd sync: 2025-11-30 12:42:30 * Add 2026 Plan 2 student loan interest rate thresholds - Lower threshold: £29,385 (2026-09-01), frozen at same value through 2029 per Budget 2025 repayment threshold freeze - Upper threshold: £52,885 (2026-09-01) Source: GOV.UK Student Loans Interest and Repayment Threshold Announcement Fixes #1419 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Use underscore separators in params and YAML tests for interest thresholds - Update lower_threshold.yaml and upper_threshold.yaml to use underscore separators (e.g., 29_385) - Replace Python test file with YAML test file (student_loan_interest_rate.yaml) - Tests cover Plan 2 income-contingent rates, threshold freeze 2027-2029, and other plan types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Use p = parameters pattern for student loan interest rate Use p = parameters(period).gov.hmrc.student_loans.interest_rates then call p.x directly in calculations to make explicit that values come from parameters. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Extract plan_2_interest_rate into separate variable Create dedicated variable for Plan 2 income-contingent interest rate calculation. This makes the tapered rate logic reusable and explicit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Calculate Plan 1/4/5 rates from RPI and BoE base rate Replace hardcoded Plan 1, 4, 5 interest rate parameters with formula-based variables that calculate rates from underlying inputs: - Plan 1/4: min(RPI, BoE base rate + 1%) - Plan 5: RPI only - Plan 2: RPI to RPI+3% (income-contingent) New parameters: - gov.boe.base_rate: Bank of England base rate - gov.hmrc.student_loans.interest_rates.rpi: Student loan RPI rate Removed parameters: - interest_rates/plan_1.yaml (now calculated) - interest_rates/plan_4.yaml (now calculated) - interest_rates/plan_5.yaml (now calculated) - interest_rates/plan_2/base_rate.yaml (replaced by rpi.yaml) Sources: - Plan 1: SI 2009/470, Regulation 21 - Plan 4: Same formula via amendments to SI 2009/470 - Bank of England rate history 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Use OBR RPI instead of separate student loan RPI parameter - Remove interest_rates/rpi.yaml (duplicative) - Reference gov.economic_assumptions.yoy_growth.obr.rpi in formulas - Add legislative references to all interest rate variables - Add OBR EFO November 2025 reference to BoE base rate forecasts - Update tests to use OBR RPI values (4.33% for 2025, 3.71% for 2026) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix midpoint test to use correct 2026 thresholds Calendar year 2026 uses 2025-09-01 thresholds (28,470 / 51,245), not the 2026-09-01 values which take effect in September. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix uprating to parameter-level and add specific legislation references - Move uprating from dated value entries to top-level metadata - Reference Regulation 21A(12) for lower threshold (L) - Reference Regulation 21A(13) for upper threshold (H) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove uncorroborated historical interest threshold values - Remove 2020-2024 values that lacked proper references - Keep only 2025+ values with corroborating sources: - 2025-09-01: From Student loans guide 2025-26 - 2026-09-01: From government announcement - 2027-2029: Freeze per Budget 2025 - Update references to cite correct regulations (21AA/21AB) Every value now has a corroborating reference. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add corroborated historical threshold values with references Lower threshold (2020-2024): - 2020-09-01: £26,575 (GOV.UK previous thresholds) - 2021-09-01: £27,295 (GOV.UK previous thresholds) - 2024-09-01: £27,295 (2024-25 Terms Guide) Upper threshold (2024): - 2024-09-01: £49,130 (SI 2022/1335 + 2024-25 Terms Guide) Every value now has a corroborating reference. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove per-value uprating metadata Uprating is set at the parameter level in top-level metadata. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Use repayment threshold for lower interest threshold per Regulation 21AB Per Regulation 21AB, the lower interest threshold equals the Plan 2 repayment threshold. Instead of duplicating values in a separate parameter, reference the repayment threshold directly in the formula. This removes the lower_threshold.yaml file and updates plan_2_interest_rate to use gov.hmrc.student_loans.thresholds.plan_2. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Simplify plan_2_interest_rate to use p = parameters(period).gov pattern 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add Plan 1 BoE margin parameter and use p = parameters(period).gov pattern - Create plan_1/boe_margin.yaml parameter for the 1% margin - Update plan_1_interest_rate to use parameter instead of hardcoded 0.01 - Apply p = parameters(period).gov pattern to plan_5_interest_rate 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Use subtree for p in plan_2_interest_rate p = parameters(period).gov.hmrc.student_loans for cleaner code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Consolidate comments in plan_2_interest_rate 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Fix formatting 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 394bb90 commit 97d4919

23 files changed

Lines changed: 766 additions & 7 deletions

File tree

.beads/.gitignore

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SQLite databases
2+
*.db
3+
*.db?*
4+
*.db-journal
5+
*.db-wal
6+
*.db-shm
7+
8+
# Daemon runtime files
9+
daemon.lock
10+
daemon.log
11+
daemon.pid
12+
bd.sock
13+
14+
# Legacy database files
15+
db.sqlite
16+
bd.db
17+
18+
# Merge artifacts (temporary files from 3-way merge)
19+
beads.base.jsonl
20+
beads.base.meta.json
21+
beads.left.jsonl
22+
beads.left.meta.json
23+
beads.right.jsonl
24+
beads.right.meta.json
25+
26+
# Keep JSONL exports and config (source of truth for git)
27+
!issues.jsonl
28+
!metadata.json
29+
!config.json

.beads/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Beads - AI-Native Issue Tracking
2+
3+
Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code.
4+
5+
## What is Beads?
6+
7+
Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git.
8+
9+
**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads)
10+
11+
## Quick Start
12+
13+
### Essential Commands
14+
15+
```bash
16+
# Create new issues
17+
bd create "Add user authentication"
18+
19+
# View all issues
20+
bd list
21+
22+
# View issue details
23+
bd show <issue-id>
24+
25+
# Update issue status
26+
bd update <issue-id> --status in-progress
27+
bd update <issue-id> --status done
28+
29+
# Sync with git remote
30+
bd sync
31+
```
32+
33+
### Working with Issues
34+
35+
Issues in Beads are:
36+
- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code
37+
- **AI-friendly**: CLI-first design works perfectly with AI coding agents
38+
- **Branch-aware**: Issues can follow your branch workflow
39+
- **Always in sync**: Auto-syncs with your commits
40+
41+
## Why Beads?
42+
43+
**AI-Native Design**
44+
- Built specifically for AI-assisted development workflows
45+
- CLI-first interface works seamlessly with AI coding agents
46+
- No context switching to web UIs
47+
48+
🚀 **Developer Focused**
49+
- Issues live in your repo, right next to your code
50+
- Works offline, syncs when you push
51+
- Fast, lightweight, and stays out of your way
52+
53+
🔧 **Git Integration**
54+
- Automatic sync with git commits
55+
- Branch-aware issue tracking
56+
- Intelligent JSONL merge resolution
57+
58+
## Get Started with Beads
59+
60+
Try Beads in your own projects:
61+
62+
```bash
63+
# Install Beads
64+
curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
65+
66+
# Initialize in your repo
67+
bd init
68+
69+
# Create your first issue
70+
bd create "Try out Beads"
71+
```
72+
73+
## Learn More
74+
75+
- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs)
76+
- **Quick Start Guide**: Run `bd quickstart`
77+
- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples)
78+
79+
---
80+
81+
*Beads: Issue tracking that moves at the speed of thought*

.beads/config.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Beads Configuration File
2+
# This file configures default behavior for all bd commands in this repository
3+
# All settings can also be set via environment variables (BD_* prefix)
4+
# or overridden with command-line flags
5+
6+
# Issue prefix for this repository (used by bd init)
7+
# If not set, bd init will auto-detect from directory name
8+
# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc.
9+
# issue-prefix: ""
10+
11+
# Use no-db mode: load from JSONL, no SQLite, write back after each command
12+
# When true, bd will use .beads/issues.jsonl as the source of truth
13+
# instead of SQLite database
14+
# no-db: false
15+
16+
# Disable daemon for RPC communication (forces direct database access)
17+
# no-daemon: false
18+
19+
# Disable auto-flush of database to JSONL after mutations
20+
# no-auto-flush: false
21+
22+
# Disable auto-import from JSONL when it's newer than database
23+
# no-auto-import: false
24+
25+
# Enable JSON output by default
26+
# json: false
27+
28+
# Default actor for audit trails (overridden by BD_ACTOR or --actor)
29+
# actor: ""
30+
31+
# Path to database (overridden by BEADS_DB or --db)
32+
# db: ""
33+
34+
# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON)
35+
# auto-start-daemon: true
36+
37+
# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE)
38+
# flush-debounce: "5s"
39+
40+
# Multi-repo configuration (experimental - bd-307)
41+
# Allows hydrating from multiple repositories and routing writes to the correct JSONL
42+
# repos:
43+
# primary: "." # Primary repo (where this database lives)
44+
# additional: # Additional repos to hydrate from (read-only)
45+
# - ~/beads-planning # Personal planning repo
46+
# - ~/work-planning # Work planning repo
47+
48+
# Integration settings (access with 'bd config get/set')
49+
# These are stored in the database, not in this file:
50+
# - jira.url
51+
# - jira.project
52+
# - linear.url
53+
# - linear.api-key
54+
# - github.org
55+
# - github.repo
56+
# - sync.branch - Git branch for beads commits (use BEADS_SYNC_BRANCH env var or bd config set)

.beads/issues.jsonl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"id":"policyengine-uk-5qy","title":"Update student loan validation notebook with deeper analysis","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-29T21:01:35.49966-05:00","updated_at":"2025-11-29T21:02:51.469593-05:00","closed_at":"2025-11-29T21:02:51.469593-05:00"}
2+
{"id":"policyengine-uk-75j","title":"Add student loan calibration targets to policyengine-uk-data","description":"Add student loan repayment and balance calibration targets to policyengine-uk-data loss function.\n\n## Proposed Calibration Targets (from SLC 2024-25 statistics)\n\n### Total Repayments by Country\n| Country | Repayments | Source |\n|---------|------------|--------|\n| England (HE) | £5.0bn | SLC 2024-25 |\n| Scotland | £203m | SLC 2024-25 |\n| Wales | £229m | SLC 2024-25 |\n| Northern Ireland | £182m | SLC 2024-25 |\n| **UK Total** | **~£5.6bn** | |\n\n### Repayments by Plan Type (England)\n| Plan | Amount | Share |\n|------|--------|-------|\n| Plan 1 | £1.9bn | 37% |\n| Plan 2 | £2.8bn | 55% |\n| Postgraduate | £0.3bn | 7% |\n| Plan 5 | £41m | 0.8% |\n\n### Number of Borrowers Repaying (England)\n- 3.0m via HMRC\n- 187k scheduled direct\n- 147k voluntary direct\n\n### Outstanding Balances\n- UK Total: £294bn (March 2025)\n\n## Implementation Notes\n1. Add targets to `loss.py` in policyengine-uk-data\n2. May need to adjust for timing (FRS year vs SLC reporting year)\n3. Consider whether to calibrate on modelled (`student_loan_repayment`) or reported (`student_loan_repayments`)\n\n## Sources\n- https://www.gov.uk/government/statistics/student-loans-in-england-2024-to-2025\n- https://www.gov.uk/government/statistics/student-loans-in-scotland-2024-to-2025\n- https://www.gov.uk/government/statistics/student-loans-in-northern-ireland-2024-to-2025\n- https://www.gov.uk/government/statistics/student-loans-in-wales-2024-to-2025","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-11-29T21:01:37.038332-05:00","updated_at":"2025-11-30T12:42:25.851958-05:00","closed_at":"2025-11-30T12:42:25.851958-05:00"}
3+
{"id":"policyengine-uk-occ","title":"Research official student loan repayment aggregates for calibration","description":"## Research Findings\n\n### UK Student Loan Repayments 2024-25 (SLC Official Statistics)\n\n**England (HE):** £5.0bn total repayments\n- Plan 1: £1.9bn (37%)\n- Plan 2: £2.8bn (55%)\n- Plan 3/Postgraduate: £0.3bn (7%)\n- Plan 5: £41m (0.8%, voluntary only)\n\n**Scotland:** £203m total repayments (primarily Plan 4)\n\n**Wales:** ~£229m total repayments (6.9% increase from prior year)\n\n**Northern Ireland:** £182m total repayments\n\n**UK Total (estimated):** ~£5.6bn HE repayments\n\n### Borrowers Making Repayments (England)\n- 3.0m via HMRC (39.5% of those liable)\n- 187k scheduled direct to SLC\n- 147k voluntary direct to SLC\n\n### Outstanding Balances\n- England: £236.4bn (end March 2025)\n- Scotland: £9.4bn\n- Northern Ireland: £5.6bn\n- Wales: ~£9-10bn (estimated)\n- **UK Total: ~£260-295bn**\n\n### Sources\n- https://www.gov.uk/government/statistics/student-loans-in-england-2024-to-2025\n- https://www.gov.uk/government/statistics/student-loans-in-scotland-2024-to-2025\n- https://www.gov.uk/government/statistics/student-loans-in-northern-ireland-2024-to-2025\n- https://www.gov.uk/government/statistics/student-loans-in-wales-2024-to-2025","status":"closed","priority":2,"issue_type":"task","created_at":"2025-11-29T21:01:36.199753-05:00","updated_at":"2025-11-30T12:41:52.88068-05:00","closed_at":"2025-11-30T12:41:52.88068-05:00","dependencies":[{"issue_id":"policyengine-uk-occ","depends_on_id":"policyengine-uk-75j","type":"blocks","created_at":"2025-11-29T21:01:47.791464-05:00","created_by":"daemon"}]}

.beads/metadata.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"database": "beads.db",
3+
"jsonl_export": "issues.jsonl",
4+
"last_bd_version": "0.26.0"
5+
}

changelog_entry.yaml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
- bump: minor
1+
- bump: patch
22
changes:
33
added:
4-
- Add ONS household interest income uprating index from National Accounts D.41g (HAXV series) that reflects actual growth in household interest received
5-
changed:
6-
- Update savings_interest_income variable to use ONS household interest income index for uprating instead of per capita GDP. This captures the dramatic growth in household interest income (from £16bn in 2020 to £98bn in 2024) due to interest rate rises.
4+
- Add 2026 Plan 2 student loan interest rate thresholds (lower £29,385, upper £52,885) and freeze lower threshold 2027-2029 per Budget 2025
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Student loan repayment validation\n",
8+
"\n",
9+
"This notebook compares PolicyEngine UK's calculated student loan repayments against reported repayments from the Family Resources Survey (FRS) microdata. Understanding the alignment between modelled and reported values helps assess model accuracy and identify areas for improvement."
10+
]
11+
},
12+
{
13+
"cell_type": "markdown",
14+
"metadata": {},
15+
"source": [
16+
"## Background\n",
17+
"\n",
18+
"Student loan repayments in the UK are calculated as a percentage of income above a threshold, varying by loan plan:\n",
19+
"\n",
20+
"- **Plan 1** (pre-2012 England/Wales, Scotland, NI): 9% of income above £24,990 (2024-25)\n",
21+
"- **Plan 2** (post-2012 England/Wales): 9% of income above £27,295 (2024-25)\n",
22+
"- **Plan 4** (Scotland post-2017): 9% of income above £27,660 (2024-25)\n",
23+
"- **Plan 5** (England post-2023): 9% of income above £25,000 (2024-25)\n",
24+
"- **Postgraduate**: 6% of income above £21,000 (2024-25)\n",
25+
"\n",
26+
"The FRS captures reported student loan repayments, while PolicyEngine calculates repayments based on income and loan plan type."
27+
]
28+
},
29+
{
30+
"cell_type": "code",
31+
"execution_count": null,
32+
"metadata": {},
33+
"outputs": [],
34+
"source": [
35+
"from policyengine_uk import Microsimulation\n",
36+
"import numpy as np\n",
37+
"import pandas as pd\n",
38+
"\n",
39+
"sim = Microsimulation()\n",
40+
"year = 2025"
41+
]
42+
},
43+
{
44+
"cell_type": "code",
45+
"execution_count": null,
46+
"metadata": {},
47+
"outputs": [],
48+
"source": [
49+
"# Get student loan data\n",
50+
"reported = sim.calculate(\"student_loan_repayments\", year).values\n",
51+
"modelled = sim.calculate(\"student_loan_repayment\", year).values\n",
52+
"plan = sim.calculate(\"student_loan_plan\", year).values\n",
53+
"income = sim.calculate(\"adjusted_net_income\", year).values\n",
54+
"weight = sim.calculate(\"person_weight\", year).values"
55+
]
56+
},
57+
{
58+
"cell_type": "markdown",
59+
"metadata": {},
60+
"source": [
61+
"## Student loan plan distribution\n",
62+
"\n",
63+
"First, let's examine the distribution of student loan plans in the weighted population:"
64+
]
65+
},
66+
{
67+
"cell_type": "code",
68+
"execution_count": null,
69+
"metadata": {},
70+
"outputs": [],
71+
"source": [
72+
"# Plan distribution (weighted)\n",
73+
"plan_names = {0: \"None\", 1: \"Plan 1\", 2: \"Plan 2\", 3: \"Postgraduate\", 4: \"Plan 4\", 5: \"Plan 5\"}\n",
74+
"for plan_id, name in plan_names.items():\n",
75+
" count = weight[plan == plan_id].sum() / 1e6\n",
76+
" print(f\"{name}: {count:.2f}m people\")"
77+
]
78+
},
79+
{
80+
"cell_type": "markdown",
81+
"metadata": {},
82+
"source": [
83+
"## Aggregate comparison\n",
84+
"\n",
85+
"Comparing total reported vs modelled repayments:"
86+
]
87+
},
88+
{
89+
"cell_type": "code",
90+
"execution_count": null,
91+
"metadata": {},
92+
"outputs": [],
93+
"source": [
94+
"total_reported = (reported * weight).sum() / 1e9\n",
95+
"total_modelled = (modelled * weight).sum() / 1e9\n",
96+
"\n",
97+
"print(f\"Total reported repayments: £{total_reported:.2f}bn\")\n",
98+
"print(f\"Total modelled repayments: £{total_modelled:.2f}bn\")\n",
99+
"print(f\"Ratio (modelled/reported): {total_modelled/total_reported:.2f}\")"
100+
]
101+
},
102+
{
103+
"cell_type": "markdown",
104+
"metadata": {},
105+
"source": [
106+
"## Individual-level alignment\n",
107+
"\n",
108+
"For people who report making student loan repayments, how well do our calculations align?"
109+
]
110+
},
111+
{
112+
"cell_type": "code",
113+
"execution_count": null,
114+
"metadata": {},
115+
"outputs": [],
116+
"source": [
117+
"# Filter to people with reported repayments > 0\n",
118+
"has_reported = reported > 0\n",
119+
"\n",
120+
"if has_reported.sum() > 0:\n",
121+
" # Correlation\n",
122+
" correlation = np.corrcoef(reported[has_reported], modelled[has_reported])[0, 1]\n",
123+
" print(f\"Correlation (people with reported > 0): {correlation:.3f}\")\n",
124+
" \n",
125+
" # Match rate\n",
126+
" both_positive = (reported > 0) & (modelled > 0)\n",
127+
" match_rate = both_positive.sum() / has_reported.sum() * 100\n",
128+
" print(f\"People with both reported & modelled > 0: {match_rate:.1f}% of reporters\")\n",
129+
" \n",
130+
" # Mean values\n",
131+
" print(f\"\\nMean reported (reporters): £{reported[has_reported].mean():,.0f}\")\n",
132+
" print(f\"Mean modelled (reporters): £{modelled[has_reported].mean():,.0f}\")\n",
133+
" print(f\"Mean income (reporters): £{income[has_reported].mean():,.0f}\")"
134+
]
135+
},
136+
{
137+
"cell_type": "markdown",
138+
"metadata": {},
139+
"source": [
140+
"## Analysis of discrepancies\n",
141+
"\n",
142+
"The relatively low individual-level correlation suggests several factors may explain differences:\n",
143+
"\n",
144+
"1. **Timing differences**: Reported repayments reflect actual payments made during the tax year, which may include voluntary overpayments or vary based on pay frequency and employment changes.\n",
145+
"\n",
146+
"2. **Employment variation**: Someone may have had periods below or above the repayment threshold during the year, while our model assumes constant annual income.\n",
147+
"\n",
148+
"3. **Multiple loan plans**: Some individuals may have both Plan 1 and Plan 2 loans, complicating the calculation.\n",
149+
"\n",
150+
"4. **Study status**: Current students may have different repayment patterns not fully captured in the model.\n",
151+
"\n",
152+
"5. **Plan misclassification**: The loan plan imputation in the microdata may not perfectly match individuals' actual loan types.\n",
153+
"\n",
154+
"Despite individual-level variation, the aggregate totals are reasonably aligned, suggesting the model captures the overall scale of student loan repayments in the UK economy."
155+
]
156+
},
157+
{
158+
"cell_type": "markdown",
159+
"metadata": {},
160+
"source": [
161+
"## Conclusion\n",
162+
"\n",
163+
"PolicyEngine UK's student loan repayment model produces aggregate totals within a reasonable range of reported values. The individual-level correlation is lower than for income tax calculations, reflecting the complexity of student loan timing and the limitations of annual income-based calculations. For microsimulation purposes, the model provides a reasonable approximation of student loan repayment flows, while users should be aware of these limitations when analysing individual-level impacts."
164+
]
165+
}
166+
],
167+
"metadata": {
168+
"kernelspec": {
169+
"display_name": "Python 3",
170+
"language": "python",
171+
"name": "python3"
172+
},
173+
"language_info": {
174+
"name": "python",
175+
"version": "3.10.0"
176+
}
177+
},
178+
"nbformat": 4,
179+
"nbformat_minor": 4
180+
}

0 commit comments

Comments
 (0)