Functional Weave
Code in Python

agri.feed-ration

Ration costing and dry matter intake from each feed's fresh weight, DM % and price per tonne, in exact money.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 15 tests, run in TypeScript, Python and Rust.

What it does

What a ration costs and how much dry matter it supplies: per head per day, for the whole group over a period, and per tonne of dry matter. Each feed is given as fed (fresh weight per head per day), with its dry matter (DM) content and its price per tonne as fed.

For a dairy cow on 40 kg grass silage (30% DM, £40/t), 15 kg maize silage (32% DM, £45/t) and 8 kg compound (88% DM, £300/t), the ration is 63 kg fresh, 23.84 kg DM (37.84% DM), 467.5p a day (shown as £4.68), £196.10 per tonne of DM, and £14,025.00 for 100 cows over 30 days.

For example

  • feed_ration(feeds ×3, 100, 30, GBP) → lines ×3, fresh kg per head 63, dry matter kg per head 23.84, dry matter basis points 37.84%, cost per head per day £4.68, total cost £14,025.00, cost per tonne dry matter £196.10 a dairy ration for 100 cows over 30 days: the group cost is 467.5p x 3000 exactly, not 468p x 3000
  • feed_ration(feeds ×3, 1, 1, GBP) → lines ×3, fresh kg per head 63, dry matter kg per head 23.84, dry matter basis points 37.84%, cost per head per day £4.68, total cost £4.68, cost per tonne dry matter £196.10 the same ration for one cow for one day
  • feed_ration(feeds ×1, 1, 1, GBP) → lines ×1, fresh kg per head 10, dry matter kg per head 8.5, dry matter basis points 85%, cost per head per day £1.20, total cost £1.20, cost per tonne dry matter £141.18 hay alone: 10 kg at 85% DM and £120/t is £1.20 a day and £141.18 a tonne of DM

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def feed_ration(feeds: Sequence[RationFeed], head: int, days: int, currency: str) -> RationCost
feedsRationFeed[]the ration per head per day; may be empty
headintanimals fed, 1 to 1000000
daysintdays fed, 1 to 3660
currencystringevery price must be in it
returnsRationCost

The types it declares, generated into your project

@dataclass(frozen=True)
class RationFeed:
    """One feed in the ration, as fed."""

    name: str
    #: kg as fed per head per day, taken to the gram
    fresh_kg_per_head: float
    #: dry matter content, 3000 = 30% DM; 0 to 10000
    dry_matter_basis_points: int
    #: per tonne as fed (fresh weight), not per tonne of DM
    price_per_tonne: Money

@dataclass(frozen=True)
class RationLine:
    """One feed's share, per head per day."""

    name: str
    fresh_kg_per_head: float
    #: to the gram, half up
    dry_matter_kg_per_head: float
    #: half up
    cost_per_head: Money

@dataclass(frozen=True)
class RationCost:
    """Intake and cost per head per day, and for the whole group and period."""

    lines: List[RationLine]
    #: total as fed
    fresh_kg_per_head: float
    #: total dry matter intake, from the exact sum, to the gram
    dry_matter_kg_per_head: float
    #: DM % of the whole ration; null when nothing is fed
    dry_matter_basis_points: Optional[int]
    #: from the exact sum, half up
    cost_per_head_per_day: Money
    #: head x days x the exact daily cost, rounded once
    total_cost: Money
    #: null when the ration has no dry matter
    cost_per_tonne_dry_matter: Optional[Money]

Your code names it in one line, in the file that uses it

from fune.agri.feed_ration import feed_ration  # agri.feed-ration@^1
impl/python.py · 79 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import math
from typing import List, Optional, Sequence

from .agri_feed_ration_types import RationCost, RationFeed, RationLine
from .math_round_float import round_float  ← from math.round-float ^1.0.0 · built alongside by fune
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune

MAX_SAFE = 9007199254740991


def _half_up(numerator: int, denominator: int) -> int:
    # Half up for a non-negative numerator and a positive denominator.
    q = (2 * numerator + denominator) // (2 * denominator)
    if q > MAX_SAFE:
        raise ValueError("total cost is too large")
    return q


def _whole(value: object) -> bool:
    return isinstance(value, int) and not isinstance(value, bool)


def feed_ration(feeds: Sequence[RationFeed], head: int, days: int, currency: str) -> RationCost:
    """Ration cost and dry matter intake.

    Weights are grams and prices minor units per tonne, so a feed's daily cost
    is exactly grams x price / 1,000,000 and every total is rounded once from
    the exact sum. Costing the group from a rounded daily cost multiplies the
    rounding: 467.5p a head a day is 1,402,500p for 100 cows over 30 days, not
    468 x 3000 = 1,404,000p.
    """
    if not _whole(head) or head < 1 or head > 1000000:
        raise ValueError("head must be a whole number from 1 to 1000000, received %r" % (head,))
    if not _whole(days) or days < 1 or days > 3660:
        raise ValueError("days must be a whole number from 1 to 3660, received %r" % (days,))
    money(0, currency)

    grams = 0
    dry_matter = 0  # grams x basis points
    cost = 0  # grams x minor units per tonne
    lines: List[RationLine] = []
    for i, feed in enumerate(feeds):
        n = i + 1
        kg = feed.fresh_kg_per_head
        if isinstance(kg, bool) or not isinstance(kg, (int, float)) or not math.isfinite(kg) or kg < 0 or kg > 1000:
            raise ValueError("feed %d: freshKgPerHead must be a finite number from 0 to 1000, received %r" % (n, kg))
        g = int(round(round_float(float(kg), 3) * 1000))
        bp = feed.dry_matter_basis_points
        if not _whole(bp) or bp < 0 or bp > 10000:
            raise ValueError("feed %d: dryMatterBasisPoints must be a whole number from 0 to 10000, received %r" % (n, bp))
        price = feed.price_per_tonne
        if price.currency != currency:
            raise ValueError("feed %d: currency mismatch: %s and %s" % (n, price.currency, currency))
        if not _whole(price.minor) or price.minor < 0 or price.minor > 100000000:
            raise ValueError("feed %d: pricePerTonne must be from 0 to 100000000 minor units, received %r" % (n, price.minor))
        line_dm = g * bp
        line_cost = g * price.minor
        grams += g
        dry_matter += line_dm
        cost += line_cost
        lines.append(
            RationLine(
                name=feed.name,
                fresh_kg_per_head=g / 1000,
                dry_matter_kg_per_head=_half_up(line_dm, 10000) / 1000,
                cost_per_head=money(_half_up(line_cost, 1000000), currency),
            )
        )

    per_tonne_dm: Optional[Money] = None if dry_matter == 0 else money(_half_up(cost * 10000, dry_matter), currency)
    return RationCost(
        lines=lines,
        fresh_kg_per_head=grams / 1000,
        dry_matter_kg_per_head=_half_up(dry_matter, 10000) / 1000,
        dry_matter_basis_points=None if grams == 0 else _half_up(dry_matter, grams),
        cost_per_head_per_day=money(_half_up(cost, 1000000), currency),
        total_cost=money(_half_up(cost * head * days, 1000000), currency),
        cost_per_tonne_dry_matter=per_tonne_dm,
    )

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add agri.feed-ration
Download for Python agri.feed-ration-1.0.0-python.fune · 24,606 bytes sha256 d935af61d5a16dc467cc554d6b9f41543d24f92f81278d44db6b434b1fad6070

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./agri.feed-ration-1.0.0-python.fune, or fetch it from a terminal with fune pull agri.feed-ration@1.0.0:python.

The whole function, every language, is one file too: agri.feed-ration-1.0.0.fune, 33,862 bytes, sha256 8a6ebd2fd6c6a7b0668704213d0f420c4a37e94443dc205a9b82954e57361167. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before agri.feed-ration

after — your function gets the result and the arguments, and returns the final result.

# fune: after agri.feed-ration

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

# fune: replace math.round-float in agri.feed-ration
# fune: replace money.amount in agri.feed-ration

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show agri.feed-ration --steps.

# fune: step agri.feed-ration after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
a dairy ration for 100 cows over 30 days: the group cost is 467.5p x 3000 exactly, not 468p x 3000 feeds ×3, 100, 30, GBP → lines ×3, fresh kg per head 63, dry matter kg per head 23.84, dry matter basis points 37.84%, cost per head per day £4.68, total cost £14,025.00, cost per tonne dry matter £196.10
the same ration for one cow for one day feeds ×3, 1, 1, GBP → lines ×3, fresh kg per head 63, dry matter kg per head 23.84, dry matter basis points 37.84%, cost per head per day £4.68, total cost £4.68, cost per tonne dry matter £196.10
hay alone: 10 kg at 85% DM and £120/t is £1.20 a day and £141.18 a tonne of DM feeds ×1, 1, 1, GBP → lines ×1, fresh kg per head 10, dry matter kg per head 8.5, dry matter basis points 85%, cost per head per day £1.20, total cost £1.20, cost per tonne dry matter £141.18
a sheep flock on a small concentrate allowance, 0.45 kg at £280/t feeds ×1, 400, 42, GBP → lines ×1, fresh kg per head 0.45, dry matter kg per head 0.392, dry matter basis points 87%, cost per head per day £0.13, total cost £2,116.80, cost per tonne dry matter £321.84
fresh weights are taken to the gram feeds ×1, 1, 1, GBP → lines ×1, fresh kg per head 2, dry matter kg per head 1.7, dry matter basis points 85%, cost per head per day £0.24, total cost £0.24, cost per tonne dry matter £141.18
water: fresh weight but no dry matter and no cost feeds ×1, 1, 1, GBP → lines ×1, fresh kg per head 60, dry matter kg per head 0, dry matter basis points 0%, cost per head per day £0.00, total cost £0.00, cost per tonne dry matter —
no feeds is zero, with no DM share or cost per tonne of DM , 10, 7, EUR → lines , fresh kg per head 0, dry matter kg per head 0, dry matter basis points —, cost per head per day €0.00, total cost €0.00, cost per tonne dry matter —
a line under half a penny rounds to zero but still counts in the total feeds ×1, 1,000, 1, GBP → lines ×1, fresh kg per head 0.1, dry matter kg per head 0.095, dry matter basis points 95%, cost per head per day £0.00, total cost £4.00, cost per tonne dry matter £42.11
zero head is an error feeds ×3, 0, 1, GBP → error: head must be a whole number from 1 to 1000000
a fractional day is an error feeds ×3, 1, 1.5, GBP → error: days must be a whole number from 1 to 3660
Show the other 5 tests
CaseArgumentsExpected
DM over 100% is an error feeds ×1, 1, 1, GBP → error: feed 1: dryMatterBasisPoints must be a whole number from 0 to 10000
a DM percentage given as a fraction is an error feeds ×1, 1, 1, GBP → error: feed 1: dryMatterBasisPoints must be a whole number from 0 to 10000
a negative fresh weight is an error feeds ×1, 1, 1, GBP → error: feed 1: freshKgPerHead must be a finite number from 0 to 1000
a negative price is an error feeds ×1, 1, 1, GBP → error: feed 1: pricePerTonne must be from 0 to 100000000 minor units
a price in another currency is an error feeds ×2, 1, 1, GBP → error: feed 2: currency mismatch: EUR and GBP

More from the author

## Exactness

Fresh weights are taken to the gram (`math.round-float`), DM is in basis points and prices are integer minor units per tonne, so every figure is an exact fraction until it is rounded, once, half up:

- a line's cost is grams x price / 1,000,000; its DM is grams x DM / 10,000; - the daily cost, the DM intake and the DM % are rounded from the exact sums, not added up from the rounded lines; - the group total is head x days x the exact daily cost, rounded once. Multiplying the rounded 468p by 3000 cow-days would overstate it by £15.

Money never goes through a float. Intermediate products are held in `bigint` (TypeScript), `int` (Python) and `i128` (Rust).

## Conventions

- The price is per tonne **as fed**, as feed merchants quote it. The cost per tonne of DM is derived: daily cost / daily DM x 1000. - DM % of the ration is total DM over total fresh weight; null when nothing is fed. The cost per tonne of DM is null when the ration has no DM (water only, or no feeds). - Nothing is added for wastage, shrink or mixing losses; put them in the quantity or the price.

It does not balance a ration for energy, protein or minerals, or check intake against an animal's appetite.

## Limits

Fresh weight 0 to 1000 kg per head per day; DM 0 to 10,000 basis points; price 0 to 100,000,000 minor units per tonne, in `currency`; head 1 to 1,000,000; days 1 to 3660. A result beyond 2^53 - 1 minor units is refused.

Files

PathBytes
README.md2,012
impl/python.py3,538
impl/rust.rs5,533
impl/typescript.ts3,414
vectors.json13,191