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(÷_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(¢res, &loads).iter().map(capacity_load_to_value).collect())
}