Functional Weave
Code in Rust

manufacturing.capacity@1.0.0

impl/rust.rs

5,520 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::{compare_rational, divide_rational, multiply_rational, rational, rational_to_integer};  ← from math.rational ^1.0.0 · built alongside by fune

const MAX_SAFE: i64 = (1i64 << 53) - 1;

fn minutes(what: &str, value: i64) {
    if !(0..=MAX_SAFE).contains(&value) {
        panic!("{} must be a whole number of minutes, not negative, received {}", what, value);
    }
}

/// Capacity, load and overload per work centre. Capacity is kept as an exact
/// fraction, so a load a fraction of a minute over it is still an overload.
///
/// # Panics
/// Panics on a duplicate or unknown work centre, negative times, negative
/// efficiency, or utilisation outside 0-10000.
pub fn work_centre_load(work_centres: &[WorkCentre], loads: &[RoutedLoad]) -> Vec<CapacityLoad> {
    let mut totals: Vec<(&str, i64)> = Vec::new();
    for wc in work_centres {
        if totals.iter().any(|(id, _)| *id == wc.id) {
            panic!("duplicate work centre \"{}\"", wc.id);
        }
        minutes(&format!("availableMinutes of \"{}\"", wc.id), wc.available_minutes);
        if !(0..=MAX_SAFE).contains(&wc.efficiency_basis_points) {
            panic!(
                "efficiencyBasisPoints of \"{}\" must be a whole number, not negative, received {}",
                wc.id, wc.efficiency_basis_points
            );
        }
        if !(0..=10000).contains(&wc.utilisation_basis_points) {
            panic!(
                "utilisationBasisPoints of \"{}\" must be a whole number from 0 to 10000, received {}",
                wc.id, wc.utilisation_basis_points
            );
        }
        totals.push((&wc.id, 0));
    }
    for load in loads {
        let slot = match totals.iter_mut().find(|(id, _)| *id == load.work_centre) {
            Some(slot) => slot,
            None => panic!("a load names work centre \"{}\", which is not in the list", load.work_centre),
        };
        minutes(&format!("a load on \"{}\"", load.work_centre), load.minutes);
        let sum = slot.1 + load.minutes;
        if sum > MAX_SAFE {
            panic!("the load on \"{}\" exceeds 2^53 - 1 minutes", load.work_centre);
        }
        slot.1 = sum;
    }
    work_centres
        .iter()
        .zip(totals.iter())
        .map(|(wc, (_, load_minutes))| {
            let capacity = multiply_rational(
                &rational(wc.available_minutes, 1),
                &multiply_rational(
                    &rational(wc.efficiency_basis_points, 10000),
                    &rational(wc.utilisation_basis_points, 10000),
                ),
            );
            let load = rational(*load_minutes, 1);
            let capacity_minutes = rational_to_integer(&capacity, "down");
            CapacityLoad {
                work_centre: wc.id.clone(),
                capacity_minutes,
                load_minutes: *load_minutes,
                spare_minutes: capacity_minutes - load_minutes,
                load_basis_points: if capacity.numerator == 0 {
                    None
                } else {
                    Some(rational_to_integer(
                        &multiply_rational(&divide_rational(&load, &capacity), &rational(10000, 1)),
                        "half-up",
                    ))
                },
                overloaded: compare_rational(&load, &capacity) > 0,
            }
        })
        .collect()
}

pub fn capacity_load_to_value(c: &CapacityLoad) -> Value {
    Value::obj(vec![
        ("workCentre", Value::str(&c.work_centre)),
        ("capacityMinutes", Value::Int(c.capacity_minutes)),
        ("loadMinutes", Value::Int(c.load_minutes)),
        ("spareMinutes", Value::Int(c.spare_minutes)),
        ("loadBasisPoints", c.load_basis_points.map(Value::Int).unwrap_or(Value::Null)),
        ("overloaded", Value::Bool(c.overloaded)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let centres: Vec<WorkCentre> = args[0]
        .as_arr()
        .iter()
        .map(|v| {
            let id = v.get("id").as_str();
            if let Value::Float(f) = v.get("availableMinutes") {
                panic!("availableMinutes of \"{}\" must be a whole number of minutes, not negative, received {}", id, f);
            }
            if let Value::Float(f) = v.get("efficiencyBasisPoints") {
                panic!("efficiencyBasisPoints of \"{}\" must be a whole number, not negative, received {}", id, f);
            }
            if let Value::Float(f) = v.get("utilisationBasisPoints") {
                panic!("utilisationBasisPoints of \"{}\" must be a whole number from 0 to 10000, received {}", id, f);
            }
            WorkCentre {
                id: id.to_string(),
                available_minutes: v.get("availableMinutes").as_i64(),
                efficiency_basis_points: v.get("efficiencyBasisPoints").as_i64(),
                utilisation_basis_points: v.get("utilisationBasisPoints").as_i64(),
            }
        })
        .collect();
    let loads: Vec<RoutedLoad> = args[1]
        .as_arr()
        .iter()
        .map(|v| {
            let wc = v.get("workCentre").as_str();
            if let Value::Float(f) = v.get("minutes") {
                panic!("a load on \"{}\" must be a whole number of minutes, not negative, received {}", wc, f);
            }
            RoutedLoad {
                work_centre: wc.to_string(),
                minutes: v.get("minutes").as_i64(),
            }
        })
        .collect();
    Value::Arr(work_centre_load(&centres, &loads).iter().map(capacity_load_to_value).collect())
}