filtered decks w/ scheduling disabled in v3 now log reviews

This commit is contained in:
Damien Elmes 2021-08-19 20:13:22 +10:00
parent 8830d33826
commit 48c121e4f3
18 changed files with 58 additions and 55 deletions

View file

@ -83,7 +83,7 @@ statistics-counts-young-cards = Young
statistics-counts-mature-cards = Mature statistics-counts-mature-cards = Mature
statistics-counts-suspended-cards = Suspended statistics-counts-suspended-cards = Suspended
statistics-counts-buried-cards = Buried statistics-counts-buried-cards = Buried
statistics-counts-early-cards = Early statistics-counts-filtered-cards = Filtered
statistics-counts-learning-cards = Learning statistics-counts-learning-cards = Learning
statistics-counts-relearning-cards = Relearning statistics-counts-relearning-cards = Relearning
statistics-counts-title = Card Counts statistics-counts-title = Card Counts

View file

@ -49,7 +49,8 @@ message RevlogEntry {
LEARNING = 0; LEARNING = 0;
REVIEW = 1; REVIEW = 1;
RELEARNING = 2; RELEARNING = 2;
EARLY_REVIEW = 3; // Recent Anki versions only use this when rescheduling disabled
FILTERED = 3;
MANUAL = 4; MANUAL = 4;
} }
int64 id = 1; int64 id = 1;

View file

@ -63,9 +63,11 @@ pub enum RevlogReviewKind {
Learning = 0, Learning = 0,
Review = 1, Review = 1,
Relearning = 2, Relearning = 2,
EarlyReview = 3, /// Old Anki versions called this "Cram" or "Early", and assigned it when
/// reviewing cards ahead. It is now only used for filtered decks with
/// rescheduling disabled.
Filtered = 3,
Manual = 4, Manual = 4,
// Preview = 5,
} }
impl Default for RevlogReviewKind { impl Default for RevlogReviewKind {

View file

@ -15,19 +15,19 @@ impl CardStateUpdater {
&mut self, &mut self,
current: CardState, current: CardState,
next: NewState, next: NewState,
) -> Option<RevlogEntryPartial> { ) -> RevlogEntryPartial {
self.card.ctype = CardType::New; self.card.ctype = CardType::New;
self.card.queue = CardQueue::New; self.card.queue = CardQueue::New;
self.card.due = next.position as i32; self.card.due = next.position as i32;
RevlogEntryPartial::maybe_new(current, next.into(), 0.0, self.secs_until_rollover()) RevlogEntryPartial::new(current, next.into(), 0.0, self.secs_until_rollover())
} }
pub(super) fn apply_learning_state( pub(super) fn apply_learning_state(
&mut self, &mut self,
current: CardState, current: CardState,
next: LearnState, next: LearnState,
) -> Option<RevlogEntryPartial> { ) -> RevlogEntryPartial {
self.card.remaining_steps = next.remaining_steps; self.card.remaining_steps = next.remaining_steps;
self.card.ctype = CardType::Learn; self.card.ctype = CardType::Learn;
@ -45,7 +45,7 @@ impl CardStateUpdater {
} }
} }
RevlogEntryPartial::maybe_new(current, next.into(), 0.0, self.secs_until_rollover()) RevlogEntryPartial::new(current, next.into(), 0.0, self.secs_until_rollover())
} }
/// Adds secs + fuzz to current time /// Adds secs + fuzz to current time

View file

@ -42,8 +42,6 @@ pub struct CardAnswer {
pub milliseconds_taken: u32, pub milliseconds_taken: u32,
} }
// fixme: log preview review
/// Holds the information required to determine a given card's /// Holds the information required to determine a given card's
/// current state, and to apply a state change to it. /// current state, and to apply a state change to it.
struct CardStateUpdater { struct CardStateUpdater {
@ -103,7 +101,7 @@ impl CardStateUpdater {
&mut self, &mut self,
current: CardState, current: CardState,
next: CardState, next: CardState,
) -> Result<Option<RevlogEntryPartial>> { ) -> Result<RevlogEntryPartial> {
let revlog = match next { let revlog = match next {
CardState::Normal(normal) => { CardState::Normal(normal) => {
// transitioning from filtered state? // transitioning from filtered state?
@ -141,7 +139,7 @@ impl CardStateUpdater {
&mut self, &mut self,
current: CardState, current: CardState,
next: NormalState, next: NormalState,
) -> Option<RevlogEntryPartial> { ) -> RevlogEntryPartial {
self.card.reps += 1; self.card.reps += 1;
self.card.original_due = 0; self.card.original_due = 0;
@ -259,9 +257,9 @@ impl Collection {
current_state, answer.current_state, current_state, answer.current_state,
))); )));
} }
if let Some(revlog_partial) = updater.apply_study_state(current_state, answer.new_state)? { let revlog_partial = updater.apply_study_state(current_state, answer.new_state)?;
self.add_partial_revlog(revlog_partial, usn, answer)?; self.add_partial_revlog(revlog_partial, usn, answer)?;
}
self.update_deck_stats_from_answer(usn, answer, &updater, original.queue)?; self.update_deck_stats_from_answer(usn, answer, &updater, original.queue)?;
self.maybe_bury_siblings(&original, &updater.config)?; self.maybe_bury_siblings(&original, &updater.config)?;
let timing = updater.timing; let timing = updater.timing;

View file

@ -9,17 +9,16 @@ use crate::{
}; };
impl CardStateUpdater { impl CardStateUpdater {
// fixme: check learning card moved into preview
// restores correctly in both learn and day-learn case
pub(super) fn apply_preview_state( pub(super) fn apply_preview_state(
&mut self, &mut self,
current: CardState, current: CardState,
next: PreviewState, next: PreviewState,
) -> Option<RevlogEntryPartial> { ) -> RevlogEntryPartial {
let revlog = RevlogEntryPartial::new(current, next.into(), 0.0, self.secs_until_rollover());
if next.finished { if next.finished {
self.card self.card
.remove_from_filtered_deck_restoring_queue(SchedulerVersion::V2); .remove_from_filtered_deck_restoring_queue(SchedulerVersion::V2);
return None; return revlog;
} }
self.card.queue = CardQueue::PreviewRepeat; self.card.queue = CardQueue::PreviewRepeat;
@ -34,7 +33,7 @@ impl CardStateUpdater {
} }
} }
RevlogEntryPartial::maybe_new(current, next.into(), 0.0, self.secs_until_rollover()) revlog
} }
} }

View file

@ -12,7 +12,7 @@ impl CardStateUpdater {
&mut self, &mut self,
current: CardState, current: CardState,
next: RelearnState, next: RelearnState,
) -> Option<RevlogEntryPartial> { ) -> RevlogEntryPartial {
self.card.interval = next.review.scheduled_days; self.card.interval = next.review.scheduled_days;
self.card.remaining_steps = next.learning.remaining_steps; self.card.remaining_steps = next.learning.remaining_steps;
self.card.ctype = CardType::Relearn; self.card.ctype = CardType::Relearn;
@ -33,7 +33,7 @@ impl CardStateUpdater {
} }
} }
RevlogEntryPartial::maybe_new( RevlogEntryPartial::new(
current, current,
next.into(), next.into(),
next.review.ease_factor, next.review.ease_factor,

View file

@ -12,7 +12,7 @@ impl CardStateUpdater {
&mut self, &mut self,
current: CardState, current: CardState,
next: ReviewState, next: ReviewState,
) -> Option<RevlogEntryPartial> { ) -> RevlogEntryPartial {
self.card.queue = CardQueue::Review; self.card.queue = CardQueue::Review;
self.card.ctype = CardType::Review; self.card.ctype = CardType::Review;
self.card.interval = next.scheduled_days; self.card.interval = next.scheduled_days;
@ -21,7 +21,7 @@ impl CardStateUpdater {
self.card.lapses = next.lapses; self.card.lapses = next.lapses;
self.card.remaining_steps = 0; self.card.remaining_steps = 0;
RevlogEntryPartial::maybe_new( RevlogEntryPartial::new(
current, current,
next.into(), next.into(),
next.ease_factor, next.ease_factor,

View file

@ -15,24 +15,21 @@ pub struct RevlogEntryPartial {
} }
impl RevlogEntryPartial { impl RevlogEntryPartial {
/// Returns None in the Preview case, since preview cards do not currently log. pub(super) fn new(
pub(super) fn maybe_new(
current: CardState, current: CardState,
next: CardState, next: CardState,
ease_factor: f32, ease_factor: f32,
secs_until_rollover: u32, secs_until_rollover: u32,
) -> Option<Self> { ) -> Self {
current.revlog_kind().map(|review_kind| { let next_interval = next.interval_kind().maybe_as_days(secs_until_rollover);
let next_interval = next.interval_kind().maybe_as_days(secs_until_rollover); let current_interval = current.interval_kind().maybe_as_days(secs_until_rollover);
let current_interval = current.interval_kind().maybe_as_days(secs_until_rollover);
RevlogEntryPartial { RevlogEntryPartial {
interval: next_interval, interval: next_interval,
last_interval: current_interval, last_interval: current_interval,
ease_factor, ease_factor,
review_kind, review_kind: current.revlog_kind(),
} }
})
} }
pub(super) fn into_revlog_entry( pub(super) fn into_revlog_entry(

View file

@ -20,10 +20,10 @@ impl FilteredState {
} }
} }
pub(crate) fn revlog_kind(self) -> Option<RevlogReviewKind> { pub(crate) fn revlog_kind(self) -> RevlogReviewKind {
match self { match self {
FilteredState::Preview(_state) => None, FilteredState::Preview(state) => state.revlog_kind(),
FilteredState::Rescheduling(state) => Some(state.revlog_kind()), FilteredState::Rescheduling(state) => state.revlog_kind(),
} }
} }

View file

@ -40,9 +40,9 @@ impl CardState {
} }
} }
pub(crate) fn revlog_kind(self) -> Option<RevlogReviewKind> { pub(crate) fn revlog_kind(self) -> RevlogReviewKind {
match self { match self {
CardState::Normal(normal) => Some(normal.revlog_kind()), CardState::Normal(normal) => normal.revlog_kind(),
CardState::Filtered(filtered) => filtered.revlog_kind(), CardState::Filtered(filtered) => filtered.revlog_kind(),
} }
} }

View file

@ -2,6 +2,7 @@
// 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 super::{IntervalKind, NextCardStates, StateContext}; use super::{IntervalKind, NextCardStates, StateContext};
use crate::revlog::RevlogReviewKind;
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct PreviewState { pub struct PreviewState {
@ -14,6 +15,10 @@ impl PreviewState {
IntervalKind::InSecs(self.scheduled_secs) IntervalKind::InSecs(self.scheduled_secs)
} }
pub(crate) fn revlog_kind(self) -> RevlogReviewKind {
RevlogReviewKind::Filtered
}
pub(crate) fn next_states(self, ctx: &StateContext) -> NextCardStates { pub(crate) fn next_states(self, ctx: &StateContext) -> NextCardStates {
NextCardStates { NextCardStates {
current: self.into(), current: self.into(),

View file

@ -46,7 +46,7 @@ impl ReviewState {
pub(crate) fn revlog_kind(self) -> RevlogReviewKind { pub(crate) fn revlog_kind(self) -> RevlogReviewKind {
if self.days_late() < 0 { if self.days_late() < 0 {
RevlogReviewKind::EarlyReview RevlogReviewKind::Filtered
} else { } else {
RevlogReviewKind::Review RevlogReviewKind::Review
} }

View file

@ -213,14 +213,14 @@ fn revlog_to_text(e: RevlogEntry, tr: &I18n) -> RevlogText {
RevlogReviewKind::Learning => tr.card_stats_review_log_type_learn().into(), RevlogReviewKind::Learning => tr.card_stats_review_log_type_learn().into(),
RevlogReviewKind::Review => tr.card_stats_review_log_type_review().into(), RevlogReviewKind::Review => tr.card_stats_review_log_type_review().into(),
RevlogReviewKind::Relearning => tr.card_stats_review_log_type_relearn().into(), RevlogReviewKind::Relearning => tr.card_stats_review_log_type_relearn().into(),
RevlogReviewKind::EarlyReview => tr.card_stats_review_log_type_filtered().into(), RevlogReviewKind::Filtered => tr.card_stats_review_log_type_filtered().into(),
RevlogReviewKind::Manual => tr.card_stats_review_log_type_manual().into(), RevlogReviewKind::Manual => tr.card_stats_review_log_type_manual().into(),
}; };
let kind_class = match e.review_kind { let kind_class = match e.review_kind {
RevlogReviewKind::Learning => String::from("revlog-learn"), RevlogReviewKind::Learning => String::from("revlog-learn"),
RevlogReviewKind::Review => String::from("revlog-review"), RevlogReviewKind::Review => String::from("revlog-review"),
RevlogReviewKind::Relearning => String::from("revlog-relearn"), RevlogReviewKind::Relearning => String::from("revlog-relearn"),
RevlogReviewKind::EarlyReview => String::from("revlog-filtered"), RevlogReviewKind::Filtered => String::from("revlog-filtered"),
RevlogReviewKind::Manual => String::from("revlog-manual"), RevlogReviewKind::Manual => String::from("revlog-manual"),
}; };
let rating = e.button_chosen.to_string(); let rating = e.button_chosen.to_string();

View file

@ -65,13 +65,14 @@ export function gatherData(data: Stats.GraphsResponse, range: GraphRange): Graph
break; break;
case ReviewKind.REVIEW: case ReviewKind.REVIEW:
case ReviewKind.EARLY_REVIEW:
if (review.lastInterval < 21) { if (review.lastInterval < 21) {
buttons = young; buttons = young;
} else { } else {
buttons = mature; buttons = mature;
} }
break; break;
case ReviewKind.FILTERED:
break;
} }
buttons[buttonNum - 1] += 1; buttons[buttonNum - 1] += 1;

View file

@ -51,7 +51,7 @@ function gatherData(data: Stats.GraphsResponse, range: GraphRange): Hour[] {
case ReviewKind.REVIEW: case ReviewKind.REVIEW:
case ReviewKind.RELEARNING: case ReviewKind.RELEARNING:
break; // from switch break; // from switch
case ReviewKind.EARLY_REVIEW: case ReviewKind.FILTERED:
case ReviewKind.MANUAL: case ReviewKind.MANUAL:
continue; // next loop iteration continue; // next loop iteration
} }

View file

@ -41,7 +41,7 @@ interface Reviews {
relearn: number; relearn: number;
young: number; young: number;
mature: number; mature: number;
early: number; filtered: number;
} }
export interface GraphData { export interface GraphData {
@ -56,7 +56,7 @@ type BinType = Bin<Map<number, Reviews[]>, number>;
export function gatherData(data: Stats.GraphsResponse): GraphData { export function gatherData(data: Stats.GraphsResponse): GraphData {
const reviewCount = new Map<number, Reviews>(); const reviewCount = new Map<number, Reviews>();
const reviewTime = new Map<number, Reviews>(); const reviewTime = new Map<number, Reviews>();
const empty = { mature: 0, young: 0, learn: 0, relearn: 0, early: 0 }; const empty = { mature: 0, young: 0, learn: 0, relearn: 0, filtered: 0 };
for (const review of data.revlog as Stats.RevlogEntry[]) { for (const review of data.revlog as Stats.RevlogEntry[]) {
if (review.reviewKind == ReviewKind.MANUAL) { if (review.reviewKind == ReviewKind.MANUAL) {
@ -89,9 +89,9 @@ export function gatherData(data: Stats.GraphsResponse): GraphData {
timeEntry.mature += review.takenMillis; timeEntry.mature += review.takenMillis;
} }
break; break;
case ReviewKind.EARLY_REVIEW: case ReviewKind.FILTERED:
countEntry.early += 1; countEntry.filtered += 1;
timeEntry.early += review.takenMillis; timeEntry.filtered += review.takenMillis;
break; break;
} }
} }
@ -106,7 +106,7 @@ function totalsForBin(bin: BinType): number[] {
total[1] += entry[1].relearn; total[1] += entry[1].relearn;
total[2] += entry[1].young; total[2] += entry[1].young;
total[3] += entry[1].mature; total[3] += entry[1].mature;
total[4] += entry[1].early; total[4] += entry[1].filtered;
} }
return total; return total;
@ -246,7 +246,7 @@ export function renderReviews(
[reds(1), tr.statisticsCountsRelearningCards(), valueLabel(totals[1])], [reds(1), tr.statisticsCountsRelearningCards(), valueLabel(totals[1])],
[lighterGreens(1), tr.statisticsCountsYoungCards(), valueLabel(totals[2])], [lighterGreens(1), tr.statisticsCountsYoungCards(), valueLabel(totals[2])],
[darkerGreens(1), tr.statisticsCountsMatureCards(), valueLabel(totals[3])], [darkerGreens(1), tr.statisticsCountsMatureCards(), valueLabel(totals[3])],
[purples(1), tr.statisticsCountsEarlyCards(), valueLabel(totals[4])], [purples(1), tr.statisticsCountsFilteredCards(), valueLabel(totals[4])],
["transparent", tr.statisticsRunningTotal(), valueLabel(cumulative)], ["transparent", tr.statisticsRunningTotal(), valueLabel(cumulative)],
]; ];
for (const [colour, label, detail] of lines) { for (const [colour, label, detail] of lines) {

View file

@ -63,7 +63,7 @@ export function gatherData(data: Stats.GraphsResponse): TodayData {
case ReviewKind.RELEARNING: case ReviewKind.RELEARNING:
relearnCount += 1; relearnCount += 1;
break; break;
case ReviewKind.EARLY_REVIEW: case ReviewKind.FILTERED:
earlyReviewCount += 1; earlyReviewCount += 1;
break; break;
} }