use std::collections::{HashMap, HashSet};
pub const OPTIMISM_FLOOR: f32 = 0.05;
pub const OPTIMISM_CEIL: f32 = 0.35;
pub const DEFAULT_SKILL: f32 = 0.0;
pub const DEFAULT_OPTIMISM: f32 = 0.1;
#[derive(Debug, Clone)]
pub struct ProfileState {
pub skill: f32,
pub optimism_bias: f32,
pub last_seen: HashMap<String, u64>,
pub category_count: HashMap<String, u32>,
pub resolved_set: HashSet<String>,
pub interaction_count: u64,
pub extended: HashMap<String, f32>,
pub extended_str: HashMap<String, String>,
}
impl ProfileState {
pub fn new() -> Self {
Self {
skill: DEFAULT_SKILL,
optimism_bias: DEFAULT_OPTIMISM,
last_seen: HashMap::new(),
category_count: HashMap::new(),
resolved_set: HashSet::new(),
interaction_count: 0,
extended: HashMap::new(),
extended_str: HashMap::new(),
}
}
pub fn target(&self) -> f32 {
(self.skill + self.optimism_bias).clamp(0.0, 1.0)
}
pub fn mean_category_count(&self) -> f32 {
if self.category_count.is_empty() {
return 1.0;
}
let total: u32 = self.category_count.values().sum();
total as f32 / self.category_count.len() as f32
}
pub fn elapsed_since(&self, item_id: &str, now: u64) -> Option<u64> {
self.last_seen.get(item_id).map(|&t| now.saturating_sub(t))
}
}
impl Default for ProfileState {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn target_clamps_to_one() {
let mut s = ProfileState::new();
s.skill = 0.9;
s.optimism_bias = 0.35;
assert!(s.target() <= 1.0);
}
#[test]
fn mean_category_count_empty() {
let s = ProfileState::new();
assert_eq!(s.mean_category_count(), 1.0);
}
#[test]
fn mean_category_count_correct() {
let mut s = ProfileState::new();
s.category_count.insert("a".into(), 4);
s.category_count.insert("b".into(), 2);
assert!((s.mean_category_count() - 3.0).abs() < 1e-6);
}
#[test]
fn elapsed_since_never_seen() {
let s = ProfileState::new();
assert_eq!(s.elapsed_since("x", 100), None);
}
}