use super::types::{ExtensionRatios, LineRatios, QualityMetrics, QualityThresholds};
use std::collections::BTreeMap;
pub struct QualityCalculator {
thresholds: QualityThresholds,
}
impl QualityCalculator {
pub fn new(thresholds: QualityThresholds) -> Self {
Self { thresholds }
}
pub fn calculate_quality_metrics(
&self,
ratios: LineRatios,
ratios_by_extension: &BTreeMap<String, ExtensionRatios>,
) -> QualityMetrics {
let doc_score = self.calculate_documentation_score(
ratios.doc,
ratios.comment,
ratios.doc_to_code,
ratios.comment_to_code,
);
let maintainability_score = self.calculate_maintainability_score(
ratios.code,
ratios.comment,
ratios.doc,
ratios.blank,
);
let readability_score = self.calculate_readability_score(ratios.comment, ratios.blank);
let consistency_score = self.calculate_consistency_score(ratios_by_extension);
let overall_quality_score = (doc_score * 0.25)
+ (maintainability_score * 0.35)
+ (readability_score * 0.25)
+ (consistency_score * 0.15);
QualityMetrics {
documentation_score: doc_score,
maintainability_score,
readability_score,
consistency_score,
overall_quality_score,
}
}
fn calculate_documentation_score(
&self,
doc_ratio: f64,
comment_ratio: f64,
doc_to_code_ratio: f64,
comment_to_code_ratio: f64,
) -> f64 {
let mut score = 0.0;
if doc_ratio >= self.thresholds.good_doc_ratio {
score += 40.0;
} else {
score += (doc_ratio / self.thresholds.good_doc_ratio) * 40.0;
}
if comment_ratio >= self.thresholds.good_comment_ratio {
score += 30.0;
} else {
score += (comment_ratio / self.thresholds.good_comment_ratio) * 30.0;
}
if doc_to_code_ratio >= self.thresholds.ideal_doc_to_code {
score += 15.0;
} else {
score += (doc_to_code_ratio / self.thresholds.ideal_doc_to_code) * 15.0;
}
if comment_to_code_ratio >= self.thresholds.ideal_comment_to_code {
score += 15.0;
} else {
score += (comment_to_code_ratio / self.thresholds.ideal_comment_to_code) * 15.0;
}
score.min(100.0)
}
fn calculate_maintainability_score(
&self,
code_ratio: f64,
comment_ratio: f64,
doc_ratio: f64,
blank_ratio: f64,
) -> f64 {
let mut score = 0.0;
score += code_ratio * 40.0;
if comment_ratio >= self.thresholds.good_comment_ratio {
score += 25.0;
} else {
score += (comment_ratio / self.thresholds.good_comment_ratio) * 25.0;
}
if doc_ratio >= self.thresholds.good_doc_ratio {
score += 25.0;
} else {
score += (doc_ratio / self.thresholds.good_doc_ratio) * 25.0;
}
if blank_ratio <= self.thresholds.max_blank_ratio {
score += 10.0;
} else {
let penalty = (blank_ratio - self.thresholds.max_blank_ratio) * 20.0;
score += (10.0 - penalty).max(0.0);
}
score.min(100.0)
}
fn calculate_readability_score(&self, comment_ratio: f64, blank_ratio: f64) -> f64 {
let mut score = 0.0;
if comment_ratio >= self.thresholds.good_comment_ratio {
score += 70.0;
} else {
score += (comment_ratio / self.thresholds.good_comment_ratio) * 70.0;
}
let ideal_blank_ratio = 0.15; if blank_ratio <= ideal_blank_ratio {
score += (blank_ratio / ideal_blank_ratio) * 30.0;
} else {
let penalty = (blank_ratio - ideal_blank_ratio) * 60.0;
score += (30.0 - penalty).max(0.0);
}
score.min(100.0)
}
fn calculate_consistency_score(
&self,
ratios_by_extension: &BTreeMap<String, ExtensionRatios>,
) -> f64 {
if ratios_by_extension.len() <= 1 {
return 100.0; }
let mut comment_ratios = Vec::new();
let mut doc_ratios = Vec::new();
for ext_ratios in ratios_by_extension.values() {
comment_ratios.push(ext_ratios.comment_ratio);
doc_ratios.push(ext_ratios.doc_ratio);
}
let comment_consistency = self.calculate_variance_score(&comment_ratios);
let doc_consistency = self.calculate_variance_score(&doc_ratios);
(comment_consistency + doc_consistency) / 2.0
}
fn calculate_variance_score(&self, values: &[f64]) -> f64 {
if values.len() <= 1 {
return 100.0;
}
let mean = values.iter().sum::<f64>() / values.len() as f64;
let variance = values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / values.len() as f64;
let std_dev = variance.sqrt();
let max_std_dev = 0.2; let score = ((max_std_dev - std_dev.min(max_std_dev)) / max_std_dev) * 100.0;
score.max(0.0)
}
pub fn get_quality_level(&self, score: f64) -> String {
match score as usize {
90..=100 => "Excellent".to_string(),
80..=89 => "Good".to_string(),
70..=79 => "Fair".to_string(),
60..=69 => "Poor".to_string(),
_ => "Very Poor".to_string(),
}
}
pub fn get_quality_class(&self, score: f64) -> String {
match score as usize {
90..=100 => "quality-excellent".to_string(),
80..=89 => "quality-good".to_string(),
70..=79 => "quality-fair".to_string(),
60..=69 => "quality-poor".to_string(),
_ => "quality-very-poor".to_string(),
}
}
}