///|
/// Fleet-level planning metrics for engineering and SaaS operations.
pub struct FleetPlan {
fleet_size : Int
horizon : Double
expected_failures : Double
spare_units : Int
stockout_probability : Double
expected_downtime : Double
}
///|
pub fn fleet_plan(
fleet_size~ : Int,
horizon~ : Double,
expected_failures~ : Double,
spare_units~ : Int,
stockout_probability~ : Double,
expected_downtime~ : Double,
) -> FleetPlan {
{
fleet_size,
horizon,
expected_failures,
spare_units,
stockout_probability,
expected_downtime,
}
}
///|
pub fn expected_fleet_failures(
model : ReliabilityModel,
fleet_size : Int,
horizon : Double,
) -> Double {
if fleet_size < 0 || horizon < 0.0 {
abort("invalid fleet dimensions")
}
fleet_size.to_double() * model.cdf(horizon)
}
///|
pub fn spare_stockout_probability(
expected_failures : Double,
spare_units : Int,
) -> Double {
if expected_failures < 0.0 || spare_units < 0 {
abort("invalid spare-stock arguments")
}
let mut cumulative = 0.0
for failures in 0..<=spare_units {
cumulative += @math.exp(-expected_failures) *
@math.pow(expected_failures, failures.to_double()) /
factorial(failures)
}
1.0 - cumulative
}
///|
pub fn plan_fleet(
model : ReliabilityModel,
fleet_size : Int,
horizon : Double,
target_stockout_probability : Double,
repair_duration : Double,
) -> FleetPlan {
let expected = expected_fleet_failures(model, fleet_size, horizon)
let mut spares = 0
while spare_stockout_probability(expected, spares) >
target_stockout_probability &&
spares < fleet_size * 2 + 100 {
spares += 1
}
let stockout = spare_stockout_probability(expected, spares)
fleet_plan(
fleet_size~,
horizon~,
expected_failures=expected,
spare_units=spares,
stockout_probability=stockout,
expected_downtime=expected * repair_duration,
)
}
///|
pub fn fleet_reliability(
model : ReliabilityModel,
fleet_size : Int,
horizon : Double,
required_units : Int,
) -> Double {
if required_units <= 0 || required_units > fleet_size {
abort("invalid fleet requirement")
}
k_out_of_n_reliability(required_units, model.survival(horizon), fleet_size)
}
///|
pub fn service_capacity(
arrival_rate : Double,
average_service_time : Double,
target_utilization : Double,
) -> Int {
if arrival_rate < 0.0 ||
average_service_time <= 0.0 ||
target_utilization <= 0.0 ||
target_utilization >= 1.0 {
abort("invalid capacity inputs")
}
(arrival_rate * average_service_time / target_utilization)
.ceil()
.to_int()
.max(1)
}
///|
pub fn expected_queue_wait(
arrival_rate : Double,
service_rate : Double,
servers : Int,
) -> Double {
if servers <= 0 || service_rate <= arrival_rate / servers.to_double() {
return 1.0e300
}
1.0 / (servers.to_double() * service_rate - arrival_rate)
}
///|
pub fn service_level(
model : ReliabilityModel,
response_time : Double,
target : Double,
) -> MetricEstimate {
let achieved = model.survival(response_time)
metric_estimate(
estimate=achieved,
lower=achieved,
upper=achieved,
confidence_level=target,
)
}