use crate::error::CupelError;
use crate::model::ContextItem;
use crate::scorer::Scorer;
#[derive(Debug, Clone)]
pub struct MetadataTrustScorer {
default_score: f64,
}
impl MetadataTrustScorer {
pub fn new(default_score: f64) -> Result<Self, CupelError> {
if !(0.0..=1.0).contains(&default_score) {
return Err(CupelError::ScorerConfig(
"defaultScore must be in [0.0, 1.0]".to_string(),
));
}
Ok(Self { default_score })
}
}
impl Scorer for MetadataTrustScorer {
fn score(&self, item: &ContextItem, _all_items: &[ContextItem]) -> f64 {
let raw = match item.metadata().get("cupel:trust") {
Some(v) => v,
None => return self.default_score,
};
let parsed = match raw.parse::<f64>() {
Ok(v) => v,
Err(_) => return self.default_score,
};
if !parsed.is_finite() {
return self.default_score;
}
parsed.clamp(0.0, 1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::ContextItemBuilder;
use std::collections::HashMap;
fn item_with_trust(value: &str) -> ContextItem {
let mut meta = HashMap::new();
meta.insert("cupel:trust".to_owned(), value.to_owned());
ContextItemBuilder::new("item", 1)
.metadata(meta)
.build()
.unwrap()
}
fn item_no_trust() -> ContextItem {
ContextItemBuilder::new("item", 1).build().unwrap()
}
#[test]
fn valid_trust_value() {
let scorer = MetadataTrustScorer::new(0.5).unwrap();
let item = item_with_trust("0.85");
assert_eq!(scorer.score(&item, std::slice::from_ref(&item)), 0.85);
}
#[test]
fn key_absent_returns_default() {
let scorer = MetadataTrustScorer::new(0.5).unwrap();
let item = item_no_trust();
assert_eq!(scorer.score(&item, std::slice::from_ref(&item)), 0.5);
}
#[test]
fn unparseable_returns_default() {
let scorer = MetadataTrustScorer::new(0.5).unwrap();
let item = item_with_trust("high");
assert_eq!(scorer.score(&item, std::slice::from_ref(&item)), 0.5);
}
#[test]
fn out_of_range_high_clamped() {
let scorer = MetadataTrustScorer::new(0.5).unwrap();
let item = item_with_trust("1.5");
assert_eq!(scorer.score(&item, std::slice::from_ref(&item)), 1.0);
}
#[test]
fn nan_returns_default() {
let scorer = MetadataTrustScorer::new(0.5).unwrap();
let item = item_with_trust("NaN");
assert_eq!(scorer.score(&item, std::slice::from_ref(&item)), 0.5);
}
#[test]
fn construction_out_of_range_errors() {
assert!(MetadataTrustScorer::new(-0.1).is_err());
assert!(MetadataTrustScorer::new(1.1).is_err());
}
#[test]
fn construction_boundary_values_ok() {
assert!(MetadataTrustScorer::new(0.0).is_ok());
assert!(MetadataTrustScorer::new(1.0).is_ok());
}
}