impl DefectProbabilityCalculator {
#[must_use]
pub fn new() -> Self {
Self {
weights: DefectWeights::default(),
}
}
#[must_use]
pub fn with_weights(weights: DefectWeights) -> Self {
Self { weights }
}
#[must_use]
pub fn calculate(&self, metrics: &FileMetrics) -> DefectScore {
let churn_norm = self.normalize_churn(metrics.churn_score);
let complexity_norm = self.normalize_complexity(metrics.complexity);
let duplicate_norm = self.normalize_duplication(metrics.duplicate_ratio);
let coupling_norm = self.normalize_coupling(metrics.afferent_coupling);
let raw_score = self.weights.churn * churn_norm
+ self.weights.complexity * complexity_norm
+ self.weights.duplication * duplicate_norm
+ self.weights.coupling * coupling_norm;
let probability = 1.0 / (1.0 + (-10.0 * (raw_score - 0.5)).exp());
let contributing_factors = vec![
("churn".to_string(), churn_norm * self.weights.churn),
(
"complexity".to_string(),
complexity_norm * self.weights.complexity,
),
(
"duplication".to_string(),
duplicate_norm * self.weights.duplication,
),
(
"coupling".to_string(),
coupling_norm * self.weights.coupling,
),
];
let confidence = self.calculate_confidence(metrics);
let risk_level = match probability {
p if p >= 0.7 => RiskLevel::High,
p if p >= 0.3 => RiskLevel::Medium,
_ => RiskLevel::Low,
};
let recommendations = self.generate_recommendations(metrics, &contributing_factors);
DefectScore {
probability,
contributing_factors,
confidence,
risk_level,
recommendations,
}
}
#[must_use]
pub fn calculate_batch(&self, metrics: &[FileMetrics]) -> Vec<(String, DefectScore)> {
metrics
.iter()
.map(|m| (m.file_path.clone(), self.calculate(m)))
.collect()
}
fn normalize_churn(&self, raw_score: f32) -> f32 {
const CHURN_PERCENTILES: [(f32, f32); 10] = [
(0.0, 0.0),
(0.1, 0.05),
(0.2, 0.15),
(0.3, 0.30),
(0.4, 0.50),
(0.5, 0.70),
(0.6, 0.85),
(0.7, 0.93),
(0.8, 0.97),
(1.0, 1.0),
];
interpolate_cdf(&CHURN_PERCENTILES, raw_score)
}
fn normalize_complexity(&self, raw_score: f32) -> f32 {
const COMPLEXITY_PERCENTILES: [(f32, f32); 10] = [
(1.0, 0.1),
(2.0, 0.2),
(3.0, 0.3),
(5.0, 0.5),
(7.0, 0.7),
(10.0, 0.8),
(15.0, 0.9),
(20.0, 0.95),
(30.0, 0.98),
(50.0, 1.0),
];
interpolate_cdf(&COMPLEXITY_PERCENTILES, raw_score)
}
fn normalize_duplication(&self, raw_score: f32) -> f32 {
raw_score.clamp(0.0, 1.0)
}
fn normalize_coupling(&self, raw_score: f32) -> f32 {
const COUPLING_PERCENTILES: [(f32, f32); 8] = [
(0.0, 0.1),
(1.0, 0.3),
(2.0, 0.5),
(3.0, 0.7),
(5.0, 0.8),
(8.0, 0.9),
(12.0, 0.95),
(20.0, 1.0),
];
interpolate_cdf(&COUPLING_PERCENTILES, raw_score)
}
fn calculate_confidence(&self, metrics: &FileMetrics) -> f32 {
let mut confidence: f32 = 1.0;
if metrics.lines_of_code < 10 {
confidence *= 0.5;
} else if metrics.lines_of_code < 50 {
confidence *= 0.8;
}
if metrics.afferent_coupling == 0.0 && metrics.efferent_coupling == 0.0 {
confidence *= 0.9;
}
if metrics.churn_score == 0.0 {
confidence *= 0.85;
}
confidence.clamp(0.0, 1.0)
}
fn generate_recommendations(
&self,
metrics: &FileMetrics,
factors: &[(String, f32)],
) -> Vec<String> {
let mut recommendations = Vec::new();
if let Some((factor_name, contribution)) = factors.iter().max_by(|a, b| a.1.total_cmp(&b.1)) {
if *contribution > 0.2 {
add_factor_recommendations(&mut recommendations, factor_name, metrics);
}
}
if factors.iter().map(|(_, v)| v).sum::<f32>() > 0.7 {
recommendations.push("This file has multiple risk factors - prioritize for refactoring".to_string());
recommendations.push("Consider increasing test coverage for this file".to_string());
recommendations.push("Add comprehensive documentation for complex sections".to_string());
}
recommendations
}
}
fn add_factor_recommendations(recommendations: &mut Vec<String>, factor_name: &str, metrics: &FileMetrics) {
match factor_name {
"complexity" => {
recommendations.push("Consider breaking down complex functions into smaller, more focused units".to_string());
if metrics.cyclomatic_complexity > 15 {
recommendations.push("Cyclomatic complexity is high - reduce conditional logic and nested structures".to_string());
}
if metrics.cognitive_complexity > 20 {
recommendations.push("Cognitive complexity is high - simplify control flow and reduce nesting".to_string());
}
}
"churn" => {
recommendations.push("High change frequency detected - consider stabilizing the interface".to_string());
recommendations.push("Review recent changes for potential design issues".to_string());
}
"duplication" => {
recommendations.push("Code duplication detected - extract common functionality into shared modules".to_string());
recommendations.push("Consider using inheritance, composition, or higher-order functions to reduce duplication".to_string());
}
"coupling" => {
recommendations.push("High coupling detected - reduce dependencies between modules".to_string());
recommendations.push("Consider using dependency injection or interfaces to decouple components".to_string());
}
_ => {}
}
}
impl Default for DefectProbabilityCalculator {
fn default() -> Self {
Self::new()
}
}