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