{
  "id": "agriculture",
  "title": "Agriculture and farm management",
  "summary": "Run a farm: field areas, seed, fertiliser and spray rates, yields, livestock stocking rates, feed rations, milk price, gross margins and growing degree days.",
  "synonyms": [
    "farm",
    "farming",
    "agriculture",
    "agri",
    "arable",
    "crops",
    "livestock",
    "dairy",
    "cattle",
    "sheep",
    "agronomy",
    "farm management",
    "smallholding"
  ],
  "apps": [
    "farm management software",
    "crop planning app",
    "field records app",
    "dairy farm app",
    "livestock management system",
    "agronomy tool"
  ],
  "language": "rust",
  "includes": [
    "money-basics",
    "dates-basics"
  ],
  "includedBy": [],
  "caveats": [
    "Spray and fertiliser figures are arithmetic on the rates you give: always follow the product label and your agronomist (BASIS/FACTS qualified), and keep the records the law requires."
  ],
  "sections": [
    {
      "key": "fields",
      "title": "Fields",
      "purpose": "Measure fields and record them.",
      "from": "agriculture",
      "members": [
        {
          "id": "agri.field-area",
          "tier": "core",
          "why": "Field area in m², hectares and acres from a boundary polygon of lat/long points.",
          "section": "fields",
          "from": "agriculture",
          "version": "1.0.0",
          "summary": "Field area in square metres, hectares and acres from a boundary polygon of latitude/longitude points.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "fieldArea"
          ],
          "signatures": [
            "pub fn field_area(boundary: &[GeoPoint]) -> FieldArea"
          ],
          "reference": "fune!(agri.field-area@^1);  // then call field_area(…)",
          "url": "http://www.functionalweave.com/functions/agri.field-area"
        },
        {
          "id": "geo.point-in-polygon",
          "tier": "optional",
          "why": "Whether a point lies inside a field boundary.",
          "section": "fields",
          "from": "agriculture",
          "version": "1.0.0",
          "summary": "Whether a latitude/longitude point lies inside a polygon (ray casting; the boundary counts as inside).",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "pointInPolygon"
          ],
          "signatures": [
            "pub fn point_in_polygon(point: &GeoPoint, polygon: &[GeoPoint]) -> bool"
          ],
          "reference": "fune!(geo.point-in-polygon@^1);  // then call point_in_polygon(…)",
          "url": "http://www.functionalweave.com/functions/geo.point-in-polygon"
        },
        {
          "id": "units.convert",
          "tier": "optional",
          "why": "Converts hectares, acres and other units exactly.",
          "section": "fields",
          "from": "agriculture",
          "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": [
            "pub fn convert_units(value: &str, from_unit: &str, to_unit: &str, decimals: i64) -> String"
          ],
          "reference": "fune!(units.convert@^1);  // then call convert_units(…)",
          "url": "http://www.functionalweave.com/functions/units.convert"
        }
      ],
      "gaps": [
        {
          "key": "mapping",
          "text": "Drawing and importing field boundaries (maps, RPA land parcels, shapefiles) is yours.",
          "section": "fields",
          "from": "agriculture"
        }
      ]
    },
    {
      "key": "crops",
      "title": "Crops",
      "purpose": "Plan inputs and measure output for arable crops.",
      "from": "agriculture",
      "members": [
        {
          "id": "agri.seed-rate",
          "tier": "core",
          "why": "Seed rate in kg/ha from target plants, thousand-grain weight and establishment.",
          "section": "crops",
          "from": "agriculture",
          "version": "1.0.0",
          "summary": "Seed rate in kg/ha from target plants per m², thousand-grain weight and expected establishment.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "seedRate"
          ],
          "signatures": [
            "pub fn seed_rate(target_plants_per_square_metre: i64, thousand_grain_weight_grams: f64, establishment_basis_points: i64) -> SeedRate"
          ],
          "reference": "fune!(agri.seed-rate@^1);  // then call seed_rate(…)",
          "url": "http://www.functionalweave.com/functions/agri.seed-rate"
        },
        {
          "id": "agri.fertiliser-application",
          "tier": "core",
          "why": "Fertiliser product rate to meet N, P2O5 and K2O targets, limiting nutrient first.",
          "section": "crops",
          "from": "agriculture",
          "version": "1.0.0",
          "summary": "Fertiliser product rate (kg/ha and total) to meet N, P2O5 and K2O targets from its analysis, limiting nutrient first.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "fertiliserApplication"
          ],
          "signatures": [
            "pub fn fertiliser_application(analysis: &NutrientAnalysis, targets: &NutrientTargets, area_hectares: f64) -> FertiliserPlan"
          ],
          "reference": "fune!(agri.fertiliser-application@^1);  // then call fertiliser_application(…)",
          "url": "http://www.functionalweave.com/functions/agri.fertiliser-application"
        },
        {
          "id": "agri.yield-per-hectare",
          "tier": "core",
          "why": "Yield per hectare and per acre from harvest and field area.",
          "section": "crops",
          "from": "agriculture",
          "version": "1.0.1",
          "summary": "Crop yield per hectare and per acre from harvested quantity and field area in any area unit, exactly, as decimal text.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "yieldPerHectare"
          ],
          "signatures": [
            "pub fn yield_per_hectare(quantity: &str, area: &str, area_unit: &str, decimals: i64) -> YieldPerArea"
          ],
          "reference": "fune!(agri.yield-per-hectare@^1);  // then call yield_per_hectare(…)",
          "url": "http://www.functionalweave.com/functions/agri.yield-per-hectare"
        },
        {
          "id": "agri.spray-mix",
          "tier": "optional",
          "why": "Sprayer tank mix: fills, and product and water per tank, for an area and dose.",
          "section": "crops",
          "from": "agriculture",
          "version": "1.0.1",
          "summary": "Sprayer tank mix: tank fills, and product and water per full and part tank, for an area, water volume and dose.",
          "status": "unreviewed",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "sprayMix"
          ],
          "signatures": [
            "pub fn spray_mix(area_hectares: f64, water_volume_litres_per_hectare: f64, product_dose_per_hectare: f64, dose_unit: &str, tank_litres: f64) -> SprayMix"
          ],
          "reference": "fune!(agri.spray-mix@^1);  // then call spray_mix(…)",
          "url": "http://www.functionalweave.com/functions/agri.spray-mix"
        },
        {
          "id": "agri.growing-degree-days",
          "tier": "optional",
          "why": "Growing degree days from daily min/max temperatures.",
          "section": "crops",
          "from": "agriculture",
          "version": "1.0.0",
          "summary": "Growing degree days (thermal time) from daily min/max temperatures, a base and an optional upper cap.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "growingDegreeDays"
          ],
          "signatures": [
            "pub fn growing_degree_days(days: &[DailyTemperature], base_celsius: f64, upper_celsius: Option<f64>, method: &str) -> DegreeDays"
          ],
          "reference": "fune!(agri.growing-degree-days@^1);  // then call growing_degree_days(…)",
          "url": "http://www.functionalweave.com/functions/agri.growing-degree-days"
        }
      ],
      "gaps": [
        {
          "key": "weather-data",
          "text": "Weather and soil data feeds are network work and not in caps.",
          "section": "crops",
          "from": "agriculture"
        },
        {
          "key": "nutrient-planning",
          "text": "Nutrient recommendations (RB209) and NVZ limits: caps applies targets you give it.",
          "section": "crops",
          "from": "agriculture"
        }
      ]
    },
    {
      "key": "livestock",
      "title": "Livestock",
      "purpose": "Stock and feed animals, and price the milk.",
      "from": "agriculture",
      "members": [
        {
          "id": "agri.stocking-rate",
          "tier": "core",
          "why": "Livestock units and LU per hectare from animal numbers.",
          "section": "livestock",
          "from": "agriculture",
          "version": "1.0.1",
          "summary": "Stocking rate: livestock units and LU per hectare from animal numbers, by a published LU coefficient table.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "stockingRate"
          ],
          "signatures": [
            "pub fn stocking_rate(scheme: &str, animals: &[LivestockCount], area_hectares: f64) -> StockingRate"
          ],
          "reference": "fune!(agri.stocking-rate@^1);  // then call stocking_rate(…)",
          "url": "http://www.functionalweave.com/functions/agri.stocking-rate"
        },
        {
          "id": "agri.feed-ration",
          "tier": "core",
          "why": "Ration cost and dry matter intake from feeds' weights, DM % and prices.",
          "section": "livestock",
          "from": "agriculture",
          "version": "1.0.0",
          "summary": "Ration costing and dry matter intake from each feed's fresh weight, DM % and price per tonne, in exact money.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "feedRation"
          ],
          "signatures": [
            "pub fn feed_ration(feeds: &[RationFeed], head: i64, days: i64, currency: &str) -> RationCost"
          ],
          "reference": "fune!(agri.feed-ration@^1);  // then call feed_ration(…)",
          "url": "http://www.functionalweave.com/functions/agri.feed-ration"
        },
        {
          "id": "agri.milk-price",
          "tier": "optional",
          "why": "Milk price per litre from a contract's base price, constituents and bonuses.",
          "section": "livestock",
          "from": "agriculture",
          "version": "1.0.0",
          "summary": "Milk price per litre and payment from a contract's base price, butterfat/protein adjustments and bonuses.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "milkPrice"
          ],
          "signatures": [
            "pub fn milk_price(contract: &MilkContract, supply: &MilkSupply) -> MilkPrice"
          ],
          "reference": "fune!(agri.milk-price@^1);  // then call milk_price(…)",
          "url": "http://www.functionalweave.com/functions/agri.milk-price"
        }
      ],
      "gaps": [
        {
          "key": "animal-records",
          "text": "Animal movements and registrations (BCMS, ARAMS) are not in caps.",
          "section": "livestock",
          "from": "agriculture"
        }
      ]
    },
    {
      "key": "finance",
      "title": "Farm finance",
      "purpose": "Know which enterprise makes money.",
      "from": "agriculture",
      "members": [
        {
          "id": "agri.gross-margin",
          "tier": "core",
          "why": "Enterprise gross margin in total and per hectare or head.",
          "section": "finance",
          "from": "agriculture",
          "version": "1.0.0",
          "summary": "Enterprise gross margin (output less variable costs), in total and per hectare or per head, in exact money.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "grossMargin"
          ],
          "signatures": [
            "pub fn gross_margin(outputs: &[MarginLine], variable_costs: &[MarginLine], per: &str, units: f64, currency: &str) -> GrossMargin"
          ],
          "reference": "fune!(agri.gross-margin@^1);  // then call gross_margin(…)",
          "url": "http://www.functionalweave.com/functions/agri.gross-margin"
        }
      ],
      "gaps": [
        {
          "key": "subsidy-schemes",
          "text": "Payment schemes (SFI and the like) and their claims are not in caps.",
          "section": "finance",
          "from": "agriculture"
        }
      ]
    },
    {
      "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": [
            "pub fn money(minor: i64, currency: &str) -> Money"
          ],
          "reference": "fune!(money.amount@^1);  // then call money(…)",
          "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": [
            "pub fn add_money(a: &Money, b: &Money) -> Money"
          ],
          "reference": "fune!(money.add@^1);  // then call add_money(…)",
          "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": [
            "pub fn sum_money(amounts: &[Money], currency: &str) -> Money"
          ],
          "reference": "fune!(money.sum@^1);  // then call sum_money(…)",
          "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": [
            "pub fn compare_money(a: &Money, b: &Money) -> i64",
            "pub fn min_money(a: &Money, b: &Money) -> Money",
            "pub fn max_money(a: &Money, b: &Money) -> Money",
            "pub fn clamp_money(amount: &Money, min: &Money, max: &Money) -> Money"
          ],
          "reference": "fune!(money.compare@^2);  // then call compare_money, min_money, max_money, clamp_money",
          "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": [
            "pub fn apply_rate(amount: &Money, basis_points: i64, mode: &RoundingMode) -> Money"
          ],
          "reference": "fune!(money.apply-rate@^1);  // then call apply_rate(…)",
          "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": [
            "pub fn round_div(numerator: i64, denominator: i64, mode: &str) -> i64"
          ],
          "reference": "fune!(math.round-div@^1);  // then call round_div(…)",
          "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": [
            "pub fn convert_rate(value: &str, from_unit: &str, to_unit: &str) -> String",
            "pub fn to_basis_points(value: &str, unit: &str) -> i64",
            "pub fn from_basis_points(basis_points: i64, unit: &str) -> String"
          ],
          "reference": "fune!(math.basis-points@^2);  // then call convert_rate, to_basis_points, from_basis_points",
          "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": [
            "pub fn percent_change(from_value: i64, to_value: i64, mode: &RoundingMode) -> Option<i64>"
          ],
          "reference": "fune!(math.percent-change@^1);  // then call percent_change(…)",
          "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": [
            "pub fn calculate_rational(a: &Rational, op: &str, b: &Rational) -> Rational",
            "pub fn rational(numerator: i64, denominator: i64) -> Rational",
            "pub fn add_rational(a: &Rational, b: &Rational) -> Rational",
            "pub fn subtract_rational(a: &Rational, b: &Rational) -> Rational",
            "pub fn multiply_rational(a: &Rational, b: &Rational) -> Rational",
            "pub fn divide_rational(a: &Rational, b: &Rational) -> Rational",
            "pub fn compare_rational(a: &Rational, b: &Rational) -> i64",
            "pub fn rational_to_integer(r: &Rational, mode: &RoundingMode) -> i64"
          ],
          "reference": "fune!(math.rational@^2);  // then call calculate_rational, rational, add_rational, subtract_rational, multiply_rational, divide_rational, compare_rational, rational_to_integer",
          "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": [
            "pub fn round_div_big(numerator: &str, denominator: &str, mode: &RoundingMode) -> String"
          ],
          "reference": "fune!(math.round-div-big@^1);  // then call round_div_big(…)",
          "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": [
            "pub fn allocate(amount: &Money, ratios: &[i64]) -> Vec<Money>"
          ],
          "reference": "fune!(money.allocate@^1);  // then call allocate(…)",
          "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": [
            "pub fn split_even(amount: &Money, parts: i64) -> Vec<Money>"
          ],
          "reference": "fune!(money.split-even@^1);  // then call split_even(…)",
          "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": [
            "pub fn format_money(amount: &Money) -> String"
          ],
          "reference": "fune!(money.format@^1);  // then call format_money(…)",
          "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": [
            "pub fn parse_money(text: &str, currency: &str, style: &str) -> Money"
          ],
          "reference": "fune!(money.parse@^1);  // then call parse_money(…)",
          "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": [
            "pub fn currency_digits(currency: &str) -> i64"
          ],
          "reference": "fune!(money.currency-digits@^1);  // then call currency_digits(…)",
          "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": [
            "pub fn convert_money(amount: &Money, rate: &ExchangeRate, mode: &RoundingMode) -> Money"
          ],
          "reference": "fune!(money.convert@^1);  // then call convert_money(…)",
          "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": [
            "pub fn add_days(iso: &str, days: i64) -> String"
          ],
          "reference": "fune!(dates.add-days@^1);  // then call add_days(…)",
          "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": [
            "pub fn add_months(iso: &str, months: i64) -> String"
          ],
          "reference": "fune!(dates.add-months@^1);  // then call add_months(…)",
          "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": [
            "pub fn days_between(start_iso: &str, end_iso: &str) -> i64"
          ],
          "reference": "fune!(dates.days-between@^1);  // then call days_between(…)",
          "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": [
            "pub fn month_boundaries(iso: &str) -> MonthBoundaries"
          ],
          "reference": "fune!(dates.month-boundaries@^1);  // then call month_boundaries(…)",
          "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": [
            "pub fn day_of_week(iso: &str) -> i64"
          ],
          "reference": "fune!(dates.day-of-week@^1);  // then call day_of_week(…)",
          "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": [
            "pub fn is_leap_year(year: i64) -> bool"
          ],
          "reference": "fune!(dates.is-leap-year@^1);  // then call is_leap_year(…)",
          "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": [
            "pub fn overlap_days(a_start: &str, a_end: &str, b_start: &str, b_end: &str, end_inclusive: bool) -> i64"
          ],
          "reference": "fune!(dates.overlap@^1);  // then call overlap_days(…)",
          "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": [
            "pub fn age(birth_date: &str, on_date: &str) -> i64"
          ],
          "reference": "fune!(dates.age@^1);  // then call age(…)",
          "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": [
            "pub fn quarter(iso: &str, fiscal_year_start_month: i64) -> Quarter"
          ],
          "reference": "fune!(dates.quarter@^1);  // then call quarter(…)",
          "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": [
            "pub fn week_number(iso: &str) -> IsoWeek"
          ],
          "reference": "fune!(dates.week-number@^1);  // then call week_number(…)",
          "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": [
            "pub fn day_count_fraction(start_iso: &str, end_iso: &str, convention: &str) -> YearFraction"
          ],
          "reference": "fune!(dates.day-count-fraction@^1);  // then call day_count_fraction(…)",
          "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": [
            "pub fn bank_holidays(region: &str, year: i64) -> Vec<BankHoliday>"
          ],
          "reference": "fune!(dates.bank-holidays@^1);  // then call bank_holidays(…)",
          "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": [
            "pub fn add_business_days(iso: &str, days: i64, holidays: &[String]) -> String"
          ],
          "reference": "fune!(dates.add-business-days@^1);  // then call add_business_days(…)",
          "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": [
            "pub fn business_days_between(start_iso: &str, end_iso: &str, holidays: &[String]) -> i64"
          ],
          "reference": "fune!(dates.business-days-between@^1);  // then call business_days_between(…)",
          "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": [
            "pub fn recurrence(rule: &RecurrenceRule, start_iso: &str, count: i64) -> Vec<String>"
          ],
          "reference": "fune!(dates.recurrence@^1);  // then call recurrence(…)",
          "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": [
            "pub fn duration(parts: &[String]) -> Duration"
          ],
          "reference": "fune!(time.duration@^1);  // then call duration(…)",
          "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": [
            "pub fn minutes_between(start_date: &str, start_time: &str, end_date: &str, end_time: &str) -> i64"
          ],
          "reference": "fune!(time.minutes-between@^1);  // then call minutes_between(…)",
          "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": [
            "pub fn round_to_increment(minutes: i64, increment: i64, mode: &RoundingMode) -> i64"
          ],
          "reference": "fune!(time.round-to-increment@^1);  // then call round_to_increment(…)",
          "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": [
            "pub fn iso_to_unix(timestamp: &str) -> i64"
          ],
          "reference": "fune!(time.iso-to-unix@^1);  // then call iso_to_unix(…)",
          "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": [
            "pub fn unix_to_iso(seconds: i64) -> String"
          ],
          "reference": "fune!(time.unix-to-iso@^1);  // then call unix_to_iso(…)",
          "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": [
            "pub fn countdown(until: i64, now: i64) -> Countdown"
          ],
          "reference": "fune!(time.countdown@^1);  // then call countdown(…)",
          "url": "http://www.functionalweave.com/functions/time.countdown"
        }
      ],
      "gaps": []
    }
  ],
  "gaps": [
    {
      "key": "mapping",
      "text": "Drawing and importing field boundaries (maps, RPA land parcels, shapefiles) is yours.",
      "section": "fields",
      "from": "agriculture"
    },
    {
      "key": "weather-data",
      "text": "Weather and soil data feeds are network work and not in caps.",
      "section": "crops",
      "from": "agriculture"
    },
    {
      "key": "nutrient-planning",
      "text": "Nutrient recommendations (RB209) and NVZ limits: caps applies targets you give it.",
      "section": "crops",
      "from": "agriculture"
    },
    {
      "key": "animal-records",
      "text": "Animal movements and registrations (BCMS, ARAMS) are not in caps.",
      "section": "livestock",
      "from": "agriculture"
    },
    {
      "key": "subsidy-schemes",
      "text": "Payment schemes (SFI and the like) and their claims are not in caps.",
      "section": "finance",
      "from": "agriculture"
    },
    {
      "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"
    }
  ],
  "counts": {
    "members": 49,
    "core": 24,
    "optional": 25,
    "unreviewed": 1,
    "deprecated": [],
    "unavailable": [],
    "missing": [],
    "gaps": 10
  },
  "warnings": [
    "1 optional capability is unreviewed too: agri.spray-mix",
    "Spray and fertiliser figures are arithmetic on the rates you give: always follow the product label and your agronomist (BASIS/FACTS qualified), and keep the records the law requires."
  ],
  "url": "http://www.functionalweave.com/suites/agriculture",
  "owner": null
}
