Feat/Add pure R-based review sorting orders

https://forums.ankiweb.net/t/follow-up-make-descending-retrievability-a-pure-r-sort/67082
This commit is contained in:
user1823 2025-10-19 09:36:41 +00:00
parent 62252f7216
commit 82456d8c74
11 changed files with 96 additions and 62 deletions

View file

@ -47,9 +47,8 @@ decks-decreasing-intervals = Decreasing intervals
decks-oldest-seen-first = Oldest seen first decks-oldest-seen-first = Oldest seen first
# Combobox entry: Sort the cards in random order # Combobox entry: Sort the cards in random order
decks-random = Random decks-random = Random
# Combobox entry: Sort the cards by relative overdueness, in descending order (most overdue to least overdue)
decks-relative-overdueness = Relative overdueness
## These strings are no longer used - you do not need to translate them if they ## These strings are no longer used - you do not need to translate them if they
## are not already translated. ## are not already translated.
# Combobox entry: Sort the cards by relative overdueness, in descending order (most overdue to least overdue)
decks-relative-overdueness = Relative overdueness

View file

@ -104,6 +104,7 @@ message DeckConfig {
REVIEW_CARD_ORDER_EASE_DESCENDING = 6; REVIEW_CARD_ORDER_EASE_DESCENDING = 6;
REVIEW_CARD_ORDER_RETRIEVABILITY_ASCENDING = 7; REVIEW_CARD_ORDER_RETRIEVABILITY_ASCENDING = 7;
REVIEW_CARD_ORDER_RETRIEVABILITY_DESCENDING = 11; REVIEW_CARD_ORDER_RETRIEVABILITY_DESCENDING = 11;
REVIEW_CARD_ORDER_RELATIVE_OVERDUENESS = 12;
REVIEW_CARD_ORDER_RANDOM = 8; REVIEW_CARD_ORDER_RANDOM = 8;
REVIEW_CARD_ORDER_ADDED = 9; REVIEW_CARD_ORDER_ADDED = 9;
REVIEW_CARD_ORDER_REVERSE_ADDED = 10; REVIEW_CARD_ORDER_REVERSE_ADDED = 10;

View file

@ -101,6 +101,7 @@ message Deck {
REVERSE_ADDED = 7; REVERSE_ADDED = 7;
RETRIEVABILITY_ASCENDING = 8; RETRIEVABILITY_ASCENDING = 8;
RETRIEVABILITY_DESCENDING = 9; RETRIEVABILITY_DESCENDING = 9;
RELATIVE_OVERDUENESS = 10;
} }
string search = 1; string search = 1;

View file

@ -66,6 +66,7 @@ fn search_order_label(order: FilteredSearchOrder, tr: &I18n) -> String {
FilteredSearchOrder::RetrievabilityDescending => { FilteredSearchOrder::RetrievabilityDescending => {
tr.deck_config_sort_order_retrievability_descending() tr.deck_config_sort_order_retrievability_descending()
} }
FilteredSearchOrder::RelativeOverdueness => tr.decks_relative_overdueness(),
} }
.into() .into()
} }

View file

@ -117,7 +117,7 @@ fn create_review_priority_fn(
} }
// Not implemented yet // Not implemented yet
Added | ReverseAdded => None, Added | ReverseAdded | RelativeOverdueness => None,
} }
} }

View file

@ -469,7 +469,7 @@ mod test {
cards.push(card); cards.push(card);
} }
col.update_cards_maybe_undoable(cards, false)?; col.update_cards_maybe_undoable(cards, false)?;
col.set_deck_review_order(&mut deck, ReviewCardOrder::RetrievabilityAscending); col.set_deck_review_order(&mut deck, ReviewCardOrder::RelativeOverdueness);
assert_eq!(col.queue_as_due_and_ivl(deck.id), expected_queue); assert_eq!(col.queue_as_due_and_ivl(deck.id), expected_queue);
Ok(()) Ok(())

View file

@ -1,7 +1,6 @@
// Copyright: Ankitects Pty Ltd and contributors // Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use crate::card::CardQueue;
use crate::decks::FilteredSearchOrder; use crate::decks::FilteredSearchOrder;
use crate::decks::FilteredSearchTerm; use crate::decks::FilteredSearchTerm;
use crate::scheduler::timing::SchedTimingToday; use crate::scheduler::timing::SchedTimingToday;
@ -40,6 +39,11 @@ pub(crate) fn order_and_limit_for_search(
build_retrievability_query(fsrs, today, next_day_at, now, SqlSortOrder::Descending); build_retrievability_query(fsrs, today, next_day_at, now, SqlSortOrder::Descending);
&temp_string &temp_string
} }
FilteredSearchOrder::RelativeOverdueness => {
temp_string =
format!("extract_fsrs_relative_retrievability(data, case when odue !=0 then odue else due end, ivl, {today}, {next_day_at}, {now}) asc");
&temp_string
}
}; };
format!("{}, fnvhash(c.id, c.mod) limit {}", order, term.limit) format!("{}, fnvhash(c.id, c.mod) limit {}", order, term.limit)
@ -54,15 +58,9 @@ fn build_retrievability_query(
) -> String { ) -> String {
if fsrs { if fsrs {
format!( format!(
"extract_fsrs_relative_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, ivl, {today}, {next_day_at}, {now}) {order}" "extract_fsrs_retrievability(c.data, case when c.odue !=0 then c.odue else c.due end, ivl, {today}, {next_day_at}, {now}) {order}"
) )
} else { } else {
format!( format!("")
"
(case when queue={rev_queue} and due <= {today}
then (ivl / cast({today}-due+0.001 as real)) else 100000+due end) {order}",
rev_queue = CardQueue::Review as i8,
today = today
)
} }
} }

View file

@ -808,6 +808,9 @@ pub(crate) enum ReviewOrderSubclause {
timing: SchedTimingToday, timing: SchedTimingToday,
order: SqlSortOrder, order: SqlSortOrder,
}, },
RelativeOverdueness {
timing: SchedTimingToday,
},
Added, Added,
ReverseAdded, ReverseAdded,
} }
@ -837,7 +840,15 @@ impl fmt::Display for ReviewOrderSubclause {
let next_day_at = timing.next_day_at.0; let next_day_at = timing.next_day_at.0;
let now = timing.now.0; let now = timing.now.0;
temp_string = temp_string =
format!("extract_fsrs_relative_retrievability(data, case when odue !=0 then odue else due end, ivl, {today}, {next_day_at}, {now}) {order}"); format!("extract_fsrs_retrievability(data, case when odue !=0 then odue else due end, ivl, {today}, {next_day_at}, {now}) {order}");
&temp_string
}
ReviewOrderSubclause::RelativeOverdueness { timing } => {
let today = timing.days_elapsed;
let next_day_at = timing.next_day_at.0;
let now = timing.now.0;
temp_string =
format!("extract_fsrs_relative_retrievability(data, case when odue !=0 then odue else due end, ivl, {today}, {next_day_at}, {now}) asc");
&temp_string &temp_string
} }
ReviewOrderSubclause::Added => "nid asc, ord asc", ReviewOrderSubclause::Added => "nid asc, ord asc",
@ -872,6 +883,9 @@ fn review_order_sql(order: ReviewCardOrder, timing: SchedTimingToday, fsrs: bool
ReviewCardOrder::RetrievabilityDescending => { ReviewCardOrder::RetrievabilityDescending => {
build_retrievability_clauses(fsrs, timing, SqlSortOrder::Descending) build_retrievability_clauses(fsrs, timing, SqlSortOrder::Descending)
} }
ReviewCardOrder::RelativeOverdueness => {
vec![ReviewOrderSubclause::RelativeOverdueness { timing }]
}
ReviewCardOrder::Random => vec![], ReviewCardOrder::Random => vec![],
ReviewCardOrder::Added => vec![ReviewOrderSubclause::Added], ReviewCardOrder::Added => vec![ReviewOrderSubclause::Added],
ReviewCardOrder::ReverseAdded => vec![ReviewOrderSubclause::ReverseAdded], ReviewCardOrder::ReverseAdded => vec![ReviewOrderSubclause::ReverseAdded],

View file

@ -654,7 +654,7 @@ impl SqliteStorage {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum SqlSortOrder { pub enum SqlSortOrder {
Ascending, Ascending,
Descending, Descending,
@ -686,8 +686,7 @@ mod test {
col.answer_easy(); col.answer_easy();
let timing = col.timing_today()?; let timing = col.timing_today()?;
let order = SqlSortOrder::Ascending; let sql_func = ReviewOrderSubclause::RelativeOverdueness { timing }
let sql_func = ReviewOrderSubclause::RetrievabilityFsrs { timing, order }
.to_string() .to_string()
.replace(" asc", ""); .replace(" asc", "");
let sql = format!("select {sql_func} from cards"); let sql = format!("select {sql_func} from cards");

View file

@ -69,7 +69,9 @@ export function newSortOrderChoices(): Choice<DeckConfig_Config_NewCardSortOrder
]; ];
} }
export function reviewOrderChoices(fsrs: boolean): Choice<DeckConfig_Config_ReviewCardOrder>[] { export function reviewOrderChoices(
fsrs: boolean,
): Choice<DeckConfig_Config_ReviewCardOrder>[] {
return [ return [
...[ ...[
{ {
@ -94,14 +96,11 @@ export function reviewOrderChoices(fsrs: boolean): Choice<DeckConfig_Config_Revi
}, },
], ],
...difficultyOrders(fsrs), ...difficultyOrders(fsrs),
...retrievabilityOrders(fsrs),
...[ ...[
{ {
label: tr.deckConfigSortOrderRetrievabilityAscending(), label: tr.decksRelativeOverdueness(),
value: DeckConfig_Config_ReviewCardOrder.RETRIEVABILITY_ASCENDING, value: DeckConfig_Config_ReviewCardOrder.RELATIVE_OVERDUENESS,
},
{
label: tr.deckConfigSortOrderRetrievabilityDescending(),
value: DeckConfig_Config_ReviewCardOrder.RETRIEVABILITY_DESCENDING,
}, },
{ {
label: tr.deckConfigSortOrderRandom(), label: tr.deckConfigSortOrderRandom(),
@ -202,11 +201,15 @@ export function questionActionChoices(): Choice<DeckConfig_Config_QuestionAction
function difficultyOrders(fsrs: boolean): Choice<DeckConfig_Config_ReviewCardOrder>[] { function difficultyOrders(fsrs: boolean): Choice<DeckConfig_Config_ReviewCardOrder>[] {
const order = [ const order = [
{ {
label: fsrs ? tr.deckConfigSortOrderDescendingDifficulty() : tr.deckConfigSortOrderAscendingEase(), label: fsrs
? tr.deckConfigSortOrderDescendingDifficulty()
: tr.deckConfigSortOrderAscendingEase(),
value: DeckConfig_Config_ReviewCardOrder.EASE_ASCENDING, value: DeckConfig_Config_ReviewCardOrder.EASE_ASCENDING,
}, },
{ {
label: fsrs ? tr.deckConfigSortOrderAscendingDifficulty() : tr.deckConfigSortOrderDescendingEase(), label: fsrs
? tr.deckConfigSortOrderAscendingDifficulty()
: tr.deckConfigSortOrderDescendingEase(),
value: DeckConfig_Config_ReviewCardOrder.EASE_DESCENDING, value: DeckConfig_Config_ReviewCardOrder.EASE_DESCENDING,
}, },
]; ];
@ -215,3 +218,21 @@ function difficultyOrders(fsrs: boolean): Choice<DeckConfig_Config_ReviewCardOrd
} }
return order; return order;
} }
function retrievabilityOrders(
fsrs: boolean,
): Choice<DeckConfig_Config_ReviewCardOrder>[] {
if (!fsrs) {
return [];
}
return [
{
label: tr.deckConfigSortOrderRetrievabilityAscending(),
value: DeckConfig_Config_ReviewCardOrder.RETRIEVABILITY_ASCENDING,
},
{
label: tr.deckConfigSortOrderRetrievabilityDescending(),
value: DeckConfig_Config_ReviewCardOrder.RETRIEVABILITY_DESCENDING,
},
];
}