ts-fsrs 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fsrs.d.ts +61 -11
- package/lib/fsrs.js +73 -16
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/scheduler.js +2 -2
- package/package.json +8 -3
package/lib/fsrs.d.ts
CHANGED
|
@@ -3,33 +3,83 @@ import { Card, FSRSParameters, SchedulingCard } from "./index";
|
|
|
3
3
|
export default class FSRS {
|
|
4
4
|
private param;
|
|
5
5
|
private readonly intervalModifier;
|
|
6
|
-
private seed
|
|
6
|
+
private seed?;
|
|
7
7
|
constructor(param?: FSRSParameters);
|
|
8
8
|
repeat: (card: Card, now: dayjs.Dayjs) => import("./models").SchedulingLog;
|
|
9
|
+
get_retrievability: (card: Card, now: dayjs.Dayjs) => undefined | string;
|
|
9
10
|
init_ds(s: SchedulingCard): void;
|
|
10
11
|
/**
|
|
11
12
|
*
|
|
12
|
-
* @param s
|
|
13
|
-
* @param last_d
|
|
14
|
-
* @param last_s
|
|
15
|
-
* @param retrievability
|
|
13
|
+
* @param s scheduling Card
|
|
14
|
+
* @param last_d Difficulty
|
|
15
|
+
* @param last_s Stability
|
|
16
|
+
* @param retrievability Retrievability
|
|
16
17
|
*/
|
|
17
18
|
next_ds(s: SchedulingCard, last_d: number, last_s: number, retrievability: number): void;
|
|
18
19
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
20
|
+
* The formula used is :
|
|
21
|
+
* $$S_0(G) = w_0 + G \cdot w_1$$
|
|
22
|
+
* $$\max \{S_0,0.1\}$$
|
|
23
|
+
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
24
|
+
* @return Stability (interval when R=90%)
|
|
21
25
|
*/
|
|
22
|
-
init_stability(
|
|
26
|
+
init_stability(g: number): number;
|
|
23
27
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
28
|
+
* The formula used is :
|
|
29
|
+
* $$D_0(G) = w_2 + (G-2) \cdot w_3$$
|
|
30
|
+
* $$\min \{\max \{D_0(G),1\},10\}$$
|
|
31
|
+
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
32
|
+
* @return Difficulty D \in [1,10]
|
|
26
33
|
*/
|
|
27
|
-
init_difficulty(
|
|
34
|
+
init_difficulty(g: number): number;
|
|
28
35
|
apply_fuzz(ivl: number): number;
|
|
29
36
|
next_interval(s: number): number;
|
|
37
|
+
/**
|
|
38
|
+
* The formula used is :
|
|
39
|
+
* $$next_d = D + w_4 \cdot (R - 2)$$
|
|
40
|
+
* $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
|
|
41
|
+
* @param d
|
|
42
|
+
* @param r Rating[0.again,1.hard,2.good,3.easy]
|
|
43
|
+
* @return next_D
|
|
44
|
+
*/
|
|
30
45
|
next_difficulty(d: number, r: number): number;
|
|
46
|
+
/**
|
|
47
|
+
* The formula used is :
|
|
48
|
+
* $$\min \{\max \{D_0,1\},10\}$$
|
|
49
|
+
*/
|
|
31
50
|
constrain_difficulty(difficulty: number): number;
|
|
51
|
+
/**
|
|
52
|
+
* The formula used is :
|
|
53
|
+
* $$w_5 \cdot init +(1 - w_5) \cdot current$$
|
|
54
|
+
* @param init $$w_2 : D_0(2) = w_2 + (R-2) \cdot w_3= w_2$$
|
|
55
|
+
* @param current $$D + w_4 \cdot (R - 2)$$
|
|
56
|
+
* @return difficulty
|
|
57
|
+
*/
|
|
32
58
|
mean_reversion(init: number, current: number): number;
|
|
59
|
+
/**
|
|
60
|
+
* The formula used is :
|
|
61
|
+
* $$S^\prime_r(D,S,R) = S\cdot(e^{w_6}\cdot (11-D)\cdot S^{w_7}\cdot(e^{w_8\cdot(1-R)}-1)+1)$$
|
|
62
|
+
* @param d Difficulty D \in [1,10]
|
|
63
|
+
* @param s Stability (interval when R=90%)
|
|
64
|
+
* @param r Retrievability (probability of recall)
|
|
65
|
+
* @return S^\prime_r new stability after recall
|
|
66
|
+
*/
|
|
33
67
|
next_recall_stability(d: number, s: number, r: number): number;
|
|
68
|
+
/**
|
|
69
|
+
* The formula used is :
|
|
70
|
+
* $$S^\prime_f(D,S,R) = w_9\cdot D^{w_{10}}\cdot S^{w_{11}}\cdot e^{w_{12}\cdot(1-R)}.$$
|
|
71
|
+
* @param d Difficulty D \in [1,10]
|
|
72
|
+
* @param s Stability (interval when R=90%)
|
|
73
|
+
* @param r Retrievability (probability of recall)
|
|
74
|
+
* @return S^\prime_f new stability after forgetting
|
|
75
|
+
*/
|
|
34
76
|
next_forget_stability(d: number, s: number, r: number): number;
|
|
77
|
+
/**
|
|
78
|
+
* The formula used is :
|
|
79
|
+
* $$R(t,S) = 0.9^{\frac{t}{S}}$$
|
|
80
|
+
* @param t t days since the last review
|
|
81
|
+
* @param s Stability (interval when R=90%)
|
|
82
|
+
* @return r Retrievability (probability of recall)
|
|
83
|
+
*/
|
|
84
|
+
current_retrievability(t: number, s: number): number;
|
|
35
85
|
}
|
package/lib/fsrs.js
CHANGED
|
@@ -30,8 +30,8 @@ class FSRS {
|
|
|
30
30
|
break;
|
|
31
31
|
case index_1.State.Learning:
|
|
32
32
|
case index_1.State.Relearning:
|
|
33
|
-
hard_interval =
|
|
34
|
-
good_interval =
|
|
33
|
+
hard_interval = 0;
|
|
34
|
+
good_interval = this.next_interval(s.good.stability);
|
|
35
35
|
easy_interval = Math.max(this.next_interval(s.easy.stability * this.param.easy_bonus), good_interval + 1);
|
|
36
36
|
s.schedule(now, hard_interval, good_interval, easy_interval);
|
|
37
37
|
break;
|
|
@@ -39,7 +39,7 @@ class FSRS {
|
|
|
39
39
|
const interval = card.elapsed_days;
|
|
40
40
|
const last_d = card.difficulty;
|
|
41
41
|
const last_s = card.stability;
|
|
42
|
-
const retrievability =
|
|
42
|
+
const retrievability = this.current_retrievability(interval, last_s);
|
|
43
43
|
this.next_ds(s, last_d, last_s, retrievability);
|
|
44
44
|
hard_interval = this.next_interval(last_s * this.param.hard_factor);
|
|
45
45
|
good_interval = this.next_interval(s.good.stability);
|
|
@@ -51,9 +51,15 @@ class FSRS {
|
|
|
51
51
|
}
|
|
52
52
|
return s.record_log(card, now);
|
|
53
53
|
};
|
|
54
|
+
this.get_retrievability = (card, now) => {
|
|
55
|
+
if (card.state !== index_1.State.Review) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const t = Math.max(now.diff((0, dayjs_1.default)(card.last_review), "days"), 0);
|
|
59
|
+
return (this.current_retrievability(t, card.stability) * 100).toFixed(2) + '%';
|
|
60
|
+
};
|
|
54
61
|
this.param = param || (0, index_1.generatorParameters)();
|
|
55
62
|
this.intervalModifier = Math.log(this.param.request_retention) / Math.log(0.9);
|
|
56
|
-
this.seed = this.param.enable_fuzz ? "" : String(this.param.maximum_interval) + String(this.param.easy_bonus);
|
|
57
63
|
}
|
|
58
64
|
init_ds(s) {
|
|
59
65
|
s.again.difficulty = this.init_difficulty(index_1.Rating.Again);
|
|
@@ -67,10 +73,10 @@ class FSRS {
|
|
|
67
73
|
}
|
|
68
74
|
/**
|
|
69
75
|
*
|
|
70
|
-
* @param s
|
|
71
|
-
* @param last_d
|
|
72
|
-
* @param last_s
|
|
73
|
-
* @param retrievability
|
|
76
|
+
* @param s scheduling Card
|
|
77
|
+
* @param last_d Difficulty
|
|
78
|
+
* @param last_s Stability
|
|
79
|
+
* @param retrievability Retrievability
|
|
74
80
|
*/
|
|
75
81
|
next_ds(s, last_d, last_s, retrievability) {
|
|
76
82
|
s.again.difficulty = this.next_difficulty(last_d, index_1.Rating.Again);
|
|
@@ -83,18 +89,24 @@ class FSRS {
|
|
|
83
89
|
s.easy.stability = this.next_recall_stability(s.easy.difficulty, last_s, retrievability);
|
|
84
90
|
}
|
|
85
91
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
92
|
+
* The formula used is :
|
|
93
|
+
* $$S_0(G) = w_0 + G \cdot w_1$$
|
|
94
|
+
* $$\max \{S_0,0.1\}$$
|
|
95
|
+
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
96
|
+
* @return Stability (interval when R=90%)
|
|
88
97
|
*/
|
|
89
|
-
init_stability(
|
|
90
|
-
return Math.max(this.param.w[0] + this.param.w[1] *
|
|
98
|
+
init_stability(g) {
|
|
99
|
+
return Math.max(this.param.w[0] + this.param.w[1] * g, 0.1);
|
|
91
100
|
}
|
|
92
101
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
102
|
+
* The formula used is :
|
|
103
|
+
* $$D_0(G) = w_2 + (G-2) \cdot w_3$$
|
|
104
|
+
* $$\min \{\max \{D_0(G),1\},10\}$$
|
|
105
|
+
* @param g Grade (rating at Anki) [0.again,1.hard,2.good,3.easy]
|
|
106
|
+
* @return Difficulty D \in [1,10]
|
|
95
107
|
*/
|
|
96
|
-
init_difficulty(
|
|
97
|
-
return Math.min(Math.max(this.param.w[2] + this.param.w[3] * (
|
|
108
|
+
init_difficulty(g) {
|
|
109
|
+
return Math.min(Math.max(this.param.w[2] + this.param.w[3] * (g - 2), 1), 10);
|
|
98
110
|
}
|
|
99
111
|
apply_fuzz(ivl) {
|
|
100
112
|
if (!this.param.enable_fuzz || ivl < 2.5)
|
|
@@ -110,24 +122,69 @@ class FSRS {
|
|
|
110
122
|
const newInterval = this.apply_fuzz(s * this.intervalModifier);
|
|
111
123
|
return Math.min(Math.max(Math.round(newInterval), 1), this.param.maximum_interval);
|
|
112
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* The formula used is :
|
|
127
|
+
* $$next_d = D + w_4 \cdot (R - 2)$$
|
|
128
|
+
* $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
|
|
129
|
+
* @param d
|
|
130
|
+
* @param r Rating[0.again,1.hard,2.good,3.easy]
|
|
131
|
+
* @return next_D
|
|
132
|
+
*/
|
|
113
133
|
next_difficulty(d, r) {
|
|
114
134
|
const next_d = d + this.param.w[4] * (r - 2);
|
|
115
135
|
return this.constrain_difficulty(this.mean_reversion(this.param.w[2], next_d));
|
|
116
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* The formula used is :
|
|
139
|
+
* $$\min \{\max \{D_0,1\},10\}$$
|
|
140
|
+
*/
|
|
117
141
|
constrain_difficulty(difficulty) {
|
|
118
142
|
return Math.min(Math.max(Number(difficulty.toFixed(2)), 1), 10);
|
|
119
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* The formula used is :
|
|
146
|
+
* $$w_5 \cdot init +(1 - w_5) \cdot current$$
|
|
147
|
+
* @param init $$w_2 : D_0(2) = w_2 + (R-2) \cdot w_3= w_2$$
|
|
148
|
+
* @param current $$D + w_4 \cdot (R - 2)$$
|
|
149
|
+
* @return difficulty
|
|
150
|
+
*/
|
|
120
151
|
mean_reversion(init, current) {
|
|
121
152
|
return this.param.w[5] * init + (1 - this.param.w[5]) * current;
|
|
122
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* The formula used is :
|
|
156
|
+
* $$S^\prime_r(D,S,R) = S\cdot(e^{w_6}\cdot (11-D)\cdot S^{w_7}\cdot(e^{w_8\cdot(1-R)}-1)+1)$$
|
|
157
|
+
* @param d Difficulty D \in [1,10]
|
|
158
|
+
* @param s Stability (interval when R=90%)
|
|
159
|
+
* @param r Retrievability (probability of recall)
|
|
160
|
+
* @return S^\prime_r new stability after recall
|
|
161
|
+
*/
|
|
123
162
|
next_recall_stability(d, s, r) {
|
|
124
163
|
return s * (1 + Math.exp(this.param.w[6]) *
|
|
125
164
|
(11 - d) *
|
|
126
165
|
Math.pow(s, this.param.w[7]) *
|
|
127
166
|
(Math.exp((1 - r) * this.param.w[8]) - 1));
|
|
128
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* The formula used is :
|
|
170
|
+
* $$S^\prime_f(D,S,R) = w_9\cdot D^{w_{10}}\cdot S^{w_{11}}\cdot e^{w_{12}\cdot(1-R)}.$$
|
|
171
|
+
* @param d Difficulty D \in [1,10]
|
|
172
|
+
* @param s Stability (interval when R=90%)
|
|
173
|
+
* @param r Retrievability (probability of recall)
|
|
174
|
+
* @return S^\prime_f new stability after forgetting
|
|
175
|
+
*/
|
|
129
176
|
next_forget_stability(d, s, r) {
|
|
130
177
|
return this.param.w[9] * Math.pow(d, this.param.w[10]) * Math.pow(s, this.param.w[11]) * Math.exp((1 - r) * this.param.w[12]);
|
|
131
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* The formula used is :
|
|
181
|
+
* $$R(t,S) = 0.9^{\frac{t}{S}}$$
|
|
182
|
+
* @param t t days since the last review
|
|
183
|
+
* @param s Stability (interval when R=90%)
|
|
184
|
+
* @return r Retrievability (probability of recall)
|
|
185
|
+
*/
|
|
186
|
+
current_retrievability(t, s) {
|
|
187
|
+
return Math.exp(Math.log(0.9) * t / s);
|
|
188
|
+
}
|
|
132
189
|
}
|
|
133
190
|
exports.default = FSRS;
|
package/lib/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ declare const generatorParameters: (props?: {
|
|
|
18
18
|
w: number[];
|
|
19
19
|
enable_fuzz: boolean;
|
|
20
20
|
};
|
|
21
|
-
declare const FSRS_Version = 1;
|
|
21
|
+
declare const FSRS_Version = 1.2;
|
|
22
22
|
export { fsrs, FSRS_Version, State, Rating, SchedulingCard, createEmptyCard, generatorParameters };
|
|
23
23
|
export type { StateType, RatingType, ReviewLog, Card, SchedulingLog, FSRSParameters };
|
|
24
24
|
export { default_request_retention, default_maximum_interval, default_easy_bonus, default_hard_factor, default_w, default_enable_fuzz };
|
package/lib/index.js
CHANGED
package/lib/scheduler.js
CHANGED
|
@@ -23,7 +23,7 @@ class SchedulingCard {
|
|
|
23
23
|
}
|
|
24
24
|
else if (state === models_1.State.Learning || state === models_1.State.Relearning) {
|
|
25
25
|
this.again.state = state;
|
|
26
|
-
this.hard.state =
|
|
26
|
+
this.hard.state = state;
|
|
27
27
|
this.good.state = models_1.State.Review;
|
|
28
28
|
this.easy.state = models_1.State.Review;
|
|
29
29
|
}
|
|
@@ -42,7 +42,7 @@ class SchedulingCard {
|
|
|
42
42
|
this.good.scheduled_days = good_interval;
|
|
43
43
|
this.easy.scheduled_days = easy_interval;
|
|
44
44
|
this.again.due = now.add(5, 'minutes');
|
|
45
|
-
this.hard.due = now.add(hard_interval, 'days');
|
|
45
|
+
this.hard.due = hard_interval > 0 ? now.add(hard_interval, 'days') : now.add(10, 'minutes');
|
|
46
46
|
this.good.due = now.add(good_interval, 'days');
|
|
47
47
|
this.easy.due = now.add(easy_interval, 'days');
|
|
48
48
|
return this;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-fsrs",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "ts-fsrs is a TypeScript package used to implement the Free Spaced Repetition Scheduler (FSRS) algorithm. It helps developers apply FSRS to their flashcard applications, thereby improving the user learning experience.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -20,11 +20,16 @@
|
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"test": "ts-node test/index.ts",
|
|
23
|
-
"build": "tsc"
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"test_publish": "yalc publish"
|
|
24
25
|
},
|
|
25
26
|
"author": "ishiko",
|
|
26
27
|
"license": "MIT",
|
|
27
|
-
"files": [
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/**/*",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
28
33
|
"repository": {
|
|
29
34
|
"type": "git",
|
|
30
35
|
"url": "git+https://github.com/ishiko732/ts-fsrs.git"
|