Use revlog to determine days_elapsed when studying/for card info

Currently prop searches and the retrievability column will continue to
derive the days from the card only, as it's difficulty to integrate revlog
upgrade lookups into those code paths, especially in a performant way.
One possible way we could solve this in the future is to store last_review_day
in the card data, so we can know it even if the due date has been shifted.
Check DB could fill it in for existing cards.
This commit is contained in:
Damien Elmes 2023-09-27 15:50:51 +10:00
parent 66b944b722
commit 0532c1f5b0
6 changed files with 40 additions and 5 deletions

View file

@ -112,14 +112,19 @@ impl Card {
if self.queue == CardQueue::Learn {
Some(TimestampSecs(self.due as i64))
} else if self.is_due_in_days() {
Some(TimestampSecs::now().adding_secs(
((self.original_or_current_due() - timing.days_elapsed as i32).saturating_mul(86400)) as i64,
))
Some(
TimestampSecs::now().adding_secs(
((self.original_or_current_due() - timing.days_elapsed as i32)
.saturating_mul(86400)) as i64,
),
)
} else {
None
}
}
/// This uses card.due and card.ivl to infer the elapsed time. If 'set due
/// date' or an add-on has changed the due date, this won't be accurate.
pub(crate) fn days_since_last_review(&self, timing: &SchedTimingToday) -> Option<u32> {
if !self.is_due_in_days() {
Some(0)

View file

@ -364,10 +364,15 @@ impl Collection {
let item = single_card_revlog_to_item(revlog, timing.next_day_at);
card.set_memory_state(&fsrs, item);
}
let days_elapsed = self
.storage
.time_of_last_review(card.id)?
.map(|ts| ts.elapsed_days_since(timing.next_day_at))
.unwrap_or_default() as u32;
Some(fsrs.next_states(
card.memory_state.map(Into::into),
config.inner.desired_retention,
card.days_since_last_review(&timing).unwrap_or_default(),
days_elapsed,
))
} else {
None

View file

@ -27,9 +27,14 @@ impl Collection {
let (average_secs, total_secs) = average_and_total_secs_strings(&revlog);
let (due_date, due_position) = self.due_date_and_position(&card)?;
let timing = self.timing_today()?;
let days_elapsed = self
.storage
.time_of_last_review(card.id)?
.map(|ts| ts.elapsed_days_since(timing.next_day_at))
.unwrap_or_default() as u32;
let fsrs_retrievability = card
.memory_state
.zip(card.days_since_last_review(&timing))
.zip(Some(days_elapsed))
.map(|(state, days)| {
FSRS::new(None)
.unwrap()

View file

@ -7,6 +7,7 @@ use rusqlite::params;
use rusqlite::types::FromSql;
use rusqlite::types::FromSqlError;
use rusqlite::types::ValueRef;
use rusqlite::OptionalExtension;
use rusqlite::Row;
use super::SqliteStorage;
@ -93,6 +94,15 @@ impl SqliteStorage {
.transpose()
}
/// Determine the the last review time based on the revlog.
pub(crate) fn time_of_last_review(&self, card_id: CardId) -> Result<Option<TimestampSecs>> {
self.db
.prepare_cached(include_str!("time_of_last_review.sql"))?
.query_row([card_id], |row| row.get(0))
.optional()
.map_err(Into::into)
}
/// Only intended to be used by the undo code, as Anki can not sync revlog
/// deletions.
pub(crate) fn remove_revlog_entry(&self, id: RevlogId) -> Result<()> {

View file

@ -0,0 +1,6 @@
SELECT id / 1000
FROM revlog
WHERE cid = $1
AND ease BETWEEN 1 AND 4
ORDER BY id DESC
LIMIT 1

View file

@ -28,6 +28,10 @@ impl TimestampSecs {
(Self::now().0 - self.0).max(0) as u64
}
pub fn elapsed_days_since(self, other: TimestampSecs) -> u64 {
(other.0 - self.0).max(0) as u64 / 86_400
}
pub fn as_millis(self) -> TimestampMillis {
TimestampMillis(self.0 * 1000)
}