Skip to main content

entrenar/quality/failure/
analysis.rs

1//! Pareto analysis for failure diagnostics.
2
3use std::collections::HashMap;
4
5use super::types::{FailureCategory, FailureContext};
6
7/// Pareto analysis result for failure categories
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct ParetoAnalysis {
10    /// Category counts sorted by frequency (descending)
11    pub categories: Vec<(FailureCategory, u32)>,
12
13    /// Total failures analyzed
14    pub total_failures: u32,
15}
16
17impl ParetoAnalysis {
18    /// Perform Pareto analysis on a list of failure contexts
19    pub fn from_failures(failures: &[FailureContext]) -> Self {
20        let mut counts: HashMap<FailureCategory, u32> = HashMap::new();
21
22        for failure in failures {
23            *counts.entry(failure.category).or_insert(0) += 1;
24        }
25
26        let mut categories: Vec<(FailureCategory, u32)> = counts.into_iter().collect();
27        categories.sort_by(|a, b| b.1.cmp(&a.1)); // Sort descending by count
28
29        Self { categories, total_failures: failures.len() as u32 }
30    }
31
32    /// Get the top N failure categories
33    pub fn top_categories(&self, n: usize) -> Vec<(FailureCategory, u32)> {
34        self.categories.iter().take(n).copied().collect()
35    }
36
37    /// Get percentage of failures for each category
38    pub fn percentages(&self) -> Vec<(FailureCategory, f64)> {
39        if self.total_failures == 0 {
40            return Vec::new();
41        }
42
43        let total = f64::from(self.total_failures);
44        self.categories
45            .iter()
46            .map(|(cat, count)| (*cat, (f64::from(*count) / total) * 100.0))
47            .collect()
48    }
49
50    /// Get cumulative percentages (for Pareto chart)
51    pub fn cumulative_percentages(&self) -> Vec<(FailureCategory, f64)> {
52        if self.total_failures == 0 {
53            return Vec::new();
54        }
55
56        let total = f64::from(self.total_failures);
57        let mut cumulative = 0.0;
58        self.categories
59            .iter()
60            .map(|(cat, count)| {
61                cumulative += (f64::from(*count) / total) * 100.0;
62                (*cat, cumulative)
63            })
64            .collect()
65    }
66
67    /// Find categories that account for ~80% of failures (Pareto principle)
68    pub fn vital_few(&self) -> Vec<(FailureCategory, u32)> {
69        let mut cumulative = 0u32;
70        let threshold = (f64::from(self.total_failures) * 0.8) as u32;
71
72        self.categories
73            .iter()
74            .take_while(|(_, count)| {
75                let result = cumulative < threshold;
76                cumulative += count;
77                result
78            })
79            .copied()
80            .collect()
81    }
82}
83
84/// Convenience function for Pareto analysis
85pub fn top_failure_categories(failures: &[FailureContext]) -> Vec<(FailureCategory, u32)> {
86    ParetoAnalysis::from_failures(failures).categories
87}