mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Record FSRS difficulty in the review log
Will allow user to see a record of difficulty changes, and allows us to identify reviews that have been done with FSRS vs SM-2, since the valid range is different.
This commit is contained in:
parent
0301ae1d8a
commit
75de1f9709
8 changed files with 62 additions and 10 deletions
|
@ -482,7 +482,7 @@ impl RowContext {
|
|||
self.cards[0]
|
||||
.memory_state
|
||||
.as_ref()
|
||||
.map(|s| format!("{:.0}%", (s.difficulty - 1.0) / 9.0 * 100.0))
|
||||
.map(|s| format!("{:.0}%", s.difficulty() * 100.0))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
|
|
@ -101,10 +101,26 @@ pub struct Card {
|
|||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct FsrsMemoryState {
|
||||
/// The expected memory stability, in days.
|
||||
pub stability: f32,
|
||||
/// A number in the range 1.0-10.0. Use difficulty() for a normalized
|
||||
/// number.
|
||||
pub difficulty: f32,
|
||||
}
|
||||
|
||||
impl FsrsMemoryState {
|
||||
/// Returns the difficulty normalized to a 0.0-1.0 range.
|
||||
pub(crate) fn difficulty(&self) -> f32 {
|
||||
(self.difficulty - 1.0) / 9.0
|
||||
}
|
||||
|
||||
/// Returns the difficulty normalized to a 0.1-1.1 range,
|
||||
/// which is used in revlog entries.
|
||||
pub(crate) fn difficulty_shifted(&self) -> f32 {
|
||||
self.difficulty() + 0.1
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Card {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
|
|
@ -48,7 +48,8 @@ pub struct RevlogEntry {
|
|||
#[serde(rename = "lastIvl", deserialize_with = "deserialize_int_from_number")]
|
||||
pub last_interval: i32,
|
||||
/// Card's ease after answering, stored as 10x the %, eg 2500 represents
|
||||
/// 250%.
|
||||
/// 250%. When FSRS is active, difficulty is normalized to 100-1100 range,
|
||||
/// so a 0 difficulty can be distinguished from SM-2 learning.
|
||||
#[serde(rename = "factor", deserialize_with = "deserialize_int_from_number")]
|
||||
pub ease_factor: u32,
|
||||
/// Amount of milliseconds taken to answer the card.
|
||||
|
|
|
@ -26,7 +26,15 @@ impl CardStateUpdater {
|
|||
self.card.original_position = None;
|
||||
self.card.memory_state = None;
|
||||
|
||||
RevlogEntryPartial::new(current, next.into(), 0.0, self.secs_until_rollover())
|
||||
RevlogEntryPartial::new(
|
||||
current,
|
||||
next.into(),
|
||||
self.card
|
||||
.memory_state
|
||||
.map(|d| d.difficulty_shifted())
|
||||
.unwrap_or_default(),
|
||||
self.secs_until_rollover(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn apply_learning_state(
|
||||
|
@ -55,7 +63,15 @@ impl CardStateUpdater {
|
|||
}
|
||||
}
|
||||
|
||||
RevlogEntryPartial::new(current, next.into(), 0.0, self.secs_until_rollover())
|
||||
RevlogEntryPartial::new(
|
||||
current,
|
||||
next.into(),
|
||||
self.card
|
||||
.memory_state
|
||||
.map(|d| d.difficulty_shifted())
|
||||
.unwrap_or_default(),
|
||||
self.secs_until_rollover(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Adds secs + fuzz to current time
|
||||
|
|
|
@ -42,7 +42,10 @@ impl CardStateUpdater {
|
|||
RevlogEntryPartial::new(
|
||||
current,
|
||||
next.into(),
|
||||
next.review.ease_factor,
|
||||
self.card
|
||||
.memory_state
|
||||
.map(|d| d.difficulty_shifted())
|
||||
.unwrap_or(next.review.ease_factor),
|
||||
self.secs_until_rollover(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,10 @@ impl CardStateUpdater {
|
|||
RevlogEntryPartial::new(
|
||||
current,
|
||||
next.into(),
|
||||
next.ease_factor,
|
||||
self.card
|
||||
.memory_state
|
||||
.map(|d| d.difficulty_shifted())
|
||||
.unwrap_or(next.ease_factor),
|
||||
self.secs_until_rollover(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -15,9 +15,7 @@ impl GraphsContext {
|
|||
if let Some(state) = card.memory_state {
|
||||
*difficulty
|
||||
.eases
|
||||
.entry(round_to_nearest_five(
|
||||
(state.difficulty - 1.0) / 9.0 * 100.0,
|
||||
))
|
||||
.entry(round_to_nearest_five(state.difficulty() * 100.0))
|
||||
.or_insert_with(Default::default) += 1;
|
||||
} else if matches!(card.ctype, CardType::Review | CardType::Relearn) {
|
||||
*eases
|
||||
|
|
|
@ -67,12 +67,27 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
rating: entry.buttonChosen,
|
||||
ratingClass: ratingClass(entry),
|
||||
interval: timeSpan(entry.interval),
|
||||
ease: entry.ease ? `${entry.ease / 10}%` : "",
|
||||
ease: formatEaseOrDifficulty(entry.ease),
|
||||
takenSecs: timeSpan(entry.takenSecs, true),
|
||||
};
|
||||
}
|
||||
|
||||
$: revlogRows = revlog.map(revlogRowFromEntry);
|
||||
|
||||
function formatEaseOrDifficulty(ease: number): string {
|
||||
if (ease === 0) {
|
||||
return "";
|
||||
}
|
||||
const asPct = ease / 10.0;
|
||||
if (asPct <= 110) {
|
||||
// FSRS
|
||||
const unshifted = asPct - 10;
|
||||
return `D:${unshifted.toFixed(0)}%`;
|
||||
} else {
|
||||
// SM-2
|
||||
return `${asPct.toFixed(0)}%`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if revlog.length > 0}
|
||||
|
|
Loading…
Reference in a new issue