aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/shared/fsrs.ts
blob: 1cf60babc0cf2620487faa0fe88e60cc3f1a08a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import {
	type Card as FSRSCard,
	type State as FSRSState,
	fsrs,
	type Grade,
} from "ts-fsrs";

const f = fsrs({ enable_fuzz: true });

export interface ScheduleInput {
	state: number;
	due: Date;
	stability: number;
	difficulty: number;
	elapsedDays: number;
	scheduledDays: number;
	reps: number;
	lapses: number;
	lastReview: Date | null;
}

export interface ScheduleResult {
	state: number;
	due: Date;
	stability: number;
	difficulty: number;
	elapsedDays: number;
	scheduledDays: number;
	reps: number;
	lapses: number;
	lastReview: Date;
	/** Days elapsed since the previous review (for ReviewLog). */
	reviewElapsedDays: number;
}

export function computeNextSchedule(
	card: ScheduleInput,
	rating: 1 | 2 | 3 | 4,
	now: Date,
): ScheduleResult {
	const fsrsCard: FSRSCard = {
		due: card.due,
		stability: card.stability,
		difficulty: card.difficulty,
		elapsed_days: card.elapsedDays,
		scheduled_days: card.scheduledDays,
		reps: card.reps,
		lapses: card.lapses,
		state: card.state as FSRSState,
		last_review: card.lastReview ?? undefined,
		learning_steps: 0,
	};

	const result = f.next(fsrsCard, now, rating as Grade);

	const reviewElapsedDays = card.lastReview
		? Math.round(
				(now.getTime() - card.lastReview.getTime()) / (1000 * 60 * 60 * 24),
			)
		: 0;

	return {
		state: result.card.state,
		due: result.card.due,
		stability: result.card.stability,
		difficulty: result.card.difficulty,
		elapsedDays: result.card.elapsed_days,
		scheduledDays: result.card.scheduled_days,
		reps: result.card.reps,
		lapses: result.card.lapses,
		lastReview: now,
		reviewElapsedDays,
	};
}