///|
/// Fleet-level state derived from reliability, services, and inventory.
pub(all) enum FleetHealthState {
FleetHealthy
FleetDegraded
FleetCritical
FleetEmpty
} derive(Debug, Eq)
///|
/// A weighted health contribution from one subsystem.
pub(all) struct HealthContribution {
source : String
score : Int
weight : Int
detail : String
}
///|
pub fn HealthContribution::new(
source : String,
score : Int,
weight : Int,
detail : String,
) -> Result[HealthContribution, ModbusError] {
if source == "" || score < 0 || score > 100 || weight < 1 || detail == "" {
Err(InvalidData)
} else {
Ok({ source, score, weight, detail })
}
}
///|
pub fn HealthContribution::source(self : HealthContribution) -> String {
self.source
}
///|
pub fn HealthContribution::score(self : HealthContribution) -> Int {
self.score
}
///|
pub fn HealthContribution::weight(self : HealthContribution) -> Int {
self.weight
}
///|
pub fn HealthContribution::detail(self : HealthContribution) -> String {
self.detail
}
///|
/// Weighted score calculation used by operator dashboards.
pub(all) struct FleetHealthRollup {
contributions : Array[HealthContribution]
mut score : Int
mut state : FleetHealthState
timestamp : Int
}
///|
pub fn FleetHealthRollup::new(
timestamp : Int,
) -> Result[FleetHealthRollup, ModbusError] {
if timestamp < 0 {
Err(InvalidData)
} else {
Ok({ contributions: [], score: 0, state: FleetEmpty, timestamp })
}
}
///|
pub fn FleetHealthRollup::timestamp(self : FleetHealthRollup) -> Int {
self.timestamp
}
///|
pub fn FleetHealthRollup::score(self : FleetHealthRollup) -> Int {
self.score
}
///|
pub fn FleetHealthRollup::state(self : FleetHealthRollup) -> FleetHealthState {
self.state
}
///|
pub fn FleetHealthRollup::contribution_count(self : FleetHealthRollup) -> Int {
self.contributions.length()
}
///|
pub fn FleetHealthRollup::add(
self : FleetHealthRollup,
contribution : HealthContribution,
) -> Result[Unit, ModbusError] {
for existing in self.contributions {
if existing.source() == contribution.source() {
return Err(InvalidData)
}
}
self.contributions.push(contribution)
self.recalculate()
Ok(())
}
///|
pub fn FleetHealthRollup::recalculate(self : FleetHealthRollup) -> Unit {
if self.contributions.length() == 0 {
self.score = 0
self.state = FleetEmpty
return
}
let mut weighted = 0
let mut total_weight = 0
for contribution in self.contributions {
weighted += contribution.score() * contribution.weight()
total_weight += contribution.weight()
}
self.score = weighted / total_weight
self.state = if self.score >= 85 {
FleetHealthy
} else if self.score >= 60 {
FleetDegraded
} else {
FleetCritical
}
}
///|
pub fn FleetHealthRollup::contributions(
self : FleetHealthRollup,
) -> Array[HealthContribution] {
self.contributions
}
///|
pub fn FleetHealthRollup::contribution(
self : FleetHealthRollup,
source : String,
) -> HealthContribution? {
for contribution in self.contributions {
if contribution.source() == source {
return Some(contribution)
}
}
None
}
///|
pub fn FleetHealthRollup::remove(
self : FleetHealthRollup,
source : String,
) -> Result[HealthContribution, ModbusError] {
for i in 0.. String {
match state {
FleetHealthy => "healthy"
FleetDegraded => "degraded"
FleetCritical => "critical"
FleetEmpty => "empty"
}
}
///|
/// Generate contributions from a reliability fleet summary.
pub fn fleet_health_from_reliability(
timestamp : Int,
summary : ReliabilityFleetSummary,
) -> Result[FleetHealthRollup, ModbusError] {
let rollup = match FleetHealthRollup::new(timestamp) {
Ok(value) => value
Err(error) => return Err(error)
}
let reliability = match
HealthContribution::new(
"reliability",
summary.average_success_rate(),
5,
"success-rate",
) {
Ok(value) => value
Err(error) => return Err(error)
}
ignore(rollup.add(reliability))
let latency_score = if summary.average_latency() >= 1000 {
0
} else {
100 - summary.average_latency() / 10
}
let latency = match
HealthContribution::new("latency", latency_score, 2, "average-latency") {
Ok(value) => value
Err(error) => return Err(error)
}
ignore(rollup.add(latency))
Ok(rollup)
}
///|
/// Generate contributions from a service registry.
pub fn fleet_health_from_services(
rollup : FleetHealthRollup,
registry : DeviceServiceRegistry,
) -> Result[Unit, ModbusError] {
let total = registry.service_count()
if total == 0 {
return Ok(())
}
let running = registry.running().length()
let score = running * 100 / total
let contribution = match
HealthContribution::new("services", score, 3, "running-services") {
Ok(value) => value
Err(error) => return Err(error)
}
rollup.add(contribution)
}
///|
/// Generate a contribution from inventory reachability.
pub fn fleet_health_from_inventory(
rollup : FleetHealthRollup,
inventory : DeviceInventory,
now : Int,
timeout : Int,
) -> Result[Unit, ModbusError] {
let offline = match inventory.mark_stale(now, timeout) {
Ok(value) => value
Err(error) => return Err(error)
}
let total = inventory.count()
let score = if total == 0 { 0 } else { (total - offline) * 100 / total }
let contribution = match
HealthContribution::new("inventory", score, 4, "reachable-devices") {
Ok(value) => value
Err(error) => return Err(error)
}
rollup.add(contribution)
}
///|
/// A serializable health line for logs and support bundles.
pub fn FleetHealthRollup::export_lines(
self : FleetHealthRollup,
) -> Array[String] {
let lines : Array[String] = []
lines.push(
"fleet_state=" +
fleet_health_state_name(self.state) +
" score=" +
self.score.to_string() +
" timestamp=" +
self.timestamp.to_string(),
)
for contribution in self.contributions {
lines.push(
"health_source=" +
contribution.source() +
" score=" +
contribution.score().to_string() +
" weight=" +
contribution.weight().to_string() +
" detail=" +
contribution.detail(),
)
}
lines
}