use crate::Recommendation;
pub struct Reranker {
diversity_weight: f32,
}
impl Reranker {
#[must_use]
pub fn new(diversity_weight: f32) -> Self {
Self {
diversity_weight: diversity_weight.clamp(0.0, 1.0),
}
}
pub fn rerank_mmr(&self, recommendations: &mut [Recommendation]) {
if recommendations.is_empty() {
return;
}
recommendations.sort_by(|a, b| {
let score_a = self.calculate_mmr_score(a);
let score_b = self.calculate_mmr_score(b);
score_b
.partial_cmp(&score_a)
.unwrap_or(std::cmp::Ordering::Equal)
});
for (idx, rec) in recommendations.iter_mut().enumerate() {
rec.rank = idx + 1;
}
}
fn calculate_mmr_score(&self, rec: &Recommendation) -> f32 {
rec.score * (1.0 - self.diversity_weight)
}
#[must_use]
pub fn interleave(sources: Vec<Vec<Recommendation>>) -> Vec<Recommendation> {
let mut result = Vec::new();
let max_len = sources.iter().map(Vec::len).max().unwrap_or(0);
for i in 0..max_len {
for source in &sources {
if i < source.len() {
result.push(source[i].clone());
}
}
}
for (idx, rec) in result.iter_mut().enumerate() {
rec.rank = idx + 1;
}
result
}
}
impl Default for Reranker {
fn default() -> Self {
Self::new(0.3)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reranker_creation() {
let reranker = Reranker::new(0.5);
assert!((reranker.diversity_weight - 0.5).abs() < f32::EPSILON);
}
}