Functional Weave
Code in Python

Suites · payroll

Payroll and HR

UK payroll for a small employer: PAYE tax, National Insurance, pensions, student loans and statutory pay into a payslip, plus holiday, notice and absence rules.

76 capabilities (34 core, 42 optional) and 14 gaps, in build order. Put each line in the file that calls it, then fune build.

UK only: PAYE, National Insurance, student loans, statutory pay and pensions follow HMRC and UK employment rules for each tax year.

Not payroll or legal advice. Run the figures past HMRC's calculators or a payroll professional before paying people from them.

1. Employee records

Check the identifiers and dates an employee record holds.

validation.uk-ni-number core
Checks a National Insurance number's format and HMRC's disallowed prefixes.
Check a UK National Insurance number's format and HMRC's disallowed prefix letters, and normalise it.
from fune.validation.uk_ni_number import validate_uk_ni_number  # validation.uk-ni-number@^2
payroll.tax-code-parse core Unreviewed
Reads a PAYE tax code (1257L, K475, BR, S/C prefixes, W1/M1) into its allowance and flags.
Parse a UK PAYE tax code (1257L, K475, BR, D0, NT, 0T, S and C prefixes, W1/M1/X) into its allowance and flags.
from fune.payroll.tax_code_parse import parse_tax_code  # payroll.tax-code-parse@^1
dates.age core
Age on a date, which decides minimum wage bands and NI category.
Age in whole years on a given date, with 29 February birthdays ageing on 1 March in other years.
from fune.dates.age import age  # dates.age@^1
hr.length-of-service optional
Continuous service in years, months and days, for notice, redundancy and benefits.
Continuous service between two dates in completed years, months and days, counting both end dates.
from fune.hr.length_of_service import length_of_service  # hr.length-of-service@^1
  • starter-leaver-forms Starter checklists, P45 and P60 documents: caps gives the figures, not the forms.

2. Pay periods

Put each pay date into the right PAYE period.

payroll.tax-period core Unreviewed
The tax week, tax month and period number a pay date falls in, weeks 53, 54 and 56 included.
The UK PAYE tax week, tax month and payroll period number that a pay date falls in, weeks 53, 54 and 56 included.
from fune.payroll.tax_period import tax_period  # payroll.tax-period@^1
dates.uk-tax-year core
Which tax year (6 April to 5 April) a pay date belongs to.
The UK personal tax year containing a date: 6 April to 5 April, with HMRC's label.
from fune.dates.uk_tax_year import uk_tax_year  # dates.uk-tax-year@^1

3. Gross pay

Work out what each employee earned in the period.

payroll.pro-rata-salary core Unreviewed
Salary for part of a period (joiners, leavers) and for part-time hours.
Salary for part of a pay period and for part-time hours, by calendar days or working days, rounded once.
from fune.payroll.pro_rata_salary import pro_rata_salary  # payroll.pro-rata-salary@^1
payroll.overtime optional Unreviewed
Overtime pay in multiplier bands: time and a half after 40 hours, double time.
Pay for the time worked in a period split into multiplier bands: basic, time and a half after 40 hours, double time.
from fune.payroll.overtime import overtime_pay  # payroll.overtime@^1
payroll.minimum-wage-check optional Unreviewed
Checks pay for the period against the minimum or living wage for the worker's age.
Check pay for a pay reference period against the minimum or living wage for age and apprenticeship.
from fune.payroll.minimum_wage_check import minimum_wage_check  # payroll.minimum-wage-check@^1
charity.payroll-giving optional
Payroll Giving donations taken before PAYE, and the tax they save.
UK Payroll Giving for one pay period: the donation comes off pay before PAYE, and the tax it saves the employee.
from fune.charity.payroll_giving import payroll_giving  # charity.payroll-giving@^1

4. Tax and deductions

Take tax, National Insurance, pension and loans off the gross, and give the payslip.

payroll.gross-to-net core Unreviewed
The whole payslip for one period: gross, pension, PAYE, NI, student loans and net pay.
A UK payslip for one pay period: gross pay, pension, PAYE tax, National Insurance, student loans and net pay.
from fune.payroll.gross_to_net import gross_to_net  # payroll.gross-to-net@^1
payroll.income-tax core Unreviewed
PAYE income tax for one period, cumulative or week 1/month 1, by HMRC's tax tables.
PAYE income tax for one pay period from a tax code, cumulative or week 1/month 1, by HMRC's tax table routines.
from fune.payroll.income_tax import income_tax  # payroll.income-tax@^1
payroll.national-insurance core Unreviewed
Employee and employer Class 1 NICs by category letter for one payment.
Employee and employer Class 1 National Insurance for one payment by category letter, exact percentage method.
from fune.payroll.national_insurance import national_insurance  # payroll.national-insurance@^1
payroll.pension-auto-enrolment core Unreviewed
Auto-enrolment pension: qualifying earnings and employee and employer contributions.
Workplace pension contributions for one pay period: qualifying earnings band, employee and employer shares, tax relief.
from fune.payroll.pension_auto_enrolment import pension_contributions  # payroll.pension-auto-enrolment@^1
payroll.student-loan core Unreviewed
Student and postgraduate loan deductions by plan.
Student loan or postgraduate loan deduction from one period's pay, by plan, rounded down to whole pounds.
from fune.payroll.student_loan import student_loan  # payroll.student-loan@^1
payroll.net-to-gross optional Unreviewed
The gross pay that gives a target net pay, for agreed-net payments.
The gross pay that gives at least a target net pay in one UK pay period, found by a deterministic search.
from fune.payroll.net_to_gross import net_to_gross  # payroll.net-to-gross@^1
fleet.company-car-bik optional Unreviewed
Company car benefit in kind for the tax year, for P11D or payrolled benefits.
UK company car benefit in kind: appropriate percentage by CO2 and electric range, car and fuel benefit for a tax year.
from fune.fleet.company_car_bik import company_car_bik  # fleet.company-car-bik@^1
fleet.mileage-allowance optional
Approved mileage allowance payments for staff using their own vehicle: tax-free and taxable parts.
HMRC approved mileage allowance payments for an employee's own vehicle: tax-free amount, taxable excess or relief.
from fune.fleet.mileage_allowance import mileage_allowance  # fleet.mileage-allowance@^1
  • rti-submission Sending FPS and EPS returns to HMRC under Real Time Information: the XML, the gateway and the credentials are yours.
  • pension-provider-files Contribution files for pension providers (NEST, The People's Pension) and enrolment letters.

5. Statutory pay

Pay what the law requires while an employee is off sick or on family leave.

payroll.statutory-sick-pay core Unreviewed
SSP for sickness spells: waiting days, linked periods, the 28-week limit and the rate by date.
UK Statutory Sick Pay for sickness spells: waiting days, 8-week linking, 28-week limit and the rate by date.
from fune.payroll.statutory_sick_pay import statutory_sick_pay  # payroll.statutory-sick-pay@^1
payroll.statutory-maternity-pay optional Unreviewed
SMP eligibility and its 39 weekly amounts.
UK Statutory Maternity Pay: LEL eligibility and 39 weekly amounts, 90% then the lower of 90% or the flat rate.
from fune.payroll.statutory_maternity_pay import statutory_maternity_pay  # payroll.statutory-maternity-pay@^1

6. Holidays and absence

Track holiday entitlement and absence.

payroll.holiday-entitlement core Unreviewed
Statutory holiday: 5.6 weeks capped at 28 days, pro-rated, or 12.07% for irregular hours.
UK statutory holiday: 5.6 weeks capped at 28 days and pro-rated, or 12.07% accrual for irregular hours.
from fune.payroll.holiday_entitlement import holiday_entitlement  # payroll.holiday-entitlement@^1
payroll.holiday-pay-accrual optional Unreviewed
Holiday pay: 12.07% rolled-up, or a week's pay averaged over 52 paid weeks.
UK holiday pay: 12.07% rolled-up holiday pay, or a week's pay averaged over the last 52 paid weeks.
from fune.payroll.holiday_pay_accrual import holiday_pay  # payroll.holiday-pay-accrual@^1
hr.absence-bradford-factor optional
Bradford factor from absence spells, to flag frequent short absences.
Bradford factor for a set of absence spells: spells squared times total days, which weights frequent short absences.
from fune.hr.absence_bradford_factor import bradford_factor  # hr.absence-bradford-factor@^1
  • leave-requests Requesting and approving leave, team calendars and clashes.

7. Leaving

Work out what a leaver is owed.

payroll.notice-period optional Unreviewed
Statutory minimum notice from length of service.
Statutory minimum notice in weeks from continuous service: one week per year from two years, capped at twelve.
from fune.payroll.notice_period import statutory_notice  # payroll.notice-period@^1
payroll.redundancy-pay optional Unreviewed
Statutory redundancy pay from age, service and a capped week's pay.
Statutory redundancy pay from age, completed years of service (at most 20) and a week's pay capped by date.
from fune.payroll.redundancy_pay import statutory_redundancy_pay  # payroll.redundancy-pay@^1

8. Paying staff

Get net pay into employees' accounts on payday.

banking.bacs-processing-date optional
When to submit a Bacs payment run so it arrives on payday, skipping weekends and bank holidays.
Bacs three-day cycle: input, processing and entry (arrival) dates for a submission, skipping weekends and bank holidays.
from fune.banking.bacs_processing_date import bacs_processing_dates  # banking.bacs-processing-date@^1
  • bacs-files Writing the Bacs Standard 18 payment file or a bank's bulk-payment CSV.

9. Amounts from money-basics

Hold money as currency-tagged integer minor units and do plain arithmetic on it safely.

money.amount core
The Money type ({ minor, currency }) every other money capability takes and returns.
A currency-tagged monetary amount held in integer minor units.
from fune.money.amount import money  # money.amount@^1
money.add core
Adds and subtracts amounts, refusing to mix currencies by accident.
Add, subtract and negate monetary amounts, refusing to mix currencies.
from fune.money.add import add_money  # money.add@^1
money.sum core
Totals a list of amounts, with an explicit currency for an empty list.
Total a list of monetary amounts, with an explicit currency for the empty case.
from fune.money.sum import sum_money  # money.sum@^1
money.compare core
Compares, mins, maxes and clamps amounts of one currency without unwrapping them.
Compare two amounts of one currency, refusing to compare across currencies; min, max and clamp too.
from fune.money.compare import compare_money, min_money, max_money, clamp_money  # money.compare@^2

10. Rates and rounding from money-basics

Apply percentages and divide amounts with the rounding stated, never a float.

money.apply-rate core
Applies a basis-point rate (VAT, discount, commission) to an amount with explicit rounding.
Apply a rate expressed in basis points to a monetary amount, with an explicit rounding mode.
from fune.money.apply_rate import apply_rate  # money.apply-rate@^1
math.round-div core
Integer division with a named rounding mode, for any money maths the others do not cover.
Integer division with an explicit rounding mode, for money arithmetic that must not drift.
from fune.math.round_div import round_div  # math.round-div@^1
math.basis-points optional
Converts "12.5%" to and from basis points exactly, for rates typed in by users.
Convert a rate between percent, basis points and a plain ratio exactly, as decimal text or integer basis points.
from fune.math.basis_points import convert_rate, to_basis_points, from_basis_points  # math.basis-points@^2
math.percent-change optional
Percentage change between two figures in basis points, for "up 4.2% on last month".
Percentage change from one value to another, in basis points, with an explicit rounding mode.
from fune.math.percent_change import percent_change  # math.percent-change@^1
math.rational optional
Exact fractions for rates that must not drift across many steps.
Exact fraction arithmetic, always reduced, for rates and ratios that must not drift.
from fune.math.rational import calculate_rational, rational, add_rational, subtract_rational, multiply_rational, divide_rational, compare_rational, rational_to_integer  # math.rational@^2
math.round-div-big optional
Rounded division on numbers past 2^53, for very large totals.
Divide two integers of any size with an explicit rounding mode, on decimal strings, for exact money sums past 2^53.
from fune.math.round_div_big import round_div_big  # math.round-div-big@^1

11. Splitting from money-basics

Divide an amount between people, periods or lines so the parts add back to the whole.

money.allocate core
Splits an amount by ratios (60/40, by quantity) without losing or inventing a penny.
Split an amount across ratios without losing or inventing a single minor unit.
from fune.money.allocate import allocate  # money.allocate@^1
money.split-even optional
Splits an amount into n near-equal parts that sum exactly, for instalments or shares.
Split an amount into n near-equal parts that add back up to exactly the whole.
from fune.money.split_even import split_even  # money.split-even@^1

12. Display and input from money-basics

Show amounts to people and read what they type.

money.format core
Renders an amount as text with the currency's real number of decimal places.
Render a monetary amount as text, using the currency's real minor-unit precision.
from fune.money.format import format_money  # money.format@^1
money.parse optional
Reads "£1,234.50" or "1.234,50 €" typed by a user into minor units, strictly.
Parse "£1,234.50" or "1.234,50 €" into integer minor units, strictly: no guessing, no rounding.
from fune.money.parse import parse_money  # money.parse@^1
money.currency-digits optional
How many decimal places each ISO 4217 currency has, for input boxes and validation.
How many decimal places a currency's minor unit has, from the ISO 4217 list.
from fune.money.currency_digits import currency_digits  # money.currency-digits@^1

13. Other currencies from money-basics

Convert between currencies at a rate you hold.

money.convert optional
Converts an amount at a supplied exact rate with explicit rounding.
Convert an amount to another currency at a supplied exact rate, with an explicit rounding mode.
from fune.money.convert import convert_money  # money.convert@^1

14. Calendar arithmetic from dates-basics

Move and measure ISO dates exactly, with no date library and no clock reads.

dates.add-days core
Shifts a date by whole days, for due dates and windows.
Shift an ISO date by a whole number of days, forwards or backwards, with exact calendar arithmetic.
from fune.dates.add_days import add_days  # dates.add-days@^1
dates.add-months core
Adds months clamping to month end, so 31 January plus a month is the end of February.
Add calendar months to an ISO date, clamping to the month end: 31 Jan + 1 month is 28 or 29 Feb.
from fune.dates.add_months import add_months  # dates.add-months@^1
dates.days-between core
Signed calendar days between two dates, for ages of invoices and stays.
Calendar days from one ISO date to another, signed: negative when the second date is earlier.
from fune.dates.days_between import days_between  # dates.days-between@^1
dates.month-boundaries core
First and last day of a date's month, for monthly periods and statements.
The first day, last day and length of the calendar month containing a date.
from fune.dates.month_boundaries import month_boundaries  # dates.month-boundaries@^1
dates.day-of-week core
ISO weekday of a date, for weekend rules and opening hours.
The ISO 8601 day of the week for a date: 1 is Monday through 7 is Sunday.
from fune.dates.day_of_week import day_of_week  # dates.day-of-week@^1
dates.is-leap-year optional
Whether a year is a leap year, for 365/366-day calculations.
Whether a year is a Gregorian leap year: every 4th, except centuries, except every 400th.
from fune.dates.is_leap_year import is_leap_year  # dates.is-leap-year@^1
dates.overlap optional
Days two date ranges share, for prorating and clash checks.
Days two date ranges have in common, with the end of each range stated as inclusive or exclusive.
from fune.dates.overlap import overlap_days  # dates.overlap@^1

15. Periods and reporting dates from dates-basics

Put dates into the periods reports are grouped by.

dates.quarter core
Calendar and fiscal quarter of a date for any year-start month.
Calendar quarter and fiscal quarter of a date, with the fiscal year and quarter date ranges, for any start month.
from fune.dates.quarter import quarter  # dates.quarter@^1
dates.week-number optional
ISO week number and week-year, for weekly reports.
ISO 8601 week number and week-numbering year of a date: weeks start Monday, week 1 holds the first Thursday.
from fune.dates.week_number import week_number  # dates.week-number@^1
dates.day-count-fraction optional
Year fraction by ACT/365F, ACT/360, 30/360 or ACT/ACT, for interest periods.
Year fraction between two dates by ACT/365F, ACT/360, 30/360, 30E/360 or ACT/ACT ISDA, as an exact fraction.
from fune.dates.day_count_fraction import day_count_fraction  # dates.day-count-fraction@^1

16. Working days and holidays from dates-basics

Skip weekends and public holidays when counting or adding days.

dates.bank-holidays core
UK bank holidays for a region and year, as gov.uk publishes them.
UK bank and public holidays for a region and year, as published by gov.uk, including one-off holidays.
from fune.dates.bank_holidays import bank_holidays  # dates.bank-holidays@^1
dates.add-business-days core
Moves a date by working days, skipping weekends and a holiday list.
Move a date forward or back by working days, skipping weekends and a caller-supplied holiday list.
from fune.dates.add_business_days import add_business_days  # dates.add-business-days@^1
dates.business-days-between core
Working days between two dates, for SLAs and notice periods.
Working days between two dates, excluding weekends and a caller-supplied holiday list.
from fune.dates.business_days_between import business_days_between  # dates.business-days-between@^1

17. Repeating schedules from dates-basics

Generate the dates of something that repeats.

dates.recurrence optional
The next n dates of a rule: monthly on the 31st, last Friday, every two weeks.
The next n dates of a repeating rule: monthly on a day, last weekday of the month, every k days or weeks, yearly.
from fune.dates.recurrence import recurrence  # dates.recurrence@^1

18. Times and durations from dates-basics

Work with times of day, durations and timestamps.

time.duration optional
Parses and adds durations like 1h30m in whole minutes, and formats the total.
Parse durations written as 1h30m, 90m or 01:30, add them up in whole minutes, and format the total.
from fune.time.duration import duration  # time.duration@^1
time.minutes-between optional
Minutes between two local date-times, across midnight.
Minutes from one local date and time to another, correct across midnight; wall-clock time, no time zones.
from fune.time.minutes_between import minutes_between  # time.minutes-between@^1
time.round-to-increment optional
Rounds minutes to a billing increment such as 6 or 15.
Round a duration in minutes to a billing increment such as 6 or 15 minutes, up, down or to nearest.
from fune.time.round_to_increment import round_to_increment  # time.round-to-increment@^1
time.iso-to-unix optional
RFC 3339 timestamps to Unix seconds.
An RFC 3339 timestamp with a Z or offset, such as 2026-09-26T13:00:00+01:00, to Unix time in whole seconds.
from fune.time.iso_to_unix import iso_to_unix  # time.iso-to-unix@^1
time.unix-to-iso optional
Unix seconds to an ISO 8601 UTC timestamp.
Unix time in seconds to an ISO 8601 UTC timestamp such as 2026-09-26T12:00:00Z, for years 0001 to 9999.
from fune.time.unix_to_iso import unix_to_iso  # time.unix-to-iso@^1
time.countdown optional
Seconds left until a moment and a 4:05 style timer text.
Seconds left until a Unix time, whether it has passed, and a timer text like 4:05, for tokens or retry waits.
from fune.time.countdown import countdown  # time.countdown@^1

19. Contact details from validation-basics

Check and normalise a person's contact details before storing them.

validation.email core
Is an email address plausible, by a documented subset of RFC 5322.
Is this a plausible email address? A documented, pragmatic subset of RFC 5322, not the full grammar.
from fune.validation.email import is_email  # validation.email@^1
validation.phone-e164 core
Normalises a phone number to +44… E.164 for a default country.
Normalise a phone number to E.164 (+447700900123) using a default country's trunk and dialling prefixes.
from fune.validation.phone_e164 import validate_phone_e164  # validation.phone-e164@^2
validation.uk-postcode core
Validates a UK postcode and puts it in canonical form.
Validate a UK postcode and normalise it to canonical upper case with one space before the inward code.
from fune.validation.uk_postcode import validate_uk_postcode  # validation.uk-postcode@^2
text.normalise-name optional
Tidies a typed name: spacing and title case with Mc, O' and hyphen rules.
Tidy a personal name: trim, collapse whitespace and title-case, with Mc, Mac, O', hyphen and van/de rules.
from fune.text.normalise_name import normalise_name  # text.normalise-name@^1
auth.normalise-email optional
Lower-cases an email's domain for storage and lookup.
Trim an email address and lower-case its domain for storage and lookup, or null if it is not a plausible address.
from fune.auth.normalise_email import normalise_email  # auth.normalise-email@^1

20. Bank details from validation-basics

Check payee and customer bank details before money moves.

validation.iban core
Checks an IBAN's mod-97 checksum and its country's length.
Check an IBAN against the ISO 13616 mod-97-10 checksum and the registered length for its country.
from fune.validation.iban import is_iban  # validation.iban@^1
validation.bic optional
Checks a SWIFT/BIC code's format and splits it.
Check the format of a SWIFT/BIC code (8 or 11 characters) and split it into its parts.
from fune.validation.bic import validate_bic  # validation.bic@^1
validation.uk-sort-code-account optional
Checks a UK sort code and account number by the Vocalink modulus rules, against a table you supply.
Check a UK sort code and account number with the Vocalink/Pay.UK modulus rules, against a table you supply.
from fune.validation.uk_sort_code_account import validate_uk_sort_code_account  # validation.uk-sort-code-account@^3
validation.uk-modulus-table optional
Parses Vocalink's VALACDOS and SCSUBTAB files into that table.
Parse Vocalink's VALACDOS.txt and SCSUBTAB.txt into the table UK sort code modulus checking needs.
from fune.validation.uk_modulus_table import parse_uk_modulus_table  # validation.uk-modulus-table@^1

21. Identifiers and check digits from validation-basics

Catch mistyped reference numbers by their check digits.

validation.luhn optional
Luhn mod-10 check, for card numbers and many reference numbers.
Check a digit string against the Luhn mod-10 checksum used by payment cards and many identifiers.
from fune.validation.luhn import is_luhn  # validation.luhn@^1
validation.uk-company-number optional
Checks a Companies House number's format and prefix.
Check a Companies House company number's format and prefix (SC, NI, OC, LP...) and zero-pad it to eight characters.
from fune.validation.uk_company_number import validate_uk_company_number  # validation.uk-company-number@^2
validation.lei optional
Checks a Legal Entity Identifier's check digits.
Check a 20-character Legal Entity Identifier against its ISO 17442 / ISO 7064 MOD 97-10 check digits.
from fune.validation.lei import validate_lei  # validation.lei@^1
validation.gtin optional
Checks an EAN/UPC/GTIN barcode number.
Check an EAN-8, UPC-A, EAN-13 or GTIN-14 barcode number against the GS1 mod-10 check digit.
from fune.validation.gtin import validate_gtin  # validation.gtin@^1

22. Displaying sensitive values from validation-basics

Show stored identifiers without exposing them.

text.mask optional
Masks all but the last digits of a card, account or phone number.
Mask all but the last n characters of a card, account or phone number, optionally keeping separators.
from fune.text.mask import mask  # text.mask@^1

Gaps

What this kind of app usually needs that Functional Weave does not have yet: write these yourself, or use a service.

  • timesheets Clocking in and out and timesheet approval: Functional Weave prices hours worked, it does not record them.
  • fx-rate-source Where exchange rates come from: money.convert takes a rate you supply; fetching and storing daily rates (ECB, a provider's API) is yours.
  • money-storage Storing amounts: the database columns (integer minor units plus a currency code) and their migrations are yours.
  • time-zones Time zones and daylight saving: caps works in ISO dates, UTC and wall-clock times; converting with the IANA tz rules is yours.
  • locale-formatting Showing dates in a user's language and format ("3 octobre 2026"): caps returns ISO dates, not localised text.
  • non-uk-holidays Public holidays outside the UK: dates.bank-holidays is UK only; pass another country's list to the working-day functions yourself.
  • address-lookup Turning a postcode into a list of addresses (Royal Mail PAF or a lookup API) is network work and not in caps.
  • email-deliverability Whether a mailbox exists (MX lookups, a confirmation email): validation.email checks the syntax only.
  • vocalink-data The Vocalink modulus tables themselves: download them from Pay.UK under its terms; caps parses them but does not ship them.