debtmap/risk/
mod.rs

1pub mod context;
2pub mod correlation;
3pub mod delegation;
4pub mod evidence;
5pub mod evidence_calculator;
6pub mod insights;
7pub mod lcov;
8pub mod priority;
9pub mod roi;
10pub mod strategy;
11pub mod thresholds;
12
13use crate::core::ComplexityMetrics;
14use im::Vector;
15use serde::{Deserialize, Serialize};
16use std::path::PathBuf;
17
18#[derive(Clone, Debug, Serialize, Deserialize)]
19pub struct FunctionRisk {
20    pub file: PathBuf,
21    pub function_name: String,
22    pub line_range: (usize, usize),
23    pub cyclomatic_complexity: u32,
24    pub cognitive_complexity: u32,
25    pub coverage_percentage: Option<f64>,
26    pub risk_score: f64,
27    pub test_effort: TestEffort,
28    pub risk_category: RiskCategory,
29    pub is_test_function: bool,
30}
31
32#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
33pub enum RiskCategory {
34    Critical,   // High complexity (>15), low coverage (<30%)
35    High,       // High complexity (>10), moderate coverage (<60%)
36    Medium,     // Moderate complexity (>5), low coverage (<50%)
37    Low,        // Low complexity or high coverage
38    WellTested, // High complexity with high coverage (good examples)
39}
40
41#[derive(Clone, Debug, Serialize, Deserialize)]
42pub struct TestEffort {
43    pub estimated_difficulty: Difficulty,
44    pub cognitive_load: u32,
45    pub branch_count: u32,
46    pub recommended_test_cases: u32,
47}
48
49#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
50pub enum Difficulty {
51    Trivial,     // Cognitive < 5
52    Simple,      // Cognitive 5-10
53    Moderate,    // Cognitive 10-20
54    Complex,     // Cognitive 20-40
55    VeryComplex, // Cognitive > 40
56}
57
58#[derive(Clone, Debug, Serialize, Deserialize)]
59pub struct RiskInsight {
60    pub top_risks: Vector<FunctionRisk>,
61    pub risk_reduction_opportunities: Vector<TestingRecommendation>,
62    pub codebase_risk_score: f64,
63    pub complexity_coverage_correlation: Option<f64>,
64    pub risk_distribution: RiskDistribution,
65}
66
67#[derive(Clone, Debug, Serialize, Deserialize)]
68pub struct TestingRecommendation {
69    pub function: String,
70    pub file: PathBuf,
71    pub line: usize,
72    pub current_risk: f64,
73    pub potential_risk_reduction: f64,
74    pub test_effort_estimate: TestEffort,
75    pub rationale: String,
76    pub roi: Option<f64>,
77    pub dependencies: Vec<String>,
78    pub dependents: Vec<String>,
79}
80
81#[derive(Clone, Debug, Serialize, Deserialize)]
82pub struct RiskDistribution {
83    pub critical_count: usize,
84    pub high_count: usize,
85    pub medium_count: usize,
86    pub low_count: usize,
87    pub well_tested_count: usize,
88    pub total_functions: usize,
89}
90
91use self::context::{AnalysisTarget, ContextAggregator, ContextualRisk};
92use self::strategy::{EnhancedRiskStrategy, RiskCalculator, RiskContext};
93
94pub struct RiskAnalyzer {
95    strategy: Box<dyn RiskCalculator>,
96    debt_score: Option<f64>,
97    debt_threshold: Option<f64>,
98    context_aggregator: Option<ContextAggregator>,
99}
100
101impl Clone for RiskAnalyzer {
102    fn clone(&self) -> Self {
103        Self {
104            strategy: self.strategy.box_clone(),
105            debt_score: self.debt_score,
106            debt_threshold: self.debt_threshold,
107            context_aggregator: None, // Don't clone aggregator, recreate if needed
108        }
109    }
110}
111
112impl Default for RiskAnalyzer {
113    fn default() -> Self {
114        Self {
115            strategy: Box::new(EnhancedRiskStrategy::default()),
116            debt_score: None,
117            debt_threshold: None,
118            context_aggregator: None,
119        }
120    }
121}
122
123impl RiskAnalyzer {
124    pub fn with_debt_context(mut self, debt_score: f64, debt_threshold: f64) -> Self {
125        self.debt_score = Some(debt_score);
126        self.debt_threshold = Some(debt_threshold);
127        self
128    }
129
130    pub fn with_context_aggregator(mut self, aggregator: ContextAggregator) -> Self {
131        self.context_aggregator = Some(aggregator);
132        self
133    }
134
135    pub fn analyze_function(
136        &self,
137        file: PathBuf,
138        function_name: String,
139        line_range: (usize, usize),
140        complexity: &ComplexityMetrics,
141        coverage: Option<f64>,
142        is_test: bool,
143    ) -> FunctionRisk {
144        let context = RiskContext {
145            file,
146            function_name,
147            line_range,
148            complexity: complexity.clone(),
149            coverage,
150            debt_score: self.debt_score,
151            debt_threshold: self.debt_threshold,
152            is_test,
153            is_recognized_pattern: false,
154            pattern_type: None,
155            pattern_confidence: 0.0,
156        };
157
158        self.strategy.calculate(&context)
159    }
160
161    #[allow(clippy::too_many_arguments)]
162    pub fn analyze_function_with_context(
163        &mut self,
164        file: PathBuf,
165        function_name: String,
166        line_range: (usize, usize),
167        complexity: &ComplexityMetrics,
168        coverage: Option<f64>,
169        is_test: bool,
170        root_path: PathBuf,
171    ) -> (FunctionRisk, Option<ContextualRisk>) {
172        let base_risk = self.analyze_function(
173            file.clone(),
174            function_name.clone(),
175            line_range,
176            complexity,
177            coverage,
178            is_test,
179        );
180
181        let contextual_risk = if let Some(ref mut aggregator) = self.context_aggregator {
182            let target = AnalysisTarget {
183                root_path,
184                file_path: file,
185                function_name,
186                line_range,
187            };
188
189            let context_map = aggregator.analyze(&target);
190            Some(ContextualRisk::new(base_risk.risk_score, &context_map))
191        } else {
192            None
193        };
194
195        (base_risk, contextual_risk)
196    }
197
198    pub fn calculate_risk_score(
199        &self,
200        cyclomatic: u32,
201        cognitive: u32,
202        coverage: Option<f64>,
203    ) -> f64 {
204        let context = RiskContext {
205            file: PathBuf::new(),
206            function_name: String::new(),
207            line_range: (0, 0),
208            complexity: ComplexityMetrics {
209                functions: vec![],
210                cyclomatic_complexity: cyclomatic,
211                cognitive_complexity: cognitive,
212            },
213            coverage,
214            debt_score: self.debt_score,
215            debt_threshold: self.debt_threshold,
216            is_test: false,
217            is_recognized_pattern: false,
218            pattern_type: None,
219            pattern_confidence: 0.0,
220        };
221
222        self.strategy.calculate_risk_score(&context)
223    }
224
225    pub fn calculate_risk_reduction(
226        &self,
227        current_risk: f64,
228        complexity: u32,
229        target_coverage: f64,
230    ) -> f64 {
231        self.strategy
232            .calculate_risk_reduction(current_risk, complexity, target_coverage)
233    }
234}