use crate::analysis::ContextDetector;
use crate::config::{get_context_multipliers, get_data_flow_scoring_config};
use crate::context::{detect_file_type, FileType};
use crate::core::FunctionMetrics;
use crate::priority::context::{generate_context_suggestion, ContextConfig};
use crate::complexity::EntropyAnalysis;
use crate::priority::scoring::ContextRecommendationEngine;
use crate::priority::unified_scorer::{
calculate_unified_priority, calculate_unified_priority_with_data_flow_and_role,
calculate_unified_priority_with_role,
};
use crate::priority::{
call_graph::{CallGraph, FunctionId},
coverage_propagation::calculate_transitive_coverage,
debt_aggregator::DebtAggregator,
scoring::debt_item::{
calculate_entropy_analysis, calculate_expected_impact, classify_all_debt_types_with_role,
classify_debt_type_enhanced, generate_recommendation,
generate_recommendation_with_coverage_and_data_flow,
},
semantic_classifier::classify_function_role,
ActionableRecommendation, DebtType, FunctionRole, ImpactMetrics, Location, TransitiveCoverage,
UnifiedDebtItem, UnifiedScore,
};
use crate::risk::lcov::LcovData;
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
pub type FileLineCountCache = HashMap<PathBuf, usize>;
fn get_file_line_count(file_path: &Path, cache: &FileLineCountCache) -> Option<usize> {
cache
.get(file_path)
.copied()
.or_else(|| calculate_file_line_count_from_disk(file_path))
}
fn calculate_file_line_count_from_disk(file_path: &Path) -> Option<usize> {
use crate::metrics::LocCounter;
let loc_counter = LocCounter::default();
loc_counter
.count_file(file_path)
.ok()
.map(|count| count.physical_lines)
}
fn calculate_context_multiplier(file_path: &Path) -> (f64, FileType) {
let file_type = detect_file_type(file_path);
let config = get_context_multipliers();
if !config.enable_context_dampening {
return (1.0, file_type);
}
let multiplier = match file_type {
FileType::Example => config.examples,
FileType::Test => config.tests,
FileType::Benchmark => config.benchmarks,
FileType::BuildScript => config.build_scripts,
FileType::Documentation => config.documentation,
FileType::Production | FileType::Configuration => 1.0, };
(multiplier, file_type)
}
fn apply_context_multiplier_to_score(mut score: UnifiedScore, multiplier: f64) -> UnifiedScore {
score.final_score *= multiplier;
score.complexity_factor *= multiplier;
score.coverage_factor *= multiplier;
score.dependency_factor *= multiplier;
if let Some(base) = score.base_score {
score.base_score = Some(base * multiplier);
}
if let Some(pre_adj) = score.pre_adjustment_score {
score.pre_adjustment_score = Some(pre_adj * multiplier);
}
score
}
pub fn apply_contextual_risk_to_score(
mut score: UnifiedScore,
contextual_risk: &crate::risk::context::ContextualRisk,
) -> UnifiedScore {
if contextual_risk.base_risk <= 0.0 {
return score;
}
let risk_multiplier = contextual_risk.contextual_risk / contextual_risk.base_risk;
let pre_ctx_score = score.final_score;
score.pre_contextual_score = Some(pre_ctx_score);
let adjusted_final = pre_ctx_score * risk_multiplier;
score.final_score = adjusted_final.max(0.0);
if score.base_score.is_none() {
score.base_score = Some(pre_ctx_score);
}
score.contextual_risk_multiplier = Some(risk_multiplier);
score
}
pub fn create_unified_debt_item_enhanced(
func: &FunctionMetrics,
call_graph: &CallGraph,
_enhanced_call_graph: Option<()>, coverage: Option<&LcovData>,
) -> Option<UnifiedDebtItem> {
let func_id = FunctionId::new(func.file.clone(), func.name.clone(), func.line);
let mut unified_score = calculate_unified_priority(
func, call_graph, coverage, None, );
let (context_multiplier, context_type) = calculate_context_multiplier(&func.file);
unified_score = apply_context_multiplier_to_score(unified_score, context_multiplier);
let role = classify_function_role(func, &func_id, call_graph);
let transitive_coverage =
coverage.map(|cov| calculate_transitive_coverage(&func_id, call_graph, cov));
let debt_type = classify_debt_type_enhanced(func, call_graph, &func_id);
let recommendation = generate_recommendation(func, &debt_type, role, &unified_score)?;
let expected_impact = calculate_expected_impact(func, &debt_type, &unified_score);
let (upstream_caller_names, downstream_callee_names) =
if func.upstream_callers.is_some() || func.downstream_callees.is_some() {
(
func.upstream_callers.clone().unwrap_or_default(),
func.downstream_callees.clone().unwrap_or_default(),
)
} else {
let upstream_callers = call_graph.get_callers(&func_id);
let downstream_callees = call_graph.get_callees(&func_id);
(
upstream_callers.iter().map(|id| id.name.clone()).collect(),
downstream_callees
.iter()
.map(|id| id.name.clone())
.collect(),
)
};
let context_detector = crate::analysis::ContextDetector::global();
let context_analysis = context_detector.detect_context(func, &func.file);
let contextual_recommendation = if context_analysis.confidence > 0.6 {
let engine = crate::priority::scoring::ContextRecommendationEngine::global();
Some(engine.generate_recommendation(
func,
context_analysis.context,
context_analysis.confidence,
unified_score.final_score,
))
} else {
None
};
let detected_pattern =
crate::priority::detected_pattern::DetectedPattern::detect(&func.language_specific);
let entropy_analysis = calculate_entropy_analysis(func);
let file_line_count = calculate_file_line_count_from_disk(&func.file);
let responsibility_category =
crate::organization::god_object::analyze_function_responsibility(&func.name);
let classified = crate::priority::caller_classification::classify_callers(
upstream_caller_names.iter(),
Some(call_graph),
);
let production_blast_radius = classified.production_count + downstream_callee_names.len();
let item = UnifiedDebtItem {
location: Location {
file: func.file.clone(),
function: func.name.clone(),
line: func.line,
},
debt_type,
unified_score,
function_role: role,
recommendation,
expected_impact,
transitive_coverage,
upstream_dependencies: upstream_caller_names.len(),
downstream_dependencies: downstream_callee_names.len(),
upstream_callers: upstream_caller_names,
downstream_callees: downstream_callee_names,
upstream_production_callers: classified.production,
upstream_test_callers: classified.test,
production_blast_radius,
nesting_depth: func.nesting,
function_length: func.length,
cyclomatic_complexity: func.cyclomatic,
cognitive_complexity: func.cognitive,
entropy_analysis: entropy_analysis.clone(),
is_pure: func.is_pure,
purity_confidence: func.purity_confidence,
purity_level: func.purity_level,
god_object_indicators: None,
tier: None,
function_context: Some(context_analysis.context),
context_confidence: Some(context_analysis.confidence),
contextual_recommendation,
pattern_analysis: None, file_context: None,
context_multiplier: Some(context_multiplier), context_type: Some(context_type), language_specific: func.language_specific.clone(), detected_pattern, contextual_risk: None,
file_line_count, responsibility_category, error_swallowing_count: func.error_swallowing_count,
error_swallowing_patterns: func.error_swallowing_patterns.clone(),
context_suggestion: None,
};
Some(apply_score_scaling(item))
}
pub fn create_unified_debt_item_with_aggregator(
func: &FunctionMetrics,
call_graph: &CallGraph,
coverage: Option<&LcovData>,
framework_exclusions: &HashSet<FunctionId>,
function_pointer_used_functions: Option<&HashSet<FunctionId>>,
debt_aggregator: &DebtAggregator,
) -> Vec<UnifiedDebtItem> {
use std::path::Path;
let empty_cache = FileLineCountCache::new();
let context_detector = ContextDetector::new();
let recommendation_engine = ContextRecommendationEngine::new();
create_unified_debt_item_with_aggregator_and_data_flow(
func,
call_graph,
coverage,
framework_exclusions,
function_pointer_used_functions,
debt_aggregator,
None, None, Path::new("."), &empty_cache, &context_detector,
&recommendation_engine,
)
}
pub(crate) fn create_function_id(func: &FunctionMetrics) -> FunctionId {
FunctionId::new(func.file.clone(), func.name.clone(), func.line)
}
pub(crate) struct FunctionScoringContext {
pub func_id: FunctionId,
pub role: FunctionRole,
pub unified_score: UnifiedScore,
pub transitive_coverage: Option<TransitiveCoverage>,
pub deps: DependencyMetrics,
pub entropy_analysis: Option<EntropyAnalysis>,
pub context_analysis: crate::analysis::ContextAnalysis,
}
impl FunctionScoringContext {
#[allow(clippy::too_many_arguments)]
pub fn compute(
func: &FunctionMetrics,
call_graph: &CallGraph,
coverage: Option<&LcovData>,
debt_aggregator: &DebtAggregator,
data_flow: Option<&crate::data_flow::DataFlowGraph>,
context_detector: &ContextDetector,
) -> Self {
let func_id = create_function_id(func);
let role = classify_function_role(func, &func_id, call_graph);
let has_coverage_data = coverage.is_some();
let unified_score = if let Some(df) = data_flow {
let config = get_data_flow_scoring_config();
calculate_unified_priority_with_data_flow_and_role(
func,
&func_id,
call_graph,
df,
coverage,
Some(debt_aggregator),
&config,
role,
)
} else {
calculate_unified_priority_with_role(
func,
&func_id,
call_graph,
coverage,
Some(debt_aggregator),
has_coverage_data,
role,
)
};
let transitive_coverage = calculate_coverage_data(&func_id, func, call_graph, coverage);
let deps = extract_dependency_metrics(func, &func_id, call_graph);
let entropy_analysis = calculate_entropy_analysis(func);
let context_analysis = context_detector.detect_context(func, &func.file);
Self {
func_id,
role,
unified_score,
transitive_coverage,
deps,
entropy_analysis,
context_analysis,
}
}
}
fn calculate_coverage_data(
func_id: &FunctionId,
func: &FunctionMetrics,
call_graph: &CallGraph,
coverage: Option<&LcovData>,
) -> Option<TransitiveCoverage> {
coverage.map(|lcov| {
let end_line = func.line + func.length.saturating_sub(1);
let _direct_coverage =
lcov.get_function_coverage_with_bounds(&func.file, &func.name, func.line, end_line);
calculate_transitive_coverage(func_id, call_graph, lcov)
})
}
#[derive(Clone)]
pub(crate) struct DependencyMetrics {
upstream_count: usize,
downstream_count: usize,
upstream_names: Vec<String>,
downstream_names: Vec<String>,
production_upstream_names: Vec<String>,
test_upstream_names: Vec<String>,
production_blast_radius: usize,
}
fn extract_dependency_metrics(
func: &FunctionMetrics,
func_id: &FunctionId,
call_graph: &CallGraph,
) -> DependencyMetrics {
use crate::priority::caller_classification::{classify_callers, ClassifiedCallers};
let (upstream_names, downstream_names) =
if func.upstream_callers.is_some() || func.downstream_callees.is_some() {
(
func.upstream_callers.clone().unwrap_or_default(),
func.downstream_callees.clone().unwrap_or_default(),
)
} else {
let upstream = call_graph.get_callers(func_id);
let downstream = call_graph.get_callees(func_id);
(
upstream.iter().map(|f| f.name.clone()).collect(),
downstream.iter().map(|f| f.name.clone()).collect(),
)
};
let classified: ClassifiedCallers = classify_callers(upstream_names.iter(), Some(call_graph));
let production_blast_radius = classified.production_count + downstream_names.len();
DependencyMetrics {
upstream_count: upstream_names.len(),
downstream_count: downstream_names.len(),
upstream_names,
downstream_names,
production_upstream_names: classified.production,
test_upstream_names: classified.test,
production_blast_radius,
}
}
fn apply_score_scaling(mut item: UnifiedDebtItem) -> UnifiedDebtItem {
use crate::priority::scoring::scaling::{calculate_final_score, ScalingConfig};
let config = ScalingConfig::default();
let base_score = item.unified_score.final_score;
let (final_score, exponent, boost, debt_multiplier) =
calculate_final_score(base_score, &item.debt_type, &item, &config);
item.unified_score.base_score = Some(base_score);
item.unified_score.exponential_factor = Some(exponent);
item.unified_score.risk_boost = Some(boost);
item.unified_score.debt_type_multiplier = Some(debt_multiplier);
if final_score > 100.0 {
item.unified_score.pre_normalization_score = Some(final_score);
}
item.unified_score.final_score = final_score.max(0.0);
item
}
fn build_unified_debt_item_from_context(
func: &FunctionMetrics,
debt_type: DebtType,
ctx: &FunctionScoringContext,
recommendation: ActionableRecommendation,
expected_impact: ImpactMetrics,
file_line_counts: &FileLineCountCache,
recommendation_engine: &ContextRecommendationEngine,
) -> UnifiedDebtItem {
let (context_multiplier, context_type) = calculate_context_multiplier(&func.file);
let unified_score =
apply_context_multiplier_to_score(ctx.unified_score.clone(), context_multiplier);
let contextual_recommendation = if ctx.context_analysis.confidence > 0.6 {
Some(recommendation_engine.generate_recommendation(
func,
ctx.context_analysis.context,
ctx.context_analysis.confidence,
unified_score.final_score,
))
} else {
None
};
let detected_pattern =
crate::priority::detected_pattern::DetectedPattern::detect(&func.language_specific);
let file_line_count = get_file_line_count(&func.file, file_line_counts);
let responsibility_category =
crate::organization::god_object::analyze_function_responsibility(&func.name);
UnifiedDebtItem {
location: Location {
file: func.file.clone(),
function: func.name.clone(),
line: func.line,
},
debt_type,
unified_score,
function_role: ctx.role,
recommendation,
expected_impact,
transitive_coverage: ctx.transitive_coverage.clone(),
file_context: None,
upstream_dependencies: ctx.deps.upstream_count,
downstream_dependencies: ctx.deps.downstream_count,
upstream_callers: ctx.deps.upstream_names.clone(),
downstream_callees: ctx.deps.downstream_names.clone(),
upstream_production_callers: ctx.deps.production_upstream_names.clone(),
upstream_test_callers: ctx.deps.test_upstream_names.clone(),
production_blast_radius: ctx.deps.production_blast_radius,
nesting_depth: func.nesting,
function_length: func.length,
cyclomatic_complexity: func.cyclomatic,
cognitive_complexity: func.cognitive,
entropy_analysis: ctx.entropy_analysis.clone(),
is_pure: func.is_pure,
purity_confidence: func.purity_confidence,
purity_level: func.purity_level,
god_object_indicators: None,
tier: None,
function_context: Some(ctx.context_analysis.context),
context_confidence: Some(ctx.context_analysis.confidence),
contextual_recommendation,
pattern_analysis: None,
context_multiplier: Some(context_multiplier),
context_type: Some(context_type),
language_specific: func.language_specific.clone(),
detected_pattern,
contextual_risk: None,
file_line_count,
responsibility_category,
error_swallowing_count: func.error_swallowing_count,
error_swallowing_patterns: func.error_swallowing_patterns.clone(),
context_suggestion: None,
}
}
#[allow(clippy::too_many_arguments)]
pub fn create_unified_debt_item_with_aggregator_and_data_flow(
func: &FunctionMetrics,
call_graph: &CallGraph,
coverage: Option<&LcovData>,
framework_exclusions: &HashSet<FunctionId>,
function_pointer_used_functions: Option<&HashSet<FunctionId>>,
debt_aggregator: &DebtAggregator,
data_flow: Option<&crate::data_flow::DataFlowGraph>,
risk_analyzer: Option<&crate::risk::RiskAnalyzer>,
project_path: &Path,
file_line_counts: &FileLineCountCache,
context_detector: &ContextDetector,
recommendation_engine: &ContextRecommendationEngine,
) -> Vec<UnifiedDebtItem> {
let ctx = FunctionScoringContext::compute(
func,
call_graph,
coverage,
debt_aggregator,
data_flow,
context_detector,
);
let debt_types = classify_all_debt_types_with_role(
func,
call_graph,
&ctx.func_id,
framework_exclusions,
function_pointer_used_functions,
ctx.transitive_coverage.as_ref(),
ctx.role,
);
debt_types
.into_iter()
.filter_map(|debt_type| {
let recommendation = generate_recommendation_with_coverage_and_data_flow(
func,
&debt_type,
ctx.role,
&ctx.unified_score,
&ctx.transitive_coverage,
data_flow,
)?;
let expected_impact = calculate_expected_impact(func, &debt_type, &ctx.unified_score);
let mut item = build_unified_debt_item_from_context(
func,
debt_type,
&ctx,
recommendation,
expected_impact,
file_line_counts,
recommendation_engine,
);
item.context_suggestion =
generate_context_suggestion(&item, call_graph, ContextConfig::global_default());
if let Some(analyzer) = risk_analyzer {
let complexity_metrics = crate::core::ComplexityMetrics::from_function(func);
let func_coverage = coverage.and_then(|cov| {
cov.get_function_coverage_with_line(&func.file, &func.name, func.line)
});
let (_, contextual_risk) = analyzer.analyze_function_with_context(
func.file.clone(),
func.name.clone(),
(func.line, func.line + func.length),
&complexity_metrics,
func_coverage,
func.is_test,
project_path.to_path_buf(),
);
if let Some(ref ctx_risk) = contextual_risk {
item.unified_score =
apply_contextual_risk_to_score(item.unified_score, ctx_risk);
}
item.contextual_risk = contextual_risk;
}
Some(apply_score_scaling(item))
})
.collect()
}
pub fn create_unified_debt_item_with_exclusions(
func: &FunctionMetrics,
call_graph: &CallGraph,
coverage: Option<&LcovData>,
framework_exclusions: &HashSet<FunctionId>,
function_pointer_used_functions: Option<&HashSet<FunctionId>>,
) -> Vec<UnifiedDebtItem> {
let empty_cache = FileLineCountCache::new();
create_unified_debt_item_with_exclusions_and_data_flow(
func,
call_graph,
coverage,
framework_exclusions,
function_pointer_used_functions,
None,
&empty_cache,
)
}
pub fn create_unified_debt_item_with_exclusions_and_data_flow(
func: &FunctionMetrics,
call_graph: &CallGraph,
coverage: Option<&LcovData>,
framework_exclusions: &HashSet<FunctionId>,
function_pointer_used_functions: Option<&HashSet<FunctionId>>,
data_flow: Option<&crate::data_flow::DataFlowGraph>,
file_line_counts: &FileLineCountCache,
) -> Vec<UnifiedDebtItem> {
let func_id = FunctionId::new(func.file.clone(), func.name.clone(), func.line);
let function_role = classify_function_role(func, &func_id, call_graph);
let transitive_coverage = coverage.map(|lcov| {
let end_line = func.line + func.length.saturating_sub(1);
let _direct_coverage =
lcov.get_function_coverage_with_bounds(&func.file, &func.name, func.line, end_line);
calculate_transitive_coverage(&func_id, call_graph, lcov)
});
let debt_types = classify_all_debt_types_with_role(
func,
call_graph,
&func_id,
framework_exclusions,
function_pointer_used_functions,
transitive_coverage.as_ref(),
function_role,
);
let mut unified_score = calculate_unified_priority(
func, call_graph, coverage, None, );
let (context_multiplier, context_type) = calculate_context_multiplier(&func.file);
unified_score = apply_context_multiplier_to_score(unified_score, context_multiplier);
let (upstream_caller_names, downstream_callee_names) =
if func.upstream_callers.is_some() || func.downstream_callees.is_some() {
(
func.upstream_callers.clone().unwrap_or_default(),
func.downstream_callees.clone().unwrap_or_default(),
)
} else {
let upstream = call_graph.get_callers(&func_id);
let downstream = call_graph.get_callees(&func_id);
(
upstream.iter().map(|f| f.name.clone()).collect(),
downstream.iter().map(|f| f.name.clone()).collect(),
)
};
let context_detector = crate::analysis::ContextDetector::global();
let context_analysis = context_detector.detect_context(func, &func.file);
let contextual_recommendation = if context_analysis.confidence > 0.6 {
let engine = crate::priority::scoring::ContextRecommendationEngine::global();
Some(engine.generate_recommendation(
func,
context_analysis.context,
context_analysis.confidence,
unified_score.final_score,
))
} else {
None
};
let detected_pattern =
crate::priority::detected_pattern::DetectedPattern::detect(&func.language_specific);
let entropy_analysis = calculate_entropy_analysis(func);
let file_line_count = get_file_line_count(&func.file, file_line_counts);
let classified = crate::priority::caller_classification::classify_callers(
upstream_caller_names.iter(),
Some(call_graph),
);
let production_blast_radius = classified.production_count + downstream_callee_names.len();
let production_callers = classified.production.clone();
let test_callers = classified.test.clone();
debt_types
.into_iter()
.filter_map(|debt_type| {
let recommendation = generate_recommendation_with_coverage_and_data_flow(
func,
&debt_type,
function_role,
&unified_score,
&transitive_coverage,
data_flow,
)?;
let expected_impact = calculate_expected_impact(func, &debt_type, &unified_score);
let mut item = UnifiedDebtItem {
location: Location {
file: func.file.clone(),
function: func.name.clone(),
line: func.line,
},
debt_type,
unified_score: unified_score.clone(),
function_role,
recommendation,
expected_impact,
transitive_coverage: transitive_coverage.clone(),
upstream_dependencies: upstream_caller_names.len(),
downstream_dependencies: downstream_callee_names.len(),
upstream_callers: upstream_caller_names.clone(),
downstream_callees: downstream_callee_names.clone(),
upstream_production_callers: production_callers.clone(),
upstream_test_callers: test_callers.clone(),
production_blast_radius,
nesting_depth: func.nesting,
function_length: func.length,
cyclomatic_complexity: func.cyclomatic,
cognitive_complexity: func.cognitive,
entropy_analysis: entropy_analysis.clone(),
is_pure: func.is_pure,
purity_confidence: func.purity_confidence,
purity_level: func.purity_level,
god_object_indicators: None,
tier: None,
function_context: Some(context_analysis.context),
context_confidence: Some(context_analysis.confidence),
contextual_recommendation: contextual_recommendation.clone(),
pattern_analysis: None,
file_context: None,
context_multiplier: Some(context_multiplier),
context_type: Some(context_type),
language_specific: func.language_specific.clone(),
detected_pattern: detected_pattern.clone(),
contextual_risk: None,
file_line_count,
responsibility_category:
crate::organization::god_object::analyze_function_responsibility(&func.name),
error_swallowing_count: func.error_swallowing_count,
error_swallowing_patterns: func.error_swallowing_patterns.clone(),
context_suggestion: None,
};
item.context_suggestion =
generate_context_suggestion(&item, call_graph, ContextConfig::global_default());
Some(item)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::FileType;
use std::path::PathBuf;
#[test]
fn test_calculate_context_multiplier_for_example() {
let path = PathBuf::from("examples/demo.rs");
let (multiplier, file_type) = calculate_context_multiplier(&path);
assert_eq!(file_type, FileType::Example);
assert_eq!(multiplier, 1.0); }
#[test]
fn test_calculate_context_multiplier_for_test() {
let path = PathBuf::from("tests/integration_test.rs");
let (multiplier, file_type) = calculate_context_multiplier(&path);
assert_eq!(file_type, FileType::Test);
assert_eq!(multiplier, 1.0); }
#[test]
fn test_calculate_context_multiplier_for_benchmark() {
let path = PathBuf::from("benches/perf.rs");
let (multiplier, file_type) = calculate_context_multiplier(&path);
assert_eq!(file_type, FileType::Benchmark);
assert_eq!(multiplier, 1.0); }
#[test]
fn test_calculate_context_multiplier_for_build_script() {
let path = PathBuf::from("build.rs");
let (multiplier, file_type) = calculate_context_multiplier(&path);
assert_eq!(file_type, FileType::BuildScript);
assert_eq!(multiplier, 1.0); }
#[test]
fn test_calculate_context_multiplier_for_production() {
let path = PathBuf::from("src/main.rs");
let (multiplier, file_type) = calculate_context_multiplier(&path);
assert_eq!(file_type, FileType::Production);
assert_eq!(multiplier, 1.0); }
#[test]
fn test_apply_context_multiplier_to_score() {
let original_score = UnifiedScore {
complexity_factor: 8.0,
coverage_factor: 10.0,
dependency_factor: 6.0,
role_multiplier: 1.0,
final_score: 24.0,
base_score: Some(20.0),
exponential_factor: None,
risk_boost: None,
pre_adjustment_score: Some(22.0),
adjustment_applied: None,
purity_factor: None,
refactorability_factor: None,
pattern_factor: None,
debt_adjustment: None,
pre_normalization_score: None,
structural_multiplier: Some(1.0),
has_coverage_data: false,
contextual_risk_multiplier: None,
pre_contextual_score: None,
debt_type_multiplier: None,
};
let adjusted = apply_context_multiplier_to_score(original_score, 0.1);
assert!((adjusted.final_score - 2.4).abs() < 0.0001);
assert!((adjusted.complexity_factor - 0.8).abs() < 0.0001);
assert!((adjusted.coverage_factor - 1.0).abs() < 0.0001);
assert!((adjusted.dependency_factor - 0.6).abs() < 0.0001);
assert!(adjusted.base_score.is_some());
assert!((adjusted.base_score.unwrap() - 2.0).abs() < 0.0001);
assert!(adjusted.pre_adjustment_score.is_some());
assert!((adjusted.pre_adjustment_score.unwrap() - 2.2).abs() < 0.0001);
}
#[test]
fn test_context_multiplier_never_increases_score() {
let original_score = UnifiedScore {
complexity_factor: 5.0,
coverage_factor: 5.0,
dependency_factor: 5.0,
role_multiplier: 1.0,
final_score: 15.0,
base_score: None,
exponential_factor: None,
risk_boost: None,
pre_adjustment_score: None,
adjustment_applied: None,
purity_factor: None,
refactorability_factor: None,
pattern_factor: None,
debt_adjustment: None,
pre_normalization_score: None,
structural_multiplier: Some(1.0),
has_coverage_data: false,
contextual_risk_multiplier: None,
pre_contextual_score: None,
debt_type_multiplier: None,
};
for multiplier in &[0.1, 0.2, 0.3, 1.0] {
let adjusted = apply_context_multiplier_to_score(original_score.clone(), *multiplier);
assert!(adjusted.final_score <= original_score.final_score);
}
}
}