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::inventory_safety_stock_data::Z_SCORES; ← this capability’s own data, compiled from data/z-scores.json into the same file by fune build
fn amount(name: &str, value: Option<f64>, method: &str) -> f64 {
match value {
None => panic!("{} needs {}", method, name),
Some(v) if !v.is_finite() || v < 0.0 => {
panic!("{} must be a finite number, not negative, received {}", name, v)
}
Some(v) => v,
}
}
// Six decimal places, half away from zero, then up to whole units: float noise
// such as 55.00000000000001 must not cost a whole extra unit.
fn whole_units_up(x: f64) -> i64 {
let y = x * 1e6;
let mut micro = y.floor();
if y - micro >= 0.5 {
micro += 1.0;
}
(micro as i64 + 999_999).div_euclid(1_000_000)
}
/// Safety stock in whole units, by the service-level or max-minus-average method.
///
/// # Panics
/// Panics on an unknown method or service level, a missing or negative input,
/// or a worst case below the average case.
pub fn safety_stock(input: &SafetyStockInput) -> i64 {
let method = input.method.as_str();
let lead_time = amount("leadTime", Some(input.lead_time), method);
match method {
"service-level" => {
let level = match input.service_level_basis_points {
Some(l) => l,
None => panic!("service-level needs serviceLevelBasisPoints"),
};
let row = match Z_SCORES.iter().find(|r| r.service_level_basis_points == level) {
Some(r) => r,
None => panic!(
"no z-score for a service level of {} basis points: use one of {}",
level,
Z_SCORES
.iter()
.map(|r| r.service_level_basis_points.to_string())
.collect::<Vec<_>>()
.join(", ")
),
};
let sd = amount("demandStdDev", input.demand_std_dev, method);
let mut variance = lead_time * sd * sd;
let sl = amount("leadTimeStdDev", Some(input.lead_time_std_dev.unwrap_or(0.0)), method);
if sl > 0.0 {
let d = amount("averageDemand", input.average_demand, "service-level with a varying lead time");
variance = variance + d * d * sl * sl;
}
whole_units_up((row.z_ten_thousandths as f64 / 10000.0) * variance.sqrt())
}
"max-minus-average" => {
let max_demand = amount("maxDemand", input.max_demand, method);
let max_lead_time = amount("maxLeadTime", input.max_lead_time, method);
let average_demand = amount("averageDemand", input.average_demand, method);
let worst = max_demand * max_lead_time;
let usual = average_demand * lead_time;
if worst < usual {
panic!(
"maxDemand x maxLeadTime ({}) must not be less than averageDemand x leadTime ({})",
worst, usual
);
}
whole_units_up(worst - usual)
}
other => panic!("unknown safety stock method \"{}\"", other),
}
}
fn optional_f64(v: &Value) -> Option<f64> {
if v.is_null() {
None
} else {
Some(v.as_f64())
}
}
pub fn safety_stock_input_from_value(v: &Value) -> SafetyStockInput {
let level = v.get("serviceLevelBasisPoints");
SafetyStockInput {
method: v.get("method").as_str().to_string(),
service_level_basis_points: if level.is_null() { None } else { Some(level.as_i64()) },
demand_std_dev: optional_f64(v.get("demandStdDev")),
lead_time: v.get("leadTime").as_f64(),
average_demand: optional_f64(v.get("averageDemand")),
lead_time_std_dev: optional_f64(v.get("leadTimeStdDev")),
max_demand: optional_f64(v.get("maxDemand")),
max_lead_time: optional_f64(v.get("maxLeadTime")),
}
}
pub fn fune_vector(args: &[Value]) -> Value {
Value::Int(safety_stock(&safety_stock_input_from_value(&args[0])))
}