use serde::{Deserialize, Serialize}; use std::time::SystemTime; use crate::intuition::Learning; #[derive(Serialize, Deserialize)] struct LastData { timestamp: SystemTime, album: String, } #[derive(Serialize, Deserialize, Default)] pub struct Trajectory { last_data: Option, streak_kind: Option, history: Vec, } #[derive(Serialize, Deserialize, Clone, PartialEq)] pub enum Action { Skip, More, } impl From> for Action { fn from(learning: Option<&Learning>) -> Action { if let Some(ts) = learning { match ts { Learning::MoreToSkip(_, _) => Action::Skip, Learning::SkipToMore(_, _) => Action::More, } } else { Action::Skip } } } impl Trajectory { pub fn slice(&self) -> &[String] { &self.history } pub fn log(&mut self, new_album: &str) { self.last_data = Some(LastData { timestamp: SystemTime::now(), album: new_album.to_string(), }); } pub fn step(&mut self, skip_window_secs: u64) -> Option { if let Some(ref last_data) = self.last_data { let action = if SystemTime::now() .duration_since(last_data.timestamp) .ok() .is_some_and(|d| d.as_secs() < skip_window_secs) { Action::Skip } else { Action::More }; let last_album = last_data.album.clone(); let current_streak = self.history.clone(); let learnt_nothing = self .streak_kind .as_ref() .is_some_and(|kind| *kind == action); let learning = match (current_streak.is_empty(), learnt_nothing, &action) { (false, false, Action::Skip) => { Some(Learning::MoreToSkip(current_streak, last_album.clone())) } (false, false, Action::More) => { Some(Learning::SkipToMore(current_streak, last_album.clone())) } _ => None, }; if !learnt_nothing { self.history.clear(); } self.history.push(last_album); self.streak_kind = Some(action); learning } else { None } } }