use std::collections::HashMap;
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct EffectivenessScore {
pub rate: Option<f64>,
pub total: u32,
}
impl EffectivenessScore {
pub const NEUTRAL: f64 = 0.5;
pub fn sort_key(&self) -> (f64, u32) {
(self.rate.unwrap_or(Self::NEUTRAL), self.total)
}
}
pub trait SkillEffectivenessProvider: Send + Sync {
fn score(&self, skill_name: &str) -> Option<EffectivenessScore>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoOpProvider;
impl SkillEffectivenessProvider for NoOpProvider {
fn score(&self, _skill_name: &str) -> Option<EffectivenessScore> {
None
}
}
pub fn no_op_provider() -> &'static dyn SkillEffectivenessProvider {
static NOOP: NoOpProvider = NoOpProvider;
&NOOP
}
#[derive(Debug, Default, Clone)]
pub struct InMemoryProvider {
scores: HashMap<String, EffectivenessScore>,
}
impl InMemoryProvider {
pub fn new(scores: HashMap<String, EffectivenessScore>) -> Self {
Self { scores }
}
pub fn insert(&mut self, name: impl Into<String>, score: EffectivenessScore) {
self.scores.insert(name.into(), score);
}
}
impl SkillEffectivenessProvider for InMemoryProvider {
fn score(&self, skill_name: &str) -> Option<EffectivenessScore> {
self.scores.get(skill_name).copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn neutral_constant_is_half() {
assert_eq!(EffectivenessScore::NEUTRAL, 0.5);
}
#[test]
fn sort_key_uses_neutral_for_unknown_rate() {
let s = EffectivenessScore {
rate: None,
total: 0,
};
assert_eq!(s.sort_key(), (0.5, 0));
}
#[test]
fn sort_key_breaks_ties_by_total() {
let a = EffectivenessScore {
rate: Some(0.8),
total: 50,
};
let b = EffectivenessScore {
rate: Some(0.8),
total: 3,
};
assert!(a.sort_key() > b.sort_key());
}
#[test]
fn no_op_provider_returns_none() {
let p = NoOpProvider;
assert!(p.score("anything").is_none());
}
#[test]
fn no_op_provider_static_works() {
let p = no_op_provider();
assert!(p.score("foo").is_none());
}
#[test]
fn in_memory_provider_returns_inserted_scores() {
let mut p = InMemoryProvider::default();
p.insert(
"foo",
EffectivenessScore {
rate: Some(0.9),
total: 10,
},
);
let s = p.score("foo").expect("foo should be present");
assert_eq!(s.rate, Some(0.9));
assert_eq!(s.total, 10);
assert!(p.score("missing").is_none());
}
}