use super::tiers::RecommendationTier;
use super::DebtItem;
#[derive(Debug, Clone, PartialEq)]
pub struct FilterMetrics {
pub total_items: usize,
pub filtered_t4_maintenance: usize,
pub filtered_below_score: usize,
pub filtered_by_debt_type: usize,
pub tier_critical_bypass: usize,
pub included: usize,
pub min_score_threshold: f64,
pub show_t4: bool,
}
impl FilterMetrics {
pub fn empty() -> Self {
Self {
total_items: 0,
filtered_t4_maintenance: 0,
filtered_below_score: 0,
filtered_by_debt_type: 0,
tier_critical_bypass: 0,
included: 0,
min_score_threshold: 0.0,
show_t4: false,
}
}
pub fn new(total: usize, min_score: f64, show_t4: bool) -> Self {
Self {
total_items: total,
min_score_threshold: min_score,
show_t4,
..Self::empty()
}
}
pub fn total_filtered(&self) -> usize {
self.filtered_t4_maintenance + self.filtered_below_score + self.filtered_by_debt_type
}
pub fn inclusion_rate(&self) -> f64 {
if self.total_items == 0 {
0.0
} else {
(self.included as f64 / self.total_items as f64) * 100.0
}
}
}
#[derive(Debug, Clone)]
pub struct FilterConfig {
pub min_score: f64,
pub show_t4: bool,
}
impl Default for FilterConfig {
fn default() -> Self {
Self {
min_score: 3.0,
show_t4: false,
}
}
}
#[derive(Debug, Clone)]
pub struct ClassifiedItem {
pub item: DebtItem,
pub tier: RecommendationTier,
pub score: f64,
}
#[derive(Debug, Clone)]
pub struct FilterResult {
pub included: Vec<DebtItem>,
pub metrics: FilterMetrics,
}
impl FilterResult {
pub fn new(included: Vec<DebtItem>, metrics: FilterMetrics) -> Self {
Self { included, metrics }
}
pub fn empty() -> Self {
Self {
included: Vec::new(),
metrics: FilterMetrics::empty(),
}
}
}
pub fn is_critical_tier(tier: RecommendationTier) -> bool {
matches!(
tier,
RecommendationTier::T1CriticalArchitecture | RecommendationTier::T2ComplexUntested
)
}
pub fn filter_with_metrics(items: Vec<ClassifiedItem>, config: &FilterConfig) -> FilterResult {
let total = items.len();
let mut metrics = FilterMetrics::new(total, config.min_score, config.show_t4);
let included: Vec<_> = items
.into_iter()
.filter_map(|classified_item| {
if is_critical_tier(classified_item.tier) {
if classified_item.score < config.min_score {
metrics.tier_critical_bypass += 1;
}
return Some(classified_item.item);
}
if !tier_passes(classified_item.tier, config) {
metrics.filtered_t4_maintenance += 1;
return None;
}
if !score_passes(classified_item.score, config.min_score) {
metrics.filtered_below_score += 1;
return None;
}
Some(classified_item.item)
})
.collect();
metrics.included = included.len();
FilterResult::new(included, metrics)
}
fn tier_passes(tier: RecommendationTier, config: &FilterConfig) -> bool {
tier != RecommendationTier::T4Maintenance || config.show_t4
}
fn score_passes(score: f64, threshold: f64) -> bool {
if score <= 0.0 {
return false;
}
score >= threshold
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_metrics() {
let m = FilterMetrics::empty();
assert_eq!(m.total_items, 0);
assert_eq!(m.total_filtered(), 0);
assert_eq!(m.inclusion_rate(), 0.0);
}
#[test]
fn test_inclusion_rate() {
let m = FilterMetrics {
total_items: 100,
included: 25,
..FilterMetrics::empty()
};
assert_eq!(m.inclusion_rate(), 25.0);
}
#[test]
fn test_total_filtered() {
let m = FilterMetrics {
filtered_t4_maintenance: 10,
filtered_below_score: 5,
filtered_by_debt_type: 3,
..FilterMetrics::empty()
};
assert_eq!(m.total_filtered(), 18);
}
#[test]
fn test_tier_passes_t4_hidden() {
let config = FilterConfig {
show_t4: false,
..Default::default()
};
assert!(!tier_passes(RecommendationTier::T4Maintenance, &config));
assert!(tier_passes(
RecommendationTier::T1CriticalArchitecture,
&config
));
}
#[test]
fn test_tier_passes_t4_shown() {
let config = FilterConfig {
show_t4: true,
..Default::default()
};
assert!(tier_passes(RecommendationTier::T4Maintenance, &config));
assert!(tier_passes(
RecommendationTier::T1CriticalArchitecture,
&config
));
}
#[test]
fn test_score_passes() {
assert!(score_passes(5.0, 3.0));
assert!(score_passes(3.0, 3.0));
assert!(!score_passes(2.9, 3.0));
assert!(!score_passes(0.0, 3.0));
}
#[test]
fn test_zero_score_always_filtered() {
assert!(!score_passes(0.0, 0.0));
assert!(!score_passes(0.0, 3.0));
assert!(!score_passes(-1.0, 0.0));
assert!(score_passes(0.1, 0.0));
assert!(score_passes(1.0, 0.0));
}
fn create_test_debt_item() -> super::super::DebtItem {
use super::super::{
ActionableRecommendation, DebtType, ImpactMetrics, Location, UnifiedDebtItem,
UnifiedScore,
};
use crate::priority::semantic_classifier::FunctionRole;
super::super::DebtItem::Function(Box::new(UnifiedDebtItem {
location: Location {
file: "test.rs".into(),
function: "test_fn".to_string(),
line: 1,
},
debt_type: DebtType::ComplexityHotspot {
cyclomatic: 10,
cognitive: 15,
},
unified_score: UnifiedScore {
complexity_factor: 0.0,
coverage_factor: 0.0,
dependency_factor: 0.0,
role_multiplier: 1.0,
final_score: 5.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,
},
function_role: FunctionRole::PureLogic,
recommendation: ActionableRecommendation {
primary_action: "Refactor".to_string(),
rationale: "Test".to_string(),
implementation_steps: vec![],
related_items: vec![],
steps: None,
estimated_effort_hours: None,
},
expected_impact: ImpactMetrics {
coverage_improvement: 0.0,
lines_reduction: 0,
complexity_reduction: 0.0,
risk_reduction: 0.0,
},
transitive_coverage: None,
upstream_dependencies: 0,
downstream_dependencies: 0,
upstream_callers: vec![],
downstream_callees: vec![],
upstream_production_callers: vec![],
upstream_test_callers: vec![],
production_blast_radius: 0,
nesting_depth: 0,
function_length: 10,
cyclomatic_complexity: 1,
cognitive_complexity: 1,
is_pure: None,
purity_confidence: None,
purity_level: None,
god_object_indicators: None,
tier: None,
function_context: None,
context_confidence: None,
contextual_recommendation: None,
pattern_analysis: None,
file_context: None,
context_multiplier: None,
context_type: None,
language_specific: None,
detected_pattern: None,
contextual_risk: None,
file_line_count: None,
responsibility_category: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
context_suggestion: None,
}))
}
#[test]
fn test_t1_bypasses_score_filter() {
let items = vec![ClassifiedItem {
item: create_test_debt_item(),
tier: RecommendationTier::T1CriticalArchitecture,
score: 2.5, }];
let config = FilterConfig::default();
let result = filter_with_metrics(items, &config);
assert_eq!(result.metrics.included, 1, "T1 item should be included");
assert_eq!(
result.metrics.tier_critical_bypass, 1,
"Should track bypass"
);
assert_eq!(
result.metrics.filtered_below_score, 0,
"Score filter not applied"
);
}
#[test]
fn test_t2_bypasses_high_threshold() {
let items = vec![ClassifiedItem {
item: create_test_debt_item(),
tier: RecommendationTier::T2ComplexUntested,
score: 2.0, }];
let config = FilterConfig {
min_score: 5.0, show_t4: false,
};
let result = filter_with_metrics(items, &config);
assert_eq!(
result.metrics.included, 1,
"T2 item should bypass threshold"
);
assert_eq!(result.metrics.tier_critical_bypass, 1);
}
#[test]
fn test_t3_respects_score_threshold() {
let items = vec![ClassifiedItem {
item: create_test_debt_item(),
tier: RecommendationTier::T3TestingGaps,
score: 2.5, }];
let config = FilterConfig::default(); let result = filter_with_metrics(items, &config);
assert_eq!(result.metrics.included, 0, "T3 should be filtered by score");
assert_eq!(result.metrics.filtered_below_score, 1);
assert_eq!(result.metrics.tier_critical_bypass, 0);
}
#[test]
fn test_t4_filtered_by_tier_flag() {
let items = vec![ClassifiedItem {
item: create_test_debt_item(),
tier: RecommendationTier::T4Maintenance,
score: 5.0, }];
let config = FilterConfig {
min_score: 3.0,
show_t4: false, };
let result = filter_with_metrics(items, &config);
assert_eq!(result.metrics.included, 0, "T4 should be filtered by tier");
assert_eq!(result.metrics.filtered_t4_maintenance, 1);
assert_eq!(result.metrics.filtered_below_score, 0);
}
#[test]
fn test_metrics_track_tier_bypass() {
let items = vec![
ClassifiedItem {
tier: RecommendationTier::T1CriticalArchitecture,
score: 1.0,
item: create_test_debt_item(),
},
ClassifiedItem {
tier: RecommendationTier::T1CriticalArchitecture,
score: 2.0,
item: create_test_debt_item(),
},
ClassifiedItem {
tier: RecommendationTier::T2ComplexUntested,
score: 1.5,
item: create_test_debt_item(),
},
];
let config = FilterConfig::default();
let result = filter_with_metrics(items, &config);
assert_eq!(
result.metrics.included, 3,
"All critical tier items included"
);
assert_eq!(
result.metrics.tier_critical_bypass, 3,
"All 3 bypassed score"
);
assert_eq!(result.metrics.filtered_below_score, 0);
}
#[test]
fn test_is_critical_tier_predicate() {
assert!(is_critical_tier(RecommendationTier::T1CriticalArchitecture));
assert!(is_critical_tier(RecommendationTier::T2ComplexUntested));
assert!(!is_critical_tier(RecommendationTier::T3TestingGaps));
assert!(!is_critical_tier(RecommendationTier::T4Maintenance));
}
#[test]
fn test_mixed_tiers_filtered_correctly() {
let items = vec![
ClassifiedItem {
tier: RecommendationTier::T1CriticalArchitecture,
score: 1.0,
item: create_test_debt_item(),
},
ClassifiedItem {
tier: RecommendationTier::T2ComplexUntested,
score: 2.0,
item: create_test_debt_item(),
},
ClassifiedItem {
tier: RecommendationTier::T3TestingGaps,
score: 2.5,
item: create_test_debt_item(),
},
ClassifiedItem {
tier: RecommendationTier::T3TestingGaps,
score: 5.0,
item: create_test_debt_item(),
},
ClassifiedItem {
tier: RecommendationTier::T4Maintenance,
score: 10.0,
item: create_test_debt_item(),
},
];
let config = FilterConfig {
min_score: 3.0,
show_t4: false,
};
let result = filter_with_metrics(items, &config);
assert_eq!(result.metrics.included, 3, "T1, T2, and high-score T3");
assert_eq!(result.metrics.tier_critical_bypass, 2, "T1 and T2 bypassed");
assert_eq!(result.metrics.filtered_below_score, 1, "Low-score T3");
assert_eq!(result.metrics.filtered_t4_maintenance, 1, "T4 by tier");
}
#[test]
fn test_t1_above_threshold_not_counted_as_bypass() {
let items = vec![ClassifiedItem {
item: create_test_debt_item(),
tier: RecommendationTier::T1CriticalArchitecture,
score: 5.0, }];
let config = FilterConfig::default();
let result = filter_with_metrics(items, &config);
assert_eq!(result.metrics.included, 1, "T1 item should be included");
assert_eq!(
result.metrics.tier_critical_bypass, 0,
"No bypass needed for high score"
);
assert_eq!(result.metrics.filtered_below_score, 0);
}
#[test]
fn test_t4_shown_respects_score_threshold() {
let items = vec![ClassifiedItem {
item: create_test_debt_item(),
tier: RecommendationTier::T4Maintenance,
score: 2.0, }];
let config = FilterConfig {
min_score: 3.0,
show_t4: true, };
let result = filter_with_metrics(items, &config);
assert_eq!(
result.metrics.included, 0,
"T4 with show_t4=true should be filtered by score"
);
assert_eq!(result.metrics.filtered_t4_maintenance, 0);
assert_eq!(
result.metrics.filtered_below_score, 1,
"T4 filtered by score, not tier"
);
}
}