diff --git a/proto/anki/stats.proto b/proto/anki/stats.proto index 8c14b5803..d664dd78c 100644 --- a/proto/anki/stats.proto +++ b/proto/anki/stats.proto @@ -176,7 +176,6 @@ message RevlogEntry { LEARNING = 0; REVIEW = 1; RELEARNING = 2; - // Recent Anki versions only use this when rescheduling disabled FILTERED = 3; MANUAL = 4; } @@ -190,3 +189,7 @@ message RevlogEntry { uint32 taken_millis = 8; ReviewKind review_kind = 9; } + +message RevlogEntries { + repeated RevlogEntry entries = 1; +} \ No newline at end of file diff --git a/rslib/src/scheduler/fsrs/weights.rs b/rslib/src/scheduler/fsrs/weights.rs index c5d5f27fe..9b58aa654 100644 --- a/rslib/src/scheduler/fsrs/weights.rs +++ b/rslib/src/scheduler/fsrs/weights.rs @@ -1,16 +1,21 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use std::iter; +use std::path::Path; use std::thread; use std::time::Duration; +use anki_io::write_file; use anki_proto::scheduler::ComputeFsrsWeightsResponse; +use anki_proto::stats::revlog_entry; +use anki_proto::stats::RevlogEntries; use fsrs::CombinedProgressState; use fsrs::FSRSItem; use fsrs::FSRSReview; use fsrs::ModelEvaluation; use fsrs::FSRS; use itertools::Itertools; +use prost::Message; use crate::prelude::*; use crate::revlog::RevlogEntry; @@ -72,6 +77,23 @@ impl Collection { .get_revlog_entries_for_searched_cards_in_card_order() } + /// Used for exporting revlogs for algorithm research. + pub fn export_revlog_entries_to_protobuf( + &mut self, + min_entries: usize, + target_path: &Path, + ) -> Result<()> { + let entries = self.storage.get_all_revlog_entries_in_card_order()?; + if entries.len() < min_entries { + return Err(AnkiError::FsrsInsufficientData); + } + let entries = entries.into_iter().map(revlog_entry_to_proto).collect_vec(); + let entries = RevlogEntries { entries }; + let data = entries.encode_to_vec(); + write_file(target_path, data)?; + Ok(()) + } + pub fn evaluate_weights(&mut self, weights: &Weights, search: &str) -> Result { let timing = self.timing_today()?; let mut anki_progress = self.new_progress_handler::(); @@ -208,6 +230,26 @@ impl RevlogEntry { } } +fn revlog_entry_to_proto(e: RevlogEntry) -> anki_proto::stats::RevlogEntry { + anki_proto::stats::RevlogEntry { + id: e.id.0, + cid: e.cid.0, + usn: 0, + button_chosen: e.button_chosen as u32, + interval: e.interval, + last_interval: e.last_interval, + ease_factor: e.ease_factor, + taken_millis: e.taken_millis, + review_kind: match e.review_kind { + RevlogReviewKind::Learning => revlog_entry::ReviewKind::Learning, + RevlogReviewKind::Review => revlog_entry::ReviewKind::Review, + RevlogReviewKind::Relearning => revlog_entry::ReviewKind::Relearning, + RevlogReviewKind::Filtered => revlog_entry::ReviewKind::Filtered, + RevlogReviewKind::Manual => revlog_entry::ReviewKind::Manual, + } as i32, + } +} + #[cfg(test)] pub(crate) mod tests { use super::*;