From c0a67c3eaa2229a7383ba9ef57beb7dce3958be4 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 1 Sep 2025 16:47:02 +0100 Subject: [PATCH] Added: start_memorized --- ftl/core/deck-config.ftl | 3 +-- proto/anki/scheduler.proto | 5 +++-- rslib/src/scheduler/fsrs/simulator.rs | 7 +++++-- ts/routes/deck-options/SimulatorModal.svelte | 1 + ts/routes/graphs/simulator.ts | 10 +++++++--- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/ftl/core/deck-config.ftl b/ftl/core/deck-config.ftl index da3e4ea34..608e9b078 100644 --- a/ftl/core/deck-config.ftl +++ b/ftl/core/deck-config.ftl @@ -522,8 +522,7 @@ deck-config-save-options-to-preset-confirm = Overwrite the options in your curre # specific date. deck-config-fsrs-simulator-radio-memorized = Memorized deck-config-fsrs-simulator-radio-ratio = Time / Memorized Ratio -# $time here is pre-formatted e.g. "10 Seconds" -deck-config-fsrs-simulator-ratio-tooltip = { $time } per memorized card +deck-config-fsrs-simulator-ratio-tooltip = { $time } memorized cards per hour ## Messages related to the FSRS scheduler’s health check. The health check determines whether the correlation between FSRS predictions and your memory is good or bad. It can be optionally triggered as part of the "Optimize" function. diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 34b350642..15aaa1d99 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -420,8 +420,9 @@ message SimulateFsrsReviewResponse { message SimulateFsrsWorkloadResponse { map cost = 1; - map memorized = 2; - map review_count = 3; + float start_memorized = 2; + map memorized = 3; + map review_count = 4; } message ComputeOptimalRetentionResponse { diff --git a/rslib/src/scheduler/fsrs/simulator.rs b/rslib/src/scheduler/fsrs/simulator.rs index ef329ee59..7bc6836dc 100644 --- a/rslib/src/scheduler/fsrs/simulator.rs +++ b/rslib/src/scheduler/fsrs/simulator.rs @@ -292,8 +292,7 @@ impl Collection { Ok(( dr, ( - *result.memorized_cnt_per_day.last().unwrap_or(&0.) - - *result.memorized_cnt_per_day.first().unwrap_or(&0.), + *result.memorized_cnt_per_day.last().unwrap_or(&0.), result.cost_per_day.iter().sum::(), result.review_cnt_per_day.iter().sum::() as u32 + result.learn_cnt_per_day.iter().sum::() as u32, @@ -301,7 +300,11 @@ impl Collection { )) }) .collect::>>()?; + let start_memorized = cards + .iter() + .fold(0., |p, c| p + c.retention_on(&req.params, 0.)); Ok(SimulateFsrsWorkloadResponse { + start_memorized, memorized: dr_workload.iter().map(|(k, v)| (*k, v.0)).collect(), cost: dr_workload.iter().map(|(k, v)| (*k, v.1)).collect(), review_count: dr_workload.iter().map(|(k, v)| (*k, v.2)).collect(), diff --git a/ts/routes/deck-options/SimulatorModal.svelte b/ts/routes/deck-options/SimulatorModal.svelte index c60f90455..d88f5790d 100644 --- a/ts/routes/deck-options/SimulatorModal.svelte +++ b/ts/routes/deck-options/SimulatorModal.svelte @@ -212,6 +212,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html x: parseInt(dr), timeCost: resp!.cost[dr], memorized: v, + start_memorized: resp!.startMemorized, count: resp!.reviewCount[dr], label: simulationNumber, learnSpan: simulateFsrsRequest.daysToSimulate, diff --git a/ts/routes/graphs/simulator.ts b/ts/routes/graphs/simulator.ts index dbd4a4e7b..e3fd94de0 100644 --- a/ts/routes/graphs/simulator.ts +++ b/ts/routes/graphs/simulator.ts @@ -34,6 +34,7 @@ export interface Point { export type WorkloadPoint = Point & { learnSpan: number; + start_memorized: number; }; export enum SimulateSubgraph { @@ -63,14 +64,17 @@ export function renderWorkloadChart( .range([bounds.marginLeft, bounds.width - bounds.marginRight]); const subgraph_data = ({ - [SimulateWorkloadSubgraph.ratio]: data.map(d => ({ ...d, y: d.memorized / d.timeCost })), + [SimulateWorkloadSubgraph.ratio]: data.map(d => ({ + ...d, + y: (60 * 60 * (d.memorized - d.start_memorized)) / d.timeCost, + })), [SimulateWorkloadSubgraph.time]: data.map(d => ({ ...d, y: d.timeCost / d.learnSpan })), [SimulateWorkloadSubgraph.count]: data.map(d => ({ ...d, y: d.count / d.learnSpan })), [SimulateWorkloadSubgraph.memorized]: data.map(d => ({ ...d, y: d.memorized })), })[subgraph]; const yTickFormat = (n: number): string => { - return subgraph == SimulateWorkloadSubgraph.time || subgraph == SimulateWorkloadSubgraph.ratio + return subgraph == SimulateWorkloadSubgraph.time ? timeSpan(n, true) : n.toString(); }; @@ -84,7 +88,7 @@ export function renderWorkloadChart( const formatY: (value: number) => string = ({ [SimulateWorkloadSubgraph.ratio]: (value: number) => - tr.deckConfigFsrsSimulatorRatioTooltip({ time: timeSpan(value) }), + tr.deckConfigFsrsSimulatorRatioTooltip({ time: value.toFixed(2) }), [SimulateWorkloadSubgraph.time]: (value: number) => tr.statisticsMinutesPerDay({ count: parseFloat((value / 60).toPrecision(2)) }), [SimulateWorkloadSubgraph.count]: (value: number) => tr.statisticsReviewsPerDay({ count: Math.round(value) }),