Estimate Agenda for Change take-home using official Scotland cash points (not England scales) plus Scottish Income Tax — with a side-by-side England comparator.
Scotland figures from Scottish Government NHS pay rates (2026/27). No London HCAS in Scotland.
Scotland estimated net monthly
£0.00/mo
The Scotland tax calculator compares Scottish vs rUK tax on the same (usually England) gross. This page uses official Scotland AfC cash points first, then applies Scottish tax — so Band 5 entry is £34,544, not £32,073.
Scotland AfC cash: Scottish Government NHS pay rates 2026/27 (+3.75% from 1 April 2026). Tax: Scottish Income Tax bands 2026/27. NI and NHS Pension tiers UK-wide. England comparator uses NHS Employers England AfC 2026/27 nearest pay point with rUK tax. Estimate only — your Board payslip is authoritative.
(function (root, factory) {
var eng = factory();
if (typeof module !== "undefined" && module.exports) {
module.exports = eng;
}
if (root) {
root.NHSPayEngine = eng;
}
if (typeof window !== "undefined") {
window.NHSPayEngine = eng;
}
})(typeof self !== "undefined" ? self : this, function () {
"use strict";
function clone(o) {
return JSON.parse(JSON.stringify(o));
}
function getPensionRate(rates, pensionablePay) {
for (var i = 0; i < rates.pension_tiers.length; i++) {
var t = rates.pension_tiers[i];
if (t.up_to === null || pensionablePay <= t.up_to) return t.rate;
}
return 0.125;
}
function calculateHCAS(rates, basicSalary, zone) {
var h = rates.hcas[zone] || rates.hcas.none;
if (!h || !h.rate) return 0;
var amount = basicSalary * h.rate;
if (amount < h.min) amount = h.min;
if (amount > h.max) amount = h.max;
return amount;
}
function calculateTax(rates, taxableIncome, region) {
var pa = rates.tax.personal_allowance;
if (taxableIncome > rates.tax.pa_taper_start) {
var excess = taxableIncome - rates.tax.pa_taper_start;
pa = Math.max(0, pa - Math.floor(excess / 2));
}
var incomeToTax = Math.max(0, taxableIncome - pa);
if (incomeToTax <= 0) return 0;
if (region === "scotland") {
var tax = 0;
var prev = pa;
var bands = rates.tax.scotland_bands;
var remaining = taxableIncome;
for (var i = 0; i < bands.length; i++) {
var band = bands[i];
var bandMax = band.limit === null ? Infinity : band.limit;
if (taxableIncome > prev) {
var width = bandMax - prev;
var taxableInBand = Math.min(taxableIncome - prev, width);
if (taxableInBand > 0) {
tax += taxableInBand * band.rate;
prev += taxableInBand;
}
}
}
return tax;
}
var eng = rates.tax.england;
var rem = incomeToTax;
var out = 0;
var basic = Math.min(rem, eng.basic_width);
out += basic * eng.basic_rate;
rem -= basic;
if (rem > 0) {
var higher = Math.min(rem, eng.higher_width);
out += higher * eng.higher_rate;
rem -= higher;
if (rem > 0) out += rem * eng.additional_rate;
}
return out;
}
function calculateNI(rates, grossPay) {
var pt = rates.ni.primary_threshold;
var uel = rates.ni.upper_earnings_limit;
if (grossPay <= pt) return 0;
var ni = 0;
ni += (Math.min(grossPay, uel) - pt) * rates.ni.main_rate;
if (grossPay > uel) ni += (grossPay - uel) * rates.ni.upper_rate;
return ni;
}
function calculateStudentLoan(rates, grossPay, plan, postgrad) {
var repayment = 0;
if (plan && plan !== "none" && rates.student_loans[plan]) {
var p = rates.student_loans[plan];
if (grossPay > p.threshold) repayment += (grossPay - p.threshold) * p.rate;
}
if (postgrad && rates.student_loans.postgrad) {
var pg = rates.student_loans.postgrad;
if (grossPay > pg.threshold) repayment += (grossPay - pg.threshold) * pg.rate;
}
return repayment;
}
function weeklyHours(rates, region) {
var m = rates.afc_unsocial.full_time_weekly_hours;
return m[region] || m.england;
}
function calculateAfC(rates, inputs) {
var basicSalary = Number(inputs.basicSalary) || 0;
var fte = Math.min(1, Math.max(0.1, Number(inputs.fte) || 1));
var hcasZone = inputs.hcasZone || "none";
var pensionEnabled = inputs.pensionEnabled !== false;
var region = inputs.region || "england";
var studentLoan = inputs.studentLoan || "none";
var postgradLoan = !!inputs.postgradLoan;
var nightHoursPerWeek = Number(inputs.nightHoursPerWeek) || 0;
var satHoursPerWeek = Number(inputs.satHoursPerWeek) || 0;
var sunHoursPerWeek = Number(inputs.sunHoursPerWeek) || 0;
var unsocialEligible = inputs.unsocialEligible !== false;
var weeks = rates.afc_unsocial.weeks_per_year;
var stdHours = weeklyHours(rates, region);
var annualHours = stdHours * weeks;
var hourly = basicSalary / annualHours;
var proRataBasic = basicSalary * fte;
var hcasAmount = calculateHCAS(rates, basicSalary, hcasZone) * fte;
var unsocial = 0;
if (unsocialEligible) {
var u = rates.afc_unsocial;
unsocial =
(nightHoursPerWeek * hourly * u.night_sat +
satHoursPerWeek * hourly * u.night_sat +
sunHoursPerWeek * hourly * u.sunday_bh) *
weeks *
fte;
}
var totalGross = proRataBasic + hcasAmount + unsocial;
var pensionable = proRataBasic + hcasAmount;
var pensionRate = 0;
var pensionAmount = 0;
if (pensionEnabled) {
pensionRate = getPensionRate(rates, pensionable);
pensionAmount = pensionable * pensionRate;
}
var taxable = Math.max(0, totalGross - pensionAmount);
var tax = calculateTax(rates, taxable, region);
var ni = calculateNI(rates, totalGross);
var student = calculateStudentLoan(rates, totalGross, studentLoan, postgradLoan);
var deductions = pensionAmount + tax + ni + student;
var net = totalGross - deductions;
var contractedAnnualHours = annualHours * fte;
var notes = [];
if (hcasAmount > 0) {
notes.push("HCAS is pensionable and taxable, so net uplift is less than the gross supplement.");
var baseRate = getPensionRate(rates, proRataBasic);
if (pensionEnabled && pensionRate > baseRate + 0.0001) {
notes.push(
"Adding HCAS moved you into a higher NHS pension tier (" +
(baseRate * 100).toFixed(1) +
"% → " +
(pensionRate * 100).toFixed(1) +
"%)."
);
}
}
return {
gross: {
basic: proRataBasic,
hcas: hcasAmount,
unsocial: unsocial,
total: totalGross,
pensionable: pensionable,
},
deductions: {
pension: pensionAmount,
pensionRate: pensionRate,
tax: tax,
ni: ni,
studentLoan: student,
total: deductions,
},
net: {
annual: net,
monthly: net / 12,
weekly: net / weeks,
hourly: contractedAnnualHours > 0 ? net / contractedAnnualHours : 0,
},
meta: {
basicHourly: hourly,
weeklyHours: stdHours,
fte: fte,
region: region,
hcasZone: hcasZone,
notes: notes,
},
};
}
function calculateJuniorDoctor(rates, inputs) {
var jd = rates.junior_doctor;
var grade = inputs.grade || "fy1";
var gradeObj = null;
for (var i = 0; i < jd.grades.length; i++) {
if (jd.grades[i].id === grade) gradeObj = jd.grades[i];
}
if (!gradeObj) gradeObj = jd.grades[0];
var ltft = Math.min(1, Math.max(0.2, Number(inputs.ltft) || 1));
var weeklyHours = Math.max(0, Number(inputs.weeklyHours) || 40);
var nightHours = Math.max(0, Number(inputs.nightHoursPerWeek) || 0);
var weekendKey = inputs.weekendFrequency || "none";
var nroc = !!inputs.nroc;
var london = inputs.london || "none";
var pensionEnabled = inputs.pensionEnabled !== false;
var region = inputs.region || "england";
var studentLoan = inputs.studentLoan || "none";
var postgradLoan = !!inputs.postgradLoan;
var basicFull = gradeObj.salary;
var basic = basicFull * ltft;
var hourly = basicFull / (jd.full_time_weekly_hours * rates.afc_unsocial.weeks_per_year);
var arhWeekly = Math.max(0, weeklyHours - jd.full_time_weekly_hours);
var arhAnnual = arhWeekly * hourly * rates.afc_unsocial.weeks_per_year * ltft;
var nightAnnual =
nightHours * hourly * jd.night_enhancement * rates.afc_unsocial.weeks_per_year * ltft;
var weekendPct = jd.weekend_allowance[weekendKey] || 0;
var weekendAnnual = basicFull * weekendPct * ltft;
var nrocAnnual = nroc ? basicFull * jd.nroc_availability * ltft : 0;
var londonAnnual = (jd.london_flat[london] || 0) * ltft;
var totalGross = basic + arhAnnual + nightAnnual + weekendAnnual + nrocAnnual + londonAnnual;
var pensionable = basic + weekendAnnual + nrocAnnual + londonAnnual;
var pensionRate = 0;
var pensionAmount = 0;
if (pensionEnabled) {
pensionRate = getPensionRate(rates, pensionable);
pensionAmount = pensionable * pensionRate;
}
var taxable = Math.max(0, totalGross - pensionAmount);
var tax = calculateTax(rates, taxable, region);
var ni = calculateNI(rates, totalGross);
var student = calculateStudentLoan(rates, totalGross, studentLoan, postgradLoan);
var deductions = pensionAmount + tax + ni + student;
var net = totalGross - deductions;
return {
lines: {
basic: basic,
additionalHours: arhAnnual,
nightEnhancement: nightAnnual,
weekendAllowance: weekendAnnual,
nroc: nrocAnnual,
london: londonAnnual,
},
gross: { total: totalGross, pensionable: pensionable },
deductions: {
pension: pensionAmount,
pensionRate: pensionRate,
tax: tax,
ni: ni,
studentLoan: student,
total: deductions,
},
net: { annual: net, monthly: net / 12 },
meta: { grade: gradeObj, ltft: ltft, weeklyHours: weeklyHours, notes: [
"England 2016 resident doctor contract estimate. Actual rota pay varies by work schedule.",
]},
};
}
function calculateConsultant(rates, inputs) {
var c = rates.consultant;
var thrId = inputs.thresholdId || "y1";
var thr = c.thresholds[0];
for (var i = 0; i < c.thresholds.length; i++) {
if (c.thresholds[i].id === thrId) thr = c.thresholds[i];
}
var pas = Math.max(1, Number(inputs.pas) || 10);
var cea = Math.max(0, Number(inputs.cea) || 0);
var onCallKey = inputs.onCall || "none";
var london = inputs.london || "none";
var pensionEnabled = inputs.pensionEnabled !== false;
var region = inputs.region || "england";
var studentLoan = inputs.studentLoan || "none";
var postgradLoan = !!inputs.postgradLoan;
var basic = thr.salary * (pas / c.standard_pas);
var onCallPct = c.on_call[onCallKey] || 0;
var onCall = thr.salary * onCallPct * (pas / c.standard_pas);
var londonAmt = c.london_flat[london] || 0;
var totalGross = basic + onCall + cea + londonAmt;
var pensionable = basic + onCall + londonAmt;
var pensionRate = 0;
var pensionAmount = 0;
if (pensionEnabled) {
pensionRate = getPensionRate(rates, pensionable);
pensionAmount = pensionable * pensionRate;
}
var taxable = Math.max(0, totalGross - pensionAmount);
var tax = calculateTax(rates, taxable, region);
var ni = calculateNI(rates, totalGross);
var student = calculateStudentLoan(rates, totalGross, studentLoan, postgradLoan);
var deductions = pensionAmount + tax + ni + student;
var net = totalGross - deductions;
return {
gross: { basic: basic, onCall: onCall, cea: cea, london: londonAmt, total: totalGross, pensionable: pensionable },
deductions: {
pension: pensionAmount,
pensionRate: pensionRate,
tax: tax,
ni: ni,
studentLoan: student,
total: deductions,
},
net: { annual: net, monthly: net / 12 },
meta: { threshold: thr, pas: pas },
};
}
function calculateGP(rates, inputs) {
var gp = rates.gp;
var sessions = Math.max(1, Number(inputs.sessions) || 6);
var rate = Math.max(gp.rate_min, Number(inputs.ratePerSession) || gp.default_rate_per_session);
var mode = inputs.mode || "salaried";
var pensionEnabled = inputs.pensionEnabled !== false;
var region = inputs.region || "england";
var studentLoan = inputs.studentLoan || "none";
var postgradLoan = !!inputs.postgradLoan;
var basic = sessions * rate;
if (mode === "partner") {
basic = basic * 1.15;
}
var totalGross = basic;
var pensionable = basic;
var pensionRate = 0;
var pensionAmount = 0;
if (pensionEnabled) {
pensionRate = getPensionRate(rates, pensionable);
pensionAmount = pensionable * pensionRate;
}
var taxable = Math.max(0, totalGross - pensionAmount);
var tax = calculateTax(rates, taxable, region);
var ni = calculateNI(rates, totalGross);
var student = calculateStudentLoan(rates, totalGross, studentLoan, postgradLoan);
var deductions = pensionAmount + tax + ni + student;
var net = totalGross - deductions;
var weeklyHours = sessions * gp.session_hours;
return {
gross: { basic: basic, total: totalGross, pensionable: pensionable },
deductions: {
pension: pensionAmount,
pensionRate: pensionRate,
tax: tax,
ni: ni,
studentLoan: student,
total: deductions,
},
net: { annual: net, monthly: net / 12, hourly: weeklyHours > 0 ? net / (weeklyHours * rates.afc_unsocial.weeks_per_year) : 0 },
meta: {
sessions: sessions,
ratePerSession: rate,
weeklyHours: weeklyHours,
mode: mode,
notes: mode === "partner" ? [gp.partner_estimate_note] : [],
},
};
}
function bikPct(rates, co2, fuel) {
if (fuel === "electric") return rates.bik_cars.electric_pct;
var bands = rates.bik_cars.co2_bands;
var pct = bands[bands.length - 1].pct;
for (var i = 0; i < bands.length; i++) {
if (bands[i].up_to === null || co2 <= bands[i].up_to) {
pct = bands[i].pct;
break;
}
}
if (fuel === "diesel") pct = Math.min(0.37, pct + rates.bik_cars.diesel_surcharge);
return pct;
}
function calculateOvertime(rates, inputs) {
var basic = Number(inputs.basicSalary) || 0;
var region = inputs.region || "england";
var hours = Number(inputs.hours) || 0;
var kind = inputs.kind || "weekday";
var std = weeklyHours(rates, region);
var hourly = basic / (std * rates.afc_unsocial.weeks_per_year);
var mult = rates.overtime[kind] || 1;
var gross = hours * hourly * mult;
var base = calculateAfC(rates, {
basicSalary: basic,
fte: Number(inputs.fte) || 1,
hcasZone: inputs.hcasZone || "none",
pensionEnabled: inputs.pensionEnabled !== false,
region: region,
studentLoan: inputs.studentLoan || "none",
postgradLoan: !!inputs.postgradLoan,
unsocialEligible: false,
});
var taxRate = basic > 50270 ? 0.4 : 0.2;
var niRate = basic > rates.ni.upper_earnings_limit ? rates.ni.upper_rate : rates.ni.main_rate;
var net = gross * (1 - taxRate - niRate);
return {
hourly: hourly,
multiplier: mult,
overtimeGross: gross,
netUplift: net,
netUpliftMonthly: net,
base: base,
notes: [rates.overtime.note],
};
}
function calculateStrike(rates, inputs) {
var basic = Number(inputs.basicSalary) || 0;
var days = Math.max(0, Number(inputs.strikeDays) || 0);
var region = inputs.region || "england";
var std = weeklyHours(rates, region);
var daily = basic / ((std * rates.afc_unsocial.weeks_per_year) / 5);
var grossLoss = daily * days;
var full = calculateAfC(rates, {
basicSalary: basic,
fte: Number(inputs.fte) || 1,
hcasZone: inputs.hcasZone || "none",
pensionEnabled: inputs.pensionEnabled !== false,
region: region,
studentLoan: inputs.studentLoan || "none",
unsocialEligible: false,
});
var taxRate = basic > 50270 ? 0.4 : 0.2;
var niRate = basic > rates.ni.upper_earnings_limit ? rates.ni.upper_rate : rates.ni.main_rate;
var pensionDaily = (full.deductions.pension / rates.afc_unsocial.weeks_per_year) / 5;
var netLoss = Math.max(0, grossLoss * (1 - taxRate - niRate) - pensionDaily * days * 0);
netLoss = grossLoss - grossLoss * taxRate - grossLoss * niRate;
return {
dailyGross: daily,
grossLoss: grossLoss,
netLoss: netLoss,
full: full,
notes: ["Strike deductions are usually unpaid leave at daily rate; tax/NI effects mean net loss is less than gross."],
};
}
function calculateBradford(rates, inputs) {
var spells = Math.max(0, Number(inputs.spells) || 0);
var days = Math.max(0, Number(inputs.days) || 0);
var score = spells * spells * days;
var label = rates.bradford.bands[rates.bradford.bands.length - 1].label;
var color = rates.bradford.bands[rates.bradford.bands.length - 1].color;
for (var i = 0; i < rates.bradford.bands.length; i++) {
var b = rates.bradford.bands[i];
if (b.up_to === null || score <= b.up_to) {
label = b.label;
color = b.color;
break;
}
}
return { score: score, spells: spells, days: days, label: label, color: color };
}
function calculateMileage(rates, inputs) {
var miles = Math.max(0, Number(inputs.miles) || 0);
var kind = inputs.kind || "standard";
var claim = 0;
if (kind === "standard") {
var first = Math.min(miles, rates.mileage.standard_threshold_miles);
var rest = Math.max(0, miles - rates.mileage.standard_threshold_miles);
claim = first * rates.mileage.standard_car_first + rest * rates.mileage.standard_car_after;
} else if (kind === "lease") claim = miles * rates.mileage.lease_car;
else if (kind === "motorcycle") claim = miles * rates.mileage.motorcycle;
else claim = miles * rates.mileage.bicycle;
return { miles: miles, claim: claim, kind: kind, monthly: claim / 12 };
}
function calculateRedundancy(rates, inputs) {
var weekly = Number(inputs.weeklyPay) || 0;
if (!weekly && inputs.basicSalary) {
weekly = Number(inputs.basicSalary) / rates.afc_unsocial.weeks_per_year;
}
var years = Math.min(rates.redundancy_afc.max_years, Math.max(0, Number(inputs.years) || 0));
var weeks = years * rates.redundancy_afc.weeks_per_year_service;
var gross = weekly * weeks;
return {
weekly: weekly,
years: years,
weeks: weeks,
gross: gross,
notes: [rates.redundancy_afc.ready_reckoner_note],
};
}
function calculateMARS(rates, inputs) {
var weekly = Number(inputs.weeklyPay) || 0;
if (!weekly && inputs.basicSalary) weekly = Number(inputs.basicSalary) / rates.afc_unsocial.weeks_per_year;
var years = Math.max(0, Number(inputs.years) || 0);
var weeks = Math.min(rates.mars.max_weeks, years * rates.mars.weeks_per_year_service);
return { weekly: weekly, years: years, weeks: weeks, gross: weekly * weeks, notes: [rates.mars.note] };
}
function calculateClawback(rates, inputs) {
var packageGross = Number(inputs.packageGross) || 0;
var monthsProtected = Number(inputs.monthsProtected) || rates.clawback.default_months;
var monthsBack = Math.max(0, Number(inputs.monthsBack) || 0);
var repay = 0;
if (monthsBack < monthsProtected && monthsProtected > 0) {
repay = packageGross * ((monthsProtected - monthsBack) / monthsProtected);
}
return {
packageGross: packageGross,
monthsProtected: monthsProtected,
monthsBack: monthsBack,
repay: repay,
notes: [rates.clawback.note],
};
}
function calculateBIK(rates, inputs) {
var listPrice = Number(inputs.listPrice) || 0;
var co2 = Number(inputs.co2) || 120;
var fuel = inputs.fuel || "petrol";
var salary = Number(inputs.basicSalary) || 0;
var leaseMonthly = Number(inputs.leaseMonthly) || 0;
var region = inputs.region || "england";
var pct = bikPct(rates, co2, fuel);
var bik = listPrice * pct;
var taxRate = 0.2;
if (salary > 50270) taxRate = 0.4;
if (salary > 125140) taxRate = 0.45;
var taxCost = bik * taxRate;
var sacrificed = leaseMonthly * 12;
var without = calculateAfC(rates, {
basicSalary: salary,
pensionEnabled: inputs.pensionEnabled !== false,
region: region,
studentLoan: "none",
unsocialEligible: false,
});
var withSac = calculateAfC(rates, {
basicSalary: Math.max(0, salary - sacrificed),
pensionEnabled: inputs.pensionEnabled !== false,
region: region,
studentLoan: "none",
unsocialEligible: false,
});
var niSave = without.deductions.ni - withSac.deductions.ni;
var taxSave = without.deductions.tax - withSac.deductions.tax;
var netCost = sacrificed - taxSave - niSave + taxCost;
return {
bik: bik,
bikPct: pct,
taxCost: taxCost,
sacrificed: sacrificed,
taxSave: taxSave,
niSave: niSave,
netAnnualCost: netCost,
netMonthlyCost: netCost / 12,
notes: ["BIK uses HMRC CO2 percentage bands; salary sacrifice changes pensionable pay."],
};
}
function calculateSalarySacrifice(rates, inputs) {
var salary = Number(inputs.basicSalary) || 0;
var amount = Number(inputs.sacrificeAnnual) || 0;
var region = inputs.region || "england";
var without = calculateAfC(rates, {
basicSalary: salary,
pensionEnabled: inputs.pensionEnabled !== false,
region: region,
studentLoan: inputs.studentLoan || "none",
unsocialEligible: false,
});
var withSac = calculateAfC(rates, {
basicSalary: Math.max(0, salary - amount),
pensionEnabled: inputs.pensionEnabled !== false,
region: region,
studentLoan: inputs.studentLoan || "none",
unsocialEligible: false,
});
return {
sacrifice: amount,
taxSave: without.deductions.tax - withSac.deductions.tax,
niSave: without.deductions.ni - withSac.deductions.ni,
pensionChange: without.deductions.pension - withSac.deductions.pension,
netCost: amount - (without.deductions.tax - withSac.deductions.tax) - (without.deductions.ni - withSac.deductions.ni),
without: without,
withSacrifice: withSac,
};
}
function calculatePaternity(rates, inputs) {
var salary = Number(inputs.basicSalary) || 0;
var weeks = Math.min(rates.leave.paternity_weeks, Math.max(1, Number(inputs.weeks) || 2));
var occupational = inputs.occupational !== false;
var weekly = salary / rates.afc_unsocial.weeks_per_year;
var statutory = Math.min(weekly, rates.leave.statutory_paternity_weekly) * weeks;
var occ = occupational ? weekly * weeks : statutory;
var taxApprox = occ * 0.2;
var niApprox = Math.max(0, occ - rates.ni.primary_threshold / rates.afc_unsocial.weeks_per_year * weeks) * rates.ni.main_rate;
return {
weeks: weeks,
weekly: weekly,
gross: occ,
statutory: statutory,
netApprox: occ - taxApprox - Math.max(0, niApprox),
notes: ["Occupational paternity pay is trust-dependent; statutory floor applied when occupational is off."],
};
}
function calculateMaternity(rates, inputs) {
var salary = Number(inputs.basicSalary) || 0;
var weeksOff = Math.max(1, Number(inputs.weeks) || 39);
var weekly = salary / rates.afc_unsocial.weeks_per_year;
var leave = rates.leave;
var fullWeeks = Math.min(weeksOff, leave.osp_weeks_full);
var halfWeeks = Math.min(Math.max(0, weeksOff - fullWeeks), leave.osp_weeks_half);
var smpLowerWeeks = Math.max(0, weeksOff - leave.smp_weeks_full);
var osp = weekly * fullWeeks + weekly * 0.5 * halfWeeks;
var smp = weekly * Math.min(weeksOff, leave.smp_weeks_full) + Math.min(weekly, leave.smp_lower_weekly) * Math.min(smpLowerWeeks, leave.smp_weeks_lower);
var chosen = Math.max(osp, smp);
return {
weekly: weekly,
weeks: weeksOff,
osp: osp,
smp: smp,
estimatedGross: chosen,
notes: ["Compares illustrative AfC occupational maternity with statutory maternity pay over the weeks selected."],
};
}
function calculateBuyLeave(rates, inputs) {
var salary = Number(inputs.basicSalary) || 0;
var days = Math.max(0, Number(inputs.days) || 0);
var fte = Number(inputs.fte) || 1;
var region = inputs.region || "england";
var std = weeklyHours(rates, region);
var daily = salary / ((std * rates.afc_unsocial.weeks_per_year) / 5);
var cost = daily * days * fte;
var withBuy = calculateSalarySacrifice(rates, {
basicSalary: salary,
sacrificeAnnual: cost,
pensionEnabled: inputs.pensionEnabled !== false,
region: region,
});
return { days: days, daily: daily, grossCost: cost, netCost: withBuy.netCost, detail: withBuy };
}
function calculateBankHolidayPT(rates, inputs) {
var salary = Number(inputs.basicSalary) || 0;
var fte = Number(inputs.fte) || 0.6;
var bh = Number(inputs.bankHolidays) || rates.leave.bank_holidays_england;
var worksBH = !!inputs.worksBankHolidays;
var region = inputs.region || "england";
var std = weeklyHours(rates, region);
var daily = salary / ((std * rates.afc_unsocial.weeks_per_year) / 5);
var entitlement = bh * fte * daily;
var workedEnh = worksBH ? daily * rates.overtime.sunday_bh : 0;
return {
fte: fte,
daily: daily,
proRataEntitlement: entitlement,
ifWorkedEnhancement: workedEnh,
notes: ["Part-time bank holiday entitlement is usually pro-rata; working a BH often attracts enhanced rates."],
};
}
function calculatePensionCompare(rates, inputs) {
var salary = Number(inputs.pensionablePay) || 0;
var years = Math.max(0, Number(inputs.years) || 0);
var out = {};
var keys = ["s1995", "s2008", "s2015"];
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
var s = rates.pension_schemes[k];
var pension = salary * s.accrual * years;
var lump = pension * (s.lump_sum_factor || 0);
out[k] = { label: s.label, annualPension: pension, lumpSum: lump, npa: s.npa, accrual: s.accrual };
}
return { salary: salary, years: years, schemes: out };
}
function calculateAnnualAllowance(rates, inputs) {
var opening = Number(inputs.openingValue) || 0;
var closing = Number(inputs.closingValue) || 0;
var thresholdIncome = Number(inputs.thresholdIncome) || 0;
var growth = Math.max(0, closing - opening);
var aa = rates.annual_allowance.standard;
if (thresholdIncome > rates.annual_allowance.taper_start) {
var taper = Math.floor((thresholdIncome - rates.annual_allowance.taper_start) / 2);
aa = Math.max(rates.annual_allowance.min_tapered, aa - taper);
}
var excess = Math.max(0, growth - aa);
return { growth: growth, allowance: aa, excess: excess, taxCharge: excess * 0.4, notes: ["Illustrative CARE growth vs annual allowance; CPI uplift omitted."] };
}
function calculateAdditionalPension(rates, inputs) {
var extra = Number(inputs.extraAnnual) || 0;
var cost = (extra / 100) * rates.additional_pension.cost_per_100_pa * 100;
return { extraAnnual: extra, estimatedCost: cost, notes: [rates.additional_pension.note] };
}
function calculatePensionRefund(rates, inputs) {
var contributions = Number(inputs.contributions) || 0;
var years = Number(inputs.years) || 0;
var eligible = years < 2;
var cep = contributions * 0.3;
var taxable = Math.max(0, contributions - cep);
var tax = eligible ? taxable * 0.2 : 0;
var net = eligible ? contributions - cep - tax : 0;
return {
eligible: eligible,
contributions: contributions,
cep: eligible ? cep : 0,
tax: tax,
net: net,
notes: eligible
? ["Refunds generally only apply with under 2 years' membership; CEP and tax may apply."]
: ["With 2+ years you usually keep deferred benefits instead of a refund."],
};
}
function calculateOptOut(rates, inputs) {
var salary = Number(inputs.basicSalary) || 0;
var region = inputs.region || "england";
var withP = calculateAfC(rates, {
basicSalary: salary,
fte: Number(inputs.fte) || 1,
hcasZone: inputs.hcasZone || "none",
pensionEnabled: true,
region: region,
studentLoan: inputs.studentLoan || "none",
unsocialEligible: false,
});
var without = calculateAfC(rates, {
basicSalary: salary,
fte: Number(inputs.fte) || 1,
hcasZone: inputs.hcasZone || "none",
pensionEnabled: false,
region: region,
studentLoan: inputs.studentLoan || "none",
unsocialEligible: false,
});
return {
monthlyGain: without.net.monthly - withP.net.monthly,
annualGain: without.net.annual - withP.net.annual,
pensionLost: withP.deductions.pension,
withPension: withP,
withoutPension: without,
notes: ["Opting out increases take-home now but loses employer contributions and death/ill-health cover."],
};
}
function calculateMcCloud(rates, inputs) {
var salary = Number(inputs.pensionablePay) || 0;
var yearsRemedy = Number(inputs.remedyYears) || 0;
var legacy = calculatePensionCompare(rates, { pensionablePay: salary, years: yearsRemedy }).schemes.s1995;
var reformed = calculatePensionCompare(rates, { pensionablePay: salary, years: yearsRemedy }).schemes.s2015;
return {
legacy: legacy,
reformed: reformed,
difference: reformed.annualPension - legacy.annualPension,
notes: ["Simplified remedy illustration comparing legacy 1995 accrual vs 2015 CARE for remedy years only."],
};
}
function calculateMortgage(rates, inputs) {
var income = Number(inputs.income) || 0;
var other = Number(inputs.otherIncome) || 0;
var joint = !!inputs.joint;
var multiple = joint ? rates.mortgage.income_multiple_joint : rates.mortgage.income_multiple_single;
var maxLoan = (income + other) * multiple;
var rate = Number(inputs.rate) || 0.045;
var years = Number(inputs.years) || 25;
var mRate = rate / 12;
var n = years * 12;
var payment = mRate === 0 ? maxLoan / n : (maxLoan * mRate * Math.pow(1 + mRate, n)) / (Math.pow(1 + mRate, n) - 1);
return { maxLoan: maxLoan, payment: payment, rate: rate, years: years, multiple: multiple };
}
function calculateUniform(rates, inputs) {
var role = inputs.role || "nurse";
var flat = role === "nurse" ? rates.uniform_tax_relief.flat_rate_nurse : rates.uniform_tax_relief.flat_rate_other;
var years = Math.max(1, Number(inputs.years) || 1);
var relief = flat * years;
var refund = relief * rates.uniform_tax_relief.tax_rate_basic;
return { flat: flat, years: years, relief: relief, refund: refund };
}
function calculateBursary(rates, inputs) {
var grant = Number(inputs.trainingGrant);
if (isNaN(grant)) grant = rates.bursary.training_grant_default;
var parental = Number(inputs.parental) || 0;
var travel = Number(inputs.travel) || rates.bursary.travel_default;
var total = grant + parental + travel;
return { grant: grant, parental: parental, travel: travel, total: total, monthly: total / 12 };
}
function calculateBackPay(rates, inputs) {
var oldSal = Number(inputs.oldSalary) || 0;
var newSal = Number(inputs.newSalary) || 0;
var months = Math.max(0, Number(inputs.months) || 0);
var region = inputs.region || "england";
var grossArrears = ((newSal - oldSal) / 12) * months;
var oldY = calculateAfC(rates, { basicSalary: oldSal, pensionEnabled: true, region: region, unsocialEligible: false });
var newY = calculateAfC(rates, { basicSalary: newSal, pensionEnabled: true, region: region, unsocialEligible: false });
var netMonthlyDiff = newY.net.monthly - oldY.net.monthly;
return {
grossArrears: grossArrears,
netArrearsApprox: netMonthlyDiff * months,
netMonthlyDiff: netMonthlyDiff,
notes: ["Net arrears approximated from full-year tax/NI/pension on old vs new salary."],
};
}
function calculatePayRise(rates, inputs) {
var oldSal = Number(inputs.oldSalary) || 0;
var pct = Number(inputs.pct);
if (isNaN(pct)) pct = rates.pay_award_2026_27.afc_pct;
var newSal = oldSal * (1 + pct);
var region = inputs.region || "england";
var before = calculateAfC(rates, {
basicSalary: oldSal,
hcasZone: inputs.hcasZone || "none",
pensionEnabled: true,
region: region,
unsocialEligible: false,
});
var after = calculateAfC(rates, {
basicSalary: newSal,
hcasZone: inputs.hcasZone || "none",
pensionEnabled: true,
region: region,
unsocialEligible: false,
});
return {
pct: pct,
oldSalary: oldSal,
newSalary: newSal,
grossUplift: newSal - oldSal,
netMonthlyUplift: after.net.monthly - before.net.monthly,
before: before,
after: after,
};
}
function calculateLocumVsPermanent(rates, inputs) {
var perm = Number(inputs.permSalary) || 0;
var locumDay = Number(inputs.locumDayRate) || 0;
var days = Number(inputs.daysPerYear) || 210;
var region = inputs.region || "england";
var permRes = calculateAfC(rates, {
basicSalary: perm,
pensionEnabled: true,
region: region,
unsocialEligible: false,
});
var locumGross = locumDay * days;
var locumTax = calculateTax(rates, locumGross, region);
var locumNi = calculateNI(rates, locumGross);
var locumNet = locumGross - locumTax - locumNi;
return {
perm: permRes,
locum: { gross: locumGross, tax: locumTax, ni: locumNi, net: locumNet, monthly: locumNet / 12 },
netDifference: locumNet - permRes.net.annual,
};
}
function calculateLocumVsUmbrella(rates, inputs) {
var dayRate = Number(inputs.dayRate) || 0;
var daysPerWeek = Number(inputs.daysPerWeek) || 5;
var weeks = Number(inputs.weeks) || 46;
var margin = Number(inputs.marginWeekly);
if (isNaN(margin)) margin = rates.umbrella.default_margin_weekly;
var assignment = dayRate * daysPerWeek * weeks;
var holiday = assignment * rates.umbrella.holiday_pct;
var employerNi = (assignment + holiday) * rates.umbrella.employer_ni_rate;
var afterCosts = assignment - margin * weeks - employerNi;
var taxable = afterCosts;
var tax = calculateTax(rates, taxable, inputs.region || "england");
var ni = calculateNI(rates, taxable);
var pension = taxable * rates.umbrella.pension_auto_enrol_pct;
var net = taxable - tax - ni - pension;
var ltdNet = assignment * 0.7;
return {
assignment: assignment,
umbrellaNet: net,
umbrellaMonthly: net / 12,
ltdIllustrative: ltdNet,
costs: { margin: margin * weeks, employerNi: employerNi, holiday: holiday, pension: pension, tax: tax, ni: ni },
notes: ["Umbrella model is illustrative; IR35 / umbrella contracts vary widely."],
};
}
function calculateInjury(rates, inputs) {
var specialDamages = Number(inputs.specialDamages) || 0;
var generalDamages = Number(inputs.generalDamages) || 0;
var care = Number(inputs.care) || 0;
var total = specialDamages + generalDamages + care;
return {
total: total,
specialDamages: specialDamages,
generalDamages: generalDamages,
care: care,
notes: ["Illustrative claim total only — not legal advice; Judicial College guidelines and case facts dominate."],
};
}
function unsocialRatesForBand(rates, band) {
var key = String(band || "5").replace(/^Bands+/i, "");
return rates.unsocial_by_band[key] || rates.unsocial_by_band["5"];
}
function calculateUnsocialDetailed(rates, inputs) {
var band = inputs.band || "5";
var basic = Number(inputs.basicSalary) || 0;
var region = inputs.region || "england";
var night = Number(inputs.nightHoursPerWeek) || 0;
var sat = Number(inputs.satHoursPerWeek) || 0;
var sun = Number(inputs.sunHoursPerWeek) || 0;
var weeks = Number(inputs.weeks) || rates.afc_unsocial.weeks_per_year;
var fte = Number(inputs.fte) || 1;
var std = weeklyHours(rates, region);
var hourly = basic / (std * rates.afc_unsocial.weeks_per_year);
var u = unsocialRatesForBand(rates, band);
var nightGross = night * hourly * u.night_sat * weeks * fte;
var satGross = sat * hourly * u.night_sat * weeks * fte;
var sunGross = sun * hourly * u.sunday_bh * weeks * fte;
var totalEnh = nightGross + satGross + sunGross;
var base = calculateAfC(rates, {
basicSalary: basic,
fte: fte,
region: region,
pensionEnabled: inputs.pensionEnabled !== false,
studentLoan: inputs.studentLoan || "none",
unsocialEligible: false,
});
var taxRate = basic > 50270 ? 0.4 : 0.2;
var niRate = basic > rates.ni.upper_earnings_limit ? rates.ni.upper_rate : rates.ni.main_rate;
var netEnh = totalEnh * (1 - taxRate - niRate);
return {
hourly: hourly,
rates: u,
nightGross: nightGross,
satGross: satGross,
sunGross: sunGross,
totalEnhancement: totalEnh,
netEnhancement: netEnh,
monthlyEnhancement: totalEnh / 12,
monthlyNet: netEnh / 12,
withBasicGross: basic * fte + totalEnh,
base: base,
notes: [
"AfC Section 2: one enhancement rate per hour (rates do not stack).",
"Weekday nights = 8pm–6am; Saturday / Sunday / BH = midnight to midnight.",
],
};
}
function calculateCommutation(rates, inputs) {
var pension = Number(inputs.annualPension) || 0;
var scheme = inputs.scheme || "s2015";
var targetLump = Number(inputs.targetLumpSum);
var c = rates.commutation;
var autoLump = scheme === "s1995" ? pension * c.auto_lump_1995 : 0;
var maxFactor = scheme === "s1995" ? c.max_factor_1995 : c.max_factor_2008_2015;
var maxTotalLump = pension * maxFactor;
var maxAdditional = Math.max(0, maxTotalLump - autoLump);
var additional = isNaN(targetLump) ? maxAdditional : Math.min(Math.max(0, targetLump - autoLump), maxAdditional);
if (!isNaN(targetLump) && targetLump < autoLump) additional = 0;
var pensionGivenUp = additional / c.factor;
var residualPension = Math.max(0, pension - pensionGivenUp);
var totalLump = autoLump + additional;
return {
scheme: scheme,
annualPension: pension,
autoLump: autoLump,
additionalLump: additional,
totalLump: totalLump,
pensionGivenUp: pensionGivenUp,
residualPension: residualPension,
maxTotalLump: maxTotalLump,
factor: c.factor,
notes: [c.note],
};
}
function calculateHealthWellbeing(rates, inputs) {
var years = Math.max(0, Number(inputs.yearsService) || 0);
var monthsService = Math.max(0, Number(inputs.monthsService) || years * 12);
var fte = Math.min(1, Math.max(0.1, Number(inputs.fte) || 1));
var nation = inputs.nation || "england";
var salary = Number(inputs.basicSalary) || 0;
var wellbeingDays = Math.max(0, Number(inputs.wellbeingDays) || 0);
var specialLeaveDays = Math.max(0, Number(inputs.specialLeaveDays) || 0);
var leaveDays = rates.leave.annual_leave_fte_days;
for (var i = 0; i < rates.leave.annual_leave_tiers.length; i++) {
var t = rates.leave.annual_leave_tiers[i];
if (years >= t.min_years) leaveDays = t.days;
}
var bhKey = "bank_holidays_" + (nation === "northern_ireland" ? "ni" : nation);
var bh = rates.leave[bhKey] || rates.leave.bank_holidays_england;
var annualLeave = leaveDays * fte;
var bankHolidays = bh * fte;
var wellbeing = wellbeingDays * fte;
var special = specialLeaveDays * fte;
var totalDays = annualLeave + bankHolidays + wellbeing + special;
var std = weeklyHours(rates, nation === "scotland" ? "scotland" : "england");
var dayHours = std / 5;
var totalHours = totalDays * dayHours;
var dailyPay = salary / ((std * rates.afc_unsocial.weeks_per_year) / 5);
var cashValue = totalDays * dailyPay;
var osp = rates.leave.osp_sick_tiers[0];
for (var j = 0; j < rates.leave.osp_sick_tiers.length; j++) {
if (monthsService >= rates.leave.osp_sick_tiers[j].min_months) osp = rates.leave.osp_sick_tiers[j];
}
return {
annualLeaveDays: annualLeave,
bankHolidayDays: bankHolidays,
wellbeingDays: wellbeing,
specialLeaveDays: special,
totalDays: totalDays,
totalHours: totalHours,
cashValue: cashValue,
dailyPay: dailyPay,
ospFullMonths: osp.full_months,
ospHalfMonths: osp.half_months,
notes: [
"AfC Section 13 annual leave tiers + public holidays. Local health & wellbeing days vary by trust.",
"Occupational sick pay (Section 14) shown as full/half months by reckonable service.",
],
};
}
function calculateTDEE(rates, inputs) {
var sex = inputs.sex || "female";
var age = Math.max(16, Number(inputs.age) || 30);
var heightCm = Number(inputs.heightCm) || 170;
var weightKg = Number(inputs.weightKg) || 70;
var unit = inputs.unit || "metric";
if (unit === "imperial") {
heightCm = (Number(inputs.heightFt) || 5) * 30.48 + (Number(inputs.heightIn) || 7) * 2.54;
weightKg = (Number(inputs.weightLb) || 154) * 0.453592;
}
var activity = inputs.activity || "moderate_nhs";
var goal = inputs.goal || "maintain";
var bmr = 10 * weightKg + 6.25 * heightCm - 5 * age + (sex === "male" ? 5 : -161);
var multipliers = {
sedentary: 1.2,
light: 1.375,
moderate: 1.55,
moderate_nhs: 1.55,
active_nhs: 1.725,
very_active: 1.9,
};
var mult = multipliers[activity] || 1.55;
var tdee = bmr * mult;
var target = tdee;
if (goal === "lose_slow") target = tdee - 250;
else if (goal === "lose") target = tdee - 500;
else if (goal === "gain_slow") target = tdee + 250;
else if (goal === "gain") target = tdee + 500;
var protein = weightKg * 1.6;
var fat = (target * 0.25) / 9;
var carbs = (target - protein * 4 - fat * 9) / 4;
var bmi = weightKg / Math.pow(heightCm / 100, 2);
var bmiLabel = "Healthy weight";
if (bmi < 18.5) bmiLabel = "Underweight";
else if (bmi >= 25 && bmi < 30) bmiLabel = "Overweight";
else if (bmi >= 30) bmiLabel = "Obese";
return {
bmr: bmr,
tdee: tdee,
targetCalories: Math.max(1200, target),
proteinG: protein,
fatG: fat,
carbsG: Math.max(0, carbs),
bmi: bmi,
bmiLabel: bmiLabel,
weightKg: weightKg,
heightCm: heightCm,
activityMult: mult,
notes: [
"BMR uses Mifflin–St Jeor. TDEE = BMR × activity factor. NHS shift presets approximate ward/community physical demand.",
"Estimates only — not medical advice. Seek clinical guidance for clinical nutrition needs.",
],
};
}
function calculateScotlandTaxCompare(rates, inputs) {
var salary = Number(inputs.basicSalary) || 0;
var pensionEnabled = inputs.pensionEnabled !== false;
var scot = calculateAfC(rates, {
basicSalary: salary,
fte: Number(inputs.fte) || 1,
region: "scotland",
pensionEnabled: pensionEnabled,
studentLoan: inputs.studentLoan || "none",
postgradLoan: !!inputs.postgradLoan,
unsocialEligible: false,
});
var eng = calculateAfC(rates, {
basicSalary: salary,
fte: Number(inputs.fte) || 1,
region: "england",
pensionEnabled: pensionEnabled,
studentLoan: inputs.studentLoan || "none",
postgradLoan: !!inputs.postgradLoan,
unsocialEligible: false,
});
return {
scotland: scot,
england: eng,
taxDifference: scot.deductions.tax - eng.deductions.tax,
netDifference: scot.net.annual - eng.net.annual,
monthlyNetDifference: scot.net.monthly - eng.net.monthly,
notes: ["Compares Scottish Income Tax bands vs rUK (England/Wales/NI) on the same NHS gross."],
};
}
return {
getPensionRate: getPensionRate,
calculateHCAS: calculateHCAS,
calculateTax: calculateTax,
calculateNI: calculateNI,
calculateStudentLoan: calculateStudentLoan,
calculateAfC: calculateAfC,
calculateJuniorDoctor: calculateJuniorDoctor,
calculateConsultant: calculateConsultant,
calculateGP: calculateGP,
calculateOvertime: calculateOvertime,
calculateStrike: calculateStrike,
calculateBradford: calculateBradford,
calculateMileage: calculateMileage,
calculateRedundancy: calculateRedundancy,
calculateMARS: calculateMARS,
calculateClawback: calculateClawback,
calculateBIK: calculateBIK,
calculateSalarySacrifice: calculateSalarySacrifice,
calculatePaternity: calculatePaternity,
calculateMaternity: calculateMaternity,
calculateBuyLeave: calculateBuyLeave,
calculateBankHolidayPT: calculateBankHolidayPT,
calculatePensionCompare: calculatePensionCompare,
calculateAnnualAllowance: calculateAnnualAllowance,
calculateAdditionalPension: calculateAdditionalPension,
calculatePensionRefund: calculatePensionRefund,
calculateOptOut: calculateOptOut,
calculateMcCloud: calculateMcCloud,
calculateMortgage: calculateMortgage,
calculateUniform: calculateUniform,
calculateBursary: calculateBursary,
calculateBackPay: calculateBackPay,
calculatePayRise: calculatePayRise,
calculateLocumVsPermanent: calculateLocumVsPermanent,
calculateLocumVsUmbrella: calculateLocumVsUmbrella,
calculateInjury: calculateInjury,
calculateUnsocialDetailed: calculateUnsocialDetailed,
calculateCommutation: calculateCommutation,
calculateHealthWellbeing: calculateHealthWellbeing,
calculateTDEE: calculateTDEE,
calculateScotlandTaxCompare: calculateScotlandTaxCompare,
unsocialRatesForBand: unsocialRatesForBand,
bikPct: bikPct,
clone: clone,
};
});
(function(){
var root=document.getElementById('nhs-sc-root');
if(!root) return;
var rates={"tax_year":"2026/27","effective_from":"2026-04-01","sources":["https://www.nhsemployers.org/articles/pay-scales-202627","HMRC Income Tax / NI / Student Loan 2026/27","NHS Pension contribution tiers from 1 April 2026","England 2016 resident doctor contract (BMA / NHS Employers)"],"afc":{"Band 2":{"slug":"2","roles":"Healthcare assistants, support workers, admin assistants","unsocial_eligible":true,"points":[{"id":"flat","label":"Flat rate","salary":25272,"hourly":12.92}]},"Band 3":{"slug":"3","roles":"Senior HCAs, admin officers, therapy assistants","unsocial_eligible":true,"points":[{"id":"entry","label":"Entry Level","salary":25760,"hourly":13.17},{"id":"top","label":"Top of Band (2 yrs)","salary":27476,"hourly":14.05}]},"Band 4":{"slug":"4","roles":"Assistant practitioners, pharmacy technicians, office supervisors","unsocial_eligible":true,"points":[{"id":"entry","label":"Entry Level","salary":28392,"hourly":14.52},{"id":"top","label":"Top of Band (3 yrs)","salary":31157,"hourly":15.93}]},"Band 5":{"slug":"5","roles":"Newly qualified nurses, midwives, AHPs, paramedics","unsocial_eligible":true,"points":[{"id":"entry","label":"Entry Level","salary":32073,"hourly":16.4},{"id":"mid","label":"Intermediate (2 yrs)","salary":34592,"hourly":17.69},{"id":"top","label":"Top of Band (4 yrs)","salary":39043,"hourly":19.97}]},"Band 6":{"slug":"6","roles":"Specialist nurses, midwives, senior AHPs","unsocial_eligible":true,"points":[{"id":"entry","label":"Entry Level","salary":39959,"hourly":20.44},{"id":"mid","label":"Intermediate (2 yrs)","salary":42170,"hourly":21.57},{"id":"top","label":"Top of Band (5 yrs)","salary":48117,"hourly":24.61}]},"Band 7":{"slug":"7","roles":"Advanced practitioners, ward managers, specialist leads","unsocial_eligible":true,"points":[{"id":"entry","label":"Entry Level","salary":49387,"hourly":25.26},{"id":"mid","label":"Intermediate (2 yrs)","salary":51932,"hourly":26.56},{"id":"top","label":"Top of Band (5 yrs)","salary":56515,"hourly":28.9}]},"Band 8a":{"slug":"8a","roles":"Modern matrons, nurse consultants, service managers","unsocial_eligible":false,"points":[{"id":"entry","label":"Entry Level","salary":57528,"hourly":29.42},{"id":"mid","label":"Intermediate (2 yrs)","salary":60417,"hourly":30.9},{"id":"top","label":"Top of Band (5 yrs)","salary":64750,"hourly":33.11}]},"Band 8b":{"slug":"8b","roles":"Assistant directors, consultant practitioners","unsocial_eligible":false,"points":[{"id":"entry","label":"Entry Level","salary":66582,"hourly":34.05},{"id":"mid","label":"Intermediate (2 yrs)","salary":70896,"hourly":36.26},{"id":"top","label":"Top of Band (5 yrs)","salary":77368,"hourly":39.57}]},"Band 8c":{"slug":"8c","roles":"Deputy directors, heads of service","unsocial_eligible":false,"points":[{"id":"entry","label":"Entry Level","salary":79504,"hourly":40.66},{"id":"mid","label":"Intermediate (2 yrs)","salary":84346,"hourly":43.14},{"id":"top","label":"Top of Band (5 yrs)","salary":91609,"hourly":46.85}]},"Band 8d":{"slug":"8d","roles":"Directors of nursing / operations (AfC)","unsocial_eligible":false,"points":[{"id":"entry","label":"Entry Level","salary":94356,"hourly":48.26},{"id":"mid","label":"Intermediate (2 yrs)","salary":100140,"hourly":51.21},{"id":"top","label":"Top of Band (5 yrs)","salary":108814,"hourly":55.65}]},"Band 9":{"slug":"9","roles":"Very senior AfC directors","unsocial_eligible":false,"points":[{"id":"entry","label":"Entry Level","salary":112782,"hourly":57.68},{"id":"mid","label":"Intermediate (2 yrs)","salary":119583,"hourly":61.16},{"id":"top","label":"Top of Band (5 yrs)","salary":129783,"hourly":66.37}]}},"hcas":{"none":{"label":"No London weighting","rate":0,"min":0,"max":0},"inner":{"label":"Inner London (20%)","rate":0.2,"min":5794,"max":8746},"outer":{"label":"Outer London (15%)","rate":0.15,"min":4870,"max":6137},"fringe":{"label":"Fringe (5%)","rate":0.05,"min":1346,"max":2270}},"pension_tiers":[{"up_to":13259,"rate":0.052},{"up_to":28854,"rate":0.065},{"up_to":35155,"rate":0.083},{"up_to":52778,"rate":0.098},{"up_to":67668,"rate":0.107},{"up_to":null,"rate":0.125}],"tax":{"personal_allowance":12570,"pa_taper_start":100000,"england":{"basic_width":37700,"higher_width":74870,"basic_rate":0.2,"higher_rate":0.4,"additional_rate":0.45},"scotland_bands":[{"limit":16537,"rate":0.19},{"limit":29526,"rate":0.2},{"limit":43662,"rate":0.21},{"limit":75000,"rate":0.42},{"limit":125140,"rate":0.45},{"limit":null,"rate":0.48}]},"ni":{"primary_threshold":12570,"upper_earnings_limit":50270,"main_rate":0.08,"upper_rate":0.02},"student_loans":{"none":{"threshold":0,"rate":0},"plan1":{"threshold":26900,"rate":0.09},"plan2":{"threshold":29385,"rate":0.09},"plan4":{"threshold":33795,"rate":0.09},"plan5":{"threshold":25000,"rate":0.09},"postgrad":{"threshold":21000,"rate":0.06}},"afc_unsocial":{"night_sat":0.3,"sunday_bh":0.6,"full_time_weekly_hours":{"england":37.5,"wales":37.5,"ni":37.5,"scotland":36.0},"weeks_per_year":52.18},"junior_doctor":{"contract":"England 2016 resident doctor contract","full_time_weekly_hours":40,"night_enhancement":0.37,"nroc_availability":0.08,"london_flat":{"none":0,"outer":2162,"inner":4395},"weekend_allowance":{"none":0,"1in8":0.03,"1in7":0.04,"1in6":0.05,"1in5":0.06,"1in4":0.075,"1in3":0.1,"1in2":0.15},"grades":[{"id":"fy1","label":"FY1 (Nodal Point 1)","salary":40190},{"id":"fy2","label":"FY2 (Nodal Point 2)","salary":45994},{"id":"ct1","label":"CT1 / ST1 (Nodal Point 3)","salary":54499},{"id":"ct2","label":"CT2 / ST2 (Nodal Point 3)","salary":54499},{"id":"st3","label":"CT3 / ST3u2013ST5 (Nodal Point 4)","salary":67325},{"id":"st6","label":"ST6u2013ST8 (Nodal Point 5)","salary":76582}]},"consultant":{"contract":"England consultant contract","standard_pas":10,"thresholds":[{"id":"y1","label":"Year 1","salary":96150},{"id":"y2","label":"Year 2","salary":99030},{"id":"y3","label":"Year 3","salary":101910},{"id":"y4","label":"Year 4","salary":104790},{"id":"y5","label":"Year 5","salary":107670},{"id":"y6_8","label":"Years 6u20138","salary":114750},{"id":"y9_11","label":"Years 9u201311","salary":121830},{"id":"y12","label":"Year 12+","salary":128910}],"on_call":{"none":0,"low":0.01,"medium":0.02,"high":0.03,"very_high":0.05,"highest":0.08},"london_flat":{"none":0,"outer":2162,"inner":4395}},"gp":{"session_hours":4.1667,"full_time_sessions":9,"default_rate_per_session":11500,"rate_min":9000,"rate_max":16000,"partner_estimate_note":"Partner drawings vary by practice; this is an illustrative PAYE-style estimate only."},"band_accents":{"2":{"accent":"#0d9488","header":"#0f766e"},"3":{"accent":"#0891b2","header":"#0e7490"},"4":{"accent":"#0284c7","header":"#0369a1"},"5":{"accent":"#005eb8","header":"#003087"},"6":{"accent":"#2563eb","header":"#1e3a8a"},"7":{"accent":"#4f46e5","header":"#312e81"},"8a":{"accent":"#6366f1","header":"#3730a3"},"8b":{"accent":"#7c3aed","header":"#5b21b6"},"8c":{"accent":"#9333ea","header":"#6b21a8"},"8d":{"accent":"#a21caf","header":"#701a75"},"9":{"accent":"#be185d","header":"#9d174d"}},"pay_award_2026_27":{"afc_pct":0.033,"label":"3.3% AfC pay award 2026/27"},"overtime":{"weekday":1.0,"saturday":1.3,"sunday_bh":1.6,"note":"AfC Section 3 enhancements used as overtime multipliers where plain overtime is not separately paid."},"mileage":{"standard_car_first":0.45,"standard_car_after":0.25,"standard_threshold_miles":3500,"lease_car":0.2,"motorcycle":0.24,"bicycle":0.2},"bradford":{"bands":[{"up_to":50,"label":"Low concern","color":"#047857"},{"up_to":200,"label":"Monitor","color":"#b45309"},{"up_to":400,"label":"Trigger review","color":"#c2410c"},{"up_to":null,"label":"High concern","color":"#b91c1c"}]},"redundancy_afc":{"weeks_per_year_service":1,"max_years":24,"ready_reckoner_note":"AfC Section 16 ready reckoner estimate (1 week per year of reckonable service, capped)."},"mars":{"weeks_per_year_service":1,"max_weeks":24,"note":"MARS packages are locally determined; this uses a common 1-week-per-year illustrative model capped at 24 weeks."},"bik_cars":{"co2_bands":[{"up_to":50,"pct":0.03},{"up_to":75,"pct":0.15},{"up_to":94,"pct":0.19},{"up_to":120,"pct":0.24},{"up_to":140,"pct":0.28},{"up_to":160,"pct":0.32},{"up_to":null,"pct":0.37}],"diesel_surcharge":0.04,"electric_pct":0.02},"leave":{"smp_weeks_full":6,"smp_weeks_lower":33,"smp_lower_weekly":187.18,"osp_weeks_full":8,"osp_weeks_half":18,"paternity_weeks":2,"statutory_paternity_weekly":187.18,"bank_holidays_england":8,"bank_holidays_wales":8,"bank_holidays_scotland":9,"bank_holidays_ni":10,"annual_leave_fte_days":27,"annual_leave_tiers":[{"min_years":0,"days":27},{"min_years":5,"days":29},{"min_years":10,"days":33}],"osp_sick_tiers":[{"min_months":0,"full_months":1,"half_months":2},{"min_months":12,"full_months":2,"half_months":2},{"min_months":24,"full_months":4,"half_months":4},{"min_months":36,"full_months":5,"half_months":5},{"min_months":60,"full_months":6,"half_months":6}]},"unsocial_by_band":{"1":{"night_sat":0.47,"sunday_bh":0.94},"2":{"night_sat":0.41,"sunday_bh":0.83},"3":{"night_sat":0.35,"sunday_bh":0.69},"4":{"night_sat":0.3,"sunday_bh":0.6},"5":{"night_sat":0.3,"sunday_bh":0.6},"6":{"night_sat":0.3,"sunday_bh":0.6},"7":{"night_sat":0.3,"sunday_bh":0.6},"8a":{"night_sat":0.3,"sunday_bh":0.6},"8b":{"night_sat":0.3,"sunday_bh":0.6},"8c":{"night_sat":0.3,"sunday_bh":0.6},"8d":{"night_sat":0.3,"sunday_bh":0.6},"9":{"night_sat":0.3,"sunday_bh":0.6}},"commutation":{"factor":12,"max_factor_1995":5.36,"max_factor_2008_2015":4.28,"auto_lump_1995":3,"note":"u00a312 lump sum for each u00a31 of annual pension given up. Max approx 25% of capital value (NHSBSA factors)."},"afc_wales":{"source":"AfC(W) 02/2026","Band 2":{"points":[{"id":"flat","label":"Flat rate","salary":26300}]},"Band 3":{"points":[{"id":"entry","label":"Entry","salary":26300},{"id":"top","label":"Top (2 yrs)","salary":27890}]},"Band 4":{"points":[{"id":"entry","label":"Entry","salary":28819},{"id":"top","label":"Top (3 yrs)","salary":31626}]},"Band 5":{"points":[{"id":"entry","label":"Entry","salary":32557},{"id":"mid","label":"Intermediate (2 yrs)","salary":35114},{"id":"top","label":"Top (4 yrs)","salary":39631}]},"Band 6":{"points":[{"id":"entry","label":"Entry","salary":40559},{"id":"mid","label":"Intermediate (2 yrs)","salary":42805},{"id":"top","label":"Top (5 yrs)","salary":48841}]},"Band 7":{"points":[{"id":"entry","label":"Entry","salary":50129},{"id":"mid","label":"Intermediate (2 yrs)","salary":52712},{"id":"top","label":"Top (5 yrs)","salary":57365}]},"Band 8a":{"points":[{"id":"entry","label":"Entry","salary":58379},{"id":"mid","label":"Intermediate (2 yrs)","salary":61317},{"id":"top","label":"Top (5 yrs)","salary":65723}]},"Band 8b":{"points":[{"id":"entry","label":"Entry","salary":67583},{"id":"mid","label":"Intermediate (2 yrs)","salary":71952},{"id":"top","label":"Top (5 yrs)","salary":78530}]},"Band 8c":{"points":[{"id":"entry","label":"Entry","salary":80698},{"id":"mid","label":"Intermediate (2 yrs)","salary":85611},{"id":"top","label":"Top (5 yrs)","salary":92984}]},"Band 8d":{"points":[{"id":"entry","label":"Entry","salary":95773},{"id":"mid","label":"Intermediate (2 yrs)","salary":101643},{"id":"top","label":"Top (5 yrs)","salary":110448}]},"Band 9":{"points":[{"id":"entry","label":"Entry","salary":114475},{"id":"mid","label":"Intermediate (2 yrs)","salary":121377},{"id":"top","label":"Top (5 yrs)","salary":131732}]}},"afc_ni":{"source":"NHSPRB 3.3% recommended 2026/27 (England-aligned; NI Executive confirmation pending)","note":"HSC NI typically mirrors England AfC cash rates. No HCAS in Northern Ireland.","use_england_scales":true},"uniform_tax_relief":{"flat_rate_nurse":125,"flat_rate_other":80,"tax_rate_basic":0.2},"bursary":{"training_grant_default":5000,"parental_support_default":2000,"travel_default":1000},"pension_schemes":{"s1995":{"accrual":0.0125,"lump_sum_factor":3,"npa":60,"label":"1995 section"},"s2008":{"accrual":0.0166667,"lump_sum_factor":0,"npa":65,"label":"2008 section"},"s2015":{"accrual":0.01854,"lump_sum_factor":0,"npa":"SPA","label":"2015 CARE"}},"annual_allowance":{"standard":60000,"taper_start":200000,"min_tapered":10000,"mpaa":10000},"additional_pension":{"cost_per_100_pa":18.5,"note":"Illustrative AP cost factor; actual NHSBSA quotes vary by age and scheme."},"mortgage":{"income_multiple_single":4.5,"income_multiple_joint":4.0,"stress_rate":0.07},"clawback":{"default_months":12,"note":"Many NHS redundancy / MARS offers claw back if you rejoin the NHS within a defined period."},"umbrella":{"default_margin_weekly":25,"employer_ni_rate":0.15,"holiday_pct":0.1207,"pension_auto_enrol_pct":0.05},"afc_scotland":{"source":"https://www.gov.scot/publications/nhs-staff-pay/","effective_from":"2026-04-01","uplift_pct":0.0375,"bands":{"Band 2":[{"id":"p1","label":"Point 1","salary":26696,"hourly":14.22},{"id":"p2","label":"Point 2 (top)","salary":28988,"hourly":15.44}],"Band 3":[{"id":"p1","label":"Point 1","salary":29103,"hourly":15.5},{"id":"p2","label":"Point 2 (top)","salary":31409,"hourly":16.73}],"Band 4":[{"id":"p1","label":"Point 1","salary":31537,"hourly":16.8},{"id":"p2","label":"Point 2 (top)","salary":34303,"hourly":18.27}],"Band 5":[{"id":"p1","label":"Point 1 (entry)","salary":34544,"hourly":18.4},{"id":"p2","label":"Point 2","salary":36911,"hourly":19.66},{"id":"p3","label":"Point 3 (top)","salary":43039,"hourly":22.93}],"Band 6":[{"id":"p1","label":"Point 1 (entry)","salary":43231,"hourly":23.03},{"id":"p2","label":"Point 2","salary":45135,"hourly":24.04},{"id":"p3","label":"Point 3 (top)","salary":52679,"hourly":28.06}],"Band 7":[{"id":"p1","label":"Point 1 (entry)","salary":52845,"hourly":28.15},{"id":"p2","label":"Point 2","salary":54863,"hourly":29.23},{"id":"p3","label":"Point 3 (top)","salary":61466,"hourly":32.74}],"Band 8a":[{"id":"p1","label":"Point 1","salary":65125,"hourly":34.69},{"id":"p2","label":"Point 2 (top)","salary":70303,"hourly":37.45}],"Band 8b":[{"id":"p1","label":"Point 1","salary":76888,"hourly":40.96},{"id":"p2","label":"Point 2 (top)","salary":82251,"hourly":43.82}],"Band 8c":[{"id":"p1","label":"Point 1","salary":90808,"hourly":48.38},{"id":"p2","label":"Point 2 (top)","salary":97338,"hourly":51.85}],"Band 8d":[{"id":"p1","label":"Point 1","salary":107810,"hourly":57.43},{"id":"p2","label":"Point 2 (top)","salary":112426,"hourly":59.89}],"Band 9":[{"id":"p1","label":"Point 1","salary":127521,"hourly":67.93},{"id":"p2","label":"Point 2 (top)","salary":133044,"hourly":70.88}]}}};
var Engine=window.NHSPayEngine;
function money(n){return '£'+Number(n||0).toLocaleString('en-GB',{minimumFractionDigits:2,maximumFractionDigits:2});}
function val(k){var el=root.querySelector('[data-k="'+k+'"]'); if(!el) return ''; if(el.type==='checkbox') return el.checked; return el.value;}
function set(k,t){var el=root.querySelector('[data-o="'+k+'"]'); if(el) el.textContent=t;}
function syncPoints(){
var band=val('band');
var sel=root.querySelector('[data-k="point"]');
if(!sel) return;
var first=null;
var opts=sel.querySelectorAll('option');
for(var i=0;i
'; } } root.querySelector('[data-k="band"]').addEventListener('change', function(){syncPoints(); calc();}); root.querySelectorAll('input,select').forEach(function(el){ el.addEventListener('input', calc); el.addEventListener('change', calc); }); root.querySelectorAll('[data-act]').forEach(function(btn){ btn.addEventListener('click', function(){ var a=btn.getAttribute('data-act'); if(a==='print') window.print(); if(a==='share' && navigator.share) navigator.share({title:document.title,url:location.href}).catch(function(){}); if(a==='share' && !navigator.share && navigator.clipboard) navigator.clipboard.writeText(location.href); }); }); syncPoints(); calc(); })();
This NHS Scotland pay calculator fills the gap left by England-scale tools: it prices Agenda for Change using official Scotland cash points for 2026/27, then applies Scottish Income Tax, UK National Insurance and NHS Pension contributions — and shows how that compares with the nearest England AfC point.
Official sources: Scottish Government NHS pay rates · NHS Employers England pay scales 2026/27 · Scottish Income Tax (GOV.UK) · Scotland pay rise guide.

Scotland negotiates Agenda for Change separately from the UK Pay Review Body. The current multi-year deal delivers 3.75% from 1 April 2026 (after 4.4% in 2025/26, including an inflation-guarantee uplift). Headline cash points for 2026/27 include:
| Band | Scotland entry | Scotland top | England entry (comparator) |
| Band 5 | £34,544 | £43,039 | £32,073 |
| Band 6 | £43,231 | £52,679 | £39,959 |
| Band 7 | £52,845 | £61,466 | £49,387 |
| Band 8a | £65,125 | £70,303 | £57,528 |
Band 5 entry is about £2,471 a year higher in Scotland than England before tax. Scottish Income Tax bands are steeper in places, so take-home is not the full gross gap — which is why this calculator shows both.

Two things change when you work for an NHS Scotland Board:
National Insurance and NHS Pension contribution tiers remain UK-wide. There is no London HCAS in Scotland.
Our separate Scotland tax calculator isolates tax regime differences on a shared gross. Use this page when you need the Scotland cash scale itself.
Scotland AfC still uses Section 2-style unsocial enhancements for eligible bands, but contracted full-time hours can differ from England’s 37.5-hour week in some Board contracts (many tools model Scotland at 36 hours for hourly maths). Always check your Board’s contract and ESR hourly rate. Add estimated night / weekend hours above to uplift gross.
Pay follows the employer you join. Starting with an NHS Scotland Board puts you on Scotland AfC cash points from your first contract. HMRC usually updates your tax code to Scottish rates after you register a Scottish address — expect a short lag where tax coding may still show rUK until corrected.
£34,544 at entry, £36,911 at the mid point, and £43,039 at the top of Band 5 — from Scottish Government NHS pay rates.
Yes on gross AfC cash at every common band for 2026/27. Net pay is still usually higher in Scotland for the same band/step, but Scottish Income Tax reduces part of the headline gap.
3.75% from 1 April 2026 under the multi-year AfC deal (following 4.4% in 2025/26).
If you are a Scottish taxpayer (usually living in Scotland), yes — Scottish Income Tax applies to your employment income.
No. That tool compares tax regimes on a shared gross. This tool uses Scotland AfC cash points first, then Scottish tax, with an England comparator.
Scottish Government publishes NHS staff pay rates on gov.scot; Health Workforce circulars (PCS(AFC)) confirm Board implementation.
See also Scotland tax vs rUK, Wales pay, Northern Ireland pay, England Band 5, and the Scotland pay rise guide.
NHS Scotland has a separate Agenda for Change settlement. The Scottish Government’s 2025–27 agreement provides a 3.75% uplift for 2026/27 after the revised 2025/26 settlement. Scottish Government — NHS pay rates
The most important calculator difference is not only the salary table: all NHS Scotland Agenda for Change staff moved to a 36-hour full-time working week from 1 April 2026 without loss of overall earnings. Scottish Government
Critical data rule: do not calculate NHS Scotland hourly pay using England’s 37.5-hour divisor.
Scottish taxpayers use Scottish income-tax bands. For 2026/27 these include 19%, 20%, 21%, 42%, 45% and 48% rates across the published thresholds. GOV.UK
Therefore an “NHS Scotland take-home calculator” needs both:
Scottish NHS pay table + Scottish income tax, not an England calculator with a Scotland label.
Queries such as “Band 5 NHS Scotland”, “Band 6 NHS Scotland salary” and “NHS Band 7 Scotland” are modifiers of the Scotland pay table. Keep them as anchored rows/sections here rather than creating `/band-6-scotland/`, `/band-7-scotland/` and dozens of near-duplicate pages.
compare NHS take-home pay by nation