ts-fsrs 3.0.6 → 3.1.0-beta2

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.
@@ -1,5 +1,5 @@
1
1
  import { SchedulingCard } from "./index";
2
- import { FSRSParameters, Rating } from "./models";
2
+ import { FSRSParameters, Grade } from "./models";
3
3
  import type { int } from "./type";
4
4
  export declare class FSRSAlgorithm {
5
5
  protected param: FSRSParameters;
@@ -23,16 +23,16 @@ export declare class FSRSAlgorithm {
23
23
  * @param g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
24
24
  * @return Stability (interval when R=90%)
25
25
  */
26
- init_stability(g: number): number;
26
+ init_stability(g: Grade): number;
27
27
  /**
28
28
  * The formula used is :
29
29
  * $$D_0(G) = w_4 - (G-3) \cdot w_5$$
30
30
  * $$\min \{\max \{D_0(G),1\},10\}$$
31
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]
32
+ * @param {Grade} g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
33
33
  * @return {number} Difficulty D \in [1,10]
34
34
  */
35
- init_difficulty(g: number): number;
35
+ init_difficulty(g: Grade): number;
36
36
  /**
37
37
  * If fuzzing is disabled or ivl is less than 2.5, it returns the original interval.
38
38
  * @param {number} ivl - The interval to be fuzzed.
@@ -51,10 +51,10 @@ export declare class FSRSAlgorithm {
51
51
  * $$next_d = D - w_6 \cdot (R - 2)$$
52
52
  * $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
53
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]
54
+ * @param {Grade} g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
55
55
  * @return {number} next_D
56
56
  */
57
- next_difficulty(d: number, g: number): number;
57
+ next_difficulty(d: number, g: Grade): number;
58
58
  /**
59
59
  * The formula used is :
60
60
  * $$\min \{\max \{D_0,1\},10\}$$
@@ -75,10 +75,10 @@ export declare class FSRSAlgorithm {
75
75
  * @param {number} d Difficulty D \in [1,10]
76
76
  * @param {number} s Stability (interval when R=90%)
77
77
  * @param {number} r Retrievability (probability of recall)
78
- * @param {Rating} g Grade (Rating[0.again,1.hard,2.good,3.easy])
78
+ * @param {Grade} g Grade (Rating[0.again,1.hard,2.good,3.easy])
79
79
  * @return {number} S^\prime_r new stability after recall
80
80
  */
81
- next_recall_stability(d: number, s: number, r: number, g: Rating): number;
81
+ next_recall_stability(d: number, s: number, r: number, g: Grade): number;
82
82
  /**
83
83
  * The formula used is :
84
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)}.$$
package/dist/algorithm.js CHANGED
@@ -61,7 +61,7 @@ class FSRSAlgorithm {
61
61
  * $$D_0(G) = w_4 - (G-3) \cdot w_5$$
62
62
  * $$\min \{\max \{D_0(G),1\},10\}$$
63
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]
64
+ * @param {Grade} g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
65
65
  * @return {number} Difficulty D \in [1,10]
66
66
  */
67
67
  init_difficulty(g) {
@@ -97,7 +97,7 @@ class FSRSAlgorithm {
97
97
  * $$next_d = D - w_6 \cdot (R - 2)$$
98
98
  * $$D^\prime(D,R) = w_5 \cdot D_0(2) +(1 - w_5) \cdot next_d$$
99
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]
100
+ * @param {Grade} g Grade (rating at Anki) [1.again,2.hard,3.good,4.easy]
101
101
  * @return {number} next_D
102
102
  */
103
103
  next_difficulty(d, g) {
@@ -128,7 +128,7 @@ class FSRSAlgorithm {
128
128
  * @param {number} d Difficulty D \in [1,10]
129
129
  * @param {number} s Stability (interval when R=90%)
130
130
  * @param {number} r Retrievability (probability of recall)
131
- * @param {Rating} g Grade (Rating[0.again,1.hard,2.good,3.easy])
131
+ * @param {Grade} g Grade (Rating[0.again,1.hard,2.good,3.easy])
132
132
  * @return {number} S^\prime_r new stability after recall
133
133
  */
134
134
  next_recall_stability(d, s, r, g) {
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.6";
32
+ exports.FSRSVersion = "3.1.0-beta2";
33
33
  const generatorParameters = (props) => {
34
34
  return {
35
35
  request_retention: props?.request_retention || exports.default_request_retention,
@@ -49,6 +49,7 @@ const createEmptyCard = (now) => {
49
49
  reps: 0,
50
50
  lapses: 0,
51
51
  state: index_1.State.New,
52
+ last_review: undefined,
52
53
  };
53
54
  };
54
55
  exports.createEmptyCard = createEmptyCard;
package/dist/fsrs.d.ts CHANGED
@@ -1,11 +1,12 @@
1
- import { FSRSParameters, Card, CardInput, DateInput, RecordLog } from "./models";
1
+ import { Card, CardInput, DateInput, FSRSParameters, RecordLog, RecordLogItem, ReviewLogInput } from "./models";
2
2
  import { FSRSAlgorithm } from "./algorithm";
3
3
  export declare class FSRS extends FSRSAlgorithm {
4
4
  constructor(param: Partial<FSRSParameters>);
5
- preProcess(_card: CardInput, _now: DateInput): {
6
- card: Card;
7
- now: Date;
8
- };
5
+ private preProcessCard;
6
+ private preProcessDate;
7
+ private preProcessLog;
9
8
  repeat: (card: CardInput, now: DateInput) => RecordLog;
10
9
  get_retrievability: (card: Card, now: Date) => undefined | string;
10
+ rollback: (card: CardInput, log: ReviewLogInput) => Card;
11
+ forget: (card: CardInput, now: DateInput, reset_count?: boolean) => RecordLogItem;
11
12
  }
package/dist/fsrs.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FSRS = void 0;
4
- const index_1 = require("./index");
4
+ const scheduler_1 = require("./scheduler");
5
5
  const help_1 = require("./help");
6
6
  const models_1 = require("./models");
7
7
  const algorithm_1 = require("./algorithm");
@@ -9,26 +9,30 @@ class FSRS extends algorithm_1.FSRSAlgorithm {
9
9
  constructor(param) {
10
10
  super(param);
11
11
  }
12
- preProcess(_card, _now) {
13
- const card = {
12
+ preProcessCard(_card) {
13
+ return {
14
14
  ..._card,
15
15
  state: (0, help_1.fixState)(_card.state),
16
16
  due: (0, help_1.fixDate)(_card.due),
17
17
  last_review: _card.last_review ? (0, help_1.fixDate)(_card.last_review) : undefined,
18
18
  };
19
- const now = (0, help_1.fixDate)(_now);
20
- return { card, now };
19
+ }
20
+ preProcessDate(_date) {
21
+ return (0, help_1.fixDate)(_date);
22
+ }
23
+ preProcessLog(_log) {
24
+ return {
25
+ ..._log,
26
+ rating: (0, help_1.fixRating)(_log.rating),
27
+ state: (0, help_1.fixState)(_log.state),
28
+ review: (0, help_1.fixDate)(_log.review),
29
+ };
21
30
  }
22
31
  repeat = (card, now) => {
23
- const process = this.preProcess(card, now);
24
- card = process.card;
25
- now = process.now;
26
- card.elapsed_days =
27
- card.state === models_1.State.New ? 0 : now.diff(card.last_review, "days"); //相距时间
28
- card.last_review = now; // 上次复习时间
29
- card.reps += 1;
30
- const s = new index_1.SchedulingCard(card).update_state(card.state);
31
- this.seed = String(card.last_review.getTime()) + String(card.elapsed_days);
32
+ card = this.preProcessCard(card);
33
+ now = this.preProcessDate(now);
34
+ const s = new scheduler_1.SchedulingCard(card, now).update_state(card.state);
35
+ this.seed = String(now.getTime()) + String(card.reps);
32
36
  let easy_interval, good_interval, hard_interval;
33
37
  switch (card.state) {
34
38
  case models_1.State.New:
@@ -65,14 +69,76 @@ class FSRS extends algorithm_1.FSRSAlgorithm {
65
69
  return s.record_log(card, now);
66
70
  };
67
71
  get_retrievability = (card, now) => {
68
- const process = this.preProcess(card, now);
69
- card = process.card;
70
- now = process.now;
72
+ card = this.preProcessCard(card);
73
+ now = this.preProcessDate(now);
71
74
  if (card.state !== models_1.State.Review) {
72
75
  return undefined;
73
76
  }
74
77
  const t = Math.max(now.diff(card.last_review, "days"), 0);
75
78
  return ((this.current_retrievability(t, card.stability) * 100).toFixed(2) + "%");
76
79
  };
80
+ rollback = (card, log) => {
81
+ card = this.preProcessCard(card);
82
+ log = this.preProcessLog(log);
83
+ if (log.rating === models_1.Rating.Manual) {
84
+ throw new Error("Cannot rollback a manual rating");
85
+ }
86
+ let last_due, last_review, last_lapses;
87
+ switch (log.state) {
88
+ case models_1.State.New:
89
+ last_due = log.due;
90
+ last_review = undefined;
91
+ last_lapses = 0;
92
+ break;
93
+ case models_1.State.Learning:
94
+ case models_1.State.Relearning:
95
+ case models_1.State.Review:
96
+ last_due = log.review;
97
+ last_review = log.due;
98
+ last_lapses =
99
+ card.lapses -
100
+ (log.rating === models_1.Rating.Again && log.state === models_1.State.Review ? 1 : 0);
101
+ break;
102
+ }
103
+ return {
104
+ ...card,
105
+ due: last_due,
106
+ stability: log.stability,
107
+ difficulty: log.difficulty,
108
+ elapsed_days: log.elapsed_days,
109
+ scheduled_days: log.scheduled_days,
110
+ reps: Math.max(0, card.reps - 1),
111
+ lapses: Math.max(0, last_lapses),
112
+ state: log.state,
113
+ last_review: last_review,
114
+ };
115
+ };
116
+ forget = (card, now, reset_count = false) => {
117
+ card = this.preProcessCard(card);
118
+ now = this.preProcessDate(now);
119
+ const forget_log = {
120
+ rating: models_1.Rating.Manual,
121
+ state: card.state,
122
+ due: card.due,
123
+ stability: card.stability,
124
+ difficulty: card.difficulty,
125
+ elapsed_days: card.elapsed_days,
126
+ scheduled_days: card.scheduled_days,
127
+ review: now,
128
+ };
129
+ const forget_card = {
130
+ ...card,
131
+ due: now,
132
+ stability: 0,
133
+ difficulty: 0,
134
+ elapsed_days: 0,
135
+ scheduled_days: 0,
136
+ reps: reset_count ? 0 : card.reps,
137
+ lapses: reset_count ? 0 : card.lapses,
138
+ state: models_1.State.New,
139
+ last_review: card.last_review,
140
+ };
141
+ return { card: forget_card, log: forget_log };
142
+ };
77
143
  }
78
144
  exports.FSRS = FSRS;
package/dist/help.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { int, unit } from "./type";
2
- import { Rating, State } from "./models";
2
+ import { Grade, Rating, State } from "./models";
3
3
  declare global {
4
4
  export interface Date {
5
5
  scheduler(t: int, isDay?: boolean): Date;
@@ -22,4 +22,4 @@ export declare function show_diff_message(due: Date, last_review: Date, unit?: b
22
22
  export declare function fixDate(value: unknown): Date;
23
23
  export declare function fixState(value: unknown): State;
24
24
  export declare function fixRating(value: unknown): Rating;
25
- export declare const Grades: Rating[];
25
+ export declare const Grades: Grade[];
package/dist/help.js CHANGED
@@ -123,4 +123,9 @@ function fixRating(value) {
123
123
  throw new Error(`Invalid rating:[${value}]`);
124
124
  }
125
125
  exports.fixRating = fixRating;
126
- exports.Grades = [models_1.Rating.Again, models_1.Rating.Hard, models_1.Rating.Good, models_1.Rating.Easy];
126
+ exports.Grades = [
127
+ models_1.Rating.Again,
128
+ models_1.Rating.Hard,
129
+ models_1.Rating.Good,
130
+ models_1.Rating.Easy,
131
+ ];
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { SchedulingCard } from "./scheduler";
2
- export { default_request_retention, default_maximum_interval, default_w, default_enable_fuzz, FSRSVersion, generatorParameters, createEmptyCard, fsrs, envParams } from "./default";
3
- export { date_scheduler, date_diff, formatDate, show_diff_message, Grades } from "./help";
2
+ export { default_request_retention, default_maximum_interval, default_w, default_enable_fuzz, FSRSVersion, generatorParameters, createEmptyCard, fsrs, envParams, } from "./default";
3
+ export { date_scheduler, date_diff, formatDate, show_diff_message, Grades, } from "./help";
4
4
  export type { int, double } from "./type";
5
- export type { FSRSParameters, Card, ReviewLog, RecordLog, RecordLogItem, StateType, RatingType, CardInput, DateInput } from "./models";
5
+ export type { FSRSParameters, Card, ReviewLog, RecordLog, RecordLogItem, StateType, RatingType, Grade, CardInput, DateInput, } from "./models";
6
6
  export { State, Rating } from "./models";
7
7
  export { FSRS } from "./fsrs";
package/dist/models.d.ts CHANGED
@@ -7,14 +7,20 @@ export declare enum State {
7
7
  }
8
8
  export type RatingType = "Again" | "Hard" | "Good" | "Easy";
9
9
  export declare enum Rating {
10
+ Manual = 0,
10
11
  Again = 1,
11
12
  Hard = 2,
12
13
  Good = 3,
13
14
  Easy = 4
14
15
  }
16
+ type ExcludeManual<T> = Exclude<T, Rating.Manual>;
17
+ export type Grade = ExcludeManual<Rating>;
15
18
  export interface ReviewLog {
16
19
  rating: Rating;
17
20
  state: State;
21
+ due: Date;
22
+ stability: number;
23
+ difficulty: number;
18
24
  elapsed_days: number;
19
25
  scheduled_days: number;
20
26
  review: Date;
@@ -24,7 +30,7 @@ export type RecordLogItem = {
24
30
  log: ReviewLog;
25
31
  };
26
32
  export type RecordLog = {
27
- [key in Rating]: RecordLogItem;
33
+ [key in Grade]: RecordLogItem;
28
34
  };
29
35
  export interface Card {
30
36
  due: Date;
@@ -41,9 +47,14 @@ export type CardInput = Card & {
41
47
  state: StateType | State;
42
48
  };
43
49
  export type DateInput = Date | number | string;
50
+ export type ReviewLogInput = ReviewLog & {
51
+ rating: RatingType | Rating;
52
+ state: StateType | State;
53
+ };
44
54
  export interface FSRSParameters {
45
55
  request_retention: number;
46
56
  maximum_interval: number;
47
57
  w: number[];
48
58
  enable_fuzz: boolean;
49
59
  }
60
+ export {};
package/dist/models.js CHANGED
@@ -10,6 +10,7 @@ var State;
10
10
  })(State || (exports.State = State = {}));
11
11
  var Rating;
12
12
  (function (Rating) {
13
+ Rating[Rating["Manual"] = 0] = "Manual";
13
14
  Rating[Rating["Again"] = 1] = "Again";
14
15
  Rating[Rating["Hard"] = 2] = "Hard";
15
16
  Rating[Rating["Good"] = 3] = "Good";
@@ -4,8 +4,10 @@ export declare class SchedulingCard {
4
4
  hard: Card;
5
5
  good: Card;
6
6
  easy: Card;
7
+ last_review: Date;
8
+ elapsed_days: number;
7
9
  private copy;
8
- constructor(card: Card);
10
+ constructor(card: Card, now: Date);
9
11
  update_state(state: State): this;
10
12
  schedule(now: Date, hard_interval: number, good_interval: number, easy_interval: number): SchedulingCard;
11
13
  record_log(card: Card, now: Date): RecordLog;
package/dist/scheduler.js CHANGED
@@ -8,12 +8,20 @@ class SchedulingCard {
8
8
  hard;
9
9
  good;
10
10
  easy;
11
+ last_review;
12
+ elapsed_days;
11
13
  copy(card) {
12
14
  return {
13
15
  ...card,
14
16
  };
15
17
  }
16
- constructor(card) {
18
+ constructor(card, now) {
19
+ this.last_review = card.last_review || card.due;
20
+ this.elapsed_days = card.elapsed_days;
21
+ card.elapsed_days =
22
+ card.state === models_1.State.New ? 0 : now.diff(card.last_review, "days"); //相距时间
23
+ card.last_review = now; // 上次复习时间
24
+ card.reps += 1;
17
25
  this.again = this.copy(card);
18
26
  this.hard = this.copy(card);
19
27
  this.good = this.copy(card);
@@ -63,8 +71,11 @@ class SchedulingCard {
63
71
  log: {
64
72
  rating: models_1.Rating.Again,
65
73
  state: card.state,
66
- elapsed_days: this.again.scheduled_days,
67
- scheduled_days: card.elapsed_days,
74
+ due: this.last_review,
75
+ stability: card.stability,
76
+ difficulty: card.difficulty,
77
+ elapsed_days: this.elapsed_days,
78
+ scheduled_days: card.scheduled_days,
68
79
  review: now,
69
80
  },
70
81
  },
@@ -73,8 +84,11 @@ class SchedulingCard {
73
84
  log: {
74
85
  rating: models_1.Rating.Hard,
75
86
  state: card.state,
76
- elapsed_days: this.hard.scheduled_days,
77
- scheduled_days: card.elapsed_days,
87
+ due: this.last_review,
88
+ stability: card.stability,
89
+ difficulty: card.difficulty,
90
+ elapsed_days: this.elapsed_days,
91
+ scheduled_days: card.scheduled_days,
78
92
  review: now,
79
93
  },
80
94
  },
@@ -83,8 +97,11 @@ class SchedulingCard {
83
97
  log: {
84
98
  rating: models_1.Rating.Good,
85
99
  state: card.state,
86
- elapsed_days: this.good.scheduled_days,
87
- scheduled_days: card.elapsed_days,
100
+ due: this.last_review,
101
+ stability: card.stability,
102
+ difficulty: card.difficulty,
103
+ elapsed_days: this.elapsed_days,
104
+ scheduled_days: card.scheduled_days,
88
105
  review: now,
89
106
  },
90
107
  },
@@ -93,8 +110,11 @@ class SchedulingCard {
93
110
  log: {
94
111
  rating: models_1.Rating.Easy,
95
112
  state: card.state,
96
- elapsed_days: this.easy.scheduled_days,
97
- scheduled_days: card.elapsed_days,
113
+ due: this.last_review,
114
+ stability: card.stability,
115
+ difficulty: card.difficulty,
116
+ elapsed_days: this.elapsed_days,
117
+ scheduled_days: card.scheduled_days,
98
118
  review: now,
99
119
  },
100
120
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-fsrs",
3
- "version": "3.0.6",
3
+ "version": "3.1.0-beta2",
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",