fable/src/course/tracker.rs
2025-04-18 13:18:48 -06:00

141 lines
4.7 KiB
Rust

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<bool>, // 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;
}
}