mirror of
https://github.com/ankitects/anki.git
synced 2026-01-13 22:13:58 -05:00
Add last_review_time to card data
This commit is contained in:
parent
b250a2f724
commit
91e729be1e
11 changed files with 68 additions and 29 deletions
|
|
@ -51,6 +51,7 @@ message Card {
|
|||
optional FsrsMemoryState memory_state = 20;
|
||||
optional float desired_retention = 21;
|
||||
optional float decay = 22;
|
||||
optional int64 last_review_time_secs = 23;
|
||||
string custom_data = 19;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -128,17 +128,22 @@ impl Card {
|
|||
/// 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(
|
||||
(timing.next_day_at.0 as u32).saturating_sub(self.original_or_current_due() as u32)
|
||||
/ 86_400,
|
||||
)
|
||||
if let Some(last_review_time) = self.last_review_time {
|
||||
Some(timing.next_day_at.elapsed_days_since(last_review_time) as u32)
|
||||
} else {
|
||||
self.due_time(timing).map(|due| {
|
||||
(due.adding_secs(-86_400 * self.interval as i64)
|
||||
.elapsed_secs()
|
||||
/ 86_400) as u32
|
||||
})
|
||||
if !self.is_due_in_days() {
|
||||
Some(
|
||||
(timing.next_day_at.0 as u32)
|
||||
.saturating_sub(self.original_or_current_due() as u32)
|
||||
/ 86_400,
|
||||
)
|
||||
} else {
|
||||
self.due_time(timing).map(|due| {
|
||||
(due.adding_secs(-86_400 * self.interval as i64)
|
||||
.elapsed_secs()
|
||||
/ 86_400) as u32
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ pub struct Card {
|
|||
pub(crate) memory_state: Option<FsrsMemoryState>,
|
||||
pub(crate) desired_retention: Option<f32>,
|
||||
pub(crate) decay: Option<f32>,
|
||||
pub(crate) last_review_time: Option<TimestampSecs>,
|
||||
/// JSON object or empty; exposed through the reviewer for persisting custom
|
||||
/// state
|
||||
pub(crate) custom_data: String,
|
||||
|
|
@ -147,6 +148,7 @@ impl Default for Card {
|
|||
memory_state: None,
|
||||
desired_retention: None,
|
||||
decay: None,
|
||||
last_review_time: None,
|
||||
custom_data: String::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ impl TryFrom<anki_proto::cards::Card> for Card {
|
|||
memory_state: c.memory_state.map(Into::into),
|
||||
desired_retention: c.desired_retention,
|
||||
decay: c.decay,
|
||||
last_review_time: c.last_review_time_secs.map(TimestampSecs),
|
||||
custom_data: c.custom_data,
|
||||
})
|
||||
}
|
||||
|
|
@ -136,6 +137,7 @@ impl From<Card> for anki_proto::cards::Card {
|
|||
memory_state: c.memory_state.map(Into::into),
|
||||
desired_retention: c.desired_retention,
|
||||
decay: c.decay,
|
||||
last_review_time_secs: c.last_review_time.map(|t| t.0),
|
||||
custom_data: c.custom_data,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -334,6 +334,7 @@ impl Collection {
|
|||
self.maybe_bury_siblings(&original, &updater.config)?;
|
||||
let timing = updater.timing;
|
||||
let mut card = updater.into_card();
|
||||
card.last_review_time = Some(answer.answered_at.as_secs());
|
||||
if let Some(data) = answer.custom_data.take() {
|
||||
card.custom_data = data;
|
||||
card.validate_custom_data()?;
|
||||
|
|
@ -448,11 +449,14 @@ impl Collection {
|
|||
)?;
|
||||
card.set_memory_state(&fsrs, item, config.inner.historical_retention)?;
|
||||
}
|
||||
let days_elapsed = self
|
||||
.storage
|
||||
.time_of_last_review(card.id)?
|
||||
.map(|ts| timing.next_day_at.elapsed_days_since(ts))
|
||||
.unwrap_or_default() as u32;
|
||||
let days_elapsed = if let Some(last_review_time) = card.last_review_time {
|
||||
timing.next_day_at.elapsed_days_since(last_review_time) as u32
|
||||
} else {
|
||||
self.storage
|
||||
.time_of_last_review(card.id)?
|
||||
.map(|ts| timing.next_day_at.elapsed_days_since(ts))
|
||||
.unwrap_or_default() as u32
|
||||
};
|
||||
Some(fsrs.next_states(
|
||||
card.memory_state.map(Into::into),
|
||||
config.inner.desired_retention,
|
||||
|
|
|
|||
|
|
@ -36,15 +36,21 @@ impl Card {
|
|||
let new_due = (today + days_from_today) as i32;
|
||||
let fsrs_enabled = self.memory_state.is_some();
|
||||
let new_interval = if fsrs_enabled {
|
||||
let due = self.original_or_current_due();
|
||||
let due_diff = if is_unix_epoch_timestamp(due) {
|
||||
let offset = (due as i64 - next_day_start) / 86_400;
|
||||
let due = (today as i64 + offset) as i32;
|
||||
new_due - due
|
||||
if let Some(last_review_time) = self.last_review_time {
|
||||
let elapsed_days =
|
||||
TimestampSecs(next_day_start).elapsed_days_since(last_review_time);
|
||||
elapsed_days as u32 + days_from_today
|
||||
} else {
|
||||
new_due - due
|
||||
};
|
||||
self.interval.saturating_add_signed(due_diff)
|
||||
let due = self.original_or_current_due();
|
||||
let due_diff = if is_unix_epoch_timestamp(due) {
|
||||
let offset = (due as i64 - next_day_start) / 86_400;
|
||||
let due = (today as i64 + offset) as i32;
|
||||
new_due - due
|
||||
} else {
|
||||
new_due - due
|
||||
};
|
||||
self.interval.saturating_add_signed(due_diff)
|
||||
}
|
||||
} else if force_reset || !matches!(self.ctype, CardType::Review | CardType::Relearn) {
|
||||
days_from_today.max(1)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -30,11 +30,14 @@ impl Collection {
|
|||
|
||||
let (average_secs, total_secs) = average_and_total_secs_strings(&revlog);
|
||||
let timing = self.timing_today()?;
|
||||
let seconds_elapsed = self
|
||||
.storage
|
||||
.time_of_last_review(card.id)?
|
||||
.map(|ts| timing.now.elapsed_secs_since(ts))
|
||||
.unwrap_or_default() as u32;
|
||||
let seconds_elapsed = if let Some(last_review_time) = card.last_review_time {
|
||||
timing.now.elapsed_secs_since(last_review_time) as u32
|
||||
} else {
|
||||
self.storage
|
||||
.time_of_last_review(card.id)?
|
||||
.map(|ts| timing.now.elapsed_secs_since(ts))
|
||||
.unwrap_or_default() as u32
|
||||
};
|
||||
let fsrs_retrievability = card
|
||||
.memory_state
|
||||
.zip(Some(seconds_elapsed))
|
||||
|
|
|
|||
|
|
@ -47,6 +47,12 @@ pub(crate) struct CardData {
|
|||
deserialize_with = "default_on_invalid"
|
||||
)]
|
||||
pub(crate) decay: Option<f32>,
|
||||
#[serde(
|
||||
rename = "lrt",
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "default_on_invalid"
|
||||
)]
|
||||
pub(crate) last_review_time: Option<TimestampSecs>,
|
||||
|
||||
/// A string representation of a JSON object storing optional data
|
||||
/// associated with the card, so v3 custom scheduling code can persist
|
||||
|
|
@ -63,6 +69,7 @@ impl CardData {
|
|||
fsrs_difficulty: card.memory_state.as_ref().map(|m| m.difficulty),
|
||||
fsrs_desired_retention: card.desired_retention,
|
||||
decay: card.decay,
|
||||
last_review_time: card.last_review_time,
|
||||
custom_data: card.custom_data.clone(),
|
||||
}
|
||||
}
|
||||
|
|
@ -169,6 +176,7 @@ mod test {
|
|||
fsrs_difficulty: Some(1.234567),
|
||||
fsrs_desired_retention: Some(0.987654),
|
||||
decay: Some(0.123456),
|
||||
last_review_time: None,
|
||||
custom_data: "".to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ fn row_to_card(row: &Row) -> result::Result<Card, rusqlite::Error> {
|
|||
memory_state: data.memory_state(),
|
||||
desired_retention: data.fsrs_desired_retention,
|
||||
decay: data.decay,
|
||||
last_review_time: data.last_review_time,
|
||||
custom_data: data.custom_data,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -314,7 +314,13 @@ fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> {
|
|||
let Ok(due) = ctx.get_raw(1).as_i64() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let days_elapsed = if due > 365_000 {
|
||||
let days_elapsed = if let Some(last_review_time) = card_data.last_review_time {
|
||||
// Use last_review_time to calculate days_elapsed
|
||||
let Ok(next_day_at) = ctx.get_raw(4).as_i64() else {
|
||||
return Ok(None);
|
||||
};
|
||||
(next_day_at as u32).saturating_sub(last_review_time.0 as u32) / 86_400
|
||||
} else if due > 365_000 {
|
||||
// (re)learning card in seconds
|
||||
let Ok(next_day_at) = ctx.get_raw(4).as_i64() else {
|
||||
return Ok(None);
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ impl From<CardEntry> for Card {
|
|||
memory_state: data.memory_state(),
|
||||
desired_retention: data.fsrs_desired_retention,
|
||||
decay: data.decay,
|
||||
last_review_time: data.last_review_time,
|
||||
custom_data: data.custom_data,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue