use crate::error::CupelError;
use crate::model::ContextItem;
use crate::scorer::Scorer;
#[derive(Debug, Clone)]
pub struct MetadataKeyScorer {
key: String,
value: String,
boost: f64,
}
impl MetadataKeyScorer {
pub fn new(
key: impl Into<String>,
value: impl Into<String>,
boost: f64,
) -> Result<Self, CupelError> {
if !boost.is_finite() || boost <= 0.0 {
return Err(CupelError::ScorerConfig(
"boost must be a finite value greater than 0.0".to_string(),
));
}
Ok(Self {
key: key.into(),
value: value.into(),
boost,
})
}
}
impl Scorer for MetadataKeyScorer {
fn score(&self, item: &ContextItem, _all_items: &[ContextItem]) -> f64 {
match item.metadata().get(&self.key) {
Some(v) if v == &self.value => self.boost,
_ => 1.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::ContextItemBuilder;
use std::collections::HashMap;
fn item_with_meta(key: &str, value: &str) -> ContextItem {
let mut meta = HashMap::new();
meta.insert(key.to_owned(), value.to_owned());
ContextItemBuilder::new("item", 1)
.metadata(meta)
.build()
.unwrap()
}
fn item_no_meta() -> ContextItem {
ContextItemBuilder::new("item", 1).build().unwrap()
}
#[test]
fn match_returns_boost() {
let scorer = MetadataKeyScorer::new("cupel:priority", "high", 1.5).unwrap();
let item = item_with_meta("cupel:priority", "high");
assert_eq!(scorer.score(&item, std::slice::from_ref(&item)), 1.5);
}
#[test]
fn value_mismatch_returns_neutral() {
let scorer = MetadataKeyScorer::new("cupel:priority", "high", 1.5).unwrap();
let item = item_with_meta("cupel:priority", "normal");
assert_eq!(scorer.score(&item, std::slice::from_ref(&item)), 1.0);
}
#[test]
fn key_absent_returns_neutral() {
let scorer = MetadataKeyScorer::new("cupel:priority", "high", 1.5).unwrap();
let item = item_no_meta();
assert_eq!(scorer.score(&item, std::slice::from_ref(&item)), 1.0);
}
#[test]
fn zero_boost_errors() {
assert!(MetadataKeyScorer::new("cupel:priority", "high", 0.0).is_err());
}
#[test]
fn negative_boost_errors() {
assert!(MetadataKeyScorer::new("cupel:priority", "high", -1.0).is_err());
}
#[test]
fn nan_boost_errors() {
assert!(MetadataKeyScorer::new("cupel:priority", "high", f64::NAN).is_err());
}
#[test]
fn infinite_boost_errors() {
assert!(MetadataKeyScorer::new("cupel:priority", "high", f64::INFINITY).is_err());
}
}