Functional Weave
Code in Python

agri.seed-rate@1.0.0

impl/rust.rs

2,341 bytes · the Rust implementation · view 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_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

/// Seed rate (kg/ha) = target plants/m² x TGW (g) / expected establishment (%).
///
/// The TGW is taken to the centigram and the establishment is in basis points,
/// so kg/ha is exactly plants x centigrams / basis points and is rounded once.
/// Doing it in floats gets 100 x 1.15 / 100 = 1.1499999999999999, which rounds
/// to 1.1 kg/ha instead of 1.2.
///
/// # Panics
/// Panics when an argument is out of range.
pub fn seed_rate(target_plants_per_square_metre: i64, thousand_grain_weight_grams: f64, establishment_basis_points: i64) -> SeedRate {
    let plants = target_plants_per_square_metre;
    if !(1..=100000).contains(&plants) {
        panic!("targetPlantsPerSquareMetre must be a whole number from 1 to 100000, received {}", plants);
    }
    let tgw = thousand_grain_weight_grams;
    if !tgw.is_finite() || tgw <= 0.0 || tgw > 1000.0 {
        panic!("thousandGrainWeightGrams must be a finite number greater than 0 and at most 1000, received {}", tgw);
    }
    let centigrams = (round_float(tgw, 2) * 100.0).round() as i64;
    if centigrams < 1 {
        panic!("thousandGrainWeightGrams must be at least 0.01 grams, received {}", tgw);
    }
    let bp = establishment_basis_points;
    if !(1..=10000).contains(&bp) {
        panic!("establishmentBasisPoints must be a whole number from 1 to 10000, received {}", bp);
    }
    SeedRate {
        kg_per_hectare: round_div(plants * centigrams * 10, bp, "half-up") as f64 / 10.0,
        seeds_per_square_metre: round_div(plants * 100000, bp, "half-up") as f64 / 10.0,
    }
}

pub fn seed_rate_to_value(s: &SeedRate) -> Value {
    Value::obj(vec![
        ("kgPerHectare", Value::Float(s.kg_per_hectare)),
        ("seedsPerSquareMetre", Value::Float(s.seeds_per_square_metre)),
    ])
}

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

pub fn fune_vector(args: &[Value]) -> Value {
    let plants = whole(&args[0], "targetPlantsPerSquareMetre must be a whole number from 1 to 100000");
    let bp = whole(&args[2], "establishmentBasisPoints must be a whole number from 1 to 10000");
    seed_rate_to_value(&seed_rate(plants, args[1].as_f64(), bp))
}