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()))
}