Skip to content

Commit

Permalink
Calculate elapsed days for intraday learning cards
Browse files Browse the repository at this point in the history
  • Loading branch information
dae committed Dec 13, 2023
1 parent 400686d commit edd38ca
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 42 deletions.
2 changes: 1 addition & 1 deletion rslib/src/browser_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ impl Card {
/// 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)
Some((timing.next_day_at.0 as u32).saturating_sub(self.due.max(0) as u32) / 86_400)
} else {
self.due_time(timing).map(|due| {
due.adding_secs(-86_400 * self.interval as i64)
Expand Down
8 changes: 5 additions & 3 deletions rslib/src/scheduler/filtered/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::decks::FilteredDeck;
use crate::decks::FilteredSearchTerm;
use crate::error::FilteredDeckError;
use crate::prelude::*;
use crate::scheduler::timing::SchedTimingToday;
use crate::search::writer::deck_search;
use crate::search::writer::normalize_search;
use crate::search::SortMode;
Expand All @@ -28,7 +29,7 @@ pub(crate) struct DeckFilterContext<'a> {
pub target_deck: DeckId,
pub config: &'a FilteredDeck,
pub usn: Usn,
pub today: u32,
pub timing: SchedTimingToday,
}

impl Collection {
Expand Down Expand Up @@ -123,7 +124,7 @@ impl Collection {
format!("({})", term.search)
}
);
let order = order_and_limit_for_search(term, ctx.today, TimestampSecs::now().0, fsrs);
let order = order_and_limit_for_search(term, ctx.timing, fsrs);

for mut card in self.all_cards_for_search_in_order(&search, SortMode::Custom(order))? {
let original = card.clone();
Expand Down Expand Up @@ -186,11 +187,12 @@ impl Collection {
}

let config = deck.filtered()?;
let timing = self.timing_today()?;
let ctx = DeckFilterContext {
target_deck: deck.id,
config,
usn,
today: self.timing_today()?.days_elapsed,
timing,
};

self.return_all_cards_in_filtered_deck(deck.id)?;
Expand Down
2 changes: 1 addition & 1 deletion rslib/src/scheduler/queue/builder/gathering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl QueueBuilder {
return Ok(());
}
col.storage.for_each_due_card_in_active_decks(
self.context.timing.days_elapsed,
self.context.timing,
self.context.sort_options.review_order,
kind,
self.context.fsrs,
Expand Down
5 changes: 3 additions & 2 deletions rslib/src/search/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,9 @@ 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, case when c.odue !=0 then c.odue else c.due end, c.ivl, {}) asc",
timing.days_elapsed
"extract_fsrs_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, c.ivl, {}, {}) asc",
timing.days_elapsed,
timing.next_day_at.0
)
.into(),
}
Expand Down
7 changes: 5 additions & 2 deletions rslib/src/search/sqlwriter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,13 @@ impl SqlWriter<'_> {
write!(self.sql, "extract_fsrs_variable(c.data, 'd') {op} {d}").unwrap()
}
PropertyKind::Retrievability(r) => {
let elap = self.col.timing_today()?.days_elapsed;
let (elap, next_day_at) = {
let timing = self.col.timing_today()?;
(timing.days_elapsed, timing.next_day_at)
};
write!(
self.sql,
"extract_fsrs_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, 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}, {next_day_at}) {op} {r}"
)
.unwrap()
}
Expand Down
11 changes: 8 additions & 3 deletions rslib/src/storage/card/filtered.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
use crate::card::CardQueue;
use crate::decks::FilteredSearchOrder;
use crate::decks::FilteredSearchTerm;
use crate::scheduler::timing::SchedTimingToday;

pub(crate) fn order_and_limit_for_search(
term: &FilteredSearchTerm,
today: u32,
current_timestamp: i64,
timing: SchedTimingToday,
fsrs: bool,
) -> String {
let temp_string;
let today = timing.days_elapsed;
let order = match term.order() {
FilteredSearchOrder::OldestReviewedFirst => "(select max(id) from revlog where cid=c.id)",
FilteredSearchOrder::Random => "random()",
Expand All @@ -21,13 +22,17 @@ pub(crate) fn order_and_limit_for_search(
FilteredSearchOrder::Added => "n.id, c.ord",
FilteredSearchOrder::ReverseAdded => "n.id desc",
FilteredSearchOrder::Due => {
let current_timestamp = timing.now.0;
temp_string = format!(
"(case when c.due > 1000000000 then due else (due - {today}) * 86400 + {current_timestamp} end), c.ord");
&temp_string
}
FilteredSearchOrder::DuePriority => {
let next_day_at = timing.next_day_at.0;
temp_string = if fsrs {
format!("extract_fsrs_relative_overdueness(c.data, due, {today}, ivl) desc")
format!(
"extract_fsrs_relative_overdueness(c.data, due, {today}, ivl, {next_day_at}) desc"
)
} else {
format!(
"
Expand Down
23 changes: 14 additions & 9 deletions rslib/src/storage/card/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use crate::scheduler::queue::BuryMode;
use crate::scheduler::queue::DueCard;
use crate::scheduler::queue::DueCardKind;
use crate::scheduler::queue::NewCard;
use crate::scheduler::timing::SchedTimingToday;
use crate::timestamp::TimestampMillis;
use crate::timestamp::TimestampSecs;
use crate::types::Usn;
Expand Down Expand Up @@ -250,7 +251,7 @@ impl super::SqliteStorage {
/// when it returns false or no more cards found.
pub(crate) fn for_each_due_card_in_active_decks<F>(
&self,
day_cutoff: u32,
timing: SchedTimingToday,
order: ReviewCardOrder,
kind: DueCardKind,
fsrs: bool,
Expand All @@ -259,7 +260,7 @@ impl super::SqliteStorage {
where
F: FnMut(DueCard) -> Result<bool>,
{
let order_clause = review_order_sql(order, day_cutoff, fsrs);
let order_clause = review_order_sql(order, timing, fsrs);
let mut stmt = self.db.prepare_cached(&format!(
"{} order by {}",
include_str!("due_cards.sql"),
Expand All @@ -269,7 +270,7 @@ impl super::SqliteStorage {
DueCardKind::Review => CardQueue::Review,
DueCardKind::Learning => CardQueue::DayLearn,
};
let mut rows = stmt.query(params![queue as i8, day_cutoff])?;
let mut rows = stmt.query(params![queue as i8, timing.days_elapsed])?;
while let Some(row) = rows.next()? {
if !func(DueCard {
id: row.get(0)?,
Expand Down Expand Up @@ -708,7 +709,7 @@ enum ReviewOrderSubclause {
today: u32,
},
RelativeOverduenessFsrs {
today: u32,
timing: SchedTimingToday,
},
}

Expand All @@ -729,17 +730,19 @@ impl fmt::Display for ReviewOrderSubclause {
temp_string = format!("ivl / cast({today}-due+0.001 as real)", today = today);
&temp_string
}
ReviewOrderSubclause::RelativeOverduenessFsrs { today } => {
ReviewOrderSubclause::RelativeOverduenessFsrs { timing } => {
let today = timing.days_elapsed;
let next_day_at = timing.next_day_at.0;
temp_string =
format!("extract_fsrs_relative_overdueness(data, due, {today}, ivl) desc");
format!("extract_fsrs_relative_overdueness(data, due, {today}, ivl, {next_day_at}) desc");
&temp_string
}
};
write!(f, "{}", clause)
}
}

fn review_order_sql(order: ReviewCardOrder, today: u32, fsrs: bool) -> String {
fn review_order_sql(order: ReviewCardOrder, timing: SchedTimingToday, fsrs: bool) -> String {
let mut subclauses = match order {
ReviewCardOrder::Day => vec![ReviewOrderSubclause::Day],
ReviewCardOrder::DayThenDeck => vec![ReviewOrderSubclause::Day, ReviewOrderSubclause::Deck],
Expand All @@ -760,9 +763,11 @@ fn review_order_sql(order: ReviewCardOrder, today: u32, fsrs: bool) -> String {
}],
ReviewCardOrder::RelativeOverdueness => {
vec![if fsrs {
ReviewOrderSubclause::RelativeOverduenessFsrs { today }
ReviewOrderSubclause::RelativeOverduenessFsrs { timing }
} else {
ReviewOrderSubclause::RelativeOverdueness { today }
ReviewOrderSubclause::RelativeOverdueness {
today: timing.days_elapsed,
}
}]
}
ReviewCardOrder::Random => vec![],
Expand Down
50 changes: 29 additions & 21 deletions rslib/src/storage/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,14 @@ fn add_extract_fsrs_variable(db: &Connection) -> rusqlite::Result<()> {
}

/// eg. extract_fsrs_retrievability(card.data, card.due, card.ivl,
/// timing.days_elapsed) -> float | null
/// timing.days_elapsed, timing.next_day_at) -> float | null
fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> {
db.create_scalar_function(
"extract_fsrs_retrievability",
4,
5,
FunctionFlags::SQLITE_DETERMINISTIC,
move |ctx| {
assert_eq!(ctx.len(), 4, "called with unexpected number of arguments");

assert_eq!(ctx.len(), 5, "called with unexpected number of arguments");
let Ok(card_data) = ctx.get_raw(0).as_str() else {
return Ok(None);
};
Expand All @@ -286,8 +285,11 @@ fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> {
return Ok(None);
};
let days_elapsed = if due > 365_000 {
// (re)learning card, assume 0 days have elapsed
0
// (re)learning card in seconds
let Ok(next_day_at) = ctx.get_raw(4).as_i64() else {
return Ok(None);
};
(next_day_at as u32).saturating_sub(due.max(0) as u32) / 86_400
} else {
let Ok(ivl) = ctx.get_raw(2).as_i64() else {
return Ok(None);
Expand All @@ -307,15 +309,16 @@ fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> {
)
}

/// eg. extract_fsrs_retrievability(card.data, card.due, timing.days_elapsed,
/// card.ivl) -> float | null. The higher the number, the more overdue.
/// eg. extract_fsrs_relative_overdueness(card.data, card.due,
/// timing.days_elapsed, card.ivl, timing.next_day_at) -> 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",
4,
5,
FunctionFlags::SQLITE_DETERMINISTIC,
move |ctx| {
assert_eq!(ctx.len(), 4, "called with unexpected number of arguments");
assert_eq!(ctx.len(), 5, "called with unexpected number of arguments");

let Ok(card_data) = ctx.get_raw(0).as_str() else {
return Ok(None);
Expand All @@ -327,15 +330,22 @@ fn add_extract_fsrs_relative_overdueness(db: &Connection) -> rusqlite::Result<()
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 Ok(interval) = ctx.get_raw(3).as_i64() else {
return Ok(None);
let days_elapsed = if due > 365_000 {
// (re)learning
let Ok(next_day_at) = ctx.get_raw(4).as_i64() else {
return Ok(None);
};
(next_day_at as u32).saturating_sub(due.max(0) as u32) / 86_400
} else {
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 review_day = due.saturating_sub(interval);

days_elapsed.saturating_sub(review_day) as u32
};
let Some(state) = card_data.memory_state() else {
return Ok(None);
Expand All @@ -346,8 +356,6 @@ 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(interval);
let days_elapsed = days_elapsed.saturating_sub(review_day) as u32;
let current_retrievability = FSRS::new(None)
.unwrap()
.current_retrievability(state.into(), days_elapsed)
Expand Down

0 comments on commit edd38ca

Please sign in to comment.