use serde::{Deserialize, Serialize}; use std::{collections::HashSet, time::SystemTime}; use crate::learner::Learning; #[derive(Serialize, Deserialize, Debug)] 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, Debug)] pub enum Action { Skip, More, } impl From> for Action { fn from(learning: Option<&Learning>) -> Action { if let Some(ts) = learning { match ts { Learning::SkipExtend | Learning::MoreToSkip(_, _) => Action::Skip, Learning::MoreExtend(_, _) | Learning::SkipToMore(_, _) => Action::More, } } else { Action::Skip } } } impl Trajectory { pub fn prune(&mut self, valid: &HashSet) { self.history.retain_mut(|x| valid.contains(x)); if let Some(ref last_data) = self.last_data && !valid.contains(&last_data.album) { self.last_data = None } } 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 learning = if let Some(ref kind) = self.streak_kind && kind == &action { self.history.push(last_album.clone()); match action { Action::Skip => Learning::SkipExtend, Action::More => Learning::MoreExtend(current_streak, last_album), } } else { self.history.clear(); self.history.push(last_album.clone()); match action { Action::Skip => Learning::MoreToSkip(current_streak, last_album), Action::More => Learning::SkipToMore(current_streak, last_album), } }; self.streak_kind = Some(action); Some(learning) } else { None } } }