Functional Weave
Code in Rust

manufacturing.variance@1.0.0

impl/rust.rs

5,042 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_rational::{multiply_rational, rational, rational_from_value, rational_to_integer, Rational};  ← from math.rational ^1.0.0 · built alongside by fune
use super::money_amount::{assert_same_currency, money, money_from_value, money_to_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune

fn not_negative(name: &str, value: &Rational) -> Rational {
    let r = rational(value.numerator, value.denominator);
    if r.numerator < 0 {
        panic!("{} must not be negative", name);
    }
    r
}

fn cost_of(name: &str, value: &Money, unit: &Money) -> i64 {
    assert_same_currency(unit, value);
    if value.minor < 0 {
        panic!("{} must not be negative, received {}", name, value.minor);
    }
    value.minor
}

fn variance(signed: i64, currency: &str) -> Variance {
    let effect = if signed > 0 {
        "adverse"
    } else if signed < 0 {
        "favourable"
    } else {
        "none"
    };
    Variance {
        amount: money(signed.abs(), currency),
        effect: effect.to_string(),
    }
}

/// Material price and usage, labour rate and efficiency variances, flexed to
/// actual output. The four flexed costs are each rounded once and every
/// variance is a difference of them, so the variances always sum exactly.
///
/// # Panics
/// Panics on negative output, prices, rates, quantities, hours or costs, mixed
/// currencies, or an unknown rounding mode.
pub fn cost_variances(material: &MaterialActual, labour: &LabourActual, actual_output: i64, mode: &str) -> CostVariances {
    if !(0..=(1i64 << 53) - 1).contains(&actual_output) {
        panic!("actualOutput must be a whole number, not negative, received {}", actual_output);
    }
    let unit = &material.standard_price;
    let currency = unit.currency.as_str();
    let sp = cost_of("material standardPrice", &material.standard_price, unit);
    let mat_actual = cost_of("material actualCost", &material.actual_cost, unit);
    let sr = cost_of("labour standardRate", &labour.standard_rate, unit);
    let lab_actual = cost_of("labour actualCost", &labour.actual_cost, unit);
    let sq = not_negative("material standardQuantity", &material.standard_quantity);
    let aq = not_negative("material actualQuantity", &material.actual_quantity);
    let sh = not_negative("labour standardHours", &labour.standard_hours);
    let ah = not_negative("labour actualHours", &labour.actual_hours);
    let output = rational(actual_output, 1);

    let actual_at_standard = rational_to_integer(&multiply_rational(&aq, &rational(sp, 1)), mode);
    let standard_material = rational_to_integer(&multiply_rational(&multiply_rational(&sq, &output), &rational(sp, 1)), mode);
    let hours_at_standard = rational_to_integer(&multiply_rational(&ah, &rational(sr, 1)), mode);
    let standard_labour = rational_to_integer(&multiply_rational(&multiply_rational(&sh, &output), &rational(sr, 1)), mode);

    let standard_cost = standard_material + standard_labour;
    let actual_cost = mat_actual + lab_actual;
    CostVariances {
        material_price: variance(mat_actual - actual_at_standard, currency),
        material_usage: variance(actual_at_standard - standard_material, currency),
        labour_rate: variance(lab_actual - hours_at_standard, currency),
        labour_efficiency: variance(hours_at_standard - standard_labour, currency),
        total: variance(actual_cost - standard_cost, currency),
        standard_cost: money(standard_cost, currency),
        actual_cost: money(actual_cost, currency),
    }
}

fn variance_to_value(v: &Variance) -> Value {
    Value::obj(vec![("amount", money_to_value(&v.amount)), ("effect", Value::str(&v.effect))])
}

pub fn cost_variances_to_value(c: &CostVariances) -> Value {
    Value::obj(vec![
        ("materialPrice", variance_to_value(&c.material_price)),
        ("materialUsage", variance_to_value(&c.material_usage)),
        ("labourRate", variance_to_value(&c.labour_rate)),
        ("labourEfficiency", variance_to_value(&c.labour_efficiency)),
        ("total", variance_to_value(&c.total)),
        ("standardCost", money_to_value(&c.standard_cost)),
        ("actualCost", money_to_value(&c.actual_cost)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let m = &args[0];
    let l = &args[1];
    if let Value::Float(f) = &args[2] {
        panic!("actualOutput must be a whole number, not negative, received {}", f);
    }
    let material = MaterialActual {
        standard_price: money_from_value(m.get("standardPrice")),
        standard_quantity: rational_from_value(m.get("standardQuantity")),
        actual_quantity: rational_from_value(m.get("actualQuantity")),
        actual_cost: money_from_value(m.get("actualCost")),
    };
    let labour = LabourActual {
        standard_rate: money_from_value(l.get("standardRate")),
        standard_hours: rational_from_value(l.get("standardHours")),
        actual_hours: rational_from_value(l.get("actualHours")),
        actual_cost: money_from_value(l.get("actualCost")),
    };
    cost_variances_to_value(&cost_variances(&material, &labour, args[2].as_i64(), args[3].as_str()))
}