Skip to main content

netconv_core/
report.rs

1use serde::{Deserialize, Serialize};
2
3use crate::profile::DomainMismatch;
4
5// ---------------------------------------------------------------------------
6// Risk Score — итоговая оценка рискованности конвертации
7// ---------------------------------------------------------------------------
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub enum RiskLevel {
11    Low,
12    Medium,
13    High,
14    Critical,
15}
16
17impl RiskLevel {
18    pub fn label(&self) -> &str {
19        match self {
20            RiskLevel::Low => "LOW",
21            RiskLevel::Medium => "MEDIUM",
22            RiskLevel::High => "HIGH",
23            RiskLevel::Critical => "CRITICAL",
24        }
25    }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct RiskScore {
30    pub level: RiskLevel,
31    pub reasons: Vec<String>,
32    pub recommendations: Vec<String>,
33}
34
35impl Default for RiskScore {
36    fn default() -> Self {
37        RiskScore {
38            level: RiskLevel::Low,
39            reasons: vec![],
40            recommendations: vec![],
41        }
42    }
43}
44
45// ---------------------------------------------------------------------------
46// Confidence level для zone classification и других эвристик
47// ---------------------------------------------------------------------------
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub enum ConfidenceLevel {
51    High,   // явный признак (NAT outside, ключевое слово)
52    Medium, // косвенный признак (default route)
53    Low,    // fallback (всё остальное)
54}
55
56impl ConfidenceLevel {
57    pub fn label(&self) -> &str {
58        match self {
59            ConfidenceLevel::High => "HIGH",
60            ConfidenceLevel::Medium => "MEDIUM",
61            ConfidenceLevel::Low => "LOW",
62        }
63    }
64}
65
66// ---------------------------------------------------------------------------
67// ConversionReport — главная ценность инструмента
68// ---------------------------------------------------------------------------
69
70#[derive(Debug, Clone, Default, Serialize, Deserialize)]
71pub struct ConversionReport {
72    pub source_vendor: String,
73    pub target_vendor: String,
74    pub summary: ReportSummary,
75    pub items: Vec<ReportItem>,
76    pub risk: RiskScore,
77    /// Сущности чужого домена (L2 в L3-профиле и наоборот), найденные
78    /// через profile::detect_domain_mismatches. Пусто, если профиль не
79    /// был указан (см. traits::convert_with_profile) или конфликтов нет.
80    pub domain_mismatches: Vec<DomainMismatch>,
81}
82
83#[derive(Debug, Clone, Default, Serialize, Deserialize)]
84pub struct ReportSummary {
85    pub total_commands: usize,
86    pub exact: usize,
87    pub approximate: usize,
88    pub manual_required: usize,
89    pub unknown: usize,
90}
91
92impl ReportSummary {
93    pub fn exact_pct(&self) -> f32 {
94        if self.total_commands == 0 {
95            return 0.0;
96        }
97        self.exact as f32 / self.total_commands as f32 * 100.0
98    }
99
100    pub fn coverage_pct(&self) -> f32 {
101        if self.total_commands == 0 {
102            return 0.0;
103        }
104        (self.exact + self.approximate) as f32 / self.total_commands as f32 * 100.0
105    }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct ReportItem {
110    pub severity: Severity,
111    pub category: String,
112    pub source_snippet: String,
113    pub target_snippet: Option<String>,
114    pub message: String,
115    pub recommendation: Option<String>,
116}
117
118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119pub enum Severity {
120    /// OK — конвертировано точно
121    Ok,
122    /// Warn — есть нюанс, проверь поведение
123    Warn,
124    /// Error — нет аналога, нужно ручное решение
125    Error,
126    /// Info — нераспознанная команда, игнорирована
127    Info,
128}
129
130impl ConversionReport {
131    pub fn new(source_vendor: &str, target_vendor: &str) -> Self {
132        ConversionReport {
133            source_vendor: source_vendor.to_string(),
134            target_vendor: target_vendor.to_string(),
135            ..Default::default()
136        }
137    }
138
139    pub fn add_exact(&mut self, category: &str, source: &str, target: &str) {
140        self.summary.exact += 1;
141        self.summary.total_commands += 1;
142        self.items.push(ReportItem {
143            severity: Severity::Ok,
144            category: category.to_string(),
145            source_snippet: source.to_string(),
146            target_snippet: Some(target.to_string()),
147            message: "Точное соответствие".to_string(),
148            recommendation: None,
149        });
150    }
151
152    pub fn add_approximate(&mut self, category: &str, source: &str, target: &str, note: &str) {
153        self.summary.approximate += 1;
154        self.summary.total_commands += 1;
155        self.items.push(ReportItem {
156            severity: Severity::Warn,
157            category: category.to_string(),
158            source_snippet: source.to_string(),
159            target_snippet: Some(target.to_string()),
160            message: note.to_string(),
161            recommendation: None,
162        });
163    }
164
165    pub fn add_manual(&mut self, category: &str, source: &str, reason: &str, rec: Option<&str>) {
166        self.summary.manual_required += 1;
167        self.summary.total_commands += 1;
168        self.items.push(ReportItem {
169            severity: Severity::Error,
170            category: category.to_string(),
171            source_snippet: source.to_string(),
172            target_snippet: None,
173            message: reason.to_string(),
174            recommendation: rec.map(|s| s.to_string()),
175        });
176    }
177
178    pub fn add_unknown(&mut self, raw: &str, context: &str) {
179        self.summary.unknown += 1;
180        self.summary.total_commands += 1;
181        self.items.push(ReportItem {
182            severity: Severity::Info,
183            category: "unknown".to_string(),
184            source_snippet: raw.to_string(),
185            target_snippet: None,
186            message: format!("Нераспознанная команда в контексте: {}", context),
187            recommendation: None,
188        });
189    }
190
191    /// Только items с severity >= Warn — для краткого вывода
192    pub fn warnings_and_errors(&self) -> Vec<&ReportItem> {
193        self.items
194            .iter()
195            .filter(|i| i.severity != Severity::Ok && i.severity != Severity::Info)
196            .collect()
197    }
198}