DEV Community

Cover image for Building a UK tax and pension calculator that shows its sources
NHS Pay Scale
NHS Pay Scale

Posted on

Building a UK tax and pension calculator that shows its sources

The problem: one job title, four pay systems

NHS pay looks like a single national table. It is not. Agenda for Change staff are paid under separate awards in England, Scotland, Wales and Northern Ireland — different circulars, sometimes different effective dates, and in Scotland a different standard working week, which changes every hourly rate even when the annual salary looks similar.

So a take-home calculator for NHS staff cannot be a generic UK salary calculator with an NHS label. The data model has to treat nation, pay year, band and step as first-class, versioned inputs — because a nurse checking a historic year, or a back-pay query that reaches back two awards, needs the table as it was then, not as it is now.

The live tool this article is about is at https://nhspayscale.co.uk/

Data model: bands, steps and years as versioned tables

Each pay year is stored as its own table per nation: band, step, annual salary, and the source document that published it. Nothing is overwritten when a new award lands — a new year is added, the old one stays. That is what makes historic checks and back-pay arithmetic possible, and it is also what makes corrections honest: if a figure changes, the change, the date and the reason go in a public change log rather than being silently edited.

Every figure carries its provenance with it. If a number cannot be traced to a named circular or pay document, it does not go in.

The order of operations is the whole game

Most disagreements between pay calculators are not about rates. They are about sequence. Apply the same rules in a different order and you get a different net figure. The engine therefore fixes one explicit order, in summary:

  1. Start with basic pay for the band and step, pro-rated by contracted hours against that nation's standard week.
  2. Add the High Cost Area Supplement where it applies (England only) — calculated on full-time basic pay, clamped to its published minimum and maximum, rounded, and only then pro-rated.
  3. Add unsocial-hours enhancements for the hours actually worked at those times, then any other pay the user enters.
  4. Take salary sacrifice off before the other deductions, because it reduces both gross and pensionable pay.
  5. Work out the pension contribution, then income tax on pay after pension, then National Insurance on gross pay without deducting pension, then student-loan repayments above the relevant plan thresholds.

Two of those points are where simpler calculators most often go wrong, and both are worth spelling out for anyone building something similar:

  • Pension does not reduce National Insurance. Contributions are deducted before income tax under a net pay arrangement, so tax relief is immediate — but NI (and student loans, which use the same base) are charged on gross pay regardless. Deduct pension before NI and you will overstate everyone's take-home.
  • NI is per pay period, tax is cumulative. Modelling NI as a simple annual calculation leaves a small, systematic error in every monthly figure. The calculator works on the monthly basis a payslip actually uses.

The pension tier trap for part-time staff

Pension contribution tiers are set by a member's actual pensionable pay — a rule that changed on 1 October 2022. The older approach, using the whole-time-equivalent salary of the post, pushes part-time staff into a higher contribution tier than they should be in, and it is still described incorrectly in a lot of places.

There is a second complication: three different administrators run three different tier tables (for England & Wales, Scotland, and Northern Ireland), so the calculator has to select the right table from the nation, not just the right rate from the salary.

Small rounding choices, checked against someone else's arithmetic

Some details only surface when you test against independently produced numbers. The London supplement is a good example: the published figure is a whole-pound amount rounded up, not plainly rounded. Checking that one choice against the full set of published basic-plus-supplement totals reproduced every one; plain rounding missed dozens of them by £1. That kind of cross-check — our arithmetic against totals the publisher produced by their own route — is worth more than any number of internal unit tests, and the engine is also run against a set of hand-worked cases.

Other edge cases the model has to state rather than hide: tax years where NI rates changed mid-year (handled as a weighted blend, and flagged as an approximation, because real payroll assesses each period separately), and hourly rates derived from the annual figure using each nation's standard week (52.143 weeks), which is why Scotland's hourly rates look high relative to its annual salaries.

Publishing the assumptions is part of the product

A calculator that hides its assumptions asks to be trusted. This one publishes them instead: the standard tax code it assumes, that pay is spread evenly across twelve months, that salary sacrifice and P11D benefits are not modelled unless entered, what is deliberately left out (emergency tax codes, added pension purchases, bank shifts at plain time, local deductions like parking and union subscriptions, and similar), and how rounding is handled (full precision internally, rounded only for display).

That page is not documentation for its own sake. When a user's payslip differs, the assumptions list is what lets them — and us — find which input is different, instead of arguing about whose calculator is "right".

What I would do the same again

  • Version the source data from day one, and never overwrite a published year.
  • Fix the deduction order in one place, document it, and test against it.
  • Store provenance with every figure, and keep a public corrections log.
  • Test against independently published totals wherever they exist, not only against your own expected outputs.
  • Write the assumptions page before anyone asks for it.

If you build calculators in a domain where the inputs are set by someone else — tax, benefits, regulated tariffs, anything with circulars and effective dates — I suspect most of this transfers. The numbers are the easy part. The trust is in the sequence, the sources and the corrections.

Top comments (0)