use crossterm::{style::Stylize, terminal::size}; use serde::{Deserialize, Serialize}; use super::units::Module; #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub(crate) struct Tracker { pub(crate) unit: Module, questions_answered: u64, questions_correct: u64, probability_known: f64, results: Vec, // Vector to store correct/incorrect results milestones: usize, milestones_completed: usize, } impl Tracker { pub(crate) fn new(unit: Module) -> Self { Self { unit, questions_answered: 0, questions_correct: 0, probability_known: 0.3, // Start with a 30% chance of knowing the answer results: Vec::new(), milestones: 0, milestones_completed: 0, } } pub(crate) fn quiz_result(&mut self, correct: bool) -> f64 { self.questions_answered += 1; self.results.push(correct); if correct { self.milestones_completed = (self.milestones_completed + 1).min(self.milestones); self.questions_correct += 1; } // Calculate streak of correct answers let mut streak = 0; for &result in self.results.iter().rev() { if result { streak += 1; } else { break; } } let prior = self.probability_known; let slip = if self.questions_answered < 10 { 0.05 // Lower slip rate for beginners } else if self.probability_known > 0.8 { 0.15 // Higher slip rate for advanced students } else { 0.1 // Default slip rate }; let guess = if self.questions_answered < 10 { 0.2 // Lower guess rate for beginners } else if self.probability_known > 0.8 { 0.3 // Higher guess rate for advanced students } else { 0.25 // Default guess rate (average of 4 options) }; let likelihood = if correct { 1.0 - slip } else { guess }; // Adjust posterior based on streak let streak_multiplier = 1.0 + (streak as f64 * 0.1); // Increase probability for streaks let posterior = if prior == 0.0 { likelihood * streak_multiplier // Start with likelihood if prior is zero } else { ((likelihood * prior) / ((likelihood * prior) + ((1.0 - likelihood) * (1.0 - prior)))) * streak_multiplier }; self.probability_known = posterior.min(1.0).max(0.0); self.probability_known } pub(crate) fn get_probability(&self) -> f64 { // self.probability_known if self.milestones == 0 { return 0.0; } if self.milestones_completed == 0 { return 0.0; } self.milestones_completed as f64 / self.milestones as f64 } // pub fn get_milestones(&self) -> usize { // self.milestones // } // pub fn get_milestones_completed(&self) -> usize { // self.milestones_completed // } pub(crate) fn print_progress(&self) { let (cols, _) = size().unwrap(); // let percent = self.probability_known * 100.0; // Assuming `probability_known` is a method in `LessonTracker` let percent = if self.milestones_completed > 0 { (self.milestones_completed as f64) / (self.milestones as f64) * 100.0 // Assuming `probability_known` is a method in `LessonTracker` } else {0.0}; let percent_name = format!("{:>4.0}%", percent).dark_blue(); let unit_name = format!("{} {} ", percent_name, self.unit); let dots = ".".repeat((cols as usize).saturating_sub(unit_name.len() + 11)); match percent { percent if percent > 0.0 => { let progress = percent as usize; let filled_percent = progress / 5; // Each '=' represents 5% let bars = format!( "|{}{}|", "█".repeat(filled_percent), " ".repeat(20_usize.saturating_sub(filled_percent)), ); println!( "{} {} {}", unit_name, dots, bars, ); } _ => { println!( "{} {} |{}|", unit_name, dots, " ".repeat(20), ); } } } pub fn set_milestones(&mut self, milestones: usize) { self.milestones = milestones; } pub fn set_completed_milestones(&mut self, completed_milestones: usize) { self.milestones_completed = completed_milestones; } }