impl ConvergenceTracker {
pub fn new() -> Self {
Self::default()
}
pub fn record(
&mut self,
metrics: ProjectMetrics,
defects_remaining: usize,
targets: &ConvergenceTargets,
) {
self.iterations += 1;
let status = targets.check(&metrics);
if self.best_metrics.is_none() || self.is_better(&metrics) {
self.best_metrics = Some(metrics.clone());
}
self.history.push(ConvergenceSnapshot {
iteration: self.iterations,
metrics,
defects_remaining,
status: status.clone(),
});
self.current_status = Some(status);
}
fn is_better(&self, metrics: &ProjectMetrics) -> bool {
let Some(best) = &self.best_metrics else {
return true;
};
let new_score = self.calculate_score(metrics);
let best_score = self.calculate_score(best);
new_score > best_score
}
fn calculate_score(&self, metrics: &ProjectMetrics) -> f32 {
let mut score = 0.0;
score += metrics.test_coverage * 0.25;
score += metrics.mutation_score * 0.15;
if metrics.compiler_errors == 0 {
score += 0.20;
}
if metrics.clippy_warnings == 0 {
score += 0.10;
}
if metrics.test_failures == 0 {
score += 0.15;
}
score += (metrics.tdg_score / 100.0) * 0.10;
score += (metrics.rust_project_score as f32 / 106.0) * 0.05;
score
}
pub fn convergence_percentage(&self, targets: &ConvergenceTargets) -> f32 {
let Some(best) = &self.best_metrics else {
return 0.0;
};
let mut achieved = 0.0;
let mut total = 0.0;
total += 1.0;
if best.test_coverage >= targets.test_coverage {
achieved += 1.0;
} else {
achieved += best.test_coverage / targets.test_coverage;
}
total += 1.0;
if best.mutation_score >= targets.mutation_score {
achieved += 1.0;
} else {
achieved += best.mutation_score / targets.mutation_score;
}
total += 1.0;
if best.compiler_errors <= targets.max_compiler_errors {
achieved += 1.0;
}
total += 1.0;
if best.clippy_warnings <= targets.max_clippy_warnings {
achieved += 1.0;
}
total += 1.0;
if best.test_failures <= targets.max_test_failures {
achieved += 1.0;
}
total += 1.0;
if best.tdg_score >= targets.min_tdg_score {
achieved += 1.0;
} else {
achieved += best.tdg_score / targets.min_tdg_score;
}
total += 1.0;
if best.rust_project_score >= targets.min_rust_project_score {
achieved += 1.0;
} else {
achieved += best.rust_project_score as f32 / targets.min_rust_project_score as f32;
}
total += 1.0;
if best.satd_markers <= targets.max_satd_markers {
achieved += 1.0;
}
total += 1.0;
if best.dead_code_items <= targets.max_dead_code {
achieved += 1.0;
}
achieved / total
}
pub fn is_converged(&self) -> bool {
matches!(self.current_status, Some(ConvergenceStatus::Converged))
}
pub fn remaining_failures(&self) -> Vec<String> {
match &self.current_status {
Some(ConvergenceStatus::NotConverged { remaining }) => remaining.clone(),
_ => Vec::new(),
}
}
pub fn trend(&self) -> f32 {
if self.history.len() < 2 {
return 0.0;
}
let recent: Vec<_> = self.history.iter().rev().take(5).collect();
if recent.len() < 2 {
return 0.0;
}
let first_defects = recent.last().expect("internal error").defects_remaining as f32;
let last_defects = recent.first().expect("internal error").defects_remaining as f32;
if first_defects == 0.0 {
return 0.0;
}
(first_defects - last_defects) / first_defects
}
}