use super::helpers::discover_machines;
use super::status_intelligence_ext::filter_targets;
use crate::core::types;
use std::path::Path;
pub(super) fn print_error_rates(rates: &[(String, f64, usize, usize)], json: bool) {
if json {
let items: Vec<String> = rates
.iter()
.map(|(m, r, f, t)| {
format!(
"{{\"machine\":\"{m}\",\"error_rate\":{r:.4},\"failed\":{f},\"total\":{t}}}"
)
})
.collect();
println!("{{\"error_rates\":[{}]}}", items.join(","));
} else if rates.is_empty() {
println!("No error rate data available.");
} else {
println!("Error rates:");
for (m, r, f, t) in rates {
println!(" {} — {:.1}% ({}/{} failed)", m, r * 100.0, f, t);
}
}
}
pub(crate) fn cmd_status_machine_resource_convergence_gap(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets = filter_targets(&machines, machine);
let mut gaps: Vec<(String, f64, usize)> = Vec::new();
for m in &targets {
let path = sd.join(m).join("state.lock.yaml");
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let total = lock.resources.len();
if total == 0 {
continue;
}
let converged = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Converged)
.count();
let gap = 1.0 - (converged as f64 / total as f64);
gaps.push(((*m).clone(), gap, total));
}
gaps.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
print_convergence_gaps(&gaps, json);
Ok(())
}
fn print_convergence_gaps(gaps: &[(String, f64, usize)], json: bool) {
if json {
let items: Vec<String> = gaps
.iter()
.map(|(m, g, t)| format!("{{\"machine\":\"{m}\",\"gap\":{g:.4},\"total\":{t}}}"))
.collect();
println!("{{\"convergence_gaps\":[{}]}}", items.join(","));
} else if gaps.is_empty() {
println!("No convergence gap data available.");
} else {
println!("Convergence gap (0=fully converged):");
for (m, g, t) in gaps {
println!(" {} — {:.1}% gap ({} resources)", m, g * 100.0, t);
}
}
}
pub(crate) fn cmd_status_fleet_resource_error_distribution(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets = filter_targets(&machines, machine);
let mut dist: Vec<(String, usize, usize)> = Vec::new();
for m in &targets {
let path = sd.join(m).join("state.lock.yaml");
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let total = lock.resources.len();
let failed = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Failed)
.count();
if total > 0 {
dist.push(((*m).clone(), failed, total));
}
}
dist.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
print_error_distribution(&dist, json);
Ok(())
}
fn print_error_distribution(dist: &[(String, usize, usize)], json: bool) {
if json {
let items: Vec<String> = dist
.iter()
.map(|(m, f, t)| format!("{{\"machine\":\"{m}\",\"failed\":{f},\"total\":{t}}}"))
.collect();
println!("{{\"error_distribution\":[{}]}}", items.join(","));
} else if dist.is_empty() {
println!("No error distribution data available.");
} else {
println!("Error distribution:");
for (m, f, t) in dist {
let bar = "█".repeat((*f * 20).checked_div(*t).unwrap_or(0));
println!(" {m:20} {f:>3}/{t:<3} {bar}");
}
}
}
pub(crate) fn cmd_status_machine_resource_convergence_stability(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets = filter_targets(&machines, machine);
let mut stabilities: Vec<(String, f64)> = Vec::new();
for m in &targets {
let path = sd.join(m).join("state.lock.yaml");
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let total = lock.resources.len();
if total == 0 {
continue;
}
let converged = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Converged)
.count();
let rate = converged as f64 / total as f64;
stabilities.push(((*m).clone(), rate));
}
stabilities.sort_by(|a, b| a.0.cmp(&b.0));
print_convergence_stability(&stabilities, json);
Ok(())
}
fn print_convergence_stability(stabilities: &[(String, f64)], json: bool) {
let mean = if stabilities.is_empty() {
0.0
} else {
stabilities.iter().map(|(_, r)| r).sum::<f64>() / stabilities.len() as f64
};
let variance = if stabilities.len() < 2 {
0.0
} else {
stabilities
.iter()
.map(|(_, r)| (r - mean).powi(2))
.sum::<f64>()
/ stabilities.len() as f64
};
let stability = 1.0 - variance.sqrt();
if json {
println!("{{\"convergence_stability\":{{\"score\":{:.4},\"mean\":{:.4},\"variance\":{:.6},\"machines\":{}}}}}", stability, mean, variance, stabilities.len());
} else if stabilities.is_empty() {
println!("No convergence stability data available.");
} else {
println!(
"Convergence stability: {:.1}% (mean: {:.1}%, {} machines)",
stability * 100.0,
mean * 100.0,
stabilities.len()
);
}
}
pub(crate) fn cmd_status_machine_resource_apply_latency_p95(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets = filter_targets(&machines, machine);
let mut latencies: Vec<(String, f64, usize)> = Vec::new();
for m in &targets {
let path = sd.join(m).join("state.lock.yaml");
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let total = lock.resources.len();
if total == 0 {
continue;
}
let estimated_p95 = total as f64 * 0.2 * 1.5;
latencies.push(((*m).clone(), estimated_p95, total));
}
latencies.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
print_apply_latency_p95(&latencies, json);
Ok(())
}
fn print_apply_latency_p95(latencies: &[(String, f64, usize)], json: bool) {
if json {
let items: Vec<String> = latencies
.iter()
.map(|(m, l, t)| {
format!("{{\"machine\":\"{m}\",\"p95_latency_sec\":{l:.2},\"resources\":{t}}}")
})
.collect();
println!("{{\"apply_latency_p95\":[{}]}}", items.join(","));
} else if latencies.is_empty() {
println!("No apply latency data available.");
} else {
println!("Apply latency p95 (estimated):");
for (m, l, t) in latencies {
println!(" {m:20} {l:.1}s ({t} resources)");
}
}
}
pub(crate) fn cmd_status_fleet_resource_security_posture_score(
sd: &Path,
machine: Option<&str>,
json: bool,
) -> Result<(), String> {
let machines = discover_machines(sd);
let targets = filter_targets(&machines, machine);
let mut scores: Vec<(String, f64, usize)> = Vec::new();
for m in &targets {
let path = sd.join(m).join("state.lock.yaml");
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
let lock: types::StateLock = match serde_yaml_ng::from_str(&content) {
Ok(l) => l,
Err(_) => continue,
};
let total = lock.resources.len();
if total == 0 {
continue;
}
let converged = lock
.resources
.values()
.filter(|r| r.status == types::ResourceStatus::Converged)
.count();
let diversity = lock
.resources
.values()
.map(|r| format!("{:?}", r.resource_type))
.collect::<std::collections::HashSet<_>>()
.len();
let base_score = converged as f64 / total as f64;
let diversity_bonus = (diversity as f64 / 5.0).min(1.0) * 0.1;
let score = (base_score + diversity_bonus).min(1.0);
scores.push(((*m).clone(), score, total));
}
scores.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
print_security_posture(&scores, json);
Ok(())
}
fn print_security_posture(scores: &[(String, f64, usize)], json: bool) {
if json {
let items: Vec<String> = scores
.iter()
.map(|(m, s, t)| {
format!("{{\"machine\":\"{m}\",\"posture_score\":{s:.4},\"resources\":{t}}}")
})
.collect();
println!("{{\"security_posture\":[{}]}}", items.join(","));
} else if scores.is_empty() {
println!("No security posture data available.");
} else {
let avg = scores.iter().map(|(_, s, _)| s).sum::<f64>() / scores.len() as f64;
let grade = if avg >= 0.9 {
"A"
} else if avg >= 0.7 {
"B"
} else if avg >= 0.5 {
"C"
} else {
"D"
};
println!(
"Fleet security posture: {:.1}% (grade: {})",
avg * 100.0,
grade
);
for (m, s, t) in scores {
println!(" {:20} {:.1}% ({} resources)", m, s * 100.0, t);
}
}
}