Functional Weave
Code in Rust

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.

pub fn feed_ration(feeds: &[RationFeed], head: i64, days: i64, 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

/// One feed in the ration, as fed.
#[derive(Debug, Clone, PartialEq)]
pub struct RationFeed {
    pub name: String,
    /// kg as fed per head per day, taken to the gram
    pub fresh_kg_per_head: f64,
    /// dry matter content, 3000 = 30% DM; 0 to 10000
    pub dry_matter_basis_points: i64,
    /// per tonne as fed (fresh weight), not per tonne of DM
    pub price_per_tonne: Money,
}

/// One feed's share, per head per day.
#[derive(Debug, Clone, PartialEq)]
pub struct RationLine {
    pub name: String,
    pub fresh_kg_per_head: f64,
    /// to the gram, half up
    pub dry_matter_kg_per_head: f64,
    /// half up
    pub cost_per_head: Money,
}

/// Intake and cost per head per day, and for the whole group and period.
#[derive(Debug, Clone, PartialEq)]
pub struct RationCost {
    pub lines: Vec<RationLine>,
    /// total as fed
    pub fresh_kg_per_head: f64,
    /// total dry matter intake, from the exact sum, to the gram
    pub dry_matter_kg_per_head: f64,
    /// DM % of the whole ration; null when nothing is fed
    pub dry_matter_basis_points: Option<i64>,
    /// from the exact sum, half up
    pub cost_per_head_per_day: Money,
    /// head x days x the exact daily cost, rounded once
    pub total_cost: Money,
    /// null when the ration has no dry matter
    pub cost_per_tonne_dry_matter: Option<Money>,
}

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

fune!(agri.feed-ration@^1);  // then call feed_ration(…)
impl/rust.rs · 135 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.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_float::round_float;  ← from math.round-float ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value};  ← from money.amount ^1.0.0 · built alongside by fune

const MAX_SAFE: i128 = 9_007_199_254_740_991;

// Half up for a non-negative numerator and a positive denominator.
fn half_up(numerator: i128, denominator: i128) -> i64 {
    let q = (2 * numerator + denominator) / (2 * denominator);
    if q > MAX_SAFE {
        panic!("total cost is too large");
    }
    q as i64
}

/// 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.
///
/// # Panics
/// Panics on an out-of-range quantity, DM, price, head or days, a price in
/// another currency, or a total beyond 2^53 - 1 minor units.
pub fn feed_ration(feeds: &[RationFeed], head: i64, days: i64, currency: &str) -> RationCost {
    if !(1..=1_000_000).contains(&head) {
        panic!("head must be a whole number from 1 to 1000000, received {}", head);
    }
    if !(1..=3660).contains(&days) {
        panic!("days must be a whole number from 1 to 3660, received {}", days);
    }
    money(0, currency);

    let mut grams: i128 = 0;
    let mut dry_matter: i128 = 0;
    let mut cost: i128 = 0;
    let mut lines: Vec<RationLine> = Vec::with_capacity(feeds.len());
    for (i, feed) in feeds.iter().enumerate() {
        let n = i + 1;
        let kg = feed.fresh_kg_per_head;
        if !kg.is_finite() || kg < 0.0 || kg > 1000.0 {
            panic!("feed {}: freshKgPerHead must be a finite number from 0 to 1000, received {}", n, kg);
        }
        let g = (round_float(kg, 3) * 1000.0).round() as i128;
        let bp = feed.dry_matter_basis_points;
        if !(0..=10000).contains(&bp) {
            panic!("feed {}: dryMatterBasisPoints must be a whole number from 0 to 10000, received {}", n, bp);
        }
        let price = &feed.price_per_tonne;
        if price.currency != currency {
            panic!("feed {}: currency mismatch: {} and {}", n, price.currency, currency);
        }
        if !(0..=100_000_000).contains(&price.minor) {
            panic!("feed {}: pricePerTonne must be from 0 to 100000000 minor units, received {}", n, price.minor);
        }
        let line_dm = g * bp as i128;
        let line_cost = g * price.minor as i128;
        grams += g;
        dry_matter += line_dm;
        cost += line_cost;
        lines.push(RationLine {
            name: feed.name.clone(),
            fresh_kg_per_head: g as f64 / 1000.0,
            dry_matter_kg_per_head: half_up(line_dm, 10000) as f64 / 1000.0,
            cost_per_head: money(half_up(line_cost, 1_000_000), currency),
        });
    }

    RationCost {
        lines,
        fresh_kg_per_head: grams as f64 / 1000.0,
        dry_matter_kg_per_head: half_up(dry_matter, 10000) as f64 / 1000.0,
        dry_matter_basis_points: if grams == 0 { None } else { Some(half_up(dry_matter, grams)) },
        cost_per_head_per_day: money(half_up(cost, 1_000_000), currency),
        total_cost: money(half_up(cost * head as i128 * days as i128, 1_000_000), currency),
        cost_per_tonne_dry_matter: if dry_matter == 0 {
            None
        } else {
            Some(money(half_up(cost * 10000, dry_matter), currency))
        },
    }
}

pub fn ration_cost_to_value(r: &RationCost) -> Value {
    let lines = r
        .lines
        .iter()
        .map(|l| {
            Value::obj(vec![
                ("name", Value::str(&l.name)),
                ("freshKgPerHead", Value::Float(l.fresh_kg_per_head)),
                ("dryMatterKgPerHead", Value::Float(l.dry_matter_kg_per_head)),
                ("costPerHead", money_to_value(&l.cost_per_head)),
            ])
        })
        .collect();
    Value::obj(vec![
        ("lines", Value::Arr(lines)),
        ("freshKgPerHead", Value::Float(r.fresh_kg_per_head)),
        ("dryMatterKgPerHead", Value::Float(r.dry_matter_kg_per_head)),
        ("dryMatterBasisPoints", r.dry_matter_basis_points.map_or(Value::Null, Value::Int)),
        ("costPerHeadPerDay", money_to_value(&r.cost_per_head_per_day)),
        ("totalCost", money_to_value(&r.total_cost)),
        ("costPerTonneDryMatter", r.cost_per_tonne_dry_matter.as_ref().map_or(Value::Null, money_to_value)),
    ])
}

fn whole(v: &Value, message: String) -> i64 {
    match v {
        Value::Int(i) => *i,
        _ => panic!("{}, received {:?}", message, v),
    }
}

pub fn fune_vector(args: &[Value]) -> Value {
    let feeds: Vec<RationFeed> = args[0]
        .as_arr()
        .iter()
        .enumerate()
        .map(|(i, v)| RationFeed {
            name: v.get("name").as_str().to_string(),
            fresh_kg_per_head: v.get("freshKgPerHead").as_f64(),
            dry_matter_basis_points: whole(
                v.get("dryMatterBasisPoints"),
                format!("feed {}: dryMatterBasisPoints must be a whole number from 0 to 10000", i + 1),
            ),
            price_per_tonne: money_from_value(v.get("pricePerTonne")),
        })
        .collect();
    let head = whole(&args[1], "head must be a whole number from 1 to 1000000".to_string());
    let days = whole(&args[2], "days must be a whole number from 1 to 3660".to_string());
    ration_cost_to_value(&feed_ration(&feeds, head, days, args[3].as_str()))
}

Install

fune build

With that line in your source, in a Rust project (language rust in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the Rust 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. A crate’s build.rs runs it before every compile. Or pin a range in fune.project and build in one step:

fune add agri.feed-ration
Download for Rust agri.feed-ration-1.0.0-rust.fune · 26,685 bytes sha256 e7df94042dd44e49ef9c9c9c0a32a82943369253c751e4923f5470e6504ca190

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

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