Feat/Prioritize previously attempted cards in intraday learning queue

This commit is contained in:
user1823 2025-10-15 13:35:55 +00:00
parent 321e23acb2
commit e97a3a15a7
6 changed files with 71 additions and 5 deletions

View file

@ -38,6 +38,7 @@ pub(crate) struct DueCard {
pub current_deck_id: DeckId,
pub original_deck_id: DeckId,
pub kind: DueCardKind,
pub reps: u32,
}
#[derive(Debug, Clone, Copy)]
@ -87,6 +88,7 @@ impl From<DueCard> for LearningQueueEntry {
due: TimestampSecs(c.due as i64),
id: c.id,
mtime: c.mtime,
reps: c.reps,
}
}
}
@ -274,9 +276,21 @@ fn merge_new(
}
}
fn sort_learning(mut learning: Vec<DueCard>) -> VecDeque<LearningQueueEntry> {
learning.sort_unstable_by(|a, b| a.due.cmp(&b.due));
learning.into_iter().map(LearningQueueEntry::from).collect()
fn sort_learning(learning: Vec<DueCard>) -> VecDeque<LearningQueueEntry> {
// Prioritize intraday learning cards that were previously attempted
// (reps > 0) before never-attempted cards (reps == 0). Preserve due-time
// ordering within each group.
let (mut previously_attempted, mut never_attempted): (Vec<DueCard>, Vec<DueCard>) =
learning.into_iter().partition(|c| c.reps > 0);
previously_attempted.sort_unstable_by(|a, b| a.due.cmp(&b.due));
never_attempted.sort_unstable_by(|a, b| a.due.cmp(&b.due));
previously_attempted
.into_iter()
.chain(never_attempted)
.map(LearningQueueEntry::from)
.collect()
}
impl Collection {
@ -543,4 +557,49 @@ mod test {
col.set_current_deck(child.id).unwrap();
assert_eq!(col.card_queue_len(), 0);
}
#[test]
fn intraday_learning_prioritizes_previously_attempted() -> Result<()> {
let mut col = Collection::new();
// create two notes with one card each
let nt = col.get_notetype_by_name("Basic")?.unwrap();
let mut n1 = nt.new_note();
n1.set_field(0, "one")?;
col.add_note(&mut n1, DeckId(1))?;
let mut n2 = nt.new_note();
n2.set_field(0, "two")?;
col.add_note(&mut n2, DeckId(1))?;
// fetch their cards and make them intraday learning cards
let mut c1 = col.storage.get_card_by_ordinal(n1.id, 0)?.unwrap();
let mut c2 = col.storage.get_card_by_ordinal(n2.id, 0)?.unwrap();
c1.queue = CardQueue::Learn;
c2.queue = CardQueue::Learn;
// c1: never attempted, due earlier (0)
c1.due = 0;
c1.reps = 0;
// c2: previously attempted, due later (600)
c2.due = 600;
c2.reps = 1;
let id1 = c1.id;
let id2 = c2.id;
col.update_cards_maybe_undoable(vec![c1, c2], false)?;
// build queues and inspect intraday learning order
let queues = col.build_queues(DeckId(1))?;
let ids: Vec<_> = queues.intraday_learning.iter().map(|e| e.id).collect();
// ensure the previously attempted card appears before the never-attempted one
assert!(
ids.iter().position(|&id| id == id2).unwrap()
< ids.iter().position(|&id| id == id1).unwrap()
);
Ok(())
}
}

View file

@ -55,6 +55,7 @@ impl From<&Card> for QueueEntry {
due: TimestampSecs(card.due as i64),
id: card.id,
mtime: card.mtime,
reps: card.reps,
});
}
CardQueue::New => MainQueueEntryKind::New,

View file

@ -12,6 +12,7 @@ pub(crate) struct LearningQueueEntry {
pub due: TimestampSecs,
pub id: CardId,
pub mtime: TimestampSecs,
pub reps: u32,
}
impl CardQueues {
@ -71,6 +72,7 @@ impl CardQueues {
due: TimestampSecs(card.due as i64),
id: card.id,
mtime: card.mtime,
reps: card.reps,
};
Some(self.requeue_learning_entry(entry))

View file

@ -4,7 +4,8 @@ SELECT id,
cast(ivl AS integer),
cast(mod AS integer),
did,
odid
odid,
reps
FROM cards
WHERE did IN (
SELECT id

View file

@ -3,7 +3,8 @@ SELECT id,
due,
cast(mod AS integer),
did,
odid
odid,
reps
FROM cards
WHERE did IN (
SELECT id

View file

@ -267,6 +267,7 @@ impl super::SqliteStorage {
mtime: row.get(3)?,
current_deck_id: row.get(4)?,
original_deck_id: row.get(5)?,
reps: row.get(6)?,
kind: DueCardKind::Learning,
})
}
@ -306,6 +307,7 @@ impl super::SqliteStorage {
mtime: row.get(4)?,
current_deck_id: row.get(5)?,
original_deck_id: row.get(6)?,
reps: row.get(7)?,
kind,
})? {
break;