Fix FSRS retrievability sorting issues

- We must use interval, not stability to infer days_elapsed
- We must use original due date in a filtered deck
- Use retrievability in filtered deck sorting, not just regular sorting
This commit is contained in:
Damien Elmes 2023-11-20 13:59:36 +10:00
parent 1e477406e6
commit 2399bf492a
6 changed files with 26 additions and 15 deletions

View File

@ -98,8 +98,9 @@ impl Collection {
fn build_filtered_deck(&mut self, ctx: DeckFilterContext) -> Result<usize> {
let start = -100_000;
let mut position = start;
let fsrs = self.get_config_bool(BoolKey::Fsrs);
for term in ctx.config.search_terms.iter().take(2) {
position = self.move_cards_matching_term(&ctx, term, position)?;
position = self.move_cards_matching_term(&ctx, term, position, fsrs)?;
}
Ok((position - start) as usize)
@ -112,6 +113,7 @@ impl Collection {
ctx: &DeckFilterContext,
term: &FilteredSearchTerm,
mut position: i32,
fsrs: bool,
) -> Result<i32> {
let search = format!(
"{} -is:suspended -is:buried -deck:filtered",
@ -121,7 +123,7 @@ impl Collection {
format!("({})", term.search)
}
);
let order = order_and_limit_for_search(term, ctx.today, TimestampSecs::now().0);
let order = order_and_limit_for_search(term, ctx.today, TimestampSecs::now().0, fsrs);
for mut card in self.all_cards_for_search_in_order(&search, SortMode::Custom(order))? {
let original = card.clone();

View File

@ -377,7 +377,7 @@ fn card_order_from_sort_column(column: Column, timing: SchedTimingToday) -> Cow<
Column::Stability => "extract_fsrs_variable(c.data, 's') asc".into(),
Column::Difficulty => "extract_fsrs_variable(c.data, 'd') asc".into(),
Column::Retrievability => format!(
"extract_fsrs_retrievability(c.data, c.due, c.ivl, {}) asc",
"extract_fsrs_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, c.ivl, {}) asc",
timing.days_elapsed
)
.into(),

View File

@ -383,7 +383,7 @@ impl SqlWriter<'_> {
let elap = self.col.timing_today()?.days_elapsed;
write!(
self.sql,
"extract_fsrs_retrievability(c.data, c.due, c.ivl, {elap}) {op} {r}"
"extract_fsrs_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, c.ivl, {elap}) {op} {r}"
)
.unwrap()
}

View File

@ -9,6 +9,7 @@ pub(crate) fn order_and_limit_for_search(
term: &FilteredSearchTerm,
today: u32,
current_timestamp: i64,
fsrs: bool,
) -> String {
let temp_string;
let order = match term.order() {
@ -25,13 +26,17 @@ pub(crate) fn order_and_limit_for_search(
&temp_string
}
FilteredSearchOrder::DuePriority => {
temp_string = format!(
temp_string = if fsrs {
format!("extract_fsrs_relative_overdueness(data, due, {today}, ivl) desc")
} else {
format!(
"
(case when queue={rev_queue} and due <= {today}
then (ivl / cast({today}-due+0.001 as real)) else 100000+due end)",
rev_queue = CardQueue::Review as i8,
today = today
);
)
};
&temp_string
}
};

View File

@ -730,7 +730,8 @@ impl fmt::Display for ReviewOrderSubclause {
&temp_string
}
ReviewOrderSubclause::RelativeOverduenessFsrs { today } => {
temp_string = format!("extract_fsrs_relative_overdueness(data, due, {today}) desc");
temp_string =
format!("extract_fsrs_relative_overdueness(data, due, {today}, ivl) desc");
&temp_string
}
};

View File

@ -307,15 +307,15 @@ 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.
/// eg. extract_fsrs_retrievability(card.data, card.due, timing.days_elapsed,
/// card.ivl) -> 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,
4,
FunctionFlags::SQLITE_DETERMINISTIC,
move |ctx| {
assert_eq!(ctx.len(), 3, "called with unexpected number of arguments");
assert_eq!(ctx.len(), 4, "called with unexpected number of arguments");
let Ok(card_data) = ctx.get_raw(0).as_str() else {
return Ok(None);
@ -334,6 +334,9 @@ fn add_extract_fsrs_relative_overdueness(db: &Connection) -> rusqlite::Result<()
let Ok(days_elapsed) = ctx.get_raw(2).as_i64() else {
return Ok(None);
};
let Ok(interval) = ctx.get_raw(3).as_i64() else {
return Ok(None);
};
let Some(state) = card_data.memory_state() else {
return Ok(None);
};
@ -343,7 +346,7 @@ fn add_extract_fsrs_relative_overdueness(db: &Connection) -> rusqlite::Result<()
// avoid div by zero
desired_retrievability = desired_retrievability.max(0.0001);
let review_day = due.saturating_sub(state.stability as i64);
let review_day = due.saturating_sub(interval);
let days_elapsed = days_elapsed.saturating_sub(review_day) as u32;
let current_retrievability = FSRS::new(None)
.unwrap()