logicaffeine_web/
guide_review.rs1use crate::progress::{ExerciseProgress, SrsData, UserProgress};
7use crate::srs::{calculate_next_review, is_due, sm2_update, ResponseQuality};
8use crate::ui::pages::guide::content::SECTIONS;
9
10pub const GUIDE_RECALL_PREFIX: &str = "guide/recall/";
11pub const GUIDE_EX_PREFIX: &str = "guide/ex/";
12
13pub fn recall_key(recall_id: &str) -> String {
15 format!("{GUIDE_RECALL_PREFIX}{recall_id}")
16}
17
18pub fn exercise_key(exercise_id: &str) -> String {
20 format!("{GUIDE_EX_PREFIX}{exercise_id}")
21}
22
23#[derive(Debug, Clone, PartialEq)]
27pub enum GuideCard {
28 Recall {
29 key: String,
30 section_id: String,
31 recall_id: String,
32 question: String,
33 answer: String,
34 },
35 Exercise {
36 key: String,
37 section_id: String,
38 exercise_id: String,
39 },
40}
41
42fn record(progress: &mut UserProgress, key: String, quality: ResponseQuality, today: &str) {
43 let entry = progress
44 .exercises
45 .entry(key.clone())
46 .or_insert_with(|| ExerciseProgress {
47 exercise_id: key,
48 attempts: 0,
49 correct_count: 0,
50 last_attempt: None,
51 srs: SrsData::default(),
52 });
53 entry.attempts += 1;
54 if quality.is_correct() {
55 entry.correct_count += 1;
56 }
57 entry.last_attempt = Some(today.to_string());
58 sm2_update(&mut entry.srs, quality);
59 entry.srs.next_review = Some(calculate_next_review(today, entry.srs.interval));
60 progress.save();
61}
62
63pub fn record_recall_mark(progress: &mut UserProgress, recall_id: &str, quality: u8, today: &str) {
66 record(
67 progress,
68 recall_key(recall_id),
69 ResponseQuality::from_score(quality),
70 today,
71 );
72}
73
74pub fn record_exercise_result(
77 progress: &mut UserProgress,
78 exercise_id: &str,
79 quality: u8,
80 today: &str,
81) {
82 record(
83 progress,
84 exercise_key(exercise_id),
85 ResponseQuality::from_score(quality),
86 today,
87 );
88}
89
90pub fn due_guide_cards(progress: &UserProgress, today: &str) -> Vec<GuideCard> {
94 let mut due = Vec::new();
95 for section in SECTIONS.iter() {
96 for r in section.recap.iter() {
97 let key = recall_key(r.id);
98 if let Some(entry) = progress.exercises.get(&key) {
99 if is_due(entry.srs.next_review.as_deref(), today) {
100 due.push(GuideCard::Recall {
101 key,
102 section_id: section.id.to_string(),
103 recall_id: r.id.to_string(),
104 question: r.question.to_string(),
105 answer: r.answer.to_string(),
106 });
107 }
108 }
109 }
110 for e in section.exercises.iter() {
111 let key = exercise_key(e.id);
112 if let Some(entry) = progress.exercises.get(&key) {
113 if is_due(entry.srs.next_review.as_deref(), today) {
114 due.push(GuideCard::Exercise {
115 key,
116 section_id: section.id.to_string(),
117 exercise_id: e.id.to_string(),
118 });
119 }
120 }
121 }
122 }
123 due
124}