arbor_graph/
confidence.rs1use crate::ImpactAnalysis;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ConfidenceLevel {
10 High,
12 Medium,
14 Low,
16}
17
18impl std::fmt::Display for ConfidenceLevel {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match self {
21 ConfidenceLevel::High => write!(f, "High"),
22 ConfidenceLevel::Medium => write!(f, "Medium"),
23 ConfidenceLevel::Low => write!(f, "Low"),
24 }
25 }
26}
27
28#[derive(Debug, Clone)]
30pub struct ConfidenceExplanation {
31 pub level: ConfidenceLevel,
32 pub reasons: Vec<String>,
33 pub suggestions: Vec<String>,
34}
35
36impl ConfidenceExplanation {
37 pub fn from_analysis(analysis: &ImpactAnalysis) -> Self {
39 let mut reasons = Vec::new();
40 let mut suggestions = Vec::new();
41
42 let upstream_count = analysis.upstream.len();
43 let downstream_count = analysis.downstream.len();
44 let total = analysis.total_affected;
45
46 let level = if upstream_count == 0 && downstream_count == 0 {
48 reasons.push("Node appears isolated (no detected connections)".to_string());
50 suggestions
51 .push("Verify if this is called dynamically or from external code".to_string());
52 ConfidenceLevel::Low
53 } else if upstream_count == 0 {
54 reasons.push("Node is an entry point (no internal callers)".to_string());
56 reasons.push(format!("Has {} downstream dependencies", downstream_count));
57 if downstream_count > 5 {
58 suggestions.push("Consider impact on downstream dependencies".to_string());
59 ConfidenceLevel::Medium
60 } else {
61 ConfidenceLevel::High
62 }
63 } else if downstream_count == 0 {
64 reasons.push("Node is a utility (no outgoing dependencies)".to_string());
66 reasons.push(format!("Called by {} upstream nodes", upstream_count));
67 ConfidenceLevel::High
68 } else {
69 reasons.push(format!(
71 "{} callers, {} dependencies",
72 upstream_count, downstream_count
73 ));
74
75 if total > 20 {
76 reasons.push("Large blast radius detected".to_string());
77 suggestions
78 .push("Consider breaking this change into smaller refactors".to_string());
79 ConfidenceLevel::Medium
80 } else if total > 50 {
81 reasons.push("Very large blast radius".to_string());
82 suggestions
83 .push("This change affects a significant portion of the codebase".to_string());
84 ConfidenceLevel::Low
85 } else {
86 reasons.push("Well-connected with manageable impact".to_string());
87 ConfidenceLevel::High
88 }
89 };
90
91 if total > 0 {
93 let direct_count = analysis
94 .upstream
95 .iter()
96 .filter(|n| n.hop_distance == 1)
97 .count();
98 if direct_count > 0 {
99 reasons.push(format!("{} nodes will break immediately", direct_count));
100 }
101 }
102
103 suggestions.push("Tests still recommended for behavioral verification".to_string());
105
106 Self {
107 level,
108 reasons,
109 suggestions,
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum NodeRole {
117 EntryPoint,
119 Utility,
121 CoreLogic,
123 Isolated,
125 Adapter,
127}
128
129impl std::fmt::Display for NodeRole {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 match self {
132 NodeRole::EntryPoint => write!(f, "Entry Point"),
133 NodeRole::Utility => write!(f, "Utility"),
134 NodeRole::CoreLogic => write!(f, "Core Logic"),
135 NodeRole::Isolated => write!(f, "Isolated"),
136 NodeRole::Adapter => write!(f, "Adapter"),
137 }
138 }
139}
140
141impl NodeRole {
142 pub fn from_analysis(analysis: &ImpactAnalysis) -> Self {
144 let has_upstream = !analysis.upstream.is_empty();
145 let has_downstream = !analysis.downstream.is_empty();
146
147 match (has_upstream, has_downstream) {
148 (false, false) => NodeRole::Isolated,
149 (false, true) => NodeRole::EntryPoint,
150 (true, false) => NodeRole::Utility,
151 (true, true) => {
152 let upstream_count = analysis.upstream.len();
154 let downstream_count = analysis.downstream.len();
155
156 if upstream_count <= 2 && downstream_count > 5 {
158 NodeRole::Adapter
159 } else if downstream_count <= 2 && upstream_count > 5 {
160 NodeRole::Adapter
161 } else {
162 NodeRole::CoreLogic
163 }
164 }
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn test_confidence_level_display() {
175 assert_eq!(ConfidenceLevel::High.to_string(), "High");
176 assert_eq!(ConfidenceLevel::Medium.to_string(), "Medium");
177 assert_eq!(ConfidenceLevel::Low.to_string(), "Low");
178 }
179
180 #[test]
181 fn test_node_role_display() {
182 assert_eq!(NodeRole::EntryPoint.to_string(), "Entry Point");
183 assert_eq!(NodeRole::Utility.to_string(), "Utility");
184 assert_eq!(NodeRole::CoreLogic.to_string(), "Core Logic");
185 assert_eq!(NodeRole::Isolated.to_string(), "Isolated");
186 assert_eq!(NodeRole::Adapter.to_string(), "Adapter");
187 }
188}