Skip to content

Commit

Permalink
Complete support for all student loan plans
Browse files Browse the repository at this point in the history
  • Loading branch information
sgb-io committed Nov 30, 2023
1 parent 18a02b8 commit 23d23ee
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 54 deletions.
54 changes: 32 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ There are 5 main APIs:
- `calculatePersonalAllowance({ taxYear?: TaxYear, taxableAnnualIncome: number })`: calculates an individual's personal allowance for a tax year, single amount.
- `calculateIncomeTax({ taxYear?: TaxYear, personalAllowance: number, taxableAnnualIncome: number })`: calculates the income tax due in a tax year on an individual's taxable income, broken down into the 3 bands (basic, higher, additional)
- `calculateEmployeeNationalInsurance({ taxYear?: TaxYear, taxableAnnualIncome: number })`: calculates the national insurance contributions due in a tax year on an individual's taxable income, single amount. Note: only supports class 1, category A
- `calculateStudentLoanRepayments({ taxYear?: TaxYear, taxableAnnualIncome: number, studentLoanPlanNo: number })`: calculates the student loan repayments due in a tax year on an individual's taxable income, single amount. `studentLoanPlanNo` can be `1` or `2`.
- `calculateStudentLoanRepayments({ taxYear?: TaxYear, taxableAnnualIncome: number, studentLoanPlanNo: number })`: calculates the student loan repayments due in a tax year on an individual's taxable income, single amount. `studentLoanPlanNo` can be `1`, `2`, `4`, `5` or `postgrad`.
- `getHmrcRates({ taxYear?: TaxYear })`: returns an underlying static set of HMRC rates for a given tax year. This is useful for doing your own arbitrary calculations.

All APIs return raw amounts and there is no formatting or display functionality.
Expand All @@ -37,66 +37,77 @@ import {
calculateIncomeTax,
calculateEmployeeNationalInsurance,
calculateStudentLoanRepayments,
} from '@saving-tool/hmrc-income-tax';
} from "@saving-tool/hmrc-income-tax";

// Mark S.
const taxableAnnualIncome = 53_900
const taxableAnnualIncome = 53_900;

const personalAllowance = calculatePersonalAllowance({ taxableAnnualIncome });
// => 12570

const incomeTax = calculateIncomeTax({ personalAllowance, taxableAnnualIncome });
const incomeTax = calculateIncomeTax({
personalAllowance,
taxableAnnualIncome,
});
const { basicRateTax, higherRateTax, additionalRateTax } = incomeTax;
const totalIncomeTax = basicRateTax + higherRateTax + additionalRateTax;
// => 8992

const nationalInsuranceContributions = calculateEmployeeNationalInsurance({ taxableAnnualIncome });
const nationalInsuranceContributions = calculateEmployeeNationalInsurance({
taxableAnnualIncome,
});
// => 5471

const studentLoanRepayments = calculateStudentLoanRepayments({ taxableAnnualIncome, studentLoanPlanNo: 1 });
const studentLoanRepayments = calculateStudentLoanRepayments({
taxableAnnualIncome,
studentLoanPlanNo: 1,
});
// => 3162

// Do whatever you want, e.g. calculate the take-home pay
const takeHome = taxableAnnualIncome
- totalIncomeTax
- nationalInsuranceContributions
- studentLoanRepayments;
const takeHome =
taxableAnnualIncome -
totalIncomeTax -
nationalInsuranceContributions -
studentLoanRepayments;
// => 36275
```


Irv B. of MDR earns £160,000. His employer contributes some amount to his pension, but he contributes nothing. He has no student loan.

```javascript
import {
calculatePersonalAllowance,
calculateIncomeTax,
calculateEmployeeNationalInsurance,
} from '@saving-tool/hmrc-income-tax';
} from "@saving-tool/hmrc-income-tax";

// Irv B.
const taxableAnnualIncome = 160_000
const taxableAnnualIncome = 160_000;

const personalAllowance = calculatePersonalAllowance({ taxableAnnualIncome });
// => 0

const incomeTax = calculateIncomeTax({ personalAllowance, taxableAnnualIncome });
const incomeTax = calculateIncomeTax({
personalAllowance,
taxableAnnualIncome,
});
const { basicRateTax, higherRateTax, additionalRateTax } = incomeTax;
const totalIncomeTax = basicRateTax + higherRateTax + additionalRateTax;
// => 57589

const nationalInsuranceContributions = calculateEmployeeNationalInsurance({ taxableAnnualIncome });
const nationalInsuranceContributions = calculateEmployeeNationalInsurance({
taxableAnnualIncome,
});
// => 8919

// Do whatever you want, e.g. calculate the take-home pay
const takeHome = taxableAnnualIncome
- totalIncomeTax
- nationalInsuranceContributions;
const takeHome =
taxableAnnualIncome - totalIncomeTax - nationalInsuranceContributions;
// => 93492
```

It's important to understand that in most cases this library is expecting *taxable* income (appropriate API naming aims to make this clear). Any salary sacrafice mechanisms should be applied before these calculations, and the appropriate taxable amount used when calling this library.

It's important to understand that in most cases this library is expecting _taxable_ income (appropriate API naming aims to make this clear). Any salary sacrafice mechanisms should be applied before these calculations, and the appropriate taxable amount used when calling this library.

## Formatting and rounding output

Expand All @@ -117,5 +128,4 @@ const gbpFormatter = new Intl.NumberFormat("en-GB", {
export const roundAndFormatGbp = (amount: number) => {
return formatGbp(Math.round(amount));
};

```
```
14 changes: 13 additions & 1 deletion src/hmrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ interface TaxRates {
// Previous rates: https://www.gov.uk/guidance/previous-annual-repayment-thresholds
STUDENT_LOAN_PLAN_1_WEEKLY_THRESHOLD: number;
STUDENT_LOAN_PLAN_2_WEEKLY_THRESHOLD: number;
STUDENT_LOAN_PLAN_4_WEEKLY_THRESHOLD: number;
STUDENT_LOAN_PLAN_5_WEEKLY_THRESHOLD: number;
STUDENT_LOAN_POSTGRAD_WEEKLY_THRESHOLD: number;
STUDENT_LOAN_REPAYMENT_AMOUNT: number;
STUDENT_LOAN_REPAYMENT_AMOUNT_POSTGRAD: number;

// National Insurance
// See https://www.gov.uk/guidance/rates-and-thresholds-for-employers-2022-to-2023 for current and previous rates
Expand All @@ -41,7 +45,11 @@ const taxRates: Record<TaxYear, TaxRates> = {
// Student loan repayments
STUDENT_LOAN_PLAN_1_WEEKLY_THRESHOLD: 388,
STUDENT_LOAN_PLAN_2_WEEKLY_THRESHOLD: 524,
STUDENT_LOAN_PLAN_4_WEEKLY_THRESHOLD: 487.98,
STUDENT_LOAN_PLAN_5_WEEKLY_THRESHOLD: 480, // Note: this was only introduced in 2023/24, so technically isn't relevant to 22/23
STUDENT_LOAN_POSTGRAD_WEEKLY_THRESHOLD: 403.84,
STUDENT_LOAN_REPAYMENT_AMOUNT: 0.09, // People on plans 1 or 2 repay 9% of the amount you earn over the threshold
STUDENT_LOAN_REPAYMENT_AMOUNT_POSTGRAD: 0.06, // People on postgrad plans repay 6% of the amount you earn over the threshold
// National Insurance
NI_MIDDLE_RATE: 0.1325,
NI_UPPER_RATE: 0.0325,
Expand All @@ -61,7 +69,11 @@ const taxRates: Record<TaxYear, TaxRates> = {
// Student loan repayments
STUDENT_LOAN_PLAN_1_WEEKLY_THRESHOLD: 423,
STUDENT_LOAN_PLAN_2_WEEKLY_THRESHOLD: 524,
STUDENT_LOAN_REPAYMENT_AMOUNT: 0.09, // People on plans 1 or 2 repay 9% of the amount you earn over the threshold
STUDENT_LOAN_PLAN_4_WEEKLY_THRESHOLD: 532,
STUDENT_LOAN_PLAN_5_WEEKLY_THRESHOLD: 480,
STUDENT_LOAN_POSTGRAD_WEEKLY_THRESHOLD: 403,
STUDENT_LOAN_REPAYMENT_AMOUNT: 0.09, // People on plans 1, 2, 4 + 5 repay 9% of the amount you earn over the threshold
STUDENT_LOAN_REPAYMENT_AMOUNT_POSTGRAD: 0.06, // People on postgrad plans repay 6% of the amount you earn over the threshold
// National Insurance
NI_MIDDLE_RATE: 0.12,
NI_UPPER_RATE: 0.02,
Expand Down
210 changes: 185 additions & 25 deletions src/studentLoan.test.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import { calculateStudentLoanRepayments } from "./studentLoan";

const expectationsPlan1 = [
{ taxableAnnualIncome: 15_000, repayments: 0 },
{ taxableAnnualIncome: 17_500, repayments: 0 },
{ taxableAnnualIncome: 20_000, repayments: 0 },
{ taxableAnnualIncome: 22_500, repayments: 209.1599999999999 },
{ taxableAnnualIncome: 25_000, repayments: 434.16 },
{ taxableAnnualIncome: 50_000, repayments: 2684.16 },
{ taxableAnnualIncome: 55_000, repayments: 3134.1599999999994 },
{ taxableAnnualIncome: 60_000, repayments: 3584.1599999999994 },
{ taxableAnnualIncome: 75_000, repayments: 4934.16 },
{ taxableAnnualIncome: 90_000, repayments: 6284.16 },
{ taxableAnnualIncome: 110_000, repayments: 8084.159999999999 },
{ taxableAnnualIncome: 120_000, repayments: 8984.16 },
{ taxableAnnualIncome: 124_500, repayments: 9389.16 },
{ taxableAnnualIncome: 125_000, repayments: 9434.159999999998 },
{ taxableAnnualIncome: 130_000, repayments: 9884.16 },
{ taxableAnnualIncome: 145_000, repayments: 11234.16 },
{ taxableAnnualIncome: 160_000, repayments: 12584.160000000002 },
{ taxableAnnualIncome: 175_000, repayments: 13934.159999999998 },
{ taxableAnnualIncome: 200_000, repayments: 16184.160000000002 },
{ taxableAnnualIncome: 250_000, repayments: 20684.16 },
{ taxableAnnualIncome: 500_000, repayments: 43184.159999999996 },
{ taxableAnnualIncome: 1_000_000, repayments: 88184.15999999999 },
];

describe("calculateStudentLoanRepayments", () => {
const expectationsPlan1 = [
{ taxableAnnualIncome: 15_000, repayments: 0 },
{ taxableAnnualIncome: 17_500, repayments: 0 },
{ taxableAnnualIncome: 20_000, repayments: 0 },
{ taxableAnnualIncome: 22_500, repayments: 209.1599999999999 },
{ taxableAnnualIncome: 25_000, repayments: 434.16 },
{ taxableAnnualIncome: 50_000, repayments: 2684.16 },
{ taxableAnnualIncome: 55_000, repayments: 3134.1599999999994 },
{ taxableAnnualIncome: 60_000, repayments: 3584.1599999999994 },
{ taxableAnnualIncome: 75_000, repayments: 4934.16 },
{ taxableAnnualIncome: 90_000, repayments: 6284.16 },
{ taxableAnnualIncome: 110_000, repayments: 8084.159999999999 },
{ taxableAnnualIncome: 120_000, repayments: 8984.16 },
{ taxableAnnualIncome: 124_500, repayments: 9389.16 },
{ taxableAnnualIncome: 125_000, repayments: 9434.159999999998 },
{ taxableAnnualIncome: 130_000, repayments: 9884.16 },
{ taxableAnnualIncome: 145_000, repayments: 11234.16 },
{ taxableAnnualIncome: 160_000, repayments: 12584.160000000002 },
{ taxableAnnualIncome: 175_000, repayments: 13934.159999999998 },
{ taxableAnnualIncome: 200_000, repayments: 16184.160000000002 },
{ taxableAnnualIncome: 250_000, repayments: 20684.16 },
{ taxableAnnualIncome: 500_000, repayments: 43184.159999999996 },
{ taxableAnnualIncome: 1_000_000, repayments: 88184.15999999999 },
];

describe("Plan 1 loans", () => {
expectationsPlan1.forEach((expectation) => {
const { taxableAnnualIncome, repayments } = expectation;
Expand All @@ -40,4 +40,164 @@ describe("calculateStudentLoanRepayments", () => {
});
});
});

const expectationsPlan2 = [
{ taxableAnnualIncome: 15_000, repayments: 0 },
{ taxableAnnualIncome: 17_500, repayments: 0 },
{ taxableAnnualIncome: 20_000, repayments: 0 },
{ taxableAnnualIncome: 22_500, repayments: 0 },
{ taxableAnnualIncome: 25_000, repayments: 0 },
{ taxableAnnualIncome: 50_000, repayments: 2047.6799999999998 },
{ taxableAnnualIncome: 55_000, repayments: 2497.6799999999994 },
{ taxableAnnualIncome: 60_000, repayments: 2947.68 },
{ taxableAnnualIncome: 75_000, repayments: 4297.68 },
{ taxableAnnualIncome: 90_000, repayments: 5647.679999999999 },
{ taxableAnnualIncome: 110_000, repayments: 7447.6799999999985 },
{ taxableAnnualIncome: 120_000, repayments: 8347.68 },
{ taxableAnnualIncome: 124_500, repayments: 8752.679999999998 },
{ taxableAnnualIncome: 125_000, repayments: 8797.68 },
{ taxableAnnualIncome: 130_000, repayments: 9247.68 },
{ taxableAnnualIncome: 145_000, repayments: 10597.68 },
{ taxableAnnualIncome: 160_000, repayments: 11947.68 },
{ taxableAnnualIncome: 175_000, repayments: 13297.679999999998 },
{ taxableAnnualIncome: 200_000, repayments: 15547.68 },
{ taxableAnnualIncome: 250_000, repayments: 20047.679999999997 },
{ taxableAnnualIncome: 500_000, repayments: 42547.68 },
{ taxableAnnualIncome: 1_000_000, repayments: 87547.68 },
];

describe("Plan 2 loans", () => {
expectationsPlan2.forEach((expectation) => {
const { taxableAnnualIncome, repayments } = expectation;
test(taxableAnnualIncome.toString(), () => {
expect(
calculateStudentLoanRepayments({
taxYear: "2022/23",
taxableAnnualIncome,
studentLoanPlanNo: 2,
})
).toEqual(repayments);
});
});
});

const expectationsPlan4 = [
{ taxableAnnualIncome: 15_000, repayments: 0 },
{ taxableAnnualIncome: 17_500, repayments: 0 },
{ taxableAnnualIncome: 20_000, repayments: 0 },
{ taxableAnnualIncome: 22_500, repayments: 0 },
{ taxableAnnualIncome: 25_000, repayments: 0 },
{ taxableAnnualIncome: 50_000, repayments: 2216.2536 },
{ taxableAnnualIncome: 55_000, repayments: 2666.2535999999996 },
{ taxableAnnualIncome: 60_000, repayments: 3116.2535999999996 },
{ taxableAnnualIncome: 75_000, repayments: 4466.2536 },
{ taxableAnnualIncome: 90_000, repayments: 5816.2536 },
{ taxableAnnualIncome: 110_000, repayments: 7616.253599999999 },
{ taxableAnnualIncome: 120_000, repayments: 8516.253599999998 },
{ taxableAnnualIncome: 124_500, repayments: 8921.2536 },
{ taxableAnnualIncome: 125_000, repayments: 8966.253599999998 },
{ taxableAnnualIncome: 130_000, repayments: 9416.2536 },
{ taxableAnnualIncome: 145_000, repayments: 10766.2536 },
{ taxableAnnualIncome: 160_000, repayments: 12116.2536 },
{ taxableAnnualIncome: 175_000, repayments: 13466.253599999998 },
{ taxableAnnualIncome: 200_000, repayments: 15716.253599999998 },
{ taxableAnnualIncome: 250_000, repayments: 20216.253599999996 },
{ taxableAnnualIncome: 500_000, repayments: 42716.253600000004 },
{ taxableAnnualIncome: 1_000_000, repayments: 87716.2536 },
];

describe("Plan 4 loans", () => {
expectationsPlan4.forEach((expectation) => {
const { taxableAnnualIncome, repayments } = expectation;
test(taxableAnnualIncome.toString(), () => {
expect(
calculateStudentLoanRepayments({
taxYear: "2022/23",
taxableAnnualIncome,
studentLoanPlanNo: 4,
})
).toEqual(repayments);
});
});
});

const expectationsPlan5 = [
{ taxableAnnualIncome: 15_000, repayments: 0 },
{ taxableAnnualIncome: 17_500, repayments: 0 },
{ taxableAnnualIncome: 20_000, repayments: 0 },
{ taxableAnnualIncome: 22_500, repayments: 0 },
{ taxableAnnualIncome: 25_000, repayments: 3.6000000000000205 },
{ taxableAnnualIncome: 50_000, repayments: 2253.6 },
{ taxableAnnualIncome: 55_000, repayments: 2703.5999999999995 },
{ taxableAnnualIncome: 60_000, repayments: 3153.6 },
{ taxableAnnualIncome: 75_000, repayments: 4503.599999999999 },
{ taxableAnnualIncome: 90_000, repayments: 5853.599999999999 },
{ taxableAnnualIncome: 110_000, repayments: 7653.599999999999 },
{ taxableAnnualIncome: 120_000, repayments: 8553.6 },
{ taxableAnnualIncome: 124_500, repayments: 8958.599999999999 },
{ taxableAnnualIncome: 125_000, repayments: 9003.599999999999 },
{ taxableAnnualIncome: 130_000, repayments: 9453.599999999999 },
{ taxableAnnualIncome: 145_000, repayments: 10803.6 },
{ taxableAnnualIncome: 160_000, repayments: 12153.6 },
{ taxableAnnualIncome: 175_000, repayments: 13503.599999999999 },
{ taxableAnnualIncome: 200_000, repayments: 15753.599999999999 },
{ taxableAnnualIncome: 250_000, repayments: 20253.6 },
{ taxableAnnualIncome: 500_000, repayments: 42753.6 },
{ taxableAnnualIncome: 1_000_000, repayments: 87753.59999999999 },
];

describe("Plan 5 loans", () => {
expectationsPlan5.forEach((expectation) => {
const { taxableAnnualIncome, repayments } = expectation;
test(taxableAnnualIncome.toString(), () => {
expect(
calculateStudentLoanRepayments({
taxYear: "2022/23",
taxableAnnualIncome,
studentLoanPlanNo: 5,
})
).toEqual(repayments);
});
});
});

const expectationsPostgrad = [
{ taxableAnnualIncome: 15_000, repayments: 0 },
{ taxableAnnualIncome: 17_500, repayments: 0 },
{ taxableAnnualIncome: 20_000, repayments: 0 },
{ taxableAnnualIncome: 22_500, repayments: 90.01920000000004 },
{ taxableAnnualIncome: 25_000, repayments: 240.0192000000001 },
{ taxableAnnualIncome: 50_000, repayments: 1740.0192 },
{ taxableAnnualIncome: 55_000, repayments: 2040.0192000000002 },
{ taxableAnnualIncome: 60_000, repayments: 2340.0192 },
{ taxableAnnualIncome: 75_000, repayments: 3240.0192 },
{ taxableAnnualIncome: 90_000, repayments: 4140.019200000001 },
{ taxableAnnualIncome: 110_000, repayments: 5340.0192 },
{ taxableAnnualIncome: 120_000, repayments: 5940.0192 },
{ taxableAnnualIncome: 124_500, repayments: 6210.0192 },
{ taxableAnnualIncome: 125_000, repayments: 6240.0192 },
{ taxableAnnualIncome: 130_000, repayments: 6540.019199999999 },
{ taxableAnnualIncome: 145_000, repayments: 7440.019199999999 },
{ taxableAnnualIncome: 160_000, repayments: 8340.019199999999 },
{ taxableAnnualIncome: 175_000, repayments: 9240.019199999999 },
{ taxableAnnualIncome: 200_000, repayments: 10740.019199999999 },
{ taxableAnnualIncome: 250_000, repayments: 13740.019199999999 },
{ taxableAnnualIncome: 500_000, repayments: 28740.0192 },
{ taxableAnnualIncome: 1_000_000, repayments: 58740.01919999999 },
];

describe("Postgrad loans", () => {
expectationsPostgrad.forEach((expectation) => {
const { taxableAnnualIncome, repayments } = expectation;
test(taxableAnnualIncome.toString(), () => {
expect(
calculateStudentLoanRepayments({
taxYear: "2022/23",
taxableAnnualIncome,
studentLoanPlanNo: "postgrad",
})
).toEqual(repayments);
});
});
});
});
Loading

0 comments on commit 23d23ee

Please sign in to comment.