{
  "id": "investments",
  "title": "Investments and wealth",
  "summary": "Portfolios and wealth: returns (TWR, XIRR, CAGR), weights and rebalancing, fees, yields, bonds, drawdown, and UK ISA, pension and capital gains tax.",
  "synonyms": [
    "investments",
    "investing",
    "wealth management",
    "portfolio",
    "portfolio tracker",
    "stocks and shares",
    "funds",
    "ISA",
    "pension",
    "SIPP",
    "capital gains tax",
    "robo advisor",
    "financial planning"
  ],
  "apps": [
    "portfolio tracker",
    "wealth management platform",
    "robo-advisor",
    "investment app",
    "financial planning tool",
    "pension drawdown calculator",
    "share dealing app"
  ],
  "language": "python",
  "includes": [
    "money-basics",
    "dates-basics",
    "uk-tax"
  ],
  "includedBy": [],
  "caveats": [
    "Not financial or tax advice. ISA, pension and capital gains tax rules are UK and dated; regulated advice needs a qualified adviser.",
    "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": "performance",
      "title": "Performance",
      "purpose": "Report how a portfolio has done.",
      "from": "investments",
      "members": [
        {
          "id": "invest.time-weighted-return",
          "tier": "core",
          "why": "Time-weighted return from valuations at each external cash flow, for comparing managers.",
          "section": "performance",
          "from": "investments",
          "version": "1.0.1",
          "summary": "Time-weighted return from valuations taken at each external cash flow, geometrically linked exactly.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "timeWeightedReturn"
          ],
          "signatures": [
            "def time_weighted_return(points: Sequence[ValuationPoint]) -> TwrResult"
          ],
          "reference": "from fune.invest.time_weighted_return import time_weighted_return  # invest.time-weighted-return@^1",
          "url": "http://www.functionalweave.com/functions/invest.time-weighted-return"
        },
        {
          "id": "invest.money-weighted-return",
          "tier": "core",
          "why": "Money-weighted return (XIRR) of dated cash flows, what the investor actually earned.",
          "section": "performance",
          "from": "investments",
          "version": "1.0.1",
          "summary": "Money-weighted return (XIRR) of dated cash flows on Excel's actual/365 convention, solved exactly in fixed point.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "moneyWeightedReturn"
          ],
          "signatures": [
            "def money_weighted_return(flows: Sequence[DatedFlow]) -> XirrResult"
          ],
          "reference": "from fune.invest.money_weighted_return import money_weighted_return  # invest.money-weighted-return@^1",
          "url": "http://www.functionalweave.com/functions/invest.money-weighted-return"
        },
        {
          "id": "invest.cagr",
          "tier": "optional",
          "why": "Compound annual growth rate between two values over whole or fractional years.",
          "section": "performance",
          "from": "investments",
          "version": "1.0.1",
          "summary": "Compound annual growth rate between two values over a whole or fractional number of years, in basis points.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "cagr"
          ],
          "signatures": [
            "def cagr(start_value: Money, end_value: Money, years_numerator: int, years_denominator: int) -> int"
          ],
          "reference": "from fune.invest.cagr import cagr  # invest.cagr@^1",
          "url": "http://www.functionalweave.com/functions/invest.cagr"
        },
        {
          "id": "math.percent-change",
          "tier": "optional",
          "why": "Percentage change between two values, for daily and period moves.",
          "section": "performance",
          "from": "investments",
          "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"
        }
      ],
      "gaps": []
    },
    {
      "key": "portfolio",
      "title": "Holdings and rebalancing",
      "purpose": "Show what a portfolio holds and bring it back to target.",
      "from": "investments",
      "members": [
        {
          "id": "invest.portfolio-weights",
          "tier": "core",
          "why": "Each holding's share of the portfolio and its drift from target, in basis points.",
          "section": "portfolio",
          "from": "investments",
          "version": "1.0.1",
          "summary": "Each holding's share of a portfolio and its drift from a target weight, in basis points, from exact arithmetic.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "portfolioWeights"
          ],
          "signatures": [
            "def portfolio_weights(holdings: Sequence[PortfolioHolding]) -> PortfolioWeights"
          ],
          "reference": "from fune.invest.portfolio_weights import portfolio_weights  # invest.portfolio-weights@^1",
          "url": "http://www.functionalweave.com/functions/invest.portfolio-weights"
        },
        {
          "id": "invest.rebalance",
          "tier": "core",
          "why": "Whole-unit buy and sell trades back to target weights, skipping trades below a minimum.",
          "section": "portfolio",
          "from": "investments",
          "version": "1.0.1",
          "summary": "Whole-unit buy and sell trades that bring a portfolio back to target weights, skipping trades below a minimum size.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "rebalance"
          ],
          "signatures": [
            "def rebalance(holdings: Sequence[RebalanceHolding], cash: Money, minimum_trade: Money) -> RebalancePlan"
          ],
          "reference": "from fune.invest.rebalance import rebalance  # invest.rebalance@^1",
          "url": "http://www.functionalweave.com/functions/invest.rebalance"
        }
      ],
      "gaps": [
        {
          "key": "market-data",
          "text": "Prices, fund data and corporate actions come from a market data provider.",
          "section": "portfolio",
          "from": "investments"
        },
        {
          "key": "order-execution",
          "text": "Placing trades with a broker or custodian (and settlement) is not in caps.",
          "section": "portfolio",
          "from": "investments"
        }
      ]
    },
    {
      "key": "income",
      "title": "Income and costs",
      "purpose": "Show yields and what charges cost.",
      "from": "investments",
      "members": [
        {
          "id": "invest.dividend-yield",
          "tier": "optional",
          "why": "Trailing and forward dividend yield of a share or fund.",
          "section": "income",
          "from": "investments",
          "version": "1.0.1",
          "summary": "Trailing and forward dividend yield of a share or fund, in basis points, from the price and the dividends per unit.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "dividendYield"
          ],
          "signatures": [
            "def dividend_yield(price: Money, trailing_dividends: Sequence[Money], forward_dividend: Money, payments_per_year: int) -> DividendYield"
          ],
          "reference": "from fune.invest.dividend_yield import dividend_yield  # invest.dividend-yield@^1",
          "url": "http://www.functionalweave.com/functions/invest.dividend-yield"
        },
        {
          "id": "invest.bond-yield",
          "tier": "optional",
          "why": "Current yield and yield to maturity of a fixed-coupon bond.",
          "section": "income",
          "from": "investments",
          "version": "1.0.2",
          "summary": "Current yield and yield to maturity of a fixed-coupon bond from its clean price, as Excel's YIELD defines it.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "bondYield"
          ],
          "signatures": [
            "def bond_yield(settlement: str, maturity: str, coupon_basis_points: int, clean_price: str, frequency: int, basis: BondDayCount) -> BondYield"
          ],
          "reference": "from fune.invest.bond_yield import bond_yield  # invest.bond-yield@^1",
          "url": "http://www.functionalweave.com/functions/invest.bond-yield"
        },
        {
          "id": "invest.fee-drag",
          "tier": "optional",
          "why": "What an annual charge costs over the years, with and without the fee.",
          "section": "income",
          "from": "investments",
          "version": "1.0.1",
          "summary": "What an annual charge costs an investment over the years: value with and without the fee, year by year, in minor units.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "feeDrag"
          ],
          "signatures": [
            "def fee_drag(start_value: Money, annual_growth_basis_points: int, annual_fee_basis_points: int, years: int) -> FeeDrag"
          ],
          "reference": "from fune.invest.fee_drag import fee_drag  # invest.fee-drag@^1",
          "url": "http://www.functionalweave.com/functions/invest.fee-drag"
        }
      ],
      "gaps": []
    },
    {
      "key": "planning",
      "title": "Planning and retirement",
      "purpose": "Project how long money lasts.",
      "from": "investments",
      "members": [
        {
          "id": "invest.drawdown-sustainability",
          "tier": "optional",
          "why": "How many years a pot lasts under a withdrawal and growth rate.",
          "section": "planning",
          "from": "investments",
          "version": "1.0.1",
          "summary": "How many years a pension or investment pot lasts under a yearly withdrawal and a constant growth rate, year by year.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "drawdownSustainability"
          ],
          "signatures": [
            "def drawdown_sustainability(pot: Money, annual_withdrawal: Money, annual_growth_basis_points: int, withdrawal_increase_basis_points: int, max_years: int) -> Drawdown"
          ],
          "reference": "from fune.invest.drawdown_sustainability import drawdown_sustainability  # invest.drawdown-sustainability@^1",
          "url": "http://www.functionalweave.com/functions/invest.drawdown-sustainability"
        },
        {
          "id": "finance.compound-interest",
          "tier": "optional",
          "why": "Compound growth period by period, for savings projections.",
          "section": "planning",
          "from": "investments",
          "version": "1.0.0",
          "summary": "Compound interest accrued period by period in minor units, the way a statement is built.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "compoundInterest"
          ],
          "signatures": [
            "def compound_interest(principal: Money, annual_rate_basis_points: int, periods_per_year: int, years: int) -> CompoundInterestResult"
          ],
          "reference": "from fune.finance.compound_interest import compound_interest  # finance.compound-interest@^1",
          "url": "http://www.functionalweave.com/functions/finance.compound-interest"
        }
      ],
      "gaps": [
        {
          "key": "cashflow-modelling",
          "text": "Stochastic (Monte Carlo) and lifetime cash-flow modelling are not in caps.",
          "section": "planning",
          "from": "investments"
        }
      ]
    },
    {
      "key": "tax",
      "title": "Tax wrappers and gains",
      "purpose": "Track allowances and work out tax on gains.",
      "from": "investments",
      "members": [
        {
          "id": "invest.isa-allowance",
          "tier": "core",
          "why": "ISA allowance used and remaining for a tax year, including Lifetime, cash and Junior ISAs.",
          "section": "tax",
          "from": "investments",
          "version": "1.0.2",
          "summary": "ISA allowance used and remaining for a UK tax year: the overall limit, Lifetime ISA, cash ISA and Junior ISA.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isaAllowance"
          ],
          "signatures": [
            "def isa_allowance(tax_year_date: str, subscriptions: Sequence[IsaSubscription], date_of_birth: Optional[str]) -> IsaAllowance"
          ],
          "reference": "from fune.invest.isa_allowance import isa_allowance  # invest.isa-allowance@^1",
          "url": "http://www.functionalweave.com/functions/invest.isa-allowance"
        },
        {
          "id": "invest.capital-gains-tax",
          "tier": "optional",
          "why": "UK Capital Gains Tax for a tax year: losses, annual exempt amount and rates.",
          "section": "tax",
          "from": "investments",
          "version": "1.0.1",
          "summary": "UK Capital Gains Tax for an individual's tax year: losses, annual exempt amount, and the rates by asset and date.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "capitalGainsTax"
          ],
          "signatures": [
            "def capital_gains_tax(tax_year_date: str, disposals: Sequence[CgtDisposal], taxable_income: Money, losses_brought_forward: Money) -> CgtResult"
          ],
          "reference": "from fune.invest.capital_gains_tax import capital_gains_tax  # invest.capital-gains-tax@^1",
          "url": "http://www.functionalweave.com/functions/invest.capital-gains-tax"
        },
        {
          "id": "invest.section-104-pool",
          "tier": "optional",
          "why": "UK share matching for CGT: same-day, 30-day and Section 104 pool cost per disposal.",
          "section": "tax",
          "from": "investments",
          "version": "1.0.1",
          "summary": "UK share matching for CGT: same-day, 30-day bed-and-breakfast, then the Section 104 pool; allowable cost per disposal.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "section104Pool"
          ],
          "signatures": [
            "def section104_pool(transactions: Sequence[ShareTransaction]) -> Section104Result"
          ],
          "reference": "from fune.invest.section_104_pool import section104_pool  # invest.section-104-pool@^1",
          "url": "http://www.functionalweave.com/functions/invest.section-104-pool"
        },
        {
          "id": "invest.pension-annual-allowance",
          "tier": "optional",
          "why": "Pension annual allowance with taper, MPAA and carry-forward, and the excess charged.",
          "section": "tax",
          "from": "investments",
          "version": "1.0.1",
          "summary": "UK pension annual allowance for a tax year with taper, MPAA and three-year carry-forward, and the excess charged.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "pensionAnnualAllowance"
          ],
          "signatures": [
            "def pension_annual_allowance(tax_year_date: str, current: PensionInputYear, previous_years: Sequence[PensionInputYear]) -> AnnualAllowanceResult"
          ],
          "reference": "from fune.invest.pension_annual_allowance import pension_annual_allowance  # invest.pension-annual-allowance@^1",
          "url": "http://www.functionalweave.com/functions/invest.pension-annual-allowance"
        }
      ],
      "gaps": []
    },
    {
      "key": "charts",
      "title": "Charts",
      "purpose": "Draw portfolio value and allocation.",
      "from": "investments",
      "members": [
        {
          "id": "charts.line-chart",
          "tier": "optional",
          "why": "Line chart geometry for value over time.",
          "section": "charts",
          "from": "investments",
          "version": "1.0.0",
          "summary": "Complete line chart geometry as data: plot area, number or date axes, gridlines, series paths, colours, legend.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "lineChart"
          ],
          "signatures": [
            "def line_chart(spec: LineChartSpec) -> LineChart"
          ],
          "reference": "from fune.charts.line_chart import line_chart  # charts.line-chart@^1",
          "url": "http://www.functionalweave.com/functions/charts.line-chart"
        },
        {
          "id": "charts.shape",
          "tier": "optional",
          "why": "Pie or donut slices for an allocation chart.",
          "section": "charts",
          "from": "investments",
          "only": [
            "pieAngles",
            "arcPath"
          ],
          "version": "1.0.1",
          "summary": "SVG path strings for lines, areas, steps, monotone curves, bars, arcs and pie slices, identical in every language.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "arcPath",
            "pieAngles"
          ],
          "signatures": [
            "def arc_path(arc: Arc) -> str",
            "def pie_angles(values: Sequence[float], start_angle: float, end_angle: float, pad_angle: float) -> List[PieSlice]"
          ],
          "reference": "from fune.charts.shape import arc_path, pie_angles  # charts.shape@^1",
          "url": "http://www.functionalweave.com/functions/charts.shape"
        }
      ],
      "gaps": []
    },
    {
      "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.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"
        },
        {
          "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": [
            "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"
        }
      ],
      "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": "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": [
            "def vat_rate(jurisdiction: str, category: str, on_date: str) -> VatRate"
          ],
          "reference": "from fune.finance.tax.vat_rate import vat_rate  # 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": [
            "def add_vat(net: Money, jurisdiction: str, category: str, on_date: str) -> VatBreakdown"
          ],
          "reference": "from fune.finance.tax.add_vat import add_vat  # 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": [
            "def remove_vat(gross: Money, jurisdiction: str, category: str, on_date: str) -> VatBreakdown"
          ],
          "reference": "from fune.finance.tax.remove_vat import remove_vat  # 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": [
            "def vat_return(transactions: Sequence[VatTransaction], period_start: str, period_end: str) -> VatReturn"
          ],
          "reference": "from fune.finance.tax.vat_return import vat_return  # 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": [
            "def flat_rate_vat(turnover: Money, sector: str, limited_cost_business: bool, registration_date: Optional[str], supply_date: str) -> FlatRateVat"
          ],
          "reference": "from fune.finance.tax.vat_flat_rate import flat_rate_vat  # 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": [
            "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": "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": [
            "def is_uk_vat_number(value: str) -> bool"
          ],
          "reference": "from fune.validation.uk_vat_number import is_uk_vat_number  # 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": [
            "def validate_uk_utr(value: str) -> UkUtr"
          ],
          "reference": "from fune.validation.uk_utr import validate_uk_utr  # 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": [
            "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"
        }
      ],
      "gaps": []
    }
  ],
  "gaps": [
    {
      "key": "market-data",
      "text": "Prices, fund data and corporate actions come from a market data provider.",
      "section": "portfolio",
      "from": "investments"
    },
    {
      "key": "order-execution",
      "text": "Placing trades with a broker or custodian (and settlement) is not in caps.",
      "section": "portfolio",
      "from": "investments"
    },
    {
      "key": "cashflow-modelling",
      "text": "Stochastic (Monte Carlo) and lifetime cash-flow modelling are not in caps.",
      "section": "planning",
      "from": "investments"
    },
    {
      "key": "suitability",
      "text": "Suitability and appropriateness assessments (risk profiling) are regulated advice processes caps does not provide.",
      "section": null,
      "from": "investments"
    },
    {
      "key": "income-tax-dividends",
      "text": "Tax on dividends and savings interest outside wrappers is not computed by caps.",
      "section": null,
      "from": "investments"
    },
    {
      "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": 62,
    "core": 27,
    "optional": 35,
    "unreviewed": 18,
    "deprecated": [],
    "unavailable": [],
    "missing": [],
    "gaps": 13
  },
  "warnings": [
    "8 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: invest.time-weighted-return, invest.money-weighted-return, invest.portfolio-weights, invest.rebalance, invest.isa-allowance, finance.tax.vat-rate, finance.tax.add-vat, finance.tax.remove-vat",
    "10 optional capabilities are unreviewed too: invest.cagr, invest.dividend-yield, invest.bond-yield, invest.fee-drag, invest.drawdown-sustainability, invest.capital-gains-tax, invest.section-104-pool, invest.pension-annual-allowance, finance.tax.vat-return, finance.tax.vat-flat-rate",
    "Not financial or tax advice. ISA, pension and capital gains tax rules are UK and dated; regulated advice needs a qualified adviser.",
    "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/investments",
  "owner": null
}
