agri.stocking-rate
Stocking rate: livestock units and LU per hectare from animal numbers, by a published LU coefficient table.
1.0.1 · published 2026-10-03 by charlie · Anterra
Pinned by 17 tests, run in TypeScript, Python and Rust.
What it does
Livestock units (LU) and the stocking rate in LU per hectare, from head counts by category and a published table of livestock unit coefficients. `stockingRate("eurostat", [{category: "dairy-cows", count: 100}, {category: "bovine-1-to-2-years", count: 80}], 120)` is 156 LU, 1.3 LU/ha.
## Two tables, deliberately separate
For example
stocking_rate(eurostat, animals ×2, 120)→ livestock units 156, units per hectare 1.3, lines ×2 Eurostat: 100 dairy cows and 80 yearlings on 120 ha is 156 LU, 1.3 LU/hastocking_rate(uk-countryside-stewardship, animals ×2, 40)→ livestock units 56, units per hectare 1.4, lines ×2 Countryside Stewardship: 300 lowland ewes with lambs and 20 suckler cows on 40 hastocking_rate(eurostat, animals ×1, 3)→ livestock units 224, units per hectare 74.67, lines ×1 16,000 laying hens on 3 ha is 224 LU, 74.67 LU/ha
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 stocking_rate(scheme: &str, animals: &[LivestockCount], area_hectares: f64) -> StockingRate
| scheme | string | eurostat or uk-countryside-stewardship |
| animals | LivestockCount[] | head counts by category of that scheme; may be empty |
| area_hectares | float | the land they are kept on, taken to 0.0001 ha (1 m²) |
| returns | StockingRate |
The types it declares, generated into your project
/// How many animals of one category.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LivestockCount {
/// a category code of the scheme, e.g. dairy-cows, lowland-ewe-and-lamb
pub category: String,
/// head, 0 or more
pub count: i64,
}
/// One category's contribution.
#[derive(Debug, Clone, PartialEq)]
pub struct LivestockUnitLine {
pub category: String,
pub count: i64,
/// livestock units per head, from the table
pub coefficient: f64,
/// count x coefficient, exact (to 0.001 LU)
pub livestock_units: f64,
}
/// Total livestock units and the rate per hectare.
#[derive(Debug, Clone, PartialEq)]
pub struct StockingRate {
/// total, exact (to 0.001 LU)
pub livestock_units: f64,
/// total / area, rounded half up to 0.01 LU/ha
pub units_per_hectare: f64,
/// one per animals entry, in order
pub lines: Vec<LivestockUnitLine>,
}
Your code names it in one line, in the file that uses it
fune!(agri.stocking-rate@^1); // then call stocking_rate(…)
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::agri_stocking_rate_data::{LivestockUnitCoefficient, LIVESTOCK_UNITS}; ← this capability’s own data, compiled from data/livestock-units.json into the same file by fune build
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_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::math_round_float::round_float; ← from math.round-float ^1.0.0 · built alongside by fune
const MAX_COUNT: i64 = 10_000_000;
/// Livestock units and the stocking rate per hectare.
///
/// Coefficients are held in thousandths of a livestock unit, so a herd's total
/// is an exact integer and only the rate per hectare is rounded. Adding float
/// coefficients drifts: 3 x 0.7 is 2.0999999999999996, which puts 3 yearlings
/// on 12 ha at 0.17 LU/ha instead of 0.18 (0.175 rounded half up).
///
/// # Panics
/// Panics on an unknown scheme or category, a count out of range, or an area
/// that is not positive.
pub fn stocking_rate(scheme: &str, animals: &[LivestockCount], area_hectares: f64) -> StockingRate {
let table: Vec<&LivestockUnitCoefficient> = LIVESTOCK_UNITS.iter().filter(|r| r.scheme == scheme).collect();
if table.is_empty() {
panic!("unknown scheme \"{}\": use eurostat or uk-countryside-stewardship", scheme);
}
if !area_hectares.is_finite() || area_hectares <= 0.0 || area_hectares > 1_000_000.0 {
panic!("areaHectares must be a finite number greater than 0 and at most 1000000, received {}", area_hectares);
}
let area = (round_float(area_hectares, 4) * 10000.0).round() as i64;
if area < 1 {
panic!("areaHectares must be at least 0.0001 hectares, received {}", area_hectares);
}
let mut total: i64 = 0;
let mut lines: Vec<LivestockUnitLine> = Vec::with_capacity(animals.len());
for (i, animal) in animals.iter().enumerate() {
let row = match table.iter().find(|r| r.category == animal.category) {
Some(r) => r,
None => panic!("unknown category \"{}\" in scheme {}", animal.category, scheme),
};
if animal.count < 0 || animal.count > MAX_COUNT {
panic!("animal {}: count must be a whole number from 0 to 10000000, received {}", i + 1, animal.count);
}
let thousandths = animal.count * row.thousandths;
total += thousandths;
lines.push(LivestockUnitLine {
category: animal.category.clone(),
count: animal.count,
coefficient: row.thousandths as f64 / 1000.0,
livestock_units: thousandths as f64 / 1000.0,
});
}
// LU/ha in hundredths = (total / 1000) / (area / 10000) x 100.
StockingRate {
livestock_units: total as f64 / 1000.0,
units_per_hectare: round_div(total * 1000, area, "half-up") as f64 / 100.0,
lines,
}
}
pub fn stocking_rate_to_value(s: &StockingRate) -> Value {
let lines = s
.lines
.iter()
.map(|l| {
Value::obj(vec![
("category", Value::str(&l.category)),
("count", Value::Int(l.count)),
("coefficient", Value::Float(l.coefficient)),
("livestockUnits", Value::Float(l.livestock_units)),
])
})
.collect();
Value::obj(vec![
("livestockUnits", Value::Float(s.livestock_units)),
("unitsPerHectare", Value::Float(s.units_per_hectare)),
("lines", Value::Arr(lines)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let animals: Vec<LivestockCount> = args[1]
.as_arr()
.iter()
.enumerate()
.map(|(i, v)| {
let count = match v.get("count") {
Value::Int(n) => *n,
other => panic!("animal {}: count must be a whole number from 0 to 10000000, received {:?}", i + 1, other),
};
LivestockCount {
category: v.get("category").as_str().to_string(),
count,
}
})
.collect();
stocking_rate_to_value(&stocking_rate(args[0].as_str(), &animals, args[2].as_f64()))
}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.stocking-rate
The manifest, vectors and README with only the Rust implementation. Install it without the registry with fune add ./agri.stocking-rate-1.0.1-rust.fune, or fetch it from a terminal with fune pull agri.stocking-rate@1.0.1:rust.
The whole function, every language, is one file too: agri.stocking-rate-1.0.1.fune, 29,126 bytes, sha256 8445c31ba2f5ec6cb65840b2379f4ef85f0522b532fb24eeb6e45dd5e6b37c21. 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.stocking-rate
after — your function gets the result and the arguments, and returns the final result.
// fune: after agri.stocking-rate
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-div in agri.stocking-rate
// fune: replace math.round-float in agri.stocking-rate
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.stocking-rate --steps.
// fune: step agri.stocking-rate 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.
| Case | Arguments | Expected | |
|---|---|---|---|
| Eurostat: 100 dairy cows and 80 yearlings on 120 ha is 156 LU, 1.3 LU/ha | eurostat, animals ×2, 120 | → | livestock units 156, units per hectare 1.3, lines ×2 |
| Countryside Stewardship: 300 lowland ewes with lambs and 20 suckler cows on 40 ha | uk-countryside-stewardship, animals ×2, 40 | → | livestock units 56, units per hectare 1.4, lines ×2 |
| 16,000 laying hens on 3 ha is 224 LU, 74.67 LU/ha | eurostat, animals ×1, 3 | → | livestock units 224, units per hectare 74.67, lines ×1 |
| piglets at 0.027 LU each | eurostat, animals ×1, 10 | → | livestock units 27, units per hectare 2.7, lines ×1 |
| 3 yearlings on 12 ha is 0.175, rounded up to 0.18, where float coefficients say 0.17 | eurostat, animals ×1, 12 | → | livestock units 2.1, units per hectare 0.18, lines ×1 |
| horses and ponies under Countryside Stewardship | uk-countryside-stewardship, animals ×2, 5.5 | → | livestock units 4.4, units per hectare 0.8, lines ×2 |
| the two tables differ: a goat is 0.1 LU in Eurostat, 0.12 in Countryside Stewardship | eurostat, animals ×1, 10 | → | livestock units 5, units per hectare 0.5, lines ×1 |
| hill ewes on rough grazing, a rate below 0.005 rounds to zero | uk-countryside-stewardship, animals ×1, 100 | → | livestock units 0.4, units per hectare 0, lines ×1 |
| no animals is zero | eurostat, , 10 | → | livestock units 0, units per hectare 0, lines |
| a count of zero keeps its line | eurostat, animals ×1, 1 | → | livestock units 0, units per hectare 0, lines ×1 |
Show the other 7 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| the smallest area, one square metre | eurostat, animals ×1, 0 | → | livestock units 0.1, units per hectare 1,000, lines ×1 |
| an unknown scheme is an error | defra, , 10 | → | error: unknown scheme "defra" |
| a category from the other table is an error | eurostat, animals ×1, 10 | → | error: unknown category "lowland-ewe-and-lamb" in scheme eurostat |
| a negative count is an error | eurostat, animals ×1, 10 | → | error: animal 1: count must be a whole number from 0 to 10000000 |
| a fractional count is an error | eurostat, animals ×1, 10 | → | error: animal 1: count must be a whole number from 0 to 10000000 |
| zero area is an error | eurostat, , 0 | → | error: areaHectares must be a finite number greater than 0 |
| an area below a square metre is an error | eurostat, , 0 | → | error: areaHectares must be at least 0.0001 hectares |
More from the author
A livestock unit is a convention, not a measurement, and the published tables disagree (a goat is 0.1 LU to Eurostat and 0.12 LU to Countryside Stewardship), so the caller names the scheme and each category belongs to one:
- `eurostat`: the EU livestock unit (LSU) coefficients used in farm statistics, from the reference of a dairy cow = 1 LSU. 21 categories: bovine animals by age and type, sheep, goats, equidae, pigs, poultry, breeding rabbits. - `uk-countryside-stewardship`: the grazing livestock unit table Defra and the RPA use to set and check stocking in England's Countryside Stewardship agreements: cattle over 2 years 1.0, 6 months to 2 years 0.6, lowland ewe and lamb or ram 0.12, hill ewe and lamb, store lamb or hogg/teg 0.08, horse 1.0, pony or donkey 0.8, goat 0.12. Cattle under 6 months are not in the table, so there is no category for them.
Category codes are listed in `data/livestock-units.json`, with each row's full name and source. The data has no `effective` dates: these are reference conventions rather than rules with commencement dates, and a revised table will be published as a new version of this capability.
## Exactness
Coefficients are held in thousandths of an LU, so each line and the total are exact (0.027 LU piglets included) and only the rate per hectare is rounded, once, half up to 0.01 LU/ha. The area is taken to 0.0001 ha (1 m²). Adding float coefficients drifts: 3 x 0.7 is 2.0999999999999996, which puts three yearlings on 12 ha at 0.17 LU/ha rather than 0.18.
It does not judge whether a rate is too high: limits differ by scheme option, land type and season.
## Limits
Counts are whole numbers from 0 to 10,000,000; area more than 0 and at most 1,000,000 ha, and at least 0.0001 ha after rounding. An empty list is 0 LU.
## Sources
- Eurostat, "Glossary: Livestock unit (LSU)", https://ec.europa.eu/eurostat/statistics-explained/index.php?title=Glossary:Livestock_unit_(LSU) (coefficients checked against the page, September 2026). - Defra and Rural Payments Agency, "Countryside Stewardship Higher Tier manual for agreements starting on 1 January 2023", Annex 8C "Convert livestock numbers into Livestock Units", https://www.gov.uk/government/publications/countryside-stewardship-higher-tier-manual-for-agreements-starting-on-1-january-2023/annex-8c-convert-livestock-numbers-into-livestock-units (the same table is Annex 6C of the Mid Tier manual).
## Notices
Contains public sector information licensed under the Open Government Licence v3.0 (https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/).
Source: Eurostat; Defra and the Rural Payments Agency.
1.0.1 adds its attribution notices (NOTICE). The code and the tests are unchanged.
Files
| Path | Bytes |
|---|---|
| NOTICE | 237 |
| README.md | 3,102 |
| data/livestock-units.json | 6,901 |
| impl/python.py | 2,603 |
| impl/rust.rs | 3,873 |
| impl/typescript.ts | 2,336 |
| vectors.json | 4,674 |