From 69c4efea8afd30b7060020f9b8cb56dd9723e422 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 24 Sep 2023 13:07:11 +1000 Subject: [PATCH] Use memory state to calculate relative overdueness --- rslib/src/storage/card/mod.rs | 13 ++++++++- rslib/src/storage/sqlite.rs | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/rslib/src/storage/card/mod.rs b/rslib/src/storage/card/mod.rs index c17479128..3313ca27b 100644 --- a/rslib/src/storage/card/mod.rs +++ b/rslib/src/storage/card/mod.rs @@ -707,6 +707,9 @@ enum ReviewOrderSubclause { RelativeOverdueness { today: u32, }, + RelativeOverduenessFsrs { + today: u32, + }, } impl fmt::Display for ReviewOrderSubclause { @@ -726,6 +729,10 @@ impl fmt::Display for ReviewOrderSubclause { temp_string = format!("ivl / cast({today}-due+0.001 as real)", today = today); &temp_string } + ReviewOrderSubclause::RelativeOverduenessFsrs { today } => { + temp_string = format!("extract_fsrs_relative_overdueness(data, due, {today}) desc"); + &temp_string + } }; write!(f, "{}", clause) } @@ -751,7 +758,11 @@ fn review_order_sql(order: ReviewCardOrder, today: u32, fsrs: bool) -> String { ReviewOrderSubclause::EaseDescending }], ReviewCardOrder::RelativeOverdueness => { - vec![ReviewOrderSubclause::RelativeOverdueness { today }] + vec![if fsrs { + ReviewOrderSubclause::RelativeOverduenessFsrs { today } + } else { + ReviewOrderSubclause::RelativeOverdueness { today } + }] } ReviewCardOrder::Random => vec![], }; diff --git a/rslib/src/storage/sqlite.rs b/rslib/src/storage/sqlite.rs index ddf81be68..30390a954 100644 --- a/rslib/src/storage/sqlite.rs +++ b/rslib/src/storage/sqlite.rs @@ -73,6 +73,7 @@ fn open_or_create_collection_db(path: &Path) -> Result { add_extract_custom_data_function(&db)?; add_extract_fsrs_variable(&db)?; add_extract_fsrs_retrievability(&db)?; + add_extract_fsrs_relative_overdueness(&db)?; db.create_collation("unicase", unicase_compare)?; @@ -306,6 +307,56 @@ fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> { ) } +/// eg. extract_fsrs_retrievability(card.data, card.due, timing.days_elapsed) -> +/// float | null. The higher the number, the more overdue. +fn add_extract_fsrs_relative_overdueness(db: &Connection) -> rusqlite::Result<()> { + db.create_scalar_function( + "extract_fsrs_relative_overdueness", + 3, + FunctionFlags::SQLITE_DETERMINISTIC, + move |ctx| { + assert_eq!(ctx.len(), 3, "called with unexpected number of arguments"); + + let Ok(card_data) = ctx.get_raw(0).as_str() else { + return Ok(None); + }; + if card_data.is_empty() { + return Ok(None); + } + let card_data = &CardData::from_str(card_data); + let Ok(due) = ctx.get_raw(1).as_i64() else { + return Ok(None); + }; + if due > 365_000 { + // learning card + return Ok(None); + } + let Ok(days_elapsed) = ctx.get_raw(2).as_i64() else { + return Ok(None); + }; + let Some(state) = card_data.memory_state() else { + return Ok(None); + }; + let Some(mut desired_retrievability) = card_data.fsrs_desired_retention else { + return Ok(None); + }; + // avoid div by zero + desired_retrievability = desired_retrievability.max(0.0001); + + let review_day = due.saturating_sub(state.stability as i64); + let days_elapsed = days_elapsed.saturating_sub(review_day) as u32; + let current_retrievability = FSRS::new(None) + .unwrap() + .current_retrievability(state.into(), days_elapsed) + .max(0.0001); + + Ok(Some( + (1. / current_retrievability - 1.) / (1. / desired_retrievability - 1.), + )) + }, + ) +} + /// Fetch schema version from database. /// Return (must_create, version) fn schema_version(db: &Connection) -> Result<(bool, u8)> {