#[derive(Debug, Clone)]
pub struct ComplexityVariance {
pub mean: f64,
pub variance: f64,
pub gini: f64,
pub percentile_90: f64,
}
#[derive(Debug, Clone)]
pub struct CouplingMetrics {
pub afferent: usize, pub efferent: usize, pub instability: f64, }
pub struct TDGCalculator {
pub(crate) config: TDGConfig,
pub(crate) cache: Arc<DashMap<PathBuf, TDGScore>>,
pub(crate) semaphore: Arc<Semaphore>,
pub(crate) provability_analyzer: Arc<LightweightProvabilityAnalyzer>,
pub(crate) ast_engine: Arc<UnifiedAstEngine>,
pub(crate) project_root: PathBuf,
pub(crate) cached_churn_analysis: Arc<Mutex<Option<crate::models::churn::CodeChurnAnalysis>>>,
}
impl TDGCalculator {
#[must_use]
pub fn new() -> Self {
Self::with_config(TDGConfig::default())
}
#[must_use]
pub fn with_config(config: TDGConfig) -> Self {
Self {
config,
cache: Arc::new(DashMap::new()),
semaphore: Arc::new(Semaphore::new(num_cpus::get() * 2)),
provability_analyzer: Arc::new(LightweightProvabilityAnalyzer::new()),
ast_engine: Arc::new(UnifiedAstEngine::new()),
project_root: PathBuf::from("."),
cached_churn_analysis: Arc::new(Mutex::new(None)),
}
}
#[must_use]
pub fn with_project_root(mut self, root: PathBuf) -> Self {
self.project_root = root;
self
}
pub async fn calculate_file(&self, path: &Path) -> Result<TDGScore> {
if let Some(cached) = self.cache.get(&path.to_path_buf()) {
return Ok(cached.clone());
}
let score = self.calculate_file_uncached(path).await?;
self.cache.insert(path.to_path_buf(), score.clone());
Ok(score)
}
async fn calculate_file_uncached(&self, path: &Path) -> Result<TDGScore> {
let (complexity, churn, coupling, duplication, provability) = tokio::try_join!(
self.calculate_complexity_factor(path),
self.calculate_churn_factor(path),
self.calculate_coupling_factor(path),
self.calculate_duplication_factor(path),
self.calculate_provability_factor(path),
)?;
let domain_risk = self.calculate_domain_risk(path).await?;
let components = TDGComponents {
complexity,
churn,
coupling,
domain_risk,
duplication,
dead_code: 0.0, };
let value = self.calculate_weighted_tdg(&components, provability);
let severity = TDGSeverity::from(value);
Ok(TDGScore {
value,
components,
severity,
percentile: 0.0, confidence: self.calculate_confidence(&components),
})
}
pub async fn calculate_batch(&self, files: Vec<PathBuf>) -> Result<Vec<TDGScore>> {
let tasks: Vec<_> = files
.into_iter()
.map(|file| {
let calculator = self.clone();
tokio::spawn(async move {
let _permit = calculator.semaphore.acquire().await?;
calculator.calculate_file(&file).await
})
})
.collect();
let mut results = Vec::with_capacity(tasks.len());
for task in tasks {
results.push(task.await??);
}
self.calculate_percentiles(&mut results);
Ok(results)
}
fn calculate_weighted_tdg(&self, components: &TDGComponents, provability_factor: f64) -> f64 {
let base_weighted = components.complexity * self.config.complexity_weight
+ components.churn * self.config.churn_weight
+ components.coupling * self.config.coupling_weight
+ components.domain_risk * self.config.domain_risk_weight
+ components.duplication * self.config.duplication_weight;
let adjusted = base_weighted * (1.0 - provability_factor * 0.2);
adjusted.clamp(0.0, 5.0)
}
fn calculate_confidence(&self, components: &TDGComponents) -> f64 {
let mut confidence = 1.0;
if components.churn == 0.0 {
confidence *= 0.8;
}
if components.coupling == 0.0 {
confidence *= 0.9;
}
if components.duplication == 0.0 {
confidence *= 0.95;
}
confidence
}
fn calculate_percentiles(&self, scores: &mut [TDGScore]) {
let mut values: Vec<f64> = scores.iter().map(|s| s.value).collect();
values.sort_by(|a, b| a.total_cmp(b));
for score in scores.iter_mut() {
let position = values
.binary_search_by(|&v| v.total_cmp(&score.value))
.unwrap_or_else(|i| i);
score.percentile = (position as f64 / values.len() as f64) * 100.0;
}
}
fn percentile(&self, sorted_values: &[f64], percentile: f64) -> f64 {
if sorted_values.is_empty() {
return 0.0;
}
let index = (sorted_values.len() as f64 * percentile) as usize;
let index = index.min(sorted_values.len() - 1);
sorted_values[index]
}
}
impl Default for TDGCalculator {
fn default() -> Self {
Self::new()
}
}
impl Clone for TDGCalculator {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
cache: self.cache.clone(),
semaphore: self.semaphore.clone(),
provability_analyzer: self.provability_analyzer.clone(),
ast_engine: self.ast_engine.clone(),
project_root: self.project_root.clone(),
cached_churn_analysis: Arc::clone(&self.cached_churn_analysis),
}
}
}