{
  "id": "construction",
  "title": "Construction and trades",
  "summary": "For builders, trades and contractors: quotes, materials take-offs, valuations with retention, CIS deductions, VAT reverse charge and Construction Act payment dates.",
  "synonyms": [
    "construction",
    "builder",
    "building contractor",
    "trades",
    "tradesperson",
    "plumber",
    "electrician",
    "roofer",
    "joiner",
    "subcontractor",
    "CIS",
    "quantity surveyor",
    "estimating",
    "job management"
  ],
  "apps": [
    "builders quoting app",
    "trades job management app",
    "construction project management",
    "materials calculator",
    "subcontractor payments system",
    "quantity surveying tool"
  ],
  "language": "typescript",
  "includes": [
    "money-basics",
    "dates-basics",
    "uk-tax"
  ],
  "includedBy": [],
  "caveats": [
    "UK first: CIS, the VAT domestic reverse charge and Construction Act payment dates follow UK legislation; it is not legal or tax advice.",
    "Calculates tax from published rules; it is not tax advice. Have an accountant check how the app uses it before anyone files a return."
  ],
  "sections": [
    {
      "key": "quoting",
      "title": "Quotes and estimates",
      "purpose": "Price a job from labour, materials and markup.",
      "from": "construction",
      "members": [
        {
          "id": "construction.day-rate-quote",
          "tier": "core",
          "why": "A quote from labour days at a day rate, materials with a markup, and VAT.",
          "section": "quoting",
          "from": "construction",
          "version": "1.0.0",
          "summary": "A trade quote from labour days at a day rate, materials with a markup, and VAT.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "dayRateQuote"
          ],
          "signatures": [
            "export function dayRateQuote(labourDaysHundredths: number, dayRate: Money, materialsCost: Money, materialsMarkupBasisPoints: number, vatCategory: string | null, onDate: string): DayRateQuote"
          ],
          "reference": "import { dayRateQuote } from \"#fune/construction.day-rate-quote@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.day-rate-quote"
        },
        {
          "id": "finance.markup-price",
          "tier": "optional",
          "why": "Selling price from a cost and a target margin or markup.",
          "section": "quoting",
          "from": "construction",
          "version": "1.0.0",
          "summary": "Selling price from a cost and a target margin or markup in basis points, with explicit rounding.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "markupPrice"
          ],
          "signatures": [
            "export function markupPrice(cost: Money, basisPoints: number, basis: PricingBasis, mode: RoundingMode): Money"
          ],
          "reference": "import { markupPrice } from \"#fune/finance.markup-price@^1\";",
          "url": "http://www.functionalweave.com/functions/finance.markup-price"
        }
      ],
      "gaps": [
        {
          "key": "quote-documents",
          "text": "Producing the quote or estimate document (PDF, e-signature acceptance) is yours.",
          "section": "quoting",
          "from": "construction"
        }
      ]
    },
    {
      "key": "materials",
      "title": "Materials take-off",
      "purpose": "Work out the materials a job needs.",
      "from": "construction",
      "members": [
        {
          "id": "construction.materials-area",
          "tier": "core",
          "why": "Packs of tiles, paint, plasterboard or flooring for an area, with waste and coats.",
          "section": "materials",
          "from": "construction",
          "version": "1.0.0",
          "summary": "Packs of tiles, paint, plasterboard or flooring to cover an area, with waste and coats, rounded up to whole packs.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "materialsArea"
          ],
          "signatures": [
            "export function materialsArea(areaSquareMetres: number, coveragePerPack: number, coats: number, wastageBasisPoints: number): MaterialsQuantity"
          ],
          "reference": "import { materialsArea } from \"#fune/construction.materials-area@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.materials-area"
        },
        {
          "id": "construction.brick-count",
          "tier": "optional",
          "why": "Bricks and mortar for a wall, openings subtracted, for UK standard bricks.",
          "section": "materials",
          "from": "construction",
          "version": "1.0.0",
          "summary": "Bricks and mortar for a wall, openings subtracted, for UK standard 215 x 102.5 x 65 mm bricks with 10 mm joints.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "brickCount"
          ],
          "signatures": [
            "export function brickCount(wallLength: number, wallHeight: number, openings: readonly Opening[], skins: number, wastageBasisPoints: number): BrickCount"
          ],
          "reference": "import { brickCount } from \"#fune/construction.brick-count@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.brick-count"
        },
        {
          "id": "construction.concrete-volume",
          "tier": "optional",
          "why": "Concrete for slabs, footings, pads and columns, and the order rounded up.",
          "section": "materials",
          "from": "construction",
          "version": "1.0.0",
          "summary": "Concrete for slabs, strip footings, pads and round columns: volume to the litre and an order rounded up to 0.1 m³.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "concreteVolume"
          ],
          "signatures": [
            "export function concreteVolume(elements: readonly ConcreteElement[], wastageBasisPoints: number): ConcreteVolume"
          ],
          "reference": "import { concreteVolume } from \"#fune/construction.concrete-volume@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.concrete-volume"
        },
        {
          "id": "construction.timber-length",
          "tier": "optional",
          "why": "A cutting list: which stock lengths to buy and what to cut from each, with kerf.",
          "section": "materials",
          "from": "construction",
          "version": "1.0.0",
          "summary": "Cutting list: which stock lengths to buy and which pieces to cut from each, allowing for saw kerf.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "timberCuttingList"
          ],
          "signatures": [
            "export function timberCuttingList(cuts: readonly number[], stockLength: number, kerf: number): CuttingList"
          ],
          "reference": "import { timberCuttingList } from \"#fune/construction.timber-length@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.timber-length"
        },
        {
          "id": "construction.roof-pitch",
          "tier": "optional",
          "why": "Roof pitch, rafter length and slope area from span, rise and length.",
          "section": "materials",
          "from": "construction",
          "version": "1.0.0",
          "summary": "Roof pitch in degrees, rafter length and roof slope area from span, rise and length.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "roofPitch"
          ],
          "signatures": [
            "export function roofPitch(span: number, rise: number, roofLength: number, roofType: RoofType): RoofPitch"
          ],
          "reference": "import { roofPitch } from \"#fune/construction.roof-pitch@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.roof-pitch"
        },
        {
          "id": "units.convert",
          "tier": "optional",
          "why": "Converts metric and imperial lengths, areas and volumes exactly.",
          "section": "materials",
          "from": "construction",
          "version": "1.0.0",
          "summary": "Convert length, mass, area, volume, temperature and energy between units, exactly, as decimal text.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "convertUnits"
          ],
          "signatures": [
            "export function convertUnits(value: string, fromUnit: string, toUnit: string, decimals: number): string"
          ],
          "reference": "import { convertUnits } from \"#fune/units.convert@^1\";",
          "url": "http://www.functionalweave.com/functions/units.convert"
        },
        {
          "id": "property.floor-area",
          "tier": "optional",
          "why": "Floor area from room dimensions, in square metres and square feet.",
          "section": "materials",
          "from": "construction",
          "version": "1.0.2",
          "summary": "Floor area from room lengths and widths, exactly, with the total in both square metres and square feet.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "floorArea"
          ],
          "signatures": [
            "export function floorArea(rooms: readonly RoomDimensions[], lengthUnit: FloorAreaUnit, decimals: number): FloorArea"
          ],
          "reference": "import { floorArea } from \"#fune/property.floor-area@^1\";",
          "url": "http://www.functionalweave.com/functions/property.floor-area"
        }
      ],
      "gaps": [
        {
          "key": "supplier-prices",
          "text": "Live prices and stock from builders' merchants: you supply pack prices.",
          "section": "materials",
          "from": "construction"
        }
      ]
    },
    {
      "key": "valuations",
      "title": "Valuations and payments",
      "purpose": "Bill work done on account and pay subcontractors correctly.",
      "from": "construction",
      "members": [
        {
          "id": "construction.valuation",
          "tier": "core",
          "why": "Interim valuation: gross value to date, retention, previous payments, amount due, VAT and CIS.",
          "section": "valuations",
          "from": "construction",
          "version": "1.0.0",
          "summary": "Interim valuation: gross value to date, retention, previous payments, amount due, VAT or reverse charge, and CIS.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "interimValuation"
          ],
          "signatures": [
            "export function interimValuation(input: ValuationInput): Valuation"
          ],
          "reference": "import { interimValuation } from \"#fune/construction.valuation@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.valuation"
        },
        {
          "id": "construction.retention",
          "tier": "core",
          "why": "Retention held and released: full rate, half at practical completion, none at the end.",
          "section": "valuations",
          "from": "construction",
          "version": "1.0.0",
          "summary": "Retention held and released on a construction valuation: full rate, half at practical completion, none at the end.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "retention"
          ],
          "signatures": [
            "export function retention(cumulativeValue: Money, basisPoints: number, stage: RetentionStage): Retention"
          ],
          "reference": "import { retention } from \"#fune/construction.retention@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.retention"
        },
        {
          "id": "construction.cis-deduction",
          "tier": "core",
          "why": "Construction Industry Scheme deduction (0, 20 or 30%) on the labour part of a payment.",
          "section": "valuations",
          "from": "construction",
          "version": "1.0.2",
          "summary": "Construction Industry Scheme deduction (0, 20 or 30%) on the labour part of a subcontractor payment.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "cisDeduction"
          ],
          "signatures": [
            "export function cisDeduction(payment: Money, materials: Money, status: CisStatus, onDate: string, mode: RoundingMode): CisDeduction"
          ],
          "reference": "import { cisDeduction } from \"#fune/construction.cis-deduction@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.cis-deduction"
        },
        {
          "id": "construction.vat-reverse-charge",
          "tier": "core",
          "why": "Whether the VAT domestic reverse charge applies to a supply, and the invoice wording.",
          "section": "valuations",
          "from": "construction",
          "version": "1.0.2",
          "summary": "Whether the UK VAT domestic reverse charge applies to a construction supply, and the invoice wording.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "domesticReverseCharge"
          ],
          "signatures": [
            "export function domesticReverseCharge(supply: ReverseChargeSupply): ReverseChargeDecision"
          ],
          "reference": "import { domesticReverseCharge } from \"#fune/construction.vat-reverse-charge@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.vat-reverse-charge"
        },
        {
          "id": "construction.payment-notice-dates",
          "tier": "core",
          "why": "Construction Act dates: payment due, payment notice, final date and pay-less notice.",
          "section": "valuations",
          "from": "construction",
          "version": "1.0.2",
          "summary": "Construction Act default dates: payment due, payment notice, final date for payment and pay-less notice.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "paymentNoticeDates"
          ],
          "signatures": [
            "export function paymentNoticeDates(kind: SchemePaymentKind, workDate: string, claimDate: string, contractDate: string, region: UkRegion): PaymentNoticeDates"
          ],
          "reference": "import { paymentNoticeDates } from \"#fune/construction.payment-notice-dates@^1\";",
          "url": "http://www.functionalweave.com/functions/construction.payment-notice-dates"
        }
      ],
      "gaps": [
        {
          "key": "cis-verification",
          "text": "Verifying subcontractors with HMRC and filing the monthly CIS return (online) are not in caps.",
          "section": "valuations",
          "from": "construction"
        }
      ]
    },
    {
      "key": "jobs",
      "title": "Jobs and scheduling",
      "purpose": "Plan the days a job takes.",
      "from": "construction",
      "members": [
        {
          "id": "dates.add-business-days",
          "tier": "optional",
          "why": "Moves a start date by working days for a programme of works.",
          "section": "jobs",
          "from": "construction",
          "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": [
            "export function addBusinessDays(iso: string, days: number, holidays: readonly string[]): string"
          ],
          "reference": "import { addBusinessDays } from \"#fune/dates.add-business-days@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.add-business-days"
        }
      ],
      "gaps": [
        {
          "key": "job-scheduling",
          "text": "Scheduling crews and jobs, site diaries and photos.",
          "section": "jobs",
          "from": "construction"
        },
        {
          "key": "health-and-safety",
          "text": "RAMS, permits and site inductions are not in caps.",
          "section": "jobs",
          "from": "construction"
        }
      ]
    },
    {
      "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": [
            "export function money(minor: number, currency: string): Money"
          ],
          "reference": "import { money } from \"#fune/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": [
            "export function addMoney(a: Money, b: Money): Money"
          ],
          "reference": "import { addMoney } from \"#fune/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": [
            "export function sumMoney(amounts: readonly Money[], currency: string): Money"
          ],
          "reference": "import { sumMoney } from \"#fune/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": [
            "export function compareMoney(a: Money, b: Money): number",
            "export function minMoney(a: Money, b: Money): Money",
            "export function maxMoney(a: Money, b: Money): Money",
            "export function clampMoney(amount: Money, min: Money, max: Money): Money"
          ],
          "reference": "import { compareMoney, minMoney, maxMoney, clampMoney } from \"#fune/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": [
            "export function applyRate(amount: Money, basisPoints: number, mode: RoundingMode): Money"
          ],
          "reference": "import { applyRate } from \"#fune/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": [
            "export function roundDiv(numerator: number, denominator: number, mode: RoundingMode): number"
          ],
          "reference": "import { roundDiv } from \"#fune/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": [
            "export function convertRate(value: string, fromUnit: RateUnit, toUnit: RateUnit): string",
            "export function toBasisPoints(value: string, unit: RateUnit): number",
            "export function fromBasisPoints(basisPoints: number, unit: RateUnit): string"
          ],
          "reference": "import { convertRate, toBasisPoints, fromBasisPoints } from \"#fune/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": [
            "export function percentChange(fromValue: number, toValue: number, mode: RoundingMode): number | null"
          ],
          "reference": "import { percentChange } from \"#fune/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": [
            "export function calculateRational(a: Rational, op: RationalOp, b: Rational): Rational",
            "export function rational(numerator: number, denominator: number): Rational",
            "export function addRational(a: Rational, b: Rational): Rational",
            "export function subtractRational(a: Rational, b: Rational): Rational",
            "export function multiplyRational(a: Rational, b: Rational): Rational",
            "export function divideRational(a: Rational, b: Rational): Rational",
            "export function compareRational(a: Rational, b: Rational): number",
            "export function rationalToInteger(r: Rational, mode: RoundingMode): number"
          ],
          "reference": "import { calculateRational, rational, addRational, subtractRational, multiplyRational, divideRational, compareRational, rationalToInteger } from \"#fune/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": [
            "export function roundDivBig(numerator: string, denominator: string, mode: RoundingMode): string"
          ],
          "reference": "import { roundDivBig } from \"#fune/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": [
            "export function allocate(amount: Money, ratios: readonly number[]): readonly Money[]"
          ],
          "reference": "import { allocate } from \"#fune/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": [
            "export function splitEven(amount: Money, parts: number): readonly Money[]"
          ],
          "reference": "import { splitEven } from \"#fune/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": [
            "export function formatMoney(amount: Money): string"
          ],
          "reference": "import { formatMoney } from \"#fune/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": [
            "export function parseMoney(text: string, currency: string, style: NumberStyle): Money"
          ],
          "reference": "import { parseMoney } from \"#fune/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": [
            "export function currencyDigits(currency: string): number"
          ],
          "reference": "import { currencyDigits } from \"#fune/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": [
            "export function convertMoney(amount: Money, rate: ExchangeRate, mode: RoundingMode): Money"
          ],
          "reference": "import { convertMoney } from \"#fune/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": [
            "export function addDays(iso: string, days: number): string"
          ],
          "reference": "import { addDays } from \"#fune/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": [
            "export function addMonths(iso: string, months: number): string"
          ],
          "reference": "import { addMonths } from \"#fune/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": [
            "export function daysBetween(startIso: string, endIso: string): number"
          ],
          "reference": "import { daysBetween } from \"#fune/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": [
            "export function monthBoundaries(iso: string): MonthBoundaries"
          ],
          "reference": "import { monthBoundaries } from \"#fune/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": [
            "export function dayOfWeek(iso: string): number"
          ],
          "reference": "import { dayOfWeek } from \"#fune/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": [
            "export function isLeapYear(year: number): boolean"
          ],
          "reference": "import { isLeapYear } from \"#fune/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": [
            "export function overlapDays(aStart: string, aEnd: string, bStart: string, bEnd: string, endInclusive: boolean): number"
          ],
          "reference": "import { overlapDays } from \"#fune/dates.overlap@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.overlap"
        },
        {
          "id": "dates.age",
          "tier": "optional",
          "why": "Age in whole years on a date, handling 29 February birthdays.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "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": [
            "export function age(birthDate: string, onDate: string): number"
          ],
          "reference": "import { age } from \"#fune/dates.age@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.age"
        }
      ],
      "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": [
            "export function quarter(iso: string, fiscalYearStartMonth: number): Quarter"
          ],
          "reference": "import { quarter } from \"#fune/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": [
            "export function weekNumber(iso: string): IsoWeek"
          ],
          "reference": "import { weekNumber } from \"#fune/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": [
            "export function dayCountFraction(startIso: string, endIso: string, convention: DayCountConvention): YearFraction"
          ],
          "reference": "import { dayCountFraction } from \"#fune/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": [
            "export function bankHolidays(region: UkRegion, year: number): readonly BankHoliday[]"
          ],
          "reference": "import { bankHolidays } from \"#fune/dates.bank-holidays@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.bank-holidays"
        },
        {
          "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": [
            "export function businessDaysBetween(startIso: string, endIso: string, holidays: readonly string[]): number"
          ],
          "reference": "import { businessDaysBetween } from \"#fune/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": [
            "export function recurrence(rule: RecurrenceRule, startIso: string, count: number): readonly string[]"
          ],
          "reference": "import { recurrence } from \"#fune/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": [
            "export function duration(parts: readonly string[]): Duration"
          ],
          "reference": "import { duration } from \"#fune/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": [
            "export function minutesBetween(startDate: string, startTime: string, endDate: string, endTime: string): number"
          ],
          "reference": "import { minutesBetween } from \"#fune/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": [
            "export function roundToIncrement(minutes: number, increment: number, mode: RoundingMode): number"
          ],
          "reference": "import { roundToIncrement } from \"#fune/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": [
            "export function isoToUnix(timestamp: string): number"
          ],
          "reference": "import { isoToUnix } from \"#fune/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": [
            "export function unixToIso(seconds: number): string"
          ],
          "reference": "import { unixToIso } from \"#fune/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": [
            "export function countdown(until: number, now: number): Countdown"
          ],
          "reference": "import { countdown } from \"#fune/time.countdown@^1\";",
          "url": "http://www.functionalweave.com/functions/time.countdown"
        }
      ],
      "gaps": []
    },
    {
      "key": "vat",
      "title": "VAT",
      "purpose": "Work out VAT from the dated rates HMRC publishes.",
      "from": "uk-tax",
      "members": [
        {
          "id": "finance.tax.vat-rate",
          "tier": "core",
          "why": "The VAT rate in force for a jurisdiction, category and date; the others look it up.",
          "section": "vat",
          "from": "uk-tax",
          "version": "1.2.2",
          "summary": "Look up the VAT rate in force for a jurisdiction, category and date.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "vatRate"
          ],
          "signatures": [
            "export function vatRate(jurisdiction: string, category: string, onDate: string): VatRate"
          ],
          "reference": "import { vatRate } from \"#fune/finance.tax.vat-rate@^1\";",
          "url": "http://www.functionalweave.com/functions/finance.tax.vat-rate"
        },
        {
          "id": "finance.tax.add-vat",
          "tier": "core",
          "why": "Net to gross: VAT on a net amount at the rate on the date of supply.",
          "section": "vat",
          "from": "uk-tax",
          "version": "1.0.1",
          "summary": "Add VAT to a net amount using the rate in force on the date of supply.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addVat"
          ],
          "signatures": [
            "export function addVat(net: Money, jurisdiction: string, category: string, onDate: string): VatBreakdown"
          ],
          "reference": "import { addVat } from \"#fune/finance.tax.add-vat@^1\";",
          "url": "http://www.functionalweave.com/functions/finance.tax.add-vat"
        },
        {
          "id": "finance.tax.remove-vat",
          "tier": "core",
          "why": "Gross to net: the VAT already inside a VAT-inclusive price.",
          "section": "vat",
          "from": "uk-tax",
          "version": "1.0.2",
          "summary": "Extract the VAT already included in a gross amount, using the rate in force on the date of supply.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "removeVat"
          ],
          "signatures": [
            "export function removeVat(gross: Money, jurisdiction: string, category: string, onDate: string): VatBreakdown"
          ],
          "reference": "import { removeVat } from \"#fune/finance.tax.remove-vat@^1\";",
          "url": "http://www.functionalweave.com/functions/finance.tax.remove-vat"
        },
        {
          "id": "finance.tax.vat-return",
          "tier": "optional",
          "why": "The nine-box VAT return totals for a period from categorised sales and purchases.",
          "section": "vat",
          "from": "uk-tax",
          "version": "1.0.1",
          "summary": "UK nine-box VAT return totals for a period from categorised sales and purchases (VAT Notice 700/12).",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "vatReturn"
          ],
          "signatures": [
            "export function vatReturn(transactions: readonly VatTransaction[], periodStart: string, periodEnd: string): VatReturn"
          ],
          "reference": "import { vatReturn } from \"#fune/finance.tax.vat-return@^1\";",
          "url": "http://www.functionalweave.com/functions/finance.tax.vat-return"
        },
        {
          "id": "finance.tax.vat-flat-rate",
          "tier": "optional",
          "why": "VAT due under the Flat Rate Scheme by business sector, for small businesses on it.",
          "section": "vat",
          "from": "uk-tax",
          "version": "1.0.1",
          "summary": "UK VAT Flat Rate Scheme: VAT due on gross turnover at the sector's dated rate, limited cost rate and first-year 1% off.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "flatRateVat"
          ],
          "signatures": [
            "export function flatRateVat(turnover: Money, sector: string, limitedCostBusiness: boolean, registrationDate: string | null, supplyDate: string): FlatRateVat"
          ],
          "reference": "import { flatRateVat } from \"#fune/finance.tax.vat-flat-rate@^1\";",
          "url": "http://www.functionalweave.com/functions/finance.tax.vat-flat-rate"
        }
      ],
      "gaps": []
    },
    {
      "key": "tax-year",
      "title": "Tax periods",
      "purpose": "Put dates into the UK periods tax is worked out over.",
      "from": "uk-tax",
      "members": [
        {
          "id": "dates.uk-tax-year",
          "tier": "core",
          "why": "Which UK tax year (6 April to 5 April) a date falls in.",
          "section": "tax-year",
          "from": "uk-tax",
          "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": [
            "export function ukTaxYear(iso: string): UkTaxYear"
          ],
          "reference": "import { ukTaxYear } from \"#fune/dates.uk-tax-year@^1\";",
          "url": "http://www.functionalweave.com/functions/dates.uk-tax-year"
        }
      ],
      "gaps": []
    },
    {
      "key": "identifiers",
      "title": "Tax identifiers",
      "purpose": "Check the numbers HMRC and Companies House issue before storing them.",
      "from": "uk-tax",
      "members": [
        {
          "id": "validation.uk-vat-number",
          "tier": "core",
          "why": "Checks a UK VAT registration number's checksum.",
          "section": "identifiers",
          "from": "uk-tax",
          "version": "1.0.0",
          "summary": "Check a UK VAT registration number against HMRC's mod-97 checksum, including the post-2010 9755 variant.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isUkVatNumber"
          ],
          "signatures": [
            "export function isUkVatNumber(value: string): boolean"
          ],
          "reference": "import { isUkVatNumber } from \"#fune/validation.uk-vat-number@^1\";",
          "url": "http://www.functionalweave.com/functions/validation.uk-vat-number"
        },
        {
          "id": "validation.uk-utr",
          "tier": "optional",
          "why": "Checks a Unique Taxpayer Reference's check digit.",
          "section": "identifiers",
          "from": "uk-tax",
          "version": "2.0.1",
          "summary": "Check an HMRC Unique Taxpayer Reference's modulus 11 check digit, accepting the K and spacing people type.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "validateUkUtr"
          ],
          "signatures": [
            "export function validateUkUtr(value: string): UkUtr"
          ],
          "reference": "import { validateUkUtr } from \"#fune/validation.uk-utr@^2\";",
          "url": "http://www.functionalweave.com/functions/validation.uk-utr"
        },
        {
          "id": "validation.uk-company-number",
          "tier": "optional",
          "why": "Checks and pads a Companies House company number.",
          "section": "identifiers",
          "from": "uk-tax",
          "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": [
            "export function validateUkCompanyNumber(value: string): UkCompanyNumber"
          ],
          "reference": "import { validateUkCompanyNumber } from \"#fune/validation.uk-company-number@^2\";",
          "url": "http://www.functionalweave.com/functions/validation.uk-company-number"
        }
      ],
      "gaps": []
    }
  ],
  "gaps": [
    {
      "key": "quote-documents",
      "text": "Producing the quote or estimate document (PDF, e-signature acceptance) is yours.",
      "section": "quoting",
      "from": "construction"
    },
    {
      "key": "supplier-prices",
      "text": "Live prices and stock from builders' merchants: you supply pack prices.",
      "section": "materials",
      "from": "construction"
    },
    {
      "key": "cis-verification",
      "text": "Verifying subcontractors with HMRC and filing the monthly CIS return (online) are not in caps.",
      "section": "valuations",
      "from": "construction"
    },
    {
      "key": "job-scheduling",
      "text": "Scheduling crews and jobs, site diaries and photos.",
      "section": "jobs",
      "from": "construction"
    },
    {
      "key": "health-and-safety",
      "text": "RAMS, permits and site inductions are not in caps.",
      "section": "jobs",
      "from": "construction"
    },
    {
      "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": "mtd-submission",
      "text": "Making Tax Digital: submitting the return to HMRC's VAT API (OAuth, fraud-prevention headers, obligations) is yours; caps gives the nine boxes.",
      "section": null,
      "from": "uk-tax"
    },
    {
      "key": "corporation-tax",
      "text": "Corporation tax and self-assessment income tax computations are not in caps.",
      "section": null,
      "from": "uk-tax"
    },
    {
      "key": "vat-number-lookup",
      "text": "Checking that a VAT number is registered (HMRC's lookup API) is network work; caps checks the checksum only.",
      "section": null,
      "from": "uk-tax"
    }
  ],
  "counts": {
    "members": 60,
    "core": 28,
    "optional": 32,
    "unreviewed": 6,
    "deprecated": [],
    "unavailable": [],
    "missing": [],
    "gaps": 13
  },
  "warnings": [
    "3 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: finance.tax.vat-rate, finance.tax.add-vat, finance.tax.remove-vat",
    "3 optional capabilities are unreviewed too: property.floor-area, finance.tax.vat-return, finance.tax.vat-flat-rate",
    "UK first: CIS, the VAT domestic reverse charge and Construction Act payment dates follow UK legislation; it is not legal or tax advice.",
    "Calculates tax from published rules; it is not tax advice. Have an accountant check how the app uses it before anyone files a return."
  ],
  "url": "http://www.functionalweave.com/suites/construction",
  "owner": null
}
