use std::collections::HashSet;
use crate::event::GitEvent;
const MATCH_RATIO_WEIGHT: f32 = 0.7;
const FILE_WEIGHT: f32 = 0.3;
#[derive(Debug, Clone)]
pub struct RelevanceScore {
pub hash: String,
pub score: f32,
pub matched_files: usize,
}
pub fn calculate_relevance<F>(
working_files: &[String],
events: &[&GitEvent],
get_commit_files: F,
) -> Vec<RelevanceScore>
where
F: Fn(&str) -> Option<Vec<String>>,
{
if working_files.is_empty() {
return Vec::new();
}
let working_set: HashSet<&str> = working_files.iter().map(|s| s.as_str()).collect();
events
.iter()
.filter_map(|event| {
let files = get_commit_files(&event.short_hash)?;
let matched = files
.iter()
.filter(|f| working_set.contains(f.as_str()))
.count();
if matched == 0 {
return None;
}
let match_ratio = matched as f32 / working_files.len() as f32;
let file_weight = (matched as f32).sqrt() / (files.len() as f32).sqrt().max(1.0);
let score = (match_ratio * MATCH_RATIO_WEIGHT + file_weight * FILE_WEIGHT).min(1.0);
Some(RelevanceScore {
hash: event.short_hash.clone(),
score,
matched_files: matched,
})
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::GitEvent;
use chrono::Local;
fn create_test_event(hash: &str) -> GitEvent {
GitEvent::commit(
hash.to_string(),
"test".to_string(),
"author".to_string(),
Local::now(),
1,
0,
)
}
#[test]
fn test_calculate_relevance_empty_working_files() {
let event = create_test_event("abc1234");
let events = vec![&event];
let scores = calculate_relevance(&[], &events, |_| Some(vec!["file.rs".to_string()]));
assert!(scores.is_empty());
}
#[test]
fn test_calculate_relevance_with_matches() {
let event = create_test_event("abc1234");
let events = vec![&event];
let working_files = vec!["src/main.rs".to_string(), "src/lib.rs".to_string()];
let scores = calculate_relevance(&working_files, &events, |_| {
Some(vec!["src/main.rs".to_string(), "other.rs".to_string()])
});
assert_eq!(scores.len(), 1);
assert_eq!(scores[0].matched_files, 1);
assert!(scores[0].score > 0.0);
}
#[test]
fn test_calculate_relevance_no_matches() {
let event = create_test_event("abc1234");
let events = vec![&event];
let working_files = vec!["src/main.rs".to_string()];
let scores = calculate_relevance(&working_files, &events, |_| {
Some(vec!["other.rs".to_string()])
});
assert!(scores.is_empty());
}
}