ts-fsrs 3.0.3 → 3.0.5

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/README.md CHANGED
@@ -30,26 +30,91 @@ const now = new Date('2022-2-2 10:00:00');// new Date();
30
30
  const scheduling_cards = f.repeat(card, now);
31
31
 
32
32
  // console.log(scheduling_cards);
33
- Object.keys(Rating).filter(key => typeof Rating[key as any] === 'number').forEach(key => {
34
- // @ts-ignore
35
- const { log, card } = scheduling_cards[Rating[key]];
36
- console.group(`${key}`);
37
- console.table({
38
- [`card_${key}`]: {
39
- ...card,
40
- due: formatDate(card.due),
41
- last_review: formatDate(card.last_review),
42
- },
43
- });
44
- console.table({
45
- [`log_${key}`]: {
46
- ...log,
47
- review: formatDate(log.review),
48
- },
49
- });
50
- console.groupEnd();
51
- console.log('----------------------------------------------------------------');
52
- });
53
- ```
54
-
55
- > More examples refer to the [Example](https://github.com/ishiko732/ts-fsrs/blob/master/example/index.ts)
33
+ Object.keys(Rating)
34
+ .filter(key => !isNaN(Number(key)))
35
+ .map(key => Number(key) as Rating) // [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy]
36
+ .forEach(grade => {
37
+ const { log, card } = scheduling_cards[grade];
38
+ console.group(`${Rating[grade]}`);
39
+ console.table({
40
+ [`card_${Rating[grade]}`]: {
41
+ ...card,
42
+ due: formatDate(card.due),
43
+ last_review: formatDate(card.last_review as Date),
44
+ },
45
+ });
46
+ console.table({
47
+ [`log_${Rating[grade]}`]: {
48
+ ...log,
49
+ review: formatDate(log.review),
50
+ },
51
+ });
52
+ console.groupEnd();
53
+ console.log('----------------------------------------------------------------');
54
+ });
55
+ ```
56
+
57
+ > More examples refer to the [Example](https://github.com/ishiko732/ts-fsrs/blob/master/example/index.ts)
58
+
59
+
60
+ # Basic Use
61
+
62
+ ## 1. **Initialization**:
63
+ To begin, create an empty card instance and set the current date:
64
+
65
+ ```typescript
66
+ import { Card, createEmptyCard } from "ts-fsrs";
67
+ let card: Card = createEmptyCard();
68
+ ```
69
+
70
+ ## 2. **Parameter Configuration**:
71
+ The library allows for customization of SRS parameters. Use `generatorParameters` to produce the final set of parameters for the SRS algorithm. Here's an example setting a maximum interval:
72
+
73
+ ```typescript
74
+ import { Card, createEmptyCard, generatorParameters, FSRSParameters } from "ts-fsrs";
75
+ let card: Card = createEmptyCard();
76
+ const params: FSRSParameters = generatorParameters({ maximum_interval: 1000 });
77
+ ```
78
+
79
+ ## 3. **Scheduling with FSRS**:
80
+ The core functionality lies in the `fsrs` function. When invoked, it returns a collection of cards scheduled based on different potential user ratings:
81
+
82
+ ```typescript
83
+ import {
84
+ Card,
85
+ createEmptyCard,
86
+ generatorParameters,
87
+ FSRSParameters,
88
+ FSRS,
89
+ RecordLog,
90
+ } from "ts-fsrs";
91
+
92
+ let card: Card = createEmptyCard();
93
+ const f: FSRS = new FSRS(); // or const f: FSRS = fsrs(params);
94
+ let scheduling_cards: RecordLog = f.repeat(card, new Date());
95
+ ```
96
+
97
+ ## 4. **Retrieving Scheduled Cards**:
98
+ Once you have the `scheduling_cards` object, you can retrieve cards based on user ratings. For instance, to access the card scheduled for a 'Good' rating:
99
+
100
+ ```typescript
101
+ const good: RecordLogItem = scheduling_cards[Rating.Good];
102
+ const newCard: Card = good.card;
103
+ ```
104
+
105
+ ## 5. **Understanding Card Attributes**:
106
+ Each `Card` object consists of various attributes that determine its status, scheduling, and other metrics:
107
+
108
+ ```typescript
109
+ type Card = {
110
+ due: Date; // Date when the card is next due for review
111
+ stability: number; // A measure of how well the information is retained
112
+ difficulty: number; // Reflects the inherent difficulty of the card content
113
+ elapsed_days: number; // Days since the card was last reviewed
114
+ scheduled_days: number; // The interval at which the card is next scheduled
115
+ reps: number; // Total number of times the card has been reviewed
116
+ lapses: number; // Times the card was forgotten or remembered incorrectly
117
+ state: State; // The current state of the card (New, Learning, Review, Relearning)
118
+ last_review?: Date; // The most recent review date, if applicable
119
+ };
120
+ ```
@@ -0,0 +1,99 @@
1
+ import { SchedulingCard } from "./index";
2
+ import { FSRSParameters, Rating } from "./models";
3
+ import type { int } from "./type";
4
+ export declare class FSRSAlgorithm {
5
+ protected param: FSRSParameters;
6
+ private readonly intervalModifier;
7
+ protected seed?: string;
8
+ constructor(param: Partial<FSRSParameters>);
9
+ init_ds(s: SchedulingCard): void;
10
+ /**
11
+ * Updates the difficulty and stability values of the scheduling card based on the last difficulty,
12
+ * last stability, and the current retrievability.
13
+ * @param {SchedulingCard} s scheduling Card
14
+ * @param {number} last_d Difficulty
15
+ * @param {number} last_s Stability
16
+ * @param retrievability Retrievability
17
+ */
18
+ next_ds(s: SchedulingCard, last_d: number, last_s: number, retrievability: number): void;
19
+ /**
20
+ * The formula used is :
21
+ * S_0(G) = w_{G-1}
22
+ * \max \{S_0,0.1\}
23
+ * @param g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
24
+ * @return Stability (interval when R=90%)
25
+ */
26
+ init_stability(g: number): number;
27
+ /**
28
+ * The formula used is :
29
+ * $$D_0(G) = w_4 - (G-3) \cdot w_5$$
30
+ * $$\min \{\max \{D_0(G),1\},10\}$$
31
+ * where the D_0(3)=w_4 when the first rating is good.
32
+ * @param {number} g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
33
+ * @return {number} Difficulty D \in [1,10]
34
+ */
35
+ init_difficulty(g: number): number;
36
+ /**
37
+ * If fuzzing is disabled or ivl is less than 2.5, it returns the original interval.
38
+ * @param {number} ivl - The interval to be fuzzed.
39
+ * @return {number} - The fuzzed interval.
40
+ **/
41
+ apply_fuzz(ivl: number): number;
42
+ /**
43
+ * Ref:
44
+ * constructor(param: Partial<FSRSParameters>)
45
+ * this.intervalModifier = 9 * (1 / this.param.request_retention - 1);
46
+ * @param {number} s - Stability (interval when R=90%)
47
+ */
48
+ next_interval(s: number): int;
49
+ /**
50
+ * The formula used is :
51
+ * $$next_d = D - w_6 \cdot (R - 2)$$
52
+ * $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
53
+ * @param {number} d Difficulty D \in [1,10]
54
+ * @param {Rating} g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
55
+ * @return {number} next_D
56
+ */
57
+ next_difficulty(d: number, g: number): number;
58
+ /**
59
+ * The formula used is :
60
+ * $$\min \{\max \{D_0,1\},10\}$$
61
+ * @param {number} difficulty D \in [1,10]
62
+ */
63
+ constrain_difficulty(difficulty: number): number;
64
+ /**
65
+ * The formula used is :
66
+ * $$w_7 \cdot init +(1 - w_7) \cdot current$$
67
+ * @param {number} init $$w_2 : D_0(3) = w_2 + (R-2) \cdot w_3= w_2$$
68
+ * @param {number} current $$D - w_6 \cdot (R - 2)$$
69
+ * @return {number} difficulty
70
+ */
71
+ mean_reversion(init: number, current: number): number;
72
+ /**
73
+ * The formula used is :
74
+ * $$S^\prime_r(D,S,R,G) = S\cdot(e^{w_8}\cdot (11-D)\cdot S^{-w_9}\cdot(e^{w_10\cdot(1-R)}-1)\cdot w_15(if G=2) \cdot w_16(if G=4)+1)$$
75
+ * @param {number} d Difficulty D \in [1,10]
76
+ * @param {number} s Stability (interval when R=90%)
77
+ * @param {number} r Retrievability (probability of recall)
78
+ * @param {Rating} g Grade (Rating[0.again,1.hard,2.good,3.easy])
79
+ * @return {number} S^\prime_r new stability after recall
80
+ */
81
+ next_recall_stability(d: number, s: number, r: number, g: Rating): number;
82
+ /**
83
+ * The formula used is :
84
+ * $$S^\prime_f(D,S,R) = w_11\cdot D^{-w_{12}}\cdot ((S+1)^{w_{13}}-1) \cdot e^{w_{14}\cdot(1-R)}.$$
85
+ * @param {number} d Difficulty D \in [1,10]
86
+ * @param {number} s Stability (interval when R=90%)
87
+ * @param {number} r Retrievability (probability of recall)
88
+ * @return {number} S^\prime_f new stability after forgetting
89
+ */
90
+ next_forget_stability(d: number, s: number, r: number): number;
91
+ /**
92
+ * The formula used is :
93
+ * $$R(t,S) = (1 + \frac{t}{9 \cdot S})^{-1},$$
94
+ * @param {number} t t days since the last review
95
+ * @param {number} s Stability (interval when R=90%)
96
+ * @return {number} r Retrievability (probability of recall)
97
+ */
98
+ current_retrievability(t: number, s: number): number;
99
+ }
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FSRSAlgorithm = void 0;
7
+ const seedrandom_1 = __importDefault(require("seedrandom"));
8
+ const index_1 = require("./index");
9
+ const models_1 = require("./models");
10
+ // Ref: https://github.com/open-spaced-repetition/fsrs4anki/wiki/The-Algorithm#fsrs-v4
11
+ class FSRSAlgorithm {
12
+ param;
13
+ intervalModifier;
14
+ seed;
15
+ constructor(param) {
16
+ this.param = (0, index_1.generatorParameters)(param);
17
+ // Ref: https://github.com/open-spaced-repetition/py-fsrs/blob/ecd68e453611eb808c7367c7a5312d7cadeedf5c/src/fsrs/fsrs.py#L79
18
+ // The formula used is : I(r,s)=9 \cdot s \cdot (\frac{1}{r}-1)
19
+ this.intervalModifier = 9 * (1 / this.param.request_retention - 1);
20
+ }
21
+ init_ds(s) {
22
+ s.again.difficulty = this.init_difficulty(models_1.Rating.Again);
23
+ s.again.stability = this.init_stability(models_1.Rating.Again);
24
+ s.hard.difficulty = this.init_difficulty(models_1.Rating.Hard);
25
+ s.hard.stability = this.init_stability(models_1.Rating.Hard);
26
+ s.good.difficulty = this.init_difficulty(models_1.Rating.Good);
27
+ s.good.stability = this.init_stability(models_1.Rating.Good);
28
+ s.easy.difficulty = this.init_difficulty(models_1.Rating.Easy);
29
+ s.easy.stability = this.init_stability(models_1.Rating.Easy);
30
+ }
31
+ /**
32
+ * Updates the difficulty and stability values of the scheduling card based on the last difficulty,
33
+ * last stability, and the current retrievability.
34
+ * @param {SchedulingCard} s scheduling Card
35
+ * @param {number} last_d Difficulty
36
+ * @param {number} last_s Stability
37
+ * @param retrievability Retrievability
38
+ */
39
+ next_ds(s, last_d, last_s, retrievability) {
40
+ s.again.difficulty = this.next_difficulty(last_d, models_1.Rating.Again);
41
+ s.again.stability = this.next_forget_stability(s.again.difficulty, last_s, retrievability);
42
+ s.hard.difficulty = this.next_difficulty(last_d, models_1.Rating.Hard);
43
+ s.hard.stability = this.next_recall_stability(s.hard.difficulty, last_s, retrievability, models_1.Rating.Hard);
44
+ s.good.difficulty = this.next_difficulty(last_d, models_1.Rating.Good);
45
+ s.good.stability = this.next_recall_stability(s.good.difficulty, last_s, retrievability, models_1.Rating.Good);
46
+ s.easy.difficulty = this.next_difficulty(last_d, models_1.Rating.Easy);
47
+ s.easy.stability = this.next_recall_stability(s.easy.difficulty, last_s, retrievability, models_1.Rating.Easy);
48
+ }
49
+ /**
50
+ * The formula used is :
51
+ * S_0(G) = w_{G-1}
52
+ * \max \{S_0,0.1\}
53
+ * @param g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
54
+ * @return Stability (interval when R=90%)
55
+ */
56
+ init_stability(g) {
57
+ return Math.max(this.param.w[g - 1], 0.1);
58
+ }
59
+ /**
60
+ * The formula used is :
61
+ * $$D_0(G) = w_4 - (G-3) \cdot w_5$$
62
+ * $$\min \{\max \{D_0(G),1\},10\}$$
63
+ * where the D_0(3)=w_4 when the first rating is good.
64
+ * @param {number} g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
65
+ * @return {number} Difficulty D \in [1,10]
66
+ */
67
+ init_difficulty(g) {
68
+ return Math.min(Math.max(this.param.w[4] - (g - 3) * this.param.w[5], 1), 10);
69
+ }
70
+ /**
71
+ * If fuzzing is disabled or ivl is less than 2.5, it returns the original interval.
72
+ * @param {number} ivl - The interval to be fuzzed.
73
+ * @return {number} - The fuzzed interval.
74
+ **/
75
+ apply_fuzz(ivl) {
76
+ if (!this.param.enable_fuzz || ivl < 2.5)
77
+ return ivl;
78
+ const generator = (0, seedrandom_1.default)(this.seed);
79
+ const fuzz_factor = generator();
80
+ ivl = Math.round(ivl);
81
+ const min_ivl = Math.max(2, Math.round(ivl * 0.95 - 1));
82
+ const max_ivl = Math.round(ivl * 1.05 + 1);
83
+ return Math.floor(fuzz_factor * (max_ivl - min_ivl + 1) + min_ivl);
84
+ }
85
+ /**
86
+ * Ref:
87
+ * constructor(param: Partial<FSRSParameters>)
88
+ * this.intervalModifier = 9 * (1 / this.param.request_retention - 1);
89
+ * @param {number} s - Stability (interval when R=90%)
90
+ */
91
+ next_interval(s) {
92
+ const newInterval = this.apply_fuzz(s * this.intervalModifier);
93
+ return Math.min(Math.max(Math.round(newInterval), 1), this.param.maximum_interval);
94
+ }
95
+ /**
96
+ * The formula used is :
97
+ * $$next_d = D - w_6 \cdot (R - 2)$$
98
+ * $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
99
+ * @param {number} d Difficulty D \in [1,10]
100
+ * @param {Rating} g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
101
+ * @return {number} next_D
102
+ */
103
+ next_difficulty(d, g) {
104
+ const next_d = d - this.param.w[6] * (g - 3);
105
+ return this.constrain_difficulty(this.mean_reversion(this.param.w[4], next_d));
106
+ }
107
+ /**
108
+ * The formula used is :
109
+ * $$\min \{\max \{D_0,1\},10\}$$
110
+ * @param {number} difficulty D \in [1,10]
111
+ */
112
+ constrain_difficulty(difficulty) {
113
+ return Math.min(Math.max(Number(difficulty.toFixed(2)), 1), 10);
114
+ }
115
+ /**
116
+ * The formula used is :
117
+ * $$w_7 \cdot init +(1 - w_7) \cdot current$$
118
+ * @param {number} init $$w_2 : D_0(3) = w_2 + (R-2) \cdot w_3= w_2$$
119
+ * @param {number} current $$D - w_6 \cdot (R - 2)$$
120
+ * @return {number} difficulty
121
+ */
122
+ mean_reversion(init, current) {
123
+ return this.param.w[7] * init + (1 - this.param.w[7]) * current;
124
+ }
125
+ /**
126
+ * The formula used is :
127
+ * $$S^\prime_r(D,S,R,G) = S\cdot(e^{w_8}\cdot (11-D)\cdot S^{-w_9}\cdot(e^{w_10\cdot(1-R)}-1)\cdot w_15(if G=2) \cdot w_16(if G=4)+1)$$
128
+ * @param {number} d Difficulty D \in [1,10]
129
+ * @param {number} s Stability (interval when R=90%)
130
+ * @param {number} r Retrievability (probability of recall)
131
+ * @param {Rating} g Grade (Rating[0.again,1.hard,2.good,3.easy])
132
+ * @return {number} S^\prime_r new stability after recall
133
+ */
134
+ next_recall_stability(d, s, r, g) {
135
+ const hard_penalty = models_1.Rating.Hard === g ? this.param.w[15] : 1;
136
+ const easy_bound = models_1.Rating.Easy === g ? this.param.w[16] : 1;
137
+ return (s *
138
+ (1 +
139
+ Math.exp(this.param.w[8]) *
140
+ (11 - d) *
141
+ Math.pow(s, -this.param.w[9]) *
142
+ (Math.exp((1 - r) * this.param.w[10]) - 1) *
143
+ hard_penalty *
144
+ easy_bound));
145
+ }
146
+ /**
147
+ * The formula used is :
148
+ * $$S^\prime_f(D,S,R) = w_11\cdot D^{-w_{12}}\cdot ((S+1)^{w_{13}}-1) \cdot e^{w_{14}\cdot(1-R)}.$$
149
+ * @param {number} d Difficulty D \in [1,10]
150
+ * @param {number} s Stability (interval when R=90%)
151
+ * @param {number} r Retrievability (probability of recall)
152
+ * @return {number} S^\prime_f new stability after forgetting
153
+ */
154
+ next_forget_stability(d, s, r) {
155
+ return (this.param.w[11] *
156
+ Math.pow(d, -this.param.w[12]) *
157
+ (Math.pow(s + 1, this.param.w[13]) - 1) *
158
+ Math.exp((1 - r) * this.param.w[14]));
159
+ }
160
+ /**
161
+ * The formula used is :
162
+ * $$R(t,S) = (1 + \frac{t}{9 \cdot S})^{-1},$$
163
+ * @param {number} t t days since the last review
164
+ * @param {number} s Stability (interval when R=90%)
165
+ * @return {number} r Retrievability (probability of recall)
166
+ */
167
+ current_retrievability(t, s) {
168
+ return Math.pow(1 + t / (9 * s), -1);
169
+ }
170
+ }
171
+ exports.FSRSAlgorithm = FSRSAlgorithm;
package/dist/default.d.ts CHANGED
@@ -6,11 +6,6 @@ export declare const default_maximum_interval: number;
6
6
  export declare const default_w: number[];
7
7
  export declare const default_enable_fuzz: boolean;
8
8
  export declare const FSRSVersion: string;
9
- export declare const generatorParameters: (props?: Partial<FSRSParameters>) => {
10
- request_retention: number;
11
- maximum_interval: number;
12
- w: number[];
13
- enable_fuzz: boolean;
14
- };
9
+ export declare const generatorParameters: (props?: Partial<FSRSParameters>) => FSRSParameters;
15
10
  export declare const createEmptyCard: (now?: Date) => Card;
16
11
  export declare const fsrs: (params?: Partial<FSRSParameters>) => FSRS;
package/dist/default.js CHANGED
@@ -29,7 +29,7 @@ exports.default_w = exports.envParams.FSRS_W || [
29
29
  0.34, 1.26, 0.29, 2.61,
30
30
  ];
31
31
  exports.default_enable_fuzz = exports.envParams.FSRS_ENABLE_FUZZ || false;
32
- exports.FSRSVersion = "3.0.3";
32
+ exports.FSRSVersion = "3.0.5";
33
33
  const generatorParameters = (props) => {
34
34
  return {
35
35
  request_retention: props?.request_retention || exports.default_request_retention,
package/dist/fsrs.d.ts CHANGED
@@ -1,138 +1,11 @@
1
- import { SchedulingCard } from "./index";
2
- import { FSRSParameters, Card, State, Rating, CardInput, DateInput } from "./models";
3
- import type { int } from "./type";
4
- export declare class FSRS {
5
- private param;
6
- private readonly intervalModifier;
7
- private seed?;
1
+ import { FSRSParameters, Card, CardInput, DateInput, RecordLog } from "./models";
2
+ import { FSRSAlgorithm } from "./algorithm";
3
+ export declare class FSRS extends FSRSAlgorithm {
8
4
  constructor(param: Partial<FSRSParameters>);
9
5
  preProcess(_card: CardInput, _now: DateInput): {
10
6
  card: Card;
11
7
  now: Date;
12
8
  };
13
- repeat: (card: CardInput, now: DateInput) => {
14
- 1: {
15
- card: Card;
16
- log: {
17
- rating: Rating;
18
- state: State;
19
- elapsed_days: number;
20
- scheduled_days: number;
21
- review: Date;
22
- };
23
- };
24
- 2: {
25
- card: Card;
26
- log: {
27
- rating: Rating;
28
- state: State;
29
- elapsed_days: number;
30
- scheduled_days: number;
31
- review: Date;
32
- };
33
- };
34
- 3: {
35
- card: Card;
36
- log: {
37
- rating: Rating;
38
- state: State;
39
- elapsed_days: number;
40
- scheduled_days: number;
41
- review: Date;
42
- };
43
- };
44
- 4: {
45
- card: Card;
46
- log: {
47
- rating: Rating;
48
- state: State;
49
- elapsed_days: number;
50
- scheduled_days: number;
51
- review: Date;
52
- };
53
- };
54
- };
9
+ repeat: (card: CardInput, now: DateInput) => RecordLog;
55
10
  get_retrievability: (card: Card, now: Date) => undefined | string;
56
- init_ds(s: SchedulingCard): void;
57
- /**
58
- *
59
- * @param s scheduling Card
60
- * @param last_d Difficulty
61
- * @param last_s Stability
62
- * @param retrievability Retrievability
63
- */
64
- next_ds(s: SchedulingCard, last_d: number, last_s: number, retrievability: number): void;
65
- /**
66
- * The formula used is :
67
- * $$S_0(G) = w_{G-1}$$
68
- * $$\max \{S_0,0.1\}$$
69
- * @param g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
70
- * @return Stability (interval when R=90%)
71
- */
72
- init_stability(g: number): number;
73
- /**
74
- * The formula used is :
75
- * $$D_0(G) = w_4 - (G-3) \cdot w_5$$
76
- * $$\min \{\max \{D_0(G),1\},10\}$$
77
- * where the D_0(3)=w_4 when the first rating is good.
78
- * @param g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
79
- * @return Difficulty D \in [1,10]
80
- */
81
- init_difficulty(g: number): number;
82
- apply_fuzz(ivl: number): number;
83
- /**
84
- * Ref:
85
- * constructor(param: Partial<FSRSParameters>)
86
- * this.intervalModifier = 9 * (1 / this.param.request_retention - 1);
87
- */
88
- next_interval(s: number): int;
89
- /**
90
- * The formula used is :
91
- * $$next_d = D - w_6 \cdot (R - 2)$$
92
- * $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
93
- * @param d
94
- * @param g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
95
- * @return next_D
96
- */
97
- next_difficulty(d: number, g: number): number;
98
- /**
99
- * The formula used is :
100
- * $$\min \{\max \{D_0,1\},10\}$$
101
- */
102
- constrain_difficulty(difficulty: number): number;
103
- /**
104
- * The formula used is :
105
- * $$w_7 \cdot init +(1 - w_7) \cdot current$$
106
- * @param init $$w_2 : D_0(3) = w_2 + (R-2) \cdot w_3= w_2$$
107
- * @param current $$D - w_6 \cdot (R - 2)$$
108
- * @return difficulty
109
- */
110
- mean_reversion(init: number, current: number): number;
111
- /**
112
- * The formula used is :
113
- * $$S^\prime_r(D,S,R,G) = S\cdot(e^{w_8}\cdot (11-D)\cdot S^{-w_9}\cdot(e^{w_10\cdot(1-R)}-1)\cdot w_15(if G=2) \cdot w_16(if G=4)+1)$$
114
- * @param d Difficulty D \in [1,10]
115
- * @param s Stability (interval when R=90%)
116
- * @param r Retrievability (probability of recall)
117
- * @param g Grade (Rating[0.again,1.hard,2.good,3.easy])
118
- * @return S^\prime_r new stability after recall
119
- */
120
- next_recall_stability(d: number, s: number, r: number, g: Rating): number;
121
- /**
122
- * The formula used is :
123
- * $$S^\prime_f(D,S,R) = w_11\cdot D^{-w_{12}}\cdot ((S+1)^{w_{13}}-1) \cdot e^{w_{14}\cdot(1-R)}.$$
124
- * @param d Difficulty D \in [1,10]
125
- * @param s Stability (interval when R=90%)
126
- * @param r Retrievability (probability of recall)
127
- * @return S^\prime_f new stability after forgetting
128
- */
129
- next_forget_stability(d: number, s: number, r: number): number;
130
- /**
131
- * The formula used is :
132
- * $$R(t,S) = (1 + \frac{t}{9 \cdot S})^{-1},$$
133
- * @param t t days since the last review
134
- * @param s Stability (interval when R=90%)
135
- * @return r Retrievability (probability of recall)
136
- */
137
- current_retrievability(t: number, s: number): number;
138
11
  }
package/dist/fsrs.js CHANGED
@@ -1,23 +1,13 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.FSRS = void 0;
7
- const seedrandom_1 = __importDefault(require("seedrandom"));
8
4
  const index_1 = require("./index");
9
5
  const help_1 = require("./help");
10
6
  const models_1 = require("./models");
11
- // Ref: https://github.com/open-spaced-repetition/fsrs4anki/wiki/The-Algorithm#fsrs-v4
12
- class FSRS {
13
- param;
14
- intervalModifier;
15
- seed;
7
+ const algorithm_1 = require("./algorithm");
8
+ class FSRS extends algorithm_1.FSRSAlgorithm {
16
9
  constructor(param) {
17
- this.param = (0, index_1.generatorParameters)(param);
18
- // Ref: https://github.com/open-spaced-repetition/py-fsrs/blob/ecd68e453611eb808c7367c7a5312d7cadeedf5c/src/fsrs/fsrs.py#L79
19
- // The formula used is : I(r,s)=9 \cdot s \cdot (\frac{1}{r}-1)
20
- this.intervalModifier = 9 * (1 / this.param.request_retention - 1);
10
+ super(param);
21
11
  }
22
12
  preProcess(_card, _now) {
23
13
  const card = {
@@ -37,8 +27,7 @@ class FSRS {
37
27
  card.state === models_1.State.New ? 0 : now.diff(card.last_review, "days"); //相距时间
38
28
  card.last_review = now; // 上次复习时间
39
29
  card.reps += 1;
40
- const s = new index_1.SchedulingCard(card);
41
- s.update_state(card.state);
30
+ const s = new index_1.SchedulingCard(card).update_state(card.state);
42
31
  this.seed = String(card.last_review.getTime()) + String(card.elapsed_days);
43
32
  let easy_interval, good_interval, hard_interval;
44
33
  switch (card.state) {
@@ -85,146 +74,5 @@ class FSRS {
85
74
  const t = Math.max(now.diff(card.last_review, "days"), 0);
86
75
  return ((this.current_retrievability(t, card.stability) * 100).toFixed(2) + "%");
87
76
  };
88
- init_ds(s) {
89
- s.again.difficulty = this.init_difficulty(models_1.Rating.Again);
90
- s.again.stability = this.init_stability(models_1.Rating.Again);
91
- s.hard.difficulty = this.init_difficulty(models_1.Rating.Hard);
92
- s.hard.stability = this.init_stability(models_1.Rating.Hard);
93
- s.good.difficulty = this.init_difficulty(models_1.Rating.Good);
94
- s.good.stability = this.init_stability(models_1.Rating.Good);
95
- s.easy.difficulty = this.init_difficulty(models_1.Rating.Easy);
96
- s.easy.stability = this.init_stability(models_1.Rating.Easy);
97
- }
98
- /**
99
- *
100
- * @param s scheduling Card
101
- * @param last_d Difficulty
102
- * @param last_s Stability
103
- * @param retrievability Retrievability
104
- */
105
- next_ds(s, last_d, last_s, retrievability) {
106
- s.again.difficulty = this.next_difficulty(last_d, models_1.Rating.Again);
107
- s.again.stability = this.next_forget_stability(s.again.difficulty, last_s, retrievability);
108
- s.hard.difficulty = this.next_difficulty(last_d, models_1.Rating.Hard);
109
- s.hard.stability = this.next_recall_stability(s.hard.difficulty, last_s, retrievability, models_1.Rating.Hard);
110
- s.good.difficulty = this.next_difficulty(last_d, models_1.Rating.Good);
111
- s.good.stability = this.next_recall_stability(s.good.difficulty, last_s, retrievability, models_1.Rating.Good);
112
- s.easy.difficulty = this.next_difficulty(last_d, models_1.Rating.Easy);
113
- s.easy.stability = this.next_recall_stability(s.easy.difficulty, last_s, retrievability, models_1.Rating.Easy);
114
- }
115
- /**
116
- * The formula used is :
117
- * $$S_0(G) = w_{G-1}$$
118
- * $$\max \{S_0,0.1\}$$
119
- * @param g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
120
- * @return Stability (interval when R=90%)
121
- */
122
- init_stability(g) {
123
- return Math.max(this.param.w[g - 1], 0.1);
124
- }
125
- /**
126
- * The formula used is :
127
- * $$D_0(G) = w_4 - (G-3) \cdot w_5$$
128
- * $$\min \{\max \{D_0(G),1\},10\}$$
129
- * where the D_0(3)=w_4 when the first rating is good.
130
- * @param g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
131
- * @return Difficulty D \in [1,10]
132
- */
133
- init_difficulty(g) {
134
- return Math.min(Math.max(this.param.w[4] - (g - 3) * this.param.w[5], 1), 10);
135
- }
136
- apply_fuzz(ivl) {
137
- if (!this.param.enable_fuzz || ivl < 2.5)
138
- return ivl;
139
- const generator = (0, seedrandom_1.default)(this.seed);
140
- const fuzz_factor = generator();
141
- ivl = Math.round(ivl);
142
- const min_ivl = Math.max(2, Math.round(ivl * 0.95 - 1));
143
- const max_ivl = Math.round(ivl * 1.05 + 1);
144
- return Math.floor(fuzz_factor * (max_ivl - min_ivl + 1) + min_ivl);
145
- }
146
- /**
147
- * Ref:
148
- * constructor(param: Partial<FSRSParameters>)
149
- * this.intervalModifier = 9 * (1 / this.param.request_retention - 1);
150
- */
151
- next_interval(s) {
152
- const newInterval = this.apply_fuzz(s * this.intervalModifier);
153
- return Math.min(Math.max(Math.round(newInterval), 1), this.param.maximum_interval);
154
- }
155
- /**
156
- * The formula used is :
157
- * $$next_d = D - w_6 \cdot (R - 2)$$
158
- * $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
159
- * @param d
160
- * @param g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
161
- * @return next_D
162
- */
163
- next_difficulty(d, g) {
164
- const next_d = d - this.param.w[6] * (g - 3);
165
- return this.constrain_difficulty(this.mean_reversion(this.param.w[4], next_d));
166
- }
167
- /**
168
- * The formula used is :
169
- * $$\min \{\max \{D_0,1\},10\}$$
170
- */
171
- constrain_difficulty(difficulty) {
172
- return Math.min(Math.max(Number(difficulty.toFixed(2)), 1), 10);
173
- }
174
- /**
175
- * The formula used is :
176
- * $$w_7 \cdot init +(1 - w_7) \cdot current$$
177
- * @param init $$w_2 : D_0(3) = w_2 + (R-2) \cdot w_3= w_2$$
178
- * @param current $$D - w_6 \cdot (R - 2)$$
179
- * @return difficulty
180
- */
181
- mean_reversion(init, current) {
182
- return this.param.w[7] * init + (1 - this.param.w[7]) * current;
183
- }
184
- /**
185
- * The formula used is :
186
- * $$S^\prime_r(D,S,R,G) = S\cdot(e^{w_8}\cdot (11-D)\cdot S^{-w_9}\cdot(e^{w_10\cdot(1-R)}-1)\cdot w_15(if G=2) \cdot w_16(if G=4)+1)$$
187
- * @param d Difficulty D \in [1,10]
188
- * @param s Stability (interval when R=90%)
189
- * @param r Retrievability (probability of recall)
190
- * @param g Grade (Rating[0.again,1.hard,2.good,3.easy])
191
- * @return S^\prime_r new stability after recall
192
- */
193
- next_recall_stability(d, s, r, g) {
194
- const hard_penalty = models_1.Rating.Hard === g ? this.param.w[15] : 1;
195
- const easy_bound = models_1.Rating.Easy === g ? this.param.w[16] : 1;
196
- return (s *
197
- (1 +
198
- Math.exp(this.param.w[8]) *
199
- (11 - d) *
200
- Math.pow(s, -this.param.w[9]) *
201
- (Math.exp((1 - r) * this.param.w[10]) - 1) *
202
- hard_penalty *
203
- easy_bound));
204
- }
205
- /**
206
- * The formula used is :
207
- * $$S^\prime_f(D,S,R) = w_11\cdot D^{-w_{12}}\cdot ((S+1)^{w_{13}}-1) \cdot e^{w_{14}\cdot(1-R)}.$$
208
- * @param d Difficulty D \in [1,10]
209
- * @param s Stability (interval when R=90%)
210
- * @param r Retrievability (probability of recall)
211
- * @return S^\prime_f new stability after forgetting
212
- */
213
- next_forget_stability(d, s, r) {
214
- return (this.param.w[11] *
215
- Math.pow(d, -this.param.w[12]) *
216
- (Math.pow(s + 1, this.param.w[13]) - 1) *
217
- Math.exp((1 - r) * this.param.w[14]));
218
- }
219
- /**
220
- * The formula used is :
221
- * $$R(t,S) = (1 + \frac{t}{9 \cdot S})^{-1},$$
222
- * @param t t days since the last review
223
- * @param s Stability (interval when R=90%)
224
- * @return r Retrievability (probability of recall)
225
- */
226
- current_retrievability(t, s) {
227
- return Math.pow(1 + t / (9 * s), -1);
228
- }
229
77
  }
230
78
  exports.FSRS = FSRS;
package/dist/help.d.ts CHANGED
@@ -8,6 +8,13 @@ declare global {
8
8
  dueFormat(last_review: Date, unit?: boolean): string;
9
9
  }
10
10
  }
11
+ /**
12
+ * 计算日期和时间的偏移,并返回一个新的日期对象。
13
+ * @param now 当前日期和时间
14
+ * @param t 时间偏移量,当 isDay 为 true 时表示天数,为 false 时表示分钟
15
+ * @param isDay (可选)是否按天数单位进行偏移,默认为 false,表示按分钟单位计算偏移
16
+ * @returns 偏移后的日期和时间对象
17
+ */
11
18
  export declare function date_scheduler(now: Date, t: number, isDay?: boolean): Date;
12
19
  export declare function date_diff(now: Date, pre: Date, unit: unit): number;
13
20
  export declare function formatDate(date: Date): string;
package/dist/help.js CHANGED
@@ -19,6 +19,13 @@ Date.prototype.format = function () {
19
19
  Date.prototype.dueFormat = function (last_review, unit) {
20
20
  return show_diff_message(this, last_review, unit);
21
21
  };
22
+ /**
23
+ * 计算日期和时间的偏移,并返回一个新的日期对象。
24
+ * @param now 当前日期和时间
25
+ * @param t 时间偏移量,当 isDay 为 true 时表示天数,为 false 时表示分钟
26
+ * @param isDay (可选)是否按天数单位进行偏移,默认为 false,表示按分钟单位计算偏移
27
+ * @returns 偏移后的日期和时间对象
28
+ */
22
29
  function date_scheduler(now, t, isDay) {
23
30
  return new Date(isDay
24
31
  ? now.getTime() + t * 24 * 60 * 60 * 1000
package/dist/index.d.ts CHANGED
@@ -2,6 +2,6 @@ export { SchedulingCard } from "./scheduler";
2
2
  export { default_request_retention, default_maximum_interval, default_w, default_enable_fuzz, FSRSVersion, generatorParameters, createEmptyCard, fsrs, envParams } from "./default";
3
3
  export { date_scheduler, date_diff, formatDate, show_diff_message, } from "./help";
4
4
  export type { int, double } from "./type";
5
- export type { FSRSParameters, Card, ReviewLog, StateType, RatingType, CardInput, DateInput } from "./models";
5
+ export type { FSRSParameters, Card, ReviewLog, RecordLog, RecordLogItem, StateType, RatingType, CardInput, DateInput } from "./models";
6
6
  export { State, Rating } from "./models";
7
7
  export { FSRS } from "./fsrs";
package/dist/models.d.ts CHANGED
@@ -19,6 +19,13 @@ export interface ReviewLog {
19
19
  scheduled_days: number;
20
20
  review: Date;
21
21
  }
22
+ export type RecordLogItem = {
23
+ card: Card;
24
+ log: ReviewLog;
25
+ };
26
+ export type RecordLog = {
27
+ [key in Rating]: RecordLogItem;
28
+ };
22
29
  export interface Card {
23
30
  due: Date;
24
31
  stability: number;
@@ -1,4 +1,4 @@
1
- import { Card, Rating, State } from "./models";
1
+ import { Card, RecordLog, State } from "./models";
2
2
  export declare class SchedulingCard {
3
3
  again: Card;
4
4
  hard: Card;
@@ -8,46 +8,5 @@ export declare class SchedulingCard {
8
8
  constructor(card: Card);
9
9
  update_state(state: State): this;
10
10
  schedule(now: Date, hard_interval: number, good_interval: number, easy_interval: number): SchedulingCard;
11
- record_log(card: Card, now: Date): {
12
- 1: {
13
- card: Card;
14
- log: {
15
- rating: Rating;
16
- state: State;
17
- elapsed_days: number;
18
- scheduled_days: number;
19
- review: Date;
20
- };
21
- };
22
- 2: {
23
- card: Card;
24
- log: {
25
- rating: Rating;
26
- state: State;
27
- elapsed_days: number;
28
- scheduled_days: number;
29
- review: Date;
30
- };
31
- };
32
- 3: {
33
- card: Card;
34
- log: {
35
- rating: Rating;
36
- state: State;
37
- elapsed_days: number;
38
- scheduled_days: number;
39
- review: Date;
40
- };
41
- };
42
- 4: {
43
- card: Card;
44
- log: {
45
- rating: Rating;
46
- state: State;
47
- elapsed_days: number;
48
- scheduled_days: number;
49
- review: Date;
50
- };
51
- };
52
- };
11
+ record_log(card: Card, now: Date): RecordLog;
53
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-fsrs",
3
- "version": "3.0.3",
3
+ "version": "3.0.5",
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": "dist/index.js",
6
6
  "types": "dist/index.d.ts",