Functional Weave
Code in TypeScript

agri.gross-margin@1.0.0

impl/rust.rs

3,776 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::money_add::subtract_money;  ← from money.add ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune
use super::money_sum::sum_money;  ← from money.sum ^1.0.0 · built alongside by fune

const MAX_MINOR: i64 = 900_000_000_000;

/// Gross margin: enterprise output less its variable costs, per hectare or head.
///
/// Each per-unit figure is its own total divided once, half up. Taking output
/// per head less costs per head instead can be a penny out: 3 head with 10000
/// output and 5000 costs is 3333 - 1667 = 1666 that way, but 5000 / 3 = 1667.
///
/// # Panics
/// Panics on a bad basis or unit count, mixed currencies, or totals too large.
pub fn gross_margin(outputs: &[MarginLine], variable_costs: &[MarginLine], per: &str, units: f64, currency: &str) -> GrossMargin {
    let (divisor, scale) = match per {
        "hectare" => {
            if !units.is_finite() || units <= 0.0 || units > 10_000_000.0 {
                panic!("units must be a finite number of hectares greater than 0 and at most 10000000, received {}", units);
            }
            let d = (units * 10000.0 + 0.5).floor() as i64;
            if d < 1 {
                panic!("units must be at least 0.0001 hectares, received {}", units);
            }
            (d, 10000)
        }
        "head" => {
            if units.fract() != 0.0 || units < 1.0 || units > 10_000_000.0 {
                panic!("units must be a whole number of head from 1 to 10000000, received {}", units);
            }
            (units as i64, 1)
        }
        other => panic!("unknown basis \"{}\": use hectare or head", other),
    };

    let output_amounts: Vec<Money> = outputs.iter().map(|l| l.amount.clone()).collect();
    let cost_amounts: Vec<Money> = variable_costs.iter().map(|l| l.amount.clone()).collect();
    let output = sum_money(&output_amounts, currency);
    let costs = sum_money(&cost_amounts, currency);
    let margin = subtract_money(&output, &costs);
    for m in [&output, &costs, &margin] {
        if m.minor.abs() > MAX_MINOR {
            panic!("totals must be within 900000000000 minor units either side of zero");
        }
    }
    let per_unit = |m: &Money| money(round_div(m.minor * scale, divisor, "half-up"), currency);
    GrossMargin {
        output_per_unit: per_unit(&output),
        variable_costs_per_unit: per_unit(&costs),
        gross_margin_per_unit: per_unit(&margin),
        margin_basis_points: if output.minor == 0 {
            None
        } else {
            Some(round_div(margin.minor * 10000, output.minor, "half-up"))
        },
        output,
        variable_costs: costs,
        gross_margin: margin,
    }
}

pub fn margin_line_from_value(v: &Value) -> MarginLine {
    MarginLine {
        name: v.get("name").as_str().to_string(),
        amount: money_from_value(v.get("amount")),
    }
}

pub fn gross_margin_to_value(g: &GrossMargin) -> Value {
    Value::obj(vec![
        ("output", money_to_value(&g.output)),
        ("variableCosts", money_to_value(&g.variable_costs)),
        ("grossMargin", money_to_value(&g.gross_margin)),
        ("outputPerUnit", money_to_value(&g.output_per_unit)),
        ("variableCostsPerUnit", money_to_value(&g.variable_costs_per_unit)),
        ("grossMarginPerUnit", money_to_value(&g.gross_margin_per_unit)),
        ("marginBasisPoints", g.margin_basis_points.map_or(Value::Null, Value::Int)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let outputs: Vec<MarginLine> = args[0].as_arr().iter().map(margin_line_from_value).collect();
    let costs: Vec<MarginLine> = args[1].as_arr().iter().map(margin_line_from_value).collect();
    gross_margin_to_value(&gross_margin(&outputs, &costs, args[2].as_str(), args[3].as_f64(), args[4].as_str()))
}