use super::extract::SymbolFeatures;
use std::collections::HashMap;
pub struct Scorer {
feature_weights: HashMap<String, f64>,
version: String,
}
impl Scorer {
pub fn new_ast_baseline() -> Self {
let mut weights = HashMap::new();
weights.insert("loc".to_string(), 0.1);
weights.insert("fan_in".to_string(), 0.2);
weights.insert("fan_out".to_string(), 0.15);
weights.insert("complexity".to_string(), 0.25);
weights.insert("cfg_block_count".to_string(), 0.1);
weights.insert("cfg_edge_count".to_string(), 0.1);
weights.insert("conditional_density".to_string(), 0.15);
weights.insert("lifetime".to_string(), -0.05);
weights.insert("churn_count".to_string(), 0.2);
Self {
feature_weights: weights,
version: "v1_ast_baseline".to_string(),
}
}
pub fn with_weights(mut weights: HashMap<String, f64>) -> Self {
let required = vec![
"loc",
"fan_in",
"fan_out",
"complexity",
"cfg_block_count",
"cfg_edge_count",
"conditional_density",
"lifetime",
"churn_count",
];
for feature in required {
if !weights.contains_key(feature) {
weights.insert(feature.to_string(), 0.0);
}
}
Self {
feature_weights: weights,
version: "v1_custom".to_string(),
}
}
pub fn score(&self, features: &SymbolFeatures) -> f64 {
self.feature_weights
.iter()
.map(|(name, weight)| features.get(name).unwrap_or(0.0) * weight)
.sum()
}
pub fn score_batch(&self, batch: &[SymbolFeatures]) -> Vec<f64> {
batch.iter().map(|f| self.score(f)).collect()
}
pub fn version(&self) -> &str {
&self.version
}
pub fn get_weight(&self, feature: &str) -> Option<f64> {
self.feature_weights.get(feature).copied()
}
pub fn weights(&self) -> &HashMap<String, f64> {
&self.feature_weights
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ast_baseline_creation() {
let scorer = Scorer::new_ast_baseline();
assert_eq!(scorer.version(), "v1_ast_baseline");
assert!(scorer.get_weight("complexity").is_some());
assert!(scorer.get_weight("unknown").is_none());
}
#[test]
fn test_score_computation() {
let scorer = Scorer::new_ast_baseline();
let features = SymbolFeatures {
symbol_id: 1,
stable_id: "test".to_string(),
snapshot_id: 100,
loc: 100,
fan_in: 5,
fan_out: 3,
complexity: 8,
cfg_block_count: 10,
cfg_edge_count: 15,
conditional_density: 0.4,
lifetime: 50,
churn_count: 3,
};
let score = scorer.score(&features);
assert!(score > 0.0);
let score2 = scorer.score(&features);
assert_eq!(score, score2);
}
#[test]
fn test_batch_scoring() {
let scorer = Scorer::new_ast_baseline();
let features = vec![
SymbolFeatures {
symbol_id: 1,
stable_id: "test1".to_string(),
snapshot_id: 100,
loc: 100,
fan_in: 5,
fan_out: 3,
complexity: 8,
cfg_block_count: 10,
cfg_edge_count: 15,
conditional_density: 0.4,
lifetime: 50,
churn_count: 3,
},
SymbolFeatures {
symbol_id: 2,
stable_id: "test2".to_string(),
snapshot_id: 100,
loc: 50,
fan_in: 2,
fan_out: 1,
complexity: 4,
cfg_block_count: 5,
cfg_edge_count: 7,
conditional_density: 0.3,
lifetime: 30,
churn_count: 1,
},
];
let scores = scorer.score_batch(&features);
assert_eq!(scores.len(), 2);
assert!(scores[0] > scores[1]);
}
#[test]
fn test_custom_weights() {
let mut weights = HashMap::new();
weights.insert("complexity".to_string(), 1.0);
weights.insert("loc".to_string(), 0.5);
let scorer = Scorer::with_weights(weights);
assert_eq!(scorer.version(), "v1_custom");
assert_eq!(scorer.get_weight("complexity"), Some(1.0));
assert_eq!(scorer.get_weight("loc"), Some(0.5));
assert_eq!(scorer.get_weight("fan_in"), Some(0.0));
}
}