Functional Weave
Code in Python

professional.utilisation@1.0.0

impl/rust.rs

2,787 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_amount::{money_from_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune

fn ratio(numerator: i64, denominator: i64) -> Option<i64> {
    // A ratio over nothing is not 0% or 100%: None says there was nothing to realise.
    if denominator == 0 {
        None
    } else {
        Some(round_div(numerator * 10000, denominator, "half-up"))
    }
}

fn check_minutes(name: &str, value: i64) {
    if value < 0 {
        panic!("{} must be a non-negative integer, received {}", name, value);
    }
}

/// Utilisation and billing, collection and overall realisation, in basis points.
///
/// # Panics
/// Panics on negative minutes, zero available time, negative amounts or mixed currencies.
pub fn utilisation_rates(
    billable_minutes: i64,
    available_minutes: i64,
    standard_value: &Money,
    billed_value: &Money,
    collected_value: &Money,
) -> UtilisationRates {
    check_minutes("billableMinutes", billable_minutes);
    check_minutes("availableMinutes", available_minutes);
    if available_minutes == 0 {
        panic!("availableMinutes must be greater than zero");
    }
    for (name, amount) in [("standardValue", standard_value), ("billedValue", billed_value), ("collectedValue", collected_value)] {
        if amount.currency != standard_value.currency {
            panic!("currency mismatch: {} and {}", standard_value.currency, amount.currency);
        }
        if amount.minor < 0 {
            panic!("{} must not be negative, received {}", name, amount.minor);
        }
    }
    UtilisationRates {
        utilisation_basis_points: round_div(billable_minutes * 10000, available_minutes, "half-up"),
        billing_realisation_basis_points: ratio(billed_value.minor, standard_value.minor),
        collection_realisation_basis_points: ratio(collected_value.minor, billed_value.minor),
        overall_realisation_basis_points: ratio(collected_value.minor, standard_value.minor),
    }
}

fn optional_int(value: Option<i64>) -> Value {
    value.map_or(Value::Null, Value::Int)
}

pub fn utilisation_rates_to_value(r: &UtilisationRates) -> Value {
    Value::obj(vec![
        ("utilisationBasisPoints", Value::Int(r.utilisation_basis_points)),
        ("billingRealisationBasisPoints", optional_int(r.billing_realisation_basis_points)),
        ("collectionRealisationBasisPoints", optional_int(r.collection_realisation_basis_points)),
        ("overallRealisationBasisPoints", optional_int(r.overall_realisation_basis_points)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    utilisation_rates_to_value(&utilisation_rates(
        args[0].as_i64(),
        args[1].as_i64(),
        &money_from_value(&args[2]),
        &money_from_value(&args[3]),
        &money_from_value(&args[4]),
    ))
}