{
  "id": "payroll",
  "title": "Payroll and HR",
  "summary": "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.",
  "synonyms": [
    "payroll",
    "payslips",
    "pay slips",
    "wages",
    "salaries",
    "paye",
    "hr",
    "human resources",
    "staff pay",
    "employee pay",
    "people management",
    "holiday tracker"
  ],
  "apps": [
    "payroll app",
    "payroll system for a small business",
    "payslip generator",
    "hr system",
    "staff holiday and absence tracker",
    "employee management software"
  ],
  "language": "python",
  "includes": [
    "money-basics",
    "dates-basics",
    "validation-basics"
  ],
  "includedBy": [],
  "caveats": [
    "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."
  ],
  "sections": [
    {
      "key": "employees",
      "title": "Employee records",
      "purpose": "Check the identifiers and dates an employee record holds.",
      "from": "payroll",
      "members": [
        {
          "id": "validation.uk-ni-number",
          "tier": "core",
          "why": "Checks a National Insurance number's format and HMRC's disallowed prefixes.",
          "section": "employees",
          "from": "payroll",
          "version": "2.0.0",
          "summary": "Check a UK National Insurance number's format and HMRC's disallowed prefix letters, and normalise it.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "validateUkNiNumber"
          ],
          "signatures": [
            "def validate_uk_ni_number(value: str) -> UkNiNumber"
          ],
          "reference": "from fune.validation.uk_ni_number import validate_uk_ni_number  # validation.uk-ni-number@^2",
          "url": "http://www.functionalweave.com/functions/validation.uk-ni-number"
        },
        {
          "id": "payroll.tax-code-parse",
          "tier": "core",
          "why": "Reads a PAYE tax code (1257L, K475, BR, S/C prefixes, W1/M1) into its allowance and flags.",
          "section": "employees",
          "from": "payroll",
          "version": "1.0.2",
          "summary": "Parse a UK PAYE tax code (1257L, K475, BR, D0, NT, 0T, S and C prefixes, W1/M1/X) into its allowance and flags.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "parseTaxCode"
          ],
          "signatures": [
            "def parse_tax_code(code: str) -> TaxCode"
          ],
          "reference": "from fune.payroll.tax_code_parse import parse_tax_code  # payroll.tax-code-parse@^1",
          "url": "http://www.functionalweave.com/functions/payroll.tax-code-parse"
        },
        {
          "id": "dates.age",
          "tier": "core",
          "why": "Age on a date, which decides minimum wage bands and NI category.",
          "section": "employees",
          "from": "payroll",
          "version": "1.0.0",
          "summary": "Age in whole years on a given date, with 29 February birthdays ageing on 1 March in other years.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "age"
          ],
          "signatures": [
            "def age(birth_date: str, on_date: str) -> int"
          ],
          "reference": "from fune.dates.age import age  # dates.age@^1",
          "url": "http://www.functionalweave.com/functions/dates.age"
        },
        {
          "id": "hr.length-of-service",
          "tier": "optional",
          "why": "Continuous service in years, months and days, for notice, redundancy and benefits.",
          "section": "employees",
          "from": "payroll",
          "version": "1.0.0",
          "summary": "Continuous service between two dates in completed years, months and days, counting both end dates.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "lengthOfService"
          ],
          "signatures": [
            "def length_of_service(start_date: str, end_date: str) -> ServiceLength"
          ],
          "reference": "from fune.hr.length_of_service import length_of_service  # hr.length-of-service@^1",
          "url": "http://www.functionalweave.com/functions/hr.length-of-service"
        }
      ],
      "gaps": [
        {
          "key": "starter-leaver-forms",
          "text": "Starter checklists, P45 and P60 documents: caps gives the figures, not the forms.",
          "section": "employees",
          "from": "payroll"
        }
      ]
    },
    {
      "key": "periods",
      "title": "Pay periods",
      "purpose": "Put each pay date into the right PAYE period.",
      "from": "payroll",
      "members": [
        {
          "id": "payroll.tax-period",
          "tier": "core",
          "why": "The tax week, tax month and period number a pay date falls in, weeks 53, 54 and 56 included.",
          "section": "periods",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "The UK PAYE tax week, tax month and payroll period number that a pay date falls in, weeks 53, 54 and 56 included.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "taxPeriod"
          ],
          "signatures": [
            "def tax_period(pay_date: str, frequency: PayFrequency) -> TaxPeriod"
          ],
          "reference": "from fune.payroll.tax_period import tax_period  # payroll.tax-period@^1",
          "url": "http://www.functionalweave.com/functions/payroll.tax-period"
        },
        {
          "id": "dates.uk-tax-year",
          "tier": "core",
          "why": "Which tax year (6 April to 5 April) a pay date belongs to.",
          "section": "periods",
          "from": "payroll",
          "version": "1.0.0",
          "summary": "The UK personal tax year containing a date: 6 April to 5 April, with HMRC's label.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "ukTaxYear"
          ],
          "signatures": [
            "def uk_tax_year(iso: str) -> UkTaxYear"
          ],
          "reference": "from fune.dates.uk_tax_year import uk_tax_year  # dates.uk-tax-year@^1",
          "url": "http://www.functionalweave.com/functions/dates.uk-tax-year"
        }
      ],
      "gaps": []
    },
    {
      "key": "gross-pay",
      "title": "Gross pay",
      "purpose": "Work out what each employee earned in the period.",
      "from": "payroll",
      "members": [
        {
          "id": "payroll.pro-rata-salary",
          "tier": "core",
          "why": "Salary for part of a period (joiners, leavers) and for part-time hours.",
          "section": "gross-pay",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "Salary for part of a pay period and for part-time hours, by calendar days or working days, rounded once.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "proRataSalary"
          ],
          "signatures": [
            "def pro_rata_salary(period_salary: Money, fte_basis_points: int, period_start: str, period_end: str, worked_from: str, worked_to: str, method: ProRataMethod, holidays: Sequence[str]) -> ProRata"
          ],
          "reference": "from fune.payroll.pro_rata_salary import pro_rata_salary  # payroll.pro-rata-salary@^1",
          "url": "http://www.functionalweave.com/functions/payroll.pro-rata-salary"
        },
        {
          "id": "payroll.overtime",
          "tier": "optional",
          "why": "Overtime pay in multiplier bands: time and a half after 40 hours, double time.",
          "section": "gross-pay",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "Pay for the time worked in a period split into multiplier bands: basic, time and a half after 40 hours, double time.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "overtimePay"
          ],
          "signatures": [
            "def overtime_pay(minutes_worked: int, hourly_rate: Money, bands: Sequence[OvertimeBand]) -> OvertimePay"
          ],
          "reference": "from fune.payroll.overtime import overtime_pay  # payroll.overtime@^1",
          "url": "http://www.functionalweave.com/functions/payroll.overtime"
        },
        {
          "id": "payroll.minimum-wage-check",
          "tier": "optional",
          "why": "Checks pay for the period against the minimum or living wage for the worker's age.",
          "section": "gross-pay",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "Check pay for a pay reference period against the minimum or living wage for age and apprenticeship.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "minimumWageCheck"
          ],
          "signatures": [
            "def minimum_wage_check(pay: Money, minutes_worked: int, birth_date: str, period_start: str, apprenticeship_start: Optional[str]) -> MinimumWageCheck"
          ],
          "reference": "from fune.payroll.minimum_wage_check import minimum_wage_check  # payroll.minimum-wage-check@^1",
          "url": "http://www.functionalweave.com/functions/payroll.minimum-wage-check"
        },
        {
          "id": "charity.payroll-giving",
          "tier": "optional",
          "why": "Payroll Giving donations taken before PAYE, and the tax they save.",
          "section": "gross-pay",
          "from": "payroll",
          "version": "1.0.0",
          "summary": "UK Payroll Giving for one pay period: the donation comes off pay before PAYE, and the tax it saves the employee.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "payrollGiving"
          ],
          "signatures": [
            "def payroll_giving(tax_code: str, frequency: PayFrequency, period: int, pay: Money, donation: Money, previous_pay_to_date: Money, previous_tax_to_date: Money, pay_date: str) -> PayrollGiving"
          ],
          "reference": "from fune.charity.payroll_giving import payroll_giving  # charity.payroll-giving@^1",
          "url": "http://www.functionalweave.com/functions/charity.payroll-giving"
        }
      ],
      "gaps": []
    },
    {
      "key": "deductions",
      "title": "Tax and deductions",
      "purpose": "Take tax, National Insurance, pension and loans off the gross, and give the payslip.",
      "from": "payroll",
      "members": [
        {
          "id": "payroll.gross-to-net",
          "tier": "core",
          "why": "The whole payslip for one period: gross, pension, PAYE, NI, student loans and net pay.",
          "section": "deductions",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "A UK payslip for one pay period: gross pay, pension, PAYE tax, National Insurance, student loans and net pay.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "grossToNet"
          ],
          "signatures": [
            "def gross_to_net(input: PayslipInput) -> Payslip"
          ],
          "reference": "from fune.payroll.gross_to_net import gross_to_net  # payroll.gross-to-net@^1",
          "url": "http://www.functionalweave.com/functions/payroll.gross-to-net"
        },
        {
          "id": "payroll.income-tax",
          "tier": "core",
          "why": "PAYE income tax for one period, cumulative or week 1/month 1, by HMRC's tax tables.",
          "section": "deductions",
          "from": "payroll",
          "version": "1.0.2",
          "summary": "PAYE income tax for one pay period from a tax code, cumulative or week 1/month 1, by HMRC's tax table routines.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "incomeTax"
          ],
          "signatures": [
            "def income_tax(tax_code: str, frequency: PayFrequency, period: int, pay: Money, previous_pay_to_date: Money, previous_tax_to_date: Money, pay_date: str) -> IncomeTax"
          ],
          "reference": "from fune.payroll.income_tax import income_tax  # payroll.income-tax@^1",
          "url": "http://www.functionalweave.com/functions/payroll.income-tax"
        },
        {
          "id": "payroll.national-insurance",
          "tier": "core",
          "why": "Employee and employer Class 1 NICs by category letter for one payment.",
          "section": "deductions",
          "from": "payroll",
          "version": "1.0.2",
          "summary": "Employee and employer Class 1 National Insurance for one payment by category letter, exact percentage method.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "nationalInsurance"
          ],
          "signatures": [
            "def national_insurance(earnings: Money, category: str, frequency: PayFrequency, pay_date: str, director: Optional[DirectorNi]) -> NationalInsurance"
          ],
          "reference": "from fune.payroll.national_insurance import national_insurance  # payroll.national-insurance@^1",
          "url": "http://www.functionalweave.com/functions/payroll.national-insurance"
        },
        {
          "id": "payroll.pension-auto-enrolment",
          "tier": "core",
          "why": "Auto-enrolment pension: qualifying earnings and employee and employer contributions.",
          "section": "deductions",
          "from": "payroll",
          "version": "1.0.2",
          "summary": "Workplace pension contributions for one pay period: qualifying earnings band, employee and employer shares, tax relief.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "pensionContributions"
          ],
          "signatures": [
            "def pension_contributions(earnings: Money, frequency: PayFrequency, scheme: PensionScheme, pay_date: str) -> PensionContributions"
          ],
          "reference": "from fune.payroll.pension_auto_enrolment import pension_contributions  # payroll.pension-auto-enrolment@^1",
          "url": "http://www.functionalweave.com/functions/payroll.pension-auto-enrolment"
        },
        {
          "id": "payroll.student-loan",
          "tier": "core",
          "why": "Student and postgraduate loan deductions by plan.",
          "section": "deductions",
          "from": "payroll",
          "version": "1.0.2",
          "summary": "Student loan or postgraduate loan deduction from one period's pay, by plan, rounded down to whole pounds.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "studentLoan"
          ],
          "signatures": [
            "def student_loan(earnings: Money, plan: StudentLoanPlan, frequency: PayFrequency, pay_date: str) -> Money"
          ],
          "reference": "from fune.payroll.student_loan import student_loan  # payroll.student-loan@^1",
          "url": "http://www.functionalweave.com/functions/payroll.student-loan"
        },
        {
          "id": "payroll.net-to-gross",
          "tier": "optional",
          "why": "The gross pay that gives a target net pay, for agreed-net payments.",
          "section": "deductions",
          "from": "payroll",
          "version": "1.0.2",
          "summary": "The gross pay that gives at least a target net pay in one UK pay period, found by a deterministic search.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "netToGross"
          ],
          "signatures": [
            "def net_to_gross(target_net: Money, input: PayslipInput) -> Payslip"
          ],
          "reference": "from fune.payroll.net_to_gross import net_to_gross  # payroll.net-to-gross@^1",
          "url": "http://www.functionalweave.com/functions/payroll.net-to-gross"
        },
        {
          "id": "fleet.company-car-bik",
          "tier": "optional",
          "why": "Company car benefit in kind for the tax year, for P11D or payrolled benefits.",
          "section": "deductions",
          "from": "payroll",
          "version": "1.0.2",
          "summary": "UK company car benefit in kind: appropriate percentage by CO2 and electric range, car and fuel benefit for a tax year.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "companyCarBik"
          ],
          "signatures": [
            "def company_car_bik(list_price: Money, capital_contributions: Money, co2: int, electric_range_miles: int, diesel_supplement: bool, first_registered: str, on_date: str, unavailable_days: int, private_use_contributions: Money, free_fuel: bool) -> CompanyCarBik"
          ],
          "reference": "from fune.fleet.company_car_bik import company_car_bik  # fleet.company-car-bik@^1",
          "url": "http://www.functionalweave.com/functions/fleet.company-car-bik"
        },
        {
          "id": "fleet.mileage-allowance",
          "tier": "optional",
          "why": "Approved mileage allowance payments for staff using their own vehicle: tax-free and taxable parts.",
          "section": "deductions",
          "from": "payroll",
          "version": "1.0.2",
          "summary": "HMRC approved mileage allowance payments for an employee's own vehicle: tax-free amount, taxable excess or relief.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "mileageAllowance"
          ],
          "signatures": [
            "def mileage_allowance(vehicle: MileageVehicle, previous_miles: int, miles: int, passenger_miles: int, paid: Money, passenger_paid: Money, on_date: str) -> MileageAllowance"
          ],
          "reference": "from fune.fleet.mileage_allowance import mileage_allowance  # fleet.mileage-allowance@^1",
          "url": "http://www.functionalweave.com/functions/fleet.mileage-allowance"
        }
      ],
      "gaps": [
        {
          "key": "rti-submission",
          "text": "Sending FPS and EPS returns to HMRC under Real Time Information: the XML, the gateway and the credentials are yours.",
          "section": "deductions",
          "from": "payroll"
        },
        {
          "key": "pension-provider-files",
          "text": "Contribution files for pension providers (NEST, The People's Pension) and enrolment letters.",
          "section": "deductions",
          "from": "payroll"
        }
      ]
    },
    {
      "key": "statutory-pay",
      "title": "Statutory pay",
      "purpose": "Pay what the law requires while an employee is off sick or on family leave.",
      "from": "payroll",
      "members": [
        {
          "id": "payroll.statutory-sick-pay",
          "tier": "core",
          "why": "SSP for sickness spells: waiting days, linked periods, the 28-week limit and the rate by date.",
          "section": "statutory-pay",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "UK Statutory Sick Pay for sickness spells: waiting days, 8-week linking, 28-week limit and the rate by date.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "statutorySickPay"
          ],
          "signatures": [
            "def statutory_sick_pay(periods: Sequence[SicknessPeriod], qualifying_weekdays: Sequence[int], normal_weekly_earnings: Money) -> SickPay"
          ],
          "reference": "from fune.payroll.statutory_sick_pay import statutory_sick_pay  # payroll.statutory-sick-pay@^1",
          "url": "http://www.functionalweave.com/functions/payroll.statutory-sick-pay"
        },
        {
          "id": "payroll.statutory-maternity-pay",
          "tier": "optional",
          "why": "SMP eligibility and its 39 weekly amounts.",
          "section": "statutory-pay",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "UK Statutory Maternity Pay: LEL eligibility and 39 weekly amounts, 90% then the lower of 90% or the flat rate.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "statutoryMaternityPay"
          ],
          "signatures": [
            "def statutory_maternity_pay(average_weekly_earnings: Money, due_date: str, pay_start: str) -> MaternityPay"
          ],
          "reference": "from fune.payroll.statutory_maternity_pay import statutory_maternity_pay  # payroll.statutory-maternity-pay@^1",
          "url": "http://www.functionalweave.com/functions/payroll.statutory-maternity-pay"
        }
      ],
      "gaps": []
    },
    {
      "key": "leave",
      "title": "Holidays and absence",
      "purpose": "Track holiday entitlement and absence.",
      "from": "payroll",
      "members": [
        {
          "id": "payroll.holiday-entitlement",
          "tier": "core",
          "why": "Statutory holiday: 5.6 weeks capped at 28 days, pro-rated, or 12.07% for irregular hours.",
          "section": "leave",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "UK statutory holiday: 5.6 weeks capped at 28 days and pro-rated, or 12.07% accrual for irregular hours.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "holidayEntitlement"
          ],
          "signatures": [
            "def holiday_entitlement(worker: HolidayWorker, leave_year_start: str) -> HolidayEntitlement"
          ],
          "reference": "from fune.payroll.holiday_entitlement import holiday_entitlement  # payroll.holiday-entitlement@^1",
          "url": "http://www.functionalweave.com/functions/payroll.holiday-entitlement"
        },
        {
          "id": "payroll.holiday-pay-accrual",
          "tier": "optional",
          "why": "Holiday pay: 12.07% rolled-up, or a week's pay averaged over 52 paid weeks.",
          "section": "leave",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "UK holiday pay: 12.07% rolled-up holiday pay, or a week's pay averaged over the last 52 paid weeks.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "holidayPay"
          ],
          "signatures": [
            "def holiday_pay(method: HolidayPayMethod, period_pay: Money, weekly_pay: Sequence[Money], on_date: str) -> HolidayPay"
          ],
          "reference": "from fune.payroll.holiday_pay_accrual import holiday_pay  # payroll.holiday-pay-accrual@^1",
          "url": "http://www.functionalweave.com/functions/payroll.holiday-pay-accrual"
        },
        {
          "id": "hr.absence-bradford-factor",
          "tier": "optional",
          "why": "Bradford factor from absence spells, to flag frequent short absences.",
          "section": "leave",
          "from": "payroll",
          "version": "1.0.0",
          "summary": "Bradford factor for a set of absence spells: spells squared times total days, which weights frequent short absences.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "bradfordFactor"
          ],
          "signatures": [
            "def bradford_factor(spell_days: Sequence[int]) -> BradfordFactor"
          ],
          "reference": "from fune.hr.absence_bradford_factor import bradford_factor  # hr.absence-bradford-factor@^1",
          "url": "http://www.functionalweave.com/functions/hr.absence-bradford-factor"
        }
      ],
      "gaps": [
        {
          "key": "leave-requests",
          "text": "Requesting and approving leave, team calendars and clashes.",
          "section": "leave",
          "from": "payroll"
        }
      ]
    },
    {
      "key": "leavers",
      "title": "Leaving",
      "purpose": "Work out what a leaver is owed.",
      "from": "payroll",
      "members": [
        {
          "id": "payroll.notice-period",
          "tier": "optional",
          "why": "Statutory minimum notice from length of service.",
          "section": "leavers",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "Statutory minimum notice in weeks from continuous service: one week per year from two years, capped at twelve.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "statutoryNotice"
          ],
          "signatures": [
            "def statutory_notice(start_date: str, notice_date: str) -> StatutoryNotice"
          ],
          "reference": "from fune.payroll.notice_period import statutory_notice  # payroll.notice-period@^1",
          "url": "http://www.functionalweave.com/functions/payroll.notice-period"
        },
        {
          "id": "payroll.redundancy-pay",
          "tier": "optional",
          "why": "Statutory redundancy pay from age, service and a capped week's pay.",
          "section": "leavers",
          "from": "payroll",
          "version": "1.0.1",
          "summary": "Statutory redundancy pay from age, completed years of service (at most 20) and a week's pay capped by date.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "statutoryRedundancyPay"
          ],
          "signatures": [
            "def statutory_redundancy_pay(birth_date: str, start_date: str, relevant_date: str, weekly_pay: Money) -> RedundancyPay"
          ],
          "reference": "from fune.payroll.redundancy_pay import statutory_redundancy_pay  # payroll.redundancy-pay@^1",
          "url": "http://www.functionalweave.com/functions/payroll.redundancy-pay"
        }
      ],
      "gaps": []
    },
    {
      "key": "payments",
      "title": "Paying staff",
      "purpose": "Get net pay into employees' accounts on payday.",
      "from": "payroll",
      "members": [
        {
          "id": "banking.bacs-processing-date",
          "tier": "optional",
          "why": "When to submit a Bacs payment run so it arrives on payday, skipping weekends and bank holidays.",
          "section": "payments",
          "from": "payroll",
          "version": "1.0.0",
          "summary": "Bacs three-day cycle: input, processing and entry (arrival) dates for a submission, skipping weekends and bank holidays.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "bacsProcessingDates"
          ],
          "signatures": [
            "def bacs_processing_dates(submission_iso: str, after_cutoff: bool) -> BacsCycle"
          ],
          "reference": "from fune.banking.bacs_processing_date import bacs_processing_dates  # banking.bacs-processing-date@^1",
          "url": "http://www.functionalweave.com/functions/banking.bacs-processing-date"
        }
      ],
      "gaps": [
        {
          "key": "bacs-files",
          "text": "Writing the Bacs Standard 18 payment file or a bank's bulk-payment CSV.",
          "section": "payments",
          "from": "payroll"
        }
      ]
    },
    {
      "key": "amounts",
      "title": "Amounts",
      "purpose": "Hold money as currency-tagged integer minor units and do plain arithmetic on it safely.",
      "from": "money-basics",
      "members": [
        {
          "id": "money.amount",
          "tier": "core",
          "why": "The Money type ({ minor, currency }) every other money capability takes and returns.",
          "section": "amounts",
          "from": "money-basics",
          "version": "1.0.2",
          "summary": "A currency-tagged monetary amount held in integer minor units.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "money"
          ],
          "signatures": [
            "def money(minor: int, currency: str) -> Money"
          ],
          "reference": "from fune.money.amount import money  # money.amount@^1",
          "url": "http://www.functionalweave.com/functions/money.amount"
        },
        {
          "id": "money.add",
          "tier": "core",
          "why": "Adds and subtracts amounts, refusing to mix currencies by accident.",
          "section": "amounts",
          "from": "money-basics",
          "version": "1.0.1",
          "summary": "Add, subtract and negate monetary amounts, refusing to mix currencies.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addMoney"
          ],
          "signatures": [
            "def add_money(a: Money, b: Money) -> Money"
          ],
          "reference": "from fune.money.add import add_money  # money.add@^1",
          "url": "http://www.functionalweave.com/functions/money.add"
        },
        {
          "id": "money.sum",
          "tier": "core",
          "why": "Totals a list of amounts, with an explicit currency for an empty list.",
          "section": "amounts",
          "from": "money-basics",
          "version": "1.0.1",
          "summary": "Total a list of monetary amounts, with an explicit currency for the empty case.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "sumMoney"
          ],
          "signatures": [
            "def sum_money(amounts: Sequence[Money], currency: str) -> Money"
          ],
          "reference": "from fune.money.sum import sum_money  # money.sum@^1",
          "url": "http://www.functionalweave.com/functions/money.sum"
        },
        {
          "id": "money.compare",
          "tier": "core",
          "why": "Compares, mins, maxes and clamps amounts of one currency without unwrapping them.",
          "section": "amounts",
          "from": "money-basics",
          "version": "2.0.0",
          "summary": "Compare two amounts of one currency, refusing to compare across currencies; min, max and clamp too.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "compareMoney",
            "minMoney",
            "maxMoney",
            "clampMoney"
          ],
          "signatures": [
            "def compare_money(a: Money, b: Money) -> int",
            "def min_money(a: Money, b: Money) -> Money",
            "def max_money(a: Money, b: Money) -> Money",
            "def clamp_money(amount: Money, min: Money, max: Money) -> Money"
          ],
          "reference": "from fune.money.compare import compare_money, min_money, max_money, clamp_money  # money.compare@^2",
          "url": "http://www.functionalweave.com/functions/money.compare"
        }
      ],
      "gaps": []
    },
    {
      "key": "rates",
      "title": "Rates and rounding",
      "purpose": "Apply percentages and divide amounts with the rounding stated, never a float.",
      "from": "money-basics",
      "members": [
        {
          "id": "money.apply-rate",
          "tier": "core",
          "why": "Applies a basis-point rate (VAT, discount, commission) to an amount with explicit rounding.",
          "section": "rates",
          "from": "money-basics",
          "version": "1.0.1",
          "summary": "Apply a rate expressed in basis points to a monetary amount, with an explicit rounding mode.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "applyRate"
          ],
          "signatures": [
            "def apply_rate(amount: Money, basis_points: int, mode: RoundingMode) -> Money"
          ],
          "reference": "from fune.money.apply_rate import apply_rate  # money.apply-rate@^1",
          "url": "http://www.functionalweave.com/functions/money.apply-rate"
        },
        {
          "id": "math.round-div",
          "tier": "core",
          "why": "Integer division with a named rounding mode, for any money maths the others do not cover.",
          "section": "rates",
          "from": "money-basics",
          "version": "1.0.0",
          "summary": "Integer division with an explicit rounding mode, for money arithmetic that must not drift.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "roundDiv"
          ],
          "signatures": [
            "def round_div(numerator: int, denominator: int, mode: RoundingMode) -> int"
          ],
          "reference": "from fune.math.round_div import round_div  # math.round-div@^1",
          "url": "http://www.functionalweave.com/functions/math.round-div"
        },
        {
          "id": "math.basis-points",
          "tier": "optional",
          "why": "Converts \"12.5%\" to and from basis points exactly, for rates typed in by users.",
          "section": "rates",
          "from": "money-basics",
          "version": "2.0.0",
          "summary": "Convert a rate between percent, basis points and a plain ratio exactly, as decimal text or integer basis points.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "convertRate",
            "toBasisPoints",
            "fromBasisPoints"
          ],
          "signatures": [
            "def convert_rate(value: str, from_unit: RateUnit, to_unit: RateUnit) -> str",
            "def to_basis_points(value: str, unit: RateUnit) -> int",
            "def from_basis_points(basis_points: int, unit: RateUnit) -> str"
          ],
          "reference": "from fune.math.basis_points import convert_rate, to_basis_points, from_basis_points  # math.basis-points@^2",
          "url": "http://www.functionalweave.com/functions/math.basis-points"
        },
        {
          "id": "math.percent-change",
          "tier": "optional",
          "why": "Percentage change between two figures in basis points, for \"up 4.2% on last month\".",
          "section": "rates",
          "from": "money-basics",
          "version": "1.0.0",
          "summary": "Percentage change from one value to another, in basis points, with an explicit rounding mode.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "percentChange"
          ],
          "signatures": [
            "def percent_change(from_value: int, to_value: int, mode: RoundingMode) -> Optional[int]"
          ],
          "reference": "from fune.math.percent_change import percent_change  # math.percent-change@^1",
          "url": "http://www.functionalweave.com/functions/math.percent-change"
        },
        {
          "id": "math.rational",
          "tier": "optional",
          "why": "Exact fractions for rates that must not drift across many steps.",
          "section": "rates",
          "from": "money-basics",
          "version": "2.0.0",
          "summary": "Exact fraction arithmetic, always reduced, for rates and ratios that must not drift.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "calculateRational",
            "rational",
            "addRational",
            "subtractRational",
            "multiplyRational",
            "divideRational",
            "compareRational",
            "rationalToInteger"
          ],
          "signatures": [
            "def calculate_rational(a: Rational, op: RationalOp, b: Rational) -> Rational",
            "def rational(numerator: int, denominator: int) -> Rational",
            "def add_rational(a: Rational, b: Rational) -> Rational",
            "def subtract_rational(a: Rational, b: Rational) -> Rational",
            "def multiply_rational(a: Rational, b: Rational) -> Rational",
            "def divide_rational(a: Rational, b: Rational) -> Rational",
            "def compare_rational(a: Rational, b: Rational) -> int",
            "def rational_to_integer(r: Rational, mode: RoundingMode) -> int"
          ],
          "reference": "from fune.math.rational import calculate_rational, rational, add_rational, subtract_rational, multiply_rational, divide_rational, compare_rational, rational_to_integer  # math.rational@^2",
          "url": "http://www.functionalweave.com/functions/math.rational"
        },
        {
          "id": "math.round-div-big",
          "tier": "optional",
          "why": "Rounded division on numbers past 2^53, for very large totals.",
          "section": "rates",
          "from": "money-basics",
          "version": "1.0.0",
          "summary": "Divide two integers of any size with an explicit rounding mode, on decimal strings, for exact money sums past 2^53.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "roundDivBig"
          ],
          "signatures": [
            "def round_div_big(numerator: str, denominator: str, mode: RoundingMode) -> str"
          ],
          "reference": "from fune.math.round_div_big import round_div_big  # math.round-div-big@^1",
          "url": "http://www.functionalweave.com/functions/math.round-div-big"
        }
      ],
      "gaps": []
    },
    {
      "key": "splitting",
      "title": "Splitting",
      "purpose": "Divide an amount between people, periods or lines so the parts add back to the whole.",
      "from": "money-basics",
      "members": [
        {
          "id": "money.allocate",
          "tier": "core",
          "why": "Splits an amount by ratios (60/40, by quantity) without losing or inventing a penny.",
          "section": "splitting",
          "from": "money-basics",
          "version": "1.0.1",
          "summary": "Split an amount across ratios without losing or inventing a single minor unit.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "allocate"
          ],
          "signatures": [
            "def allocate(amount: Money, ratios: Sequence[int]) -> List[Money]"
          ],
          "reference": "from fune.money.allocate import allocate  # money.allocate@^1",
          "url": "http://www.functionalweave.com/functions/money.allocate"
        },
        {
          "id": "money.split-even",
          "tier": "optional",
          "why": "Splits an amount into n near-equal parts that sum exactly, for instalments or shares.",
          "section": "splitting",
          "from": "money-basics",
          "version": "1.0.0",
          "summary": "Split an amount into n near-equal parts that add back up to exactly the whole.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "splitEven"
          ],
          "signatures": [
            "def split_even(amount: Money, parts: int) -> List[Money]"
          ],
          "reference": "from fune.money.split_even import split_even  # money.split-even@^1",
          "url": "http://www.functionalweave.com/functions/money.split-even"
        }
      ],
      "gaps": []
    },
    {
      "key": "display",
      "title": "Display and input",
      "purpose": "Show amounts to people and read what they type.",
      "from": "money-basics",
      "members": [
        {
          "id": "money.format",
          "tier": "core",
          "why": "Renders an amount as text with the currency's real number of decimal places.",
          "section": "display",
          "from": "money-basics",
          "version": "1.0.0",
          "summary": "Render a monetary amount as text, using the currency's real minor-unit precision.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "formatMoney"
          ],
          "signatures": [
            "def format_money(amount: Money) -> str"
          ],
          "reference": "from fune.money.format import format_money  # money.format@^1",
          "url": "http://www.functionalweave.com/functions/money.format"
        },
        {
          "id": "money.parse",
          "tier": "optional",
          "why": "Reads \"£1,234.50\" or \"1.234,50 €\" typed by a user into minor units, strictly.",
          "section": "display",
          "from": "money-basics",
          "version": "1.0.0",
          "summary": "Parse \"£1,234.50\" or \"1.234,50 €\" into integer minor units, strictly: no guessing, no rounding.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "parseMoney"
          ],
          "signatures": [
            "def parse_money(text: str, currency: str, style: NumberStyle) -> Money"
          ],
          "reference": "from fune.money.parse import parse_money  # money.parse@^1",
          "url": "http://www.functionalweave.com/functions/money.parse"
        },
        {
          "id": "money.currency-digits",
          "tier": "optional",
          "why": "How many decimal places each ISO 4217 currency has, for input boxes and validation.",
          "section": "display",
          "from": "money-basics",
          "version": "1.0.0",
          "summary": "How many decimal places a currency's minor unit has, from the ISO 4217 list.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "currencyDigits"
          ],
          "signatures": [
            "def currency_digits(currency: str) -> int"
          ],
          "reference": "from fune.money.currency_digits import currency_digits  # money.currency-digits@^1",
          "url": "http://www.functionalweave.com/functions/money.currency-digits"
        }
      ],
      "gaps": []
    },
    {
      "key": "currencies",
      "title": "Other currencies",
      "purpose": "Convert between currencies at a rate you hold.",
      "from": "money-basics",
      "members": [
        {
          "id": "money.convert",
          "tier": "optional",
          "why": "Converts an amount at a supplied exact rate with explicit rounding.",
          "section": "currencies",
          "from": "money-basics",
          "version": "1.0.0",
          "summary": "Convert an amount to another currency at a supplied exact rate, with an explicit rounding mode.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "convertMoney"
          ],
          "signatures": [
            "def convert_money(amount: Money, rate: ExchangeRate, mode: RoundingMode) -> Money"
          ],
          "reference": "from fune.money.convert import convert_money  # money.convert@^1",
          "url": "http://www.functionalweave.com/functions/money.convert"
        }
      ],
      "gaps": []
    },
    {
      "key": "date-arithmetic",
      "title": "Calendar arithmetic",
      "purpose": "Move and measure ISO dates exactly, with no date library and no clock reads.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.add-days",
          "tier": "core",
          "why": "Shifts a date by whole days, for due dates and windows.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Shift an ISO date by a whole number of days, forwards or backwards, with exact calendar arithmetic.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addDays"
          ],
          "signatures": [
            "def add_days(iso: str, days: int) -> str"
          ],
          "reference": "from fune.dates.add_days import add_days  # dates.add-days@^1",
          "url": "http://www.functionalweave.com/functions/dates.add-days"
        },
        {
          "id": "dates.add-months",
          "tier": "core",
          "why": "Adds months clamping to month end, so 31 January plus a month is the end of February.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Add calendar months to an ISO date, clamping to the month end: 31 Jan + 1 month is 28 or 29 Feb.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addMonths"
          ],
          "signatures": [
            "def add_months(iso: str, months: int) -> str"
          ],
          "reference": "from fune.dates.add_months import add_months  # dates.add-months@^1",
          "url": "http://www.functionalweave.com/functions/dates.add-months"
        },
        {
          "id": "dates.days-between",
          "tier": "core",
          "why": "Signed calendar days between two dates, for ages of invoices and stays.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Calendar days from one ISO date to another, signed: negative when the second date is earlier.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "daysBetween"
          ],
          "signatures": [
            "def days_between(start_iso: str, end_iso: str) -> int"
          ],
          "reference": "from fune.dates.days_between import days_between  # dates.days-between@^1",
          "url": "http://www.functionalweave.com/functions/dates.days-between"
        },
        {
          "id": "dates.month-boundaries",
          "tier": "core",
          "why": "First and last day of a date's month, for monthly periods and statements.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "The first day, last day and length of the calendar month containing a date.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "monthBoundaries"
          ],
          "signatures": [
            "def month_boundaries(iso: str) -> MonthBoundaries"
          ],
          "reference": "from fune.dates.month_boundaries import month_boundaries  # dates.month-boundaries@^1",
          "url": "http://www.functionalweave.com/functions/dates.month-boundaries"
        },
        {
          "id": "dates.day-of-week",
          "tier": "core",
          "why": "ISO weekday of a date, for weekend rules and opening hours.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "The ISO 8601 day of the week for a date: 1 is Monday through 7 is Sunday.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "dayOfWeek"
          ],
          "signatures": [
            "def day_of_week(iso: str) -> int"
          ],
          "reference": "from fune.dates.day_of_week import day_of_week  # dates.day-of-week@^1",
          "url": "http://www.functionalweave.com/functions/dates.day-of-week"
        },
        {
          "id": "dates.is-leap-year",
          "tier": "optional",
          "why": "Whether a year is a leap year, for 365/366-day calculations.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Whether a year is a Gregorian leap year: every 4th, except centuries, except every 400th.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isLeapYear"
          ],
          "signatures": [
            "def is_leap_year(year: int) -> bool"
          ],
          "reference": "from fune.dates.is_leap_year import is_leap_year  # dates.is-leap-year@^1",
          "url": "http://www.functionalweave.com/functions/dates.is-leap-year"
        },
        {
          "id": "dates.overlap",
          "tier": "optional",
          "why": "Days two date ranges share, for prorating and clash checks.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Days two date ranges have in common, with the end of each range stated as inclusive or exclusive.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "overlapDays"
          ],
          "signatures": [
            "def overlap_days(a_start: str, a_end: str, b_start: str, b_end: str, end_inclusive: bool) -> int"
          ],
          "reference": "from fune.dates.overlap import overlap_days  # dates.overlap@^1",
          "url": "http://www.functionalweave.com/functions/dates.overlap"
        }
      ],
      "gaps": []
    },
    {
      "key": "reporting-periods",
      "title": "Periods and reporting dates",
      "purpose": "Put dates into the periods reports are grouped by.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.quarter",
          "tier": "core",
          "why": "Calendar and fiscal quarter of a date for any year-start month.",
          "section": "reporting-periods",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Calendar quarter and fiscal quarter of a date, with the fiscal year and quarter date ranges, for any start month.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "quarter"
          ],
          "signatures": [
            "def quarter(iso: str, fiscal_year_start_month: int) -> Quarter"
          ],
          "reference": "from fune.dates.quarter import quarter  # dates.quarter@^1",
          "url": "http://www.functionalweave.com/functions/dates.quarter"
        },
        {
          "id": "dates.week-number",
          "tier": "optional",
          "why": "ISO week number and week-year, for weekly reports.",
          "section": "reporting-periods",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "ISO 8601 week number and week-numbering year of a date: weeks start Monday, week 1 holds the first Thursday.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "weekNumber"
          ],
          "signatures": [
            "def week_number(iso: str) -> IsoWeek"
          ],
          "reference": "from fune.dates.week_number import week_number  # dates.week-number@^1",
          "url": "http://www.functionalweave.com/functions/dates.week-number"
        },
        {
          "id": "dates.day-count-fraction",
          "tier": "optional",
          "why": "Year fraction by ACT/365F, ACT/360, 30/360 or ACT/ACT, for interest periods.",
          "section": "reporting-periods",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Year fraction between two dates by ACT/365F, ACT/360, 30/360, 30E/360 or ACT/ACT ISDA, as an exact fraction.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "dayCountFraction"
          ],
          "signatures": [
            "def day_count_fraction(start_iso: str, end_iso: str, convention: DayCountConvention) -> YearFraction"
          ],
          "reference": "from fune.dates.day_count_fraction import day_count_fraction  # dates.day-count-fraction@^1",
          "url": "http://www.functionalweave.com/functions/dates.day-count-fraction"
        }
      ],
      "gaps": []
    },
    {
      "key": "working-days",
      "title": "Working days and holidays",
      "purpose": "Skip weekends and public holidays when counting or adding days.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.bank-holidays",
          "tier": "core",
          "why": "UK bank holidays for a region and year, as gov.uk publishes them.",
          "section": "working-days",
          "from": "dates-basics",
          "version": "1.0.1",
          "summary": "UK bank and public holidays for a region and year, as published by gov.uk, including one-off holidays.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "bankHolidays"
          ],
          "signatures": [
            "def bank_holidays(region: UkRegion, year: int) -> List[BankHoliday]"
          ],
          "reference": "from fune.dates.bank_holidays import bank_holidays  # dates.bank-holidays@^1",
          "url": "http://www.functionalweave.com/functions/dates.bank-holidays"
        },
        {
          "id": "dates.add-business-days",
          "tier": "core",
          "why": "Moves a date by working days, skipping weekends and a holiday list.",
          "section": "working-days",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Move a date forward or back by working days, skipping weekends and a caller-supplied holiday list.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addBusinessDays"
          ],
          "signatures": [
            "def add_business_days(iso: str, days: int, holidays: Sequence[str]) -> str"
          ],
          "reference": "from fune.dates.add_business_days import add_business_days  # dates.add-business-days@^1",
          "url": "http://www.functionalweave.com/functions/dates.add-business-days"
        },
        {
          "id": "dates.business-days-between",
          "tier": "core",
          "why": "Working days between two dates, for SLAs and notice periods.",
          "section": "working-days",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Working days between two dates, excluding weekends and a caller-supplied holiday list.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "businessDaysBetween"
          ],
          "signatures": [
            "def business_days_between(start_iso: str, end_iso: str, holidays: Sequence[str]) -> int"
          ],
          "reference": "from fune.dates.business_days_between import business_days_between  # dates.business-days-between@^1",
          "url": "http://www.functionalweave.com/functions/dates.business-days-between"
        }
      ],
      "gaps": []
    },
    {
      "key": "repeating-dates",
      "title": "Repeating schedules",
      "purpose": "Generate the dates of something that repeats.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.recurrence",
          "tier": "optional",
          "why": "The next n dates of a rule: monthly on the 31st, last Friday, every two weeks.",
          "section": "repeating-dates",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "The next n dates of a repeating rule: monthly on a day, last weekday of the month, every k days or weeks, yearly.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "recurrence"
          ],
          "signatures": [
            "def recurrence(rule: RecurrenceRule, start_iso: str, count: int) -> List[str]"
          ],
          "reference": "from fune.dates.recurrence import recurrence  # dates.recurrence@^1",
          "url": "http://www.functionalweave.com/functions/dates.recurrence"
        }
      ],
      "gaps": []
    },
    {
      "key": "times",
      "title": "Times and durations",
      "purpose": "Work with times of day, durations and timestamps.",
      "from": "dates-basics",
      "members": [
        {
          "id": "time.duration",
          "tier": "optional",
          "why": "Parses and adds durations like 1h30m in whole minutes, and formats the total.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Parse durations written as 1h30m, 90m or 01:30, add them up in whole minutes, and format the total.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "duration"
          ],
          "signatures": [
            "def duration(parts: Sequence[str]) -> Duration"
          ],
          "reference": "from fune.time.duration import duration  # time.duration@^1",
          "url": "http://www.functionalweave.com/functions/time.duration"
        },
        {
          "id": "time.minutes-between",
          "tier": "optional",
          "why": "Minutes between two local date-times, across midnight.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Minutes from one local date and time to another, correct across midnight; wall-clock time, no time zones.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "minutesBetween"
          ],
          "signatures": [
            "def minutes_between(start_date: str, start_time: str, end_date: str, end_time: str) -> int"
          ],
          "reference": "from fune.time.minutes_between import minutes_between  # time.minutes-between@^1",
          "url": "http://www.functionalweave.com/functions/time.minutes-between"
        },
        {
          "id": "time.round-to-increment",
          "tier": "optional",
          "why": "Rounds minutes to a billing increment such as 6 or 15.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Round a duration in minutes to a billing increment such as 6 or 15 minutes, up, down or to nearest.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "roundToIncrement"
          ],
          "signatures": [
            "def round_to_increment(minutes: int, increment: int, mode: RoundingMode) -> int"
          ],
          "reference": "from fune.time.round_to_increment import round_to_increment  # time.round-to-increment@^1",
          "url": "http://www.functionalweave.com/functions/time.round-to-increment"
        },
        {
          "id": "time.iso-to-unix",
          "tier": "optional",
          "why": "RFC 3339 timestamps to Unix seconds.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "An RFC 3339 timestamp with a Z or offset, such as 2026-09-26T13:00:00+01:00, to Unix time in whole seconds.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isoToUnix"
          ],
          "signatures": [
            "def iso_to_unix(timestamp: str) -> int"
          ],
          "reference": "from fune.time.iso_to_unix import iso_to_unix  # time.iso-to-unix@^1",
          "url": "http://www.functionalweave.com/functions/time.iso-to-unix"
        },
        {
          "id": "time.unix-to-iso",
          "tier": "optional",
          "why": "Unix seconds to an ISO 8601 UTC timestamp.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Unix time in seconds to an ISO 8601 UTC timestamp such as 2026-09-26T12:00:00Z, for years 0001 to 9999.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "unixToIso"
          ],
          "signatures": [
            "def unix_to_iso(seconds: int) -> str"
          ],
          "reference": "from fune.time.unix_to_iso import unix_to_iso  # time.unix-to-iso@^1",
          "url": "http://www.functionalweave.com/functions/time.unix-to-iso"
        },
        {
          "id": "time.countdown",
          "tier": "optional",
          "why": "Seconds left until a moment and a 4:05 style timer text.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Seconds left until a Unix time, whether it has passed, and a timer text like 4:05, for tokens or retry waits.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "countdown"
          ],
          "signatures": [
            "def countdown(until: int, now: int) -> Countdown"
          ],
          "reference": "from fune.time.countdown import countdown  # time.countdown@^1",
          "url": "http://www.functionalweave.com/functions/time.countdown"
        }
      ],
      "gaps": []
    },
    {
      "key": "contact",
      "title": "Contact details",
      "purpose": "Check and normalise a person's contact details before storing them.",
      "from": "validation-basics",
      "members": [
        {
          "id": "validation.email",
          "tier": "core",
          "why": "Is an email address plausible, by a documented subset of RFC 5322.",
          "section": "contact",
          "from": "validation-basics",
          "version": "1.0.0",
          "summary": "Is this a plausible email address? A documented, pragmatic subset of RFC 5322, not the full grammar.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isEmail"
          ],
          "signatures": [
            "def is_email(value: str) -> bool"
          ],
          "reference": "from fune.validation.email import is_email  # validation.email@^1",
          "url": "http://www.functionalweave.com/functions/validation.email"
        },
        {
          "id": "validation.phone-e164",
          "tier": "core",
          "why": "Normalises a phone number to +44… E.164 for a default country.",
          "section": "contact",
          "from": "validation-basics",
          "version": "2.0.0",
          "summary": "Normalise a phone number to E.164 (+447700900123) using a default country's trunk and dialling prefixes.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "validatePhoneE164"
          ],
          "signatures": [
            "def validate_phone_e164(value: str, default_country: str) -> PhoneE164"
          ],
          "reference": "from fune.validation.phone_e164 import validate_phone_e164  # validation.phone-e164@^2",
          "url": "http://www.functionalweave.com/functions/validation.phone-e164"
        },
        {
          "id": "validation.uk-postcode",
          "tier": "core",
          "why": "Validates a UK postcode and puts it in canonical form.",
          "section": "contact",
          "from": "validation-basics",
          "version": "2.0.0",
          "summary": "Validate a UK postcode and normalise it to canonical upper case with one space before the inward code.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "validateUkPostcode"
          ],
          "signatures": [
            "def validate_uk_postcode(value: str) -> UkPostcode"
          ],
          "reference": "from fune.validation.uk_postcode import validate_uk_postcode  # validation.uk-postcode@^2",
          "url": "http://www.functionalweave.com/functions/validation.uk-postcode"
        },
        {
          "id": "text.normalise-name",
          "tier": "optional",
          "why": "Tidies a typed name: spacing and title case with Mc, O' and hyphen rules.",
          "section": "contact",
          "from": "validation-basics",
          "version": "1.0.0",
          "summary": "Tidy a personal name: trim, collapse whitespace and title-case, with Mc, Mac, O', hyphen and van/de rules.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "normaliseName"
          ],
          "signatures": [
            "def normalise_name(value: str) -> str"
          ],
          "reference": "from fune.text.normalise_name import normalise_name  # text.normalise-name@^1",
          "url": "http://www.functionalweave.com/functions/text.normalise-name"
        },
        {
          "id": "auth.normalise-email",
          "tier": "optional",
          "why": "Lower-cases an email's domain for storage and lookup.",
          "section": "contact",
          "from": "validation-basics",
          "version": "1.0.0",
          "summary": "Trim an email address and lower-case its domain for storage and lookup, or null if it is not a plausible address.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "normaliseEmail"
          ],
          "signatures": [
            "def normalise_email(value: str) -> Optional[str]"
          ],
          "reference": "from fune.auth.normalise_email import normalise_email  # auth.normalise-email@^1",
          "url": "http://www.functionalweave.com/functions/auth.normalise-email"
        }
      ],
      "gaps": []
    },
    {
      "key": "bank",
      "title": "Bank details",
      "purpose": "Check payee and customer bank details before money moves.",
      "from": "validation-basics",
      "members": [
        {
          "id": "validation.iban",
          "tier": "core",
          "why": "Checks an IBAN's mod-97 checksum and its country's length.",
          "section": "bank",
          "from": "validation-basics",
          "version": "1.0.0",
          "summary": "Check an IBAN against the ISO 13616 mod-97-10 checksum and the registered length for its country.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isIban"
          ],
          "signatures": [
            "def is_iban(value: str) -> bool"
          ],
          "reference": "from fune.validation.iban import is_iban  # validation.iban@^1",
          "url": "http://www.functionalweave.com/functions/validation.iban"
        },
        {
          "id": "validation.bic",
          "tier": "optional",
          "why": "Checks a SWIFT/BIC code's format and splits it.",
          "section": "bank",
          "from": "validation-basics",
          "version": "1.0.0",
          "summary": "Check the format of a SWIFT/BIC code (8 or 11 characters) and split it into its parts.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "validateBic"
          ],
          "signatures": [
            "def validate_bic(value: str) -> BicCheck"
          ],
          "reference": "from fune.validation.bic import validate_bic  # validation.bic@^1",
          "url": "http://www.functionalweave.com/functions/validation.bic"
        },
        {
          "id": "validation.uk-sort-code-account",
          "tier": "optional",
          "why": "Checks a UK sort code and account number by the Vocalink modulus rules, against a table you supply.",
          "section": "bank",
          "from": "validation-basics",
          "version": "3.0.0",
          "summary": "Check a UK sort code and account number with the Vocalink/Pay.UK modulus rules, against a table you supply.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "validateUkSortCodeAccount"
          ],
          "signatures": [
            "def validate_uk_sort_code_account(sort_code: str, account_number: str, table: UkModulusTable) -> UkSortCodeAccount"
          ],
          "reference": "from fune.validation.uk_sort_code_account import validate_uk_sort_code_account  # validation.uk-sort-code-account@^3",
          "url": "http://www.functionalweave.com/functions/validation.uk-sort-code-account"
        },
        {
          "id": "validation.uk-modulus-table",
          "tier": "optional",
          "why": "Parses Vocalink's VALACDOS and SCSUBTAB files into that table.",
          "section": "bank",
          "from": "validation-basics",
          "version": "1.0.0",
          "summary": "Parse Vocalink's VALACDOS.txt and SCSUBTAB.txt into the table UK sort code modulus checking needs.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "parseUkModulusTable"
          ],
          "signatures": [
            "def parse_uk_modulus_table(valacdos: str, scsubtab: str) -> UkModulusTable"
          ],
          "reference": "from fune.validation.uk_modulus_table import parse_uk_modulus_table  # validation.uk-modulus-table@^1",
          "url": "http://www.functionalweave.com/functions/validation.uk-modulus-table"
        }
      ],
      "gaps": []
    },
    {
      "key": "check-digits",
      "title": "Identifiers and check digits",
      "purpose": "Catch mistyped reference numbers by their check digits.",
      "from": "validation-basics",
      "members": [
        {
          "id": "validation.luhn",
          "tier": "optional",
          "why": "Luhn mod-10 check, for card numbers and many reference numbers.",
          "section": "check-digits",
          "from": "validation-basics",
          "version": "1.0.0",
          "summary": "Check a digit string against the Luhn mod-10 checksum used by payment cards and many identifiers.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isLuhn"
          ],
          "signatures": [
            "def is_luhn(value: str) -> bool"
          ],
          "reference": "from fune.validation.luhn import is_luhn  # validation.luhn@^1",
          "url": "http://www.functionalweave.com/functions/validation.luhn"
        },
        {
          "id": "validation.uk-company-number",
          "tier": "optional",
          "why": "Checks a Companies House number's format and prefix.",
          "section": "check-digits",
          "from": "validation-basics",
          "version": "2.0.1",
          "summary": "Check a Companies House company number's format and prefix (SC, NI, OC, LP...) and zero-pad it to eight characters.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "validateUkCompanyNumber"
          ],
          "signatures": [
            "def validate_uk_company_number(value: str) -> UkCompanyNumber"
          ],
          "reference": "from fune.validation.uk_company_number import validate_uk_company_number  # validation.uk-company-number@^2",
          "url": "http://www.functionalweave.com/functions/validation.uk-company-number"
        },
        {
          "id": "validation.lei",
          "tier": "optional",
          "why": "Checks a Legal Entity Identifier's check digits.",
          "section": "check-digits",
          "from": "validation-basics",
          "version": "1.0.0",
          "summary": "Check a 20-character Legal Entity Identifier against its ISO 17442 / ISO 7064 MOD 97-10 check digits.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "validateLei"
          ],
          "signatures": [
            "def validate_lei(value: str) -> LeiCheck"
          ],
          "reference": "from fune.validation.lei import validate_lei  # validation.lei@^1",
          "url": "http://www.functionalweave.com/functions/validation.lei"
        },
        {
          "id": "validation.gtin",
          "tier": "optional",
          "why": "Checks an EAN/UPC/GTIN barcode number.",
          "section": "check-digits",
          "from": "validation-basics",
          "version": "1.1.0",
          "summary": "Check an EAN-8, UPC-A, EAN-13 or GTIN-14 barcode number against the GS1 mod-10 check digit.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "validateGtin"
          ],
          "signatures": [
            "def validate_gtin(value: str) -> GtinCheck"
          ],
          "reference": "from fune.validation.gtin import validate_gtin  # validation.gtin@^1",
          "url": "http://www.functionalweave.com/functions/validation.gtin"
        }
      ],
      "gaps": []
    },
    {
      "key": "masking",
      "title": "Displaying sensitive values",
      "purpose": "Show stored identifiers without exposing them.",
      "from": "validation-basics",
      "members": [
        {
          "id": "text.mask",
          "tier": "optional",
          "why": "Masks all but the last digits of a card, account or phone number.",
          "section": "masking",
          "from": "validation-basics",
          "version": "1.0.0",
          "summary": "Mask all but the last n characters of a card, account or phone number, optionally keeping separators.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "mask"
          ],
          "signatures": [
            "def mask(value: str, visible: int, mask_char: str, keep_separators: bool) -> str"
          ],
          "reference": "from fune.text.mask import mask  # text.mask@^1",
          "url": "http://www.functionalweave.com/functions/text.mask"
        }
      ],
      "gaps": []
    }
  ],
  "gaps": [
    {
      "key": "starter-leaver-forms",
      "text": "Starter checklists, P45 and P60 documents: caps gives the figures, not the forms.",
      "section": "employees",
      "from": "payroll"
    },
    {
      "key": "rti-submission",
      "text": "Sending FPS and EPS returns to HMRC under Real Time Information: the XML, the gateway and the credentials are yours.",
      "section": "deductions",
      "from": "payroll"
    },
    {
      "key": "pension-provider-files",
      "text": "Contribution files for pension providers (NEST, The People's Pension) and enrolment letters.",
      "section": "deductions",
      "from": "payroll"
    },
    {
      "key": "leave-requests",
      "text": "Requesting and approving leave, team calendars and clashes.",
      "section": "leave",
      "from": "payroll"
    },
    {
      "key": "bacs-files",
      "text": "Writing the Bacs Standard 18 payment file or a bank's bulk-payment CSV.",
      "section": "payments",
      "from": "payroll"
    },
    {
      "key": "timesheets",
      "text": "Clocking in and out and timesheet approval: Functional Weave prices hours worked, it does not record them.",
      "section": null,
      "from": "payroll"
    },
    {
      "key": "fx-rate-source",
      "text": "Where exchange rates come from: money.convert takes a rate you supply; fetching and storing daily rates (ECB, a provider's API) is yours.",
      "section": null,
      "from": "money-basics"
    },
    {
      "key": "money-storage",
      "text": "Storing amounts: the database columns (integer minor units plus a currency code) and their migrations are yours.",
      "section": null,
      "from": "money-basics"
    },
    {
      "key": "time-zones",
      "text": "Time zones and daylight saving: caps works in ISO dates, UTC and wall-clock times; converting with the IANA tz rules is yours.",
      "section": null,
      "from": "dates-basics"
    },
    {
      "key": "locale-formatting",
      "text": "Showing dates in a user's language and format (\"3 octobre 2026\"): caps returns ISO dates, not localised text.",
      "section": null,
      "from": "dates-basics"
    },
    {
      "key": "non-uk-holidays",
      "text": "Public holidays outside the UK: dates.bank-holidays is UK only; pass another country's list to the working-day functions yourself.",
      "section": null,
      "from": "dates-basics"
    },
    {
      "key": "address-lookup",
      "text": "Turning a postcode into a list of addresses (Royal Mail PAF or a lookup API) is network work and not in caps.",
      "section": null,
      "from": "validation-basics"
    },
    {
      "key": "email-deliverability",
      "text": "Whether a mailbox exists (MX lookups, a confirmation email): validation.email checks the syntax only.",
      "section": null,
      "from": "validation-basics"
    },
    {
      "key": "vocalink-data",
      "text": "The Vocalink modulus tables themselves: download them from Pay.UK under its terms; caps parses them but does not ship them.",
      "section": null,
      "from": "validation-basics"
    }
  ],
  "counts": {
    "members": 76,
    "core": 34,
    "optional": 42,
    "unreviewed": 18,
    "deprecated": [],
    "unavailable": [],
    "missing": [],
    "gaps": 14
  },
  "warnings": [
    "10 core capabilities are unreviewed - they pass their tests but await an expert's sign-off; tell the user before relying on them, and never use one silently for tax, payroll, legal, clinical or other regulated rules: payroll.tax-code-parse, payroll.tax-period, payroll.pro-rata-salary, payroll.gross-to-net, payroll.income-tax, payroll.national-insurance, payroll.pension-auto-enrolment, payroll.student-loan, payroll.statutory-sick-pay, payroll.holiday-entitlement",
    "8 optional capabilities are unreviewed too: payroll.overtime, payroll.minimum-wage-check, payroll.net-to-gross, fleet.company-car-bik, payroll.statutory-maternity-pay, payroll.holiday-pay-accrual, payroll.notice-period, payroll.redundancy-pay",
    "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."
  ],
  "url": "http://www.functionalweave.com/suites/payroll",
  "owner": null
}
