@contractspec/module.learning-journey 1.57.0 → 1.58.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/dist/browser/contracts/index.js +578 -0
- package/dist/browser/contracts/models.js +193 -0
- package/dist/browser/contracts/onboarding.js +417 -0
- package/dist/browser/contracts/operations.js +326 -0
- package/dist/browser/contracts/shared.js +5 -0
- package/dist/browser/docs/index.js +124 -0
- package/dist/browser/docs/learning-journey.docblock.js +124 -0
- package/dist/browser/engines/index.js +526 -0
- package/dist/browser/engines/srs.js +198 -0
- package/dist/browser/engines/streak.js +159 -0
- package/dist/browser/engines/xp.js +171 -0
- package/dist/browser/entities/ai.js +343 -0
- package/dist/browser/entities/course.js +276 -0
- package/dist/browser/entities/flashcard.js +222 -0
- package/dist/browser/entities/gamification.js +340 -0
- package/dist/browser/entities/index.js +2136 -0
- package/dist/browser/entities/learner.js +329 -0
- package/dist/browser/entities/onboarding.js +301 -0
- package/dist/browser/entities/quiz.js +304 -0
- package/dist/browser/events.js +423 -0
- package/dist/browser/index.js +3833 -0
- package/dist/browser/learning-journey.capability.js +40 -0
- package/dist/browser/learning-journey.feature.js +56 -0
- package/dist/browser/track-spec.js +0 -0
- package/dist/contracts/index.d.ts +5 -5
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +578 -5
- package/dist/contracts/models.d.ts +426 -431
- package/dist/contracts/models.d.ts.map +1 -1
- package/dist/contracts/models.js +178 -372
- package/dist/contracts/onboarding.d.ts +621 -627
- package/dist/contracts/onboarding.d.ts.map +1 -1
- package/dist/contracts/onboarding.js +404 -388
- package/dist/contracts/operations.d.ts +243 -249
- package/dist/contracts/operations.d.ts.map +1 -1
- package/dist/contracts/operations.js +324 -148
- package/dist/contracts/shared.d.ts +1 -4
- package/dist/contracts/shared.d.ts.map +1 -1
- package/dist/contracts/shared.js +6 -6
- package/dist/docs/index.d.ts +2 -1
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +125 -1
- package/dist/docs/learning-journey.docblock.d.ts +2 -1
- package/dist/docs/learning-journey.docblock.d.ts.map +1 -0
- package/dist/docs/learning-journey.docblock.js +47 -58
- package/dist/engines/index.d.ts +4 -4
- package/dist/engines/index.d.ts.map +1 -0
- package/dist/engines/index.js +526 -4
- package/dist/engines/srs.d.ts +89 -92
- package/dist/engines/srs.d.ts.map +1 -1
- package/dist/engines/srs.js +197 -217
- package/dist/engines/streak.d.ts +84 -87
- package/dist/engines/streak.d.ts.map +1 -1
- package/dist/engines/streak.js +158 -192
- package/dist/engines/xp.d.ts +80 -83
- package/dist/engines/xp.d.ts.map +1 -1
- package/dist/engines/xp.js +170 -211
- package/dist/entities/ai.d.ts +199 -204
- package/dist/entities/ai.d.ts.map +1 -1
- package/dist/entities/ai.js +336 -368
- package/dist/entities/course.d.ts +149 -154
- package/dist/entities/course.d.ts.map +1 -1
- package/dist/entities/course.js +267 -306
- package/dist/entities/flashcard.d.ts +144 -149
- package/dist/entities/flashcard.d.ts.map +1 -1
- package/dist/entities/flashcard.js +217 -243
- package/dist/entities/gamification.d.ts +197 -202
- package/dist/entities/gamification.d.ts.map +1 -1
- package/dist/entities/gamification.js +331 -382
- package/dist/entities/index.d.ts +613 -618
- package/dist/entities/index.d.ts.map +1 -1
- package/dist/entities/index.js +2135 -43
- package/dist/entities/learner.d.ts +191 -196
- package/dist/entities/learner.d.ts.map +1 -1
- package/dist/entities/learner.js +322 -357
- package/dist/entities/onboarding.d.ts +164 -169
- package/dist/entities/onboarding.d.ts.map +1 -1
- package/dist/entities/onboarding.js +296 -301
- package/dist/entities/quiz.d.ts +184 -189
- package/dist/entities/quiz.d.ts.map +1 -1
- package/dist/entities/quiz.js +296 -361
- package/dist/events.d.ts +608 -614
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +421 -687
- package/dist/index.d.ts +8 -20
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3834 -22
- package/dist/learning-journey.capability.d.ts +3 -8
- package/dist/learning-journey.capability.d.ts.map +1 -1
- package/dist/learning-journey.capability.js +41 -46
- package/dist/learning-journey.feature.d.ts +1 -6
- package/dist/learning-journey.feature.d.ts.map +1 -1
- package/dist/learning-journey.feature.js +55 -155
- package/dist/node/contracts/index.js +578 -0
- package/dist/node/contracts/models.js +193 -0
- package/dist/node/contracts/onboarding.js +417 -0
- package/dist/node/contracts/operations.js +326 -0
- package/dist/node/contracts/shared.js +5 -0
- package/dist/node/docs/index.js +124 -0
- package/dist/node/docs/learning-journey.docblock.js +124 -0
- package/dist/node/engines/index.js +526 -0
- package/dist/node/engines/srs.js +198 -0
- package/dist/node/engines/streak.js +159 -0
- package/dist/node/engines/xp.js +171 -0
- package/dist/node/entities/ai.js +343 -0
- package/dist/node/entities/course.js +276 -0
- package/dist/node/entities/flashcard.js +222 -0
- package/dist/node/entities/gamification.js +340 -0
- package/dist/node/entities/index.js +2136 -0
- package/dist/node/entities/learner.js +329 -0
- package/dist/node/entities/onboarding.js +301 -0
- package/dist/node/entities/quiz.js +304 -0
- package/dist/node/events.js +423 -0
- package/dist/node/index.js +3833 -0
- package/dist/node/learning-journey.capability.js +40 -0
- package/dist/node/learning-journey.feature.js +56 -0
- package/dist/node/track-spec.js +0 -0
- package/dist/track-spec.d.ts +115 -118
- package/dist/track-spec.d.ts.map +1 -1
- package/dist/track-spec.js +1 -0
- package/package.json +237 -60
- package/dist/contracts/models.js.map +0 -1
- package/dist/contracts/onboarding.js.map +0 -1
- package/dist/contracts/operations.js.map +0 -1
- package/dist/contracts/shared.js.map +0 -1
- package/dist/docs/learning-journey.docblock.js.map +0 -1
- package/dist/engines/srs.js.map +0 -1
- package/dist/engines/streak.js.map +0 -1
- package/dist/engines/xp.js.map +0 -1
- package/dist/entities/ai.js.map +0 -1
- package/dist/entities/course.js.map +0 -1
- package/dist/entities/flashcard.js.map +0 -1
- package/dist/entities/gamification.js.map +0 -1
- package/dist/entities/index.js.map +0 -1
- package/dist/entities/learner.js.map +0 -1
- package/dist/entities/onboarding.js.map +0 -1
- package/dist/entities/quiz.js.map +0 -1
- package/dist/events.js.map +0 -1
- package/dist/learning-journey.capability.js.map +0 -1
- package/dist/learning-journey.feature.js.map +0 -1
package/dist/engines/srs.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/engines/srs.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* Spaced Repetition System (SRS) Engine
|
|
4
3
|
*
|
|
@@ -10,102 +9,100 @@
|
|
|
10
9
|
* - Ease factor (how easy the card is for this user)
|
|
11
10
|
* - Number of successful repetitions
|
|
12
11
|
*/
|
|
13
|
-
type CardRating = 'AGAIN' | 'HARD' | 'GOOD' | 'EASY';
|
|
14
|
-
interface SRSState {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
12
|
+
export type CardRating = 'AGAIN' | 'HARD' | 'GOOD' | 'EASY';
|
|
13
|
+
export interface SRSState {
|
|
14
|
+
/** Current interval in days */
|
|
15
|
+
interval: number;
|
|
16
|
+
/** Ease factor (typically 1.3 to 2.5+) */
|
|
17
|
+
easeFactor: number;
|
|
18
|
+
/** Number of successful repetitions */
|
|
19
|
+
repetitions: number;
|
|
20
|
+
/** Current learning step (for new cards) */
|
|
21
|
+
learningStep: number;
|
|
22
|
+
/** Whether card has graduated to review phase */
|
|
23
|
+
isGraduated: boolean;
|
|
24
|
+
/** Whether card is being relearned after a lapse */
|
|
25
|
+
isRelearning: boolean;
|
|
26
|
+
/** Number of times card was forgotten */
|
|
27
|
+
lapses: number;
|
|
29
28
|
}
|
|
30
|
-
interface ReviewResult {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
29
|
+
export interface ReviewResult {
|
|
30
|
+
/** New interval in days */
|
|
31
|
+
interval: number;
|
|
32
|
+
/** New ease factor */
|
|
33
|
+
easeFactor: number;
|
|
34
|
+
/** New repetition count */
|
|
35
|
+
repetitions: number;
|
|
36
|
+
/** Next review date */
|
|
37
|
+
nextReviewAt: Date;
|
|
38
|
+
/** New learning step */
|
|
39
|
+
learningStep: number;
|
|
40
|
+
/** Whether card has graduated */
|
|
41
|
+
isGraduated: boolean;
|
|
42
|
+
/** Whether card is being relearned */
|
|
43
|
+
isRelearning: boolean;
|
|
44
|
+
/** Updated lapse count */
|
|
45
|
+
lapses: number;
|
|
47
46
|
}
|
|
48
|
-
interface SRSConfig {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
47
|
+
export interface SRSConfig {
|
|
48
|
+
/** Learning steps in minutes [1, 10] = 1 min, 10 min */
|
|
49
|
+
learningSteps: number[];
|
|
50
|
+
/** Graduating interval in days */
|
|
51
|
+
graduatingInterval: number;
|
|
52
|
+
/** Easy interval (for easy button on new cards) */
|
|
53
|
+
easyInterval: number;
|
|
54
|
+
/** Relearning steps in minutes */
|
|
55
|
+
relearningSteps: number[];
|
|
56
|
+
/** Minimum ease factor */
|
|
57
|
+
minEaseFactor: number;
|
|
58
|
+
/** Maximum interval in days */
|
|
59
|
+
maxInterval: number;
|
|
60
|
+
/** Interval modifier (1.0 = 100%) */
|
|
61
|
+
intervalModifier: number;
|
|
62
|
+
/** New cards interval modifier */
|
|
63
|
+
newIntervalModifier: number;
|
|
64
|
+
/** Hard interval modifier */
|
|
65
|
+
hardIntervalModifier: number;
|
|
66
|
+
/** Easy bonus modifier */
|
|
67
|
+
easyBonus: number;
|
|
69
68
|
}
|
|
70
|
-
declare const DEFAULT_SRS_CONFIG: SRSConfig;
|
|
71
|
-
declare class SRSEngine {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
69
|
+
export declare const DEFAULT_SRS_CONFIG: SRSConfig;
|
|
70
|
+
export declare class SRSEngine {
|
|
71
|
+
private config;
|
|
72
|
+
constructor(config?: Partial<SRSConfig>);
|
|
73
|
+
/**
|
|
74
|
+
* Calculate the next review state based on rating.
|
|
75
|
+
*/
|
|
76
|
+
calculateNextReview(state: SRSState, rating: CardRating, now?: Date): ReviewResult;
|
|
77
|
+
/**
|
|
78
|
+
* Get initial SRS state for a new card.
|
|
79
|
+
*/
|
|
80
|
+
getInitialState(): SRSState;
|
|
81
|
+
/**
|
|
82
|
+
* Check if a card is due for review.
|
|
83
|
+
*/
|
|
84
|
+
isDue(nextReviewAt: Date, now?: Date): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Calculate overdue days (negative if not yet due).
|
|
87
|
+
*/
|
|
88
|
+
getOverdueDays(nextReviewAt: Date, now?: Date): number;
|
|
89
|
+
/**
|
|
90
|
+
* Handle learning phase (new cards).
|
|
91
|
+
*/
|
|
92
|
+
private handleLearningCard;
|
|
93
|
+
/**
|
|
94
|
+
* Handle relearning phase (lapsed cards).
|
|
95
|
+
*/
|
|
96
|
+
private handleRelearningCard;
|
|
97
|
+
/**
|
|
98
|
+
* Handle review phase (graduated cards).
|
|
99
|
+
*/
|
|
100
|
+
private handleReviewCard;
|
|
101
|
+
private addMinutes;
|
|
102
|
+
private addDays;
|
|
104
103
|
}
|
|
105
104
|
/**
|
|
106
105
|
* Default SRS engine instance.
|
|
107
106
|
*/
|
|
108
|
-
declare const srsEngine: SRSEngine;
|
|
109
|
-
//#endregion
|
|
110
|
-
export { CardRating, DEFAULT_SRS_CONFIG, ReviewResult, SRSConfig, SRSEngine, SRSState, srsEngine };
|
|
107
|
+
export declare const srsEngine: SRSEngine;
|
|
111
108
|
//# sourceMappingURL=srs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"srs.d.ts","
|
|
1
|
+
{"version":3,"file":"srs.d.ts","sourceRoot":"","sources":["../../src/engines/srs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,QAAQ;IACvB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,WAAW,EAAE,OAAO,CAAC;IACrB,oDAAoD;IACpD,YAAY,EAAE,OAAO,CAAC;IACtB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,sBAAsB;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,YAAY,EAAE,IAAI,CAAC;IACnB,wBAAwB;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,WAAW,EAAE,OAAO,CAAC;IACrB,sCAAsC;IACtC,YAAY,EAAE,OAAO,CAAC;IACtB,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,wDAAwD;IACxD,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,kCAAkC;IAClC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,0BAA0B;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,gBAAgB,EAAE,MAAM,CAAC;IACzB,kCAAkC;IAClC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,6BAA6B;IAC7B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,eAAO,MAAM,kBAAkB,EAAE,SAWhC,CAAC;AAIF,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAY;gBAEd,MAAM,GAAE,OAAO,CAAC,SAAS,CAAM;IAI3C;;OAEG;IACH,mBAAmB,CACjB,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,UAAU,EAClB,GAAG,GAAE,IAAiB,GACrB,YAAY;IAef;;OAEG;IACH,eAAe,IAAI,QAAQ;IAY3B;;OAEG;IACH,KAAK,CAAC,YAAY,EAAE,IAAI,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO;IAI1D;;OAEG;IACH,cAAc,CAAC,YAAY,EAAE,IAAI,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM;IAKlE;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA2D1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiE5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAsFxB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,OAAO;CAGhB;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,WAAkB,CAAC"}
|
package/dist/engines/srs.js
CHANGED
|
@@ -1,219 +1,199 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
// @bun
|
|
2
|
+
// src/engines/srs.ts
|
|
3
|
+
var DEFAULT_SRS_CONFIG = {
|
|
4
|
+
learningSteps: [1, 10],
|
|
5
|
+
graduatingInterval: 1,
|
|
6
|
+
easyInterval: 4,
|
|
7
|
+
relearningSteps: [10],
|
|
8
|
+
minEaseFactor: 1.3,
|
|
9
|
+
maxInterval: 365,
|
|
10
|
+
intervalModifier: 1,
|
|
11
|
+
newIntervalModifier: 0.5,
|
|
12
|
+
hardIntervalModifier: 1.2,
|
|
13
|
+
easyBonus: 1.3
|
|
13
14
|
};
|
|
14
|
-
var SRSEngine = class {
|
|
15
|
-
config;
|
|
16
|
-
constructor(config = {}) {
|
|
17
|
-
this.config = {
|
|
18
|
-
...DEFAULT_SRS_CONFIG,
|
|
19
|
-
...config
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Calculate the next review state based on rating.
|
|
24
|
-
*/
|
|
25
|
-
calculateNextReview(state, rating, now = /* @__PURE__ */ new Date()) {
|
|
26
|
-
if (!state.isGraduated && !state.isRelearning) return this.handleLearningCard(state, rating, now);
|
|
27
|
-
if (state.isRelearning) return this.handleRelearningCard(state, rating, now);
|
|
28
|
-
return this.handleReviewCard(state, rating, now);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Get initial SRS state for a new card.
|
|
32
|
-
*/
|
|
33
|
-
getInitialState() {
|
|
34
|
-
return {
|
|
35
|
-
interval: 0,
|
|
36
|
-
easeFactor: 2.5,
|
|
37
|
-
repetitions: 0,
|
|
38
|
-
learningStep: 0,
|
|
39
|
-
isGraduated: false,
|
|
40
|
-
isRelearning: false,
|
|
41
|
-
lapses: 0
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Check if a card is due for review.
|
|
46
|
-
*/
|
|
47
|
-
isDue(nextReviewAt, now = /* @__PURE__ */ new Date()) {
|
|
48
|
-
return nextReviewAt <= now;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Calculate overdue days (negative if not yet due).
|
|
52
|
-
*/
|
|
53
|
-
getOverdueDays(nextReviewAt, now = /* @__PURE__ */ new Date()) {
|
|
54
|
-
const diff = now.getTime() - nextReviewAt.getTime();
|
|
55
|
-
return Math.floor(diff / (1e3 * 60 * 60 * 24));
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Handle learning phase (new cards).
|
|
59
|
-
*/
|
|
60
|
-
handleLearningCard(state, rating, now) {
|
|
61
|
-
const steps = this.config.learningSteps;
|
|
62
|
-
let newStep = state.learningStep;
|
|
63
|
-
let isGraduated = false;
|
|
64
|
-
let interval = 0;
|
|
65
|
-
let nextReviewAt;
|
|
66
|
-
switch (rating) {
|
|
67
|
-
case "AGAIN":
|
|
68
|
-
newStep = 0;
|
|
69
|
-
interval = steps[0] ?? 1;
|
|
70
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
71
|
-
break;
|
|
72
|
-
case "HARD":
|
|
73
|
-
interval = steps[newStep] ?? steps[0] ?? 1;
|
|
74
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
75
|
-
break;
|
|
76
|
-
case "GOOD":
|
|
77
|
-
newStep++;
|
|
78
|
-
if (newStep >= steps.length) {
|
|
79
|
-
isGraduated = true;
|
|
80
|
-
interval = this.config.graduatingInterval;
|
|
81
|
-
nextReviewAt = this.addDays(now, interval);
|
|
82
|
-
} else {
|
|
83
|
-
interval = steps[newStep] ?? 10;
|
|
84
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
85
|
-
}
|
|
86
|
-
break;
|
|
87
|
-
case "EASY":
|
|
88
|
-
isGraduated = true;
|
|
89
|
-
interval = this.config.easyInterval;
|
|
90
|
-
nextReviewAt = this.addDays(now, interval);
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
interval: isGraduated ? interval : 0,
|
|
95
|
-
easeFactor: state.easeFactor,
|
|
96
|
-
repetitions: isGraduated ? 1 : 0,
|
|
97
|
-
nextReviewAt,
|
|
98
|
-
learningStep: newStep,
|
|
99
|
-
isGraduated,
|
|
100
|
-
isRelearning: false,
|
|
101
|
-
lapses: state.lapses
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Handle relearning phase (lapsed cards).
|
|
106
|
-
*/
|
|
107
|
-
handleRelearningCard(state, rating, now) {
|
|
108
|
-
const steps = this.config.relearningSteps;
|
|
109
|
-
let newStep = state.learningStep;
|
|
110
|
-
let isRelearning = true;
|
|
111
|
-
let interval = 0;
|
|
112
|
-
let nextReviewAt;
|
|
113
|
-
switch (rating) {
|
|
114
|
-
case "AGAIN":
|
|
115
|
-
newStep = 0;
|
|
116
|
-
interval = steps[0] ?? 10;
|
|
117
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
118
|
-
break;
|
|
119
|
-
case "HARD":
|
|
120
|
-
interval = steps[newStep] ?? steps[0] ?? 10;
|
|
121
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
122
|
-
break;
|
|
123
|
-
case "GOOD":
|
|
124
|
-
newStep++;
|
|
125
|
-
if (newStep >= steps.length) {
|
|
126
|
-
isRelearning = false;
|
|
127
|
-
interval = Math.max(1, Math.floor(state.interval * this.config.newIntervalModifier));
|
|
128
|
-
nextReviewAt = this.addDays(now, interval);
|
|
129
|
-
} else {
|
|
130
|
-
interval = steps[newStep] ?? 10;
|
|
131
|
-
nextReviewAt = this.addMinutes(now, interval);
|
|
132
|
-
}
|
|
133
|
-
break;
|
|
134
|
-
case "EASY":
|
|
135
|
-
isRelearning = false;
|
|
136
|
-
interval = Math.max(1, Math.floor(state.interval * this.config.newIntervalModifier * 1.5));
|
|
137
|
-
nextReviewAt = this.addDays(now, interval);
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
return {
|
|
141
|
-
interval: isRelearning ? state.interval : interval,
|
|
142
|
-
easeFactor: state.easeFactor,
|
|
143
|
-
repetitions: isRelearning ? state.repetitions : state.repetitions + 1,
|
|
144
|
-
nextReviewAt,
|
|
145
|
-
learningStep: newStep,
|
|
146
|
-
isGraduated: true,
|
|
147
|
-
isRelearning,
|
|
148
|
-
lapses: state.lapses
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Handle review phase (graduated cards).
|
|
153
|
-
*/
|
|
154
|
-
handleReviewCard(state, rating, now) {
|
|
155
|
-
let newInterval;
|
|
156
|
-
let newEaseFactor = state.easeFactor;
|
|
157
|
-
let repetitions = state.repetitions;
|
|
158
|
-
let isRelearning = false;
|
|
159
|
-
let learningStep = 0;
|
|
160
|
-
let lapses = state.lapses;
|
|
161
|
-
switch (rating) {
|
|
162
|
-
case "AGAIN":
|
|
163
|
-
lapses++;
|
|
164
|
-
isRelearning = true;
|
|
165
|
-
learningStep = 0;
|
|
166
|
-
newEaseFactor = Math.max(this.config.minEaseFactor, newEaseFactor - .2);
|
|
167
|
-
newInterval = state.interval;
|
|
168
|
-
return {
|
|
169
|
-
interval: newInterval,
|
|
170
|
-
easeFactor: newEaseFactor,
|
|
171
|
-
repetitions,
|
|
172
|
-
nextReviewAt: this.addMinutes(now, this.config.relearningSteps[0] ?? 10),
|
|
173
|
-
learningStep,
|
|
174
|
-
isGraduated: true,
|
|
175
|
-
isRelearning: true,
|
|
176
|
-
lapses
|
|
177
|
-
};
|
|
178
|
-
case "HARD":
|
|
179
|
-
newEaseFactor = Math.max(this.config.minEaseFactor, newEaseFactor - .15);
|
|
180
|
-
newInterval = Math.max(state.interval + 1, state.interval * this.config.hardIntervalModifier);
|
|
181
|
-
break;
|
|
182
|
-
case "GOOD":
|
|
183
|
-
newInterval = state.interval * newEaseFactor * this.config.intervalModifier;
|
|
184
|
-
repetitions++;
|
|
185
|
-
break;
|
|
186
|
-
case "EASY":
|
|
187
|
-
newEaseFactor = newEaseFactor + .15;
|
|
188
|
-
newInterval = state.interval * newEaseFactor * this.config.easyBonus * this.config.intervalModifier;
|
|
189
|
-
repetitions++;
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
newInterval = Math.min(Math.round(newInterval), this.config.maxInterval);
|
|
193
|
-
newInterval = Math.max(1, newInterval);
|
|
194
|
-
return {
|
|
195
|
-
interval: newInterval,
|
|
196
|
-
easeFactor: newEaseFactor,
|
|
197
|
-
repetitions,
|
|
198
|
-
nextReviewAt: this.addDays(now, newInterval),
|
|
199
|
-
learningStep,
|
|
200
|
-
isGraduated: true,
|
|
201
|
-
isRelearning,
|
|
202
|
-
lapses
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
addMinutes(date, minutes) {
|
|
206
|
-
return new Date(date.getTime() + minutes * 60 * 1e3);
|
|
207
|
-
}
|
|
208
|
-
addDays(date, days) {
|
|
209
|
-
return new Date(date.getTime() + days * 24 * 60 * 60 * 1e3);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
/**
|
|
213
|
-
* Default SRS engine instance.
|
|
214
|
-
*/
|
|
215
|
-
const srsEngine = new SRSEngine();
|
|
216
15
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
16
|
+
class SRSEngine {
|
|
17
|
+
config;
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
this.config = { ...DEFAULT_SRS_CONFIG, ...config };
|
|
20
|
+
}
|
|
21
|
+
calculateNextReview(state, rating, now = new Date) {
|
|
22
|
+
if (!state.isGraduated && !state.isRelearning) {
|
|
23
|
+
return this.handleLearningCard(state, rating, now);
|
|
24
|
+
}
|
|
25
|
+
if (state.isRelearning) {
|
|
26
|
+
return this.handleRelearningCard(state, rating, now);
|
|
27
|
+
}
|
|
28
|
+
return this.handleReviewCard(state, rating, now);
|
|
29
|
+
}
|
|
30
|
+
getInitialState() {
|
|
31
|
+
return {
|
|
32
|
+
interval: 0,
|
|
33
|
+
easeFactor: 2.5,
|
|
34
|
+
repetitions: 0,
|
|
35
|
+
learningStep: 0,
|
|
36
|
+
isGraduated: false,
|
|
37
|
+
isRelearning: false,
|
|
38
|
+
lapses: 0
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
isDue(nextReviewAt, now = new Date) {
|
|
42
|
+
return nextReviewAt <= now;
|
|
43
|
+
}
|
|
44
|
+
getOverdueDays(nextReviewAt, now = new Date) {
|
|
45
|
+
const diff = now.getTime() - nextReviewAt.getTime();
|
|
46
|
+
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
47
|
+
}
|
|
48
|
+
handleLearningCard(state, rating, now) {
|
|
49
|
+
const steps = this.config.learningSteps;
|
|
50
|
+
let newStep = state.learningStep;
|
|
51
|
+
let isGraduated = false;
|
|
52
|
+
let interval = 0;
|
|
53
|
+
let nextReviewAt;
|
|
54
|
+
switch (rating) {
|
|
55
|
+
case "AGAIN":
|
|
56
|
+
newStep = 0;
|
|
57
|
+
interval = steps[0] ?? 1;
|
|
58
|
+
nextReviewAt = this.addMinutes(now, interval);
|
|
59
|
+
break;
|
|
60
|
+
case "HARD":
|
|
61
|
+
interval = steps[newStep] ?? steps[0] ?? 1;
|
|
62
|
+
nextReviewAt = this.addMinutes(now, interval);
|
|
63
|
+
break;
|
|
64
|
+
case "GOOD":
|
|
65
|
+
newStep++;
|
|
66
|
+
if (newStep >= steps.length) {
|
|
67
|
+
isGraduated = true;
|
|
68
|
+
interval = this.config.graduatingInterval;
|
|
69
|
+
nextReviewAt = this.addDays(now, interval);
|
|
70
|
+
} else {
|
|
71
|
+
interval = steps[newStep] ?? 10;
|
|
72
|
+
nextReviewAt = this.addMinutes(now, interval);
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
case "EASY":
|
|
76
|
+
isGraduated = true;
|
|
77
|
+
interval = this.config.easyInterval;
|
|
78
|
+
nextReviewAt = this.addDays(now, interval);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
interval: isGraduated ? interval : 0,
|
|
83
|
+
easeFactor: state.easeFactor,
|
|
84
|
+
repetitions: isGraduated ? 1 : 0,
|
|
85
|
+
nextReviewAt,
|
|
86
|
+
learningStep: newStep,
|
|
87
|
+
isGraduated,
|
|
88
|
+
isRelearning: false,
|
|
89
|
+
lapses: state.lapses
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
handleRelearningCard(state, rating, now) {
|
|
93
|
+
const steps = this.config.relearningSteps;
|
|
94
|
+
let newStep = state.learningStep;
|
|
95
|
+
let isRelearning = true;
|
|
96
|
+
let interval = 0;
|
|
97
|
+
let nextReviewAt;
|
|
98
|
+
switch (rating) {
|
|
99
|
+
case "AGAIN":
|
|
100
|
+
newStep = 0;
|
|
101
|
+
interval = steps[0] ?? 10;
|
|
102
|
+
nextReviewAt = this.addMinutes(now, interval);
|
|
103
|
+
break;
|
|
104
|
+
case "HARD":
|
|
105
|
+
interval = steps[newStep] ?? steps[0] ?? 10;
|
|
106
|
+
nextReviewAt = this.addMinutes(now, interval);
|
|
107
|
+
break;
|
|
108
|
+
case "GOOD":
|
|
109
|
+
newStep++;
|
|
110
|
+
if (newStep >= steps.length) {
|
|
111
|
+
isRelearning = false;
|
|
112
|
+
interval = Math.max(1, Math.floor(state.interval * this.config.newIntervalModifier));
|
|
113
|
+
nextReviewAt = this.addDays(now, interval);
|
|
114
|
+
} else {
|
|
115
|
+
interval = steps[newStep] ?? 10;
|
|
116
|
+
nextReviewAt = this.addMinutes(now, interval);
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
case "EASY":
|
|
120
|
+
isRelearning = false;
|
|
121
|
+
interval = Math.max(1, Math.floor(state.interval * this.config.newIntervalModifier * 1.5));
|
|
122
|
+
nextReviewAt = this.addDays(now, interval);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
interval: isRelearning ? state.interval : interval,
|
|
127
|
+
easeFactor: state.easeFactor,
|
|
128
|
+
repetitions: isRelearning ? state.repetitions : state.repetitions + 1,
|
|
129
|
+
nextReviewAt,
|
|
130
|
+
learningStep: newStep,
|
|
131
|
+
isGraduated: true,
|
|
132
|
+
isRelearning,
|
|
133
|
+
lapses: state.lapses
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
handleReviewCard(state, rating, now) {
|
|
137
|
+
let newInterval;
|
|
138
|
+
let newEaseFactor = state.easeFactor;
|
|
139
|
+
let repetitions = state.repetitions;
|
|
140
|
+
let isRelearning = false;
|
|
141
|
+
let learningStep = 0;
|
|
142
|
+
let lapses = state.lapses;
|
|
143
|
+
switch (rating) {
|
|
144
|
+
case "AGAIN":
|
|
145
|
+
lapses++;
|
|
146
|
+
isRelearning = true;
|
|
147
|
+
learningStep = 0;
|
|
148
|
+
newEaseFactor = Math.max(this.config.minEaseFactor, newEaseFactor - 0.2);
|
|
149
|
+
newInterval = state.interval;
|
|
150
|
+
return {
|
|
151
|
+
interval: newInterval,
|
|
152
|
+
easeFactor: newEaseFactor,
|
|
153
|
+
repetitions,
|
|
154
|
+
nextReviewAt: this.addMinutes(now, this.config.relearningSteps[0] ?? 10),
|
|
155
|
+
learningStep,
|
|
156
|
+
isGraduated: true,
|
|
157
|
+
isRelearning: true,
|
|
158
|
+
lapses
|
|
159
|
+
};
|
|
160
|
+
case "HARD":
|
|
161
|
+
newEaseFactor = Math.max(this.config.minEaseFactor, newEaseFactor - 0.15);
|
|
162
|
+
newInterval = Math.max(state.interval + 1, state.interval * this.config.hardIntervalModifier);
|
|
163
|
+
break;
|
|
164
|
+
case "GOOD":
|
|
165
|
+
newInterval = state.interval * newEaseFactor * this.config.intervalModifier;
|
|
166
|
+
repetitions++;
|
|
167
|
+
break;
|
|
168
|
+
case "EASY":
|
|
169
|
+
newEaseFactor = newEaseFactor + 0.15;
|
|
170
|
+
newInterval = state.interval * newEaseFactor * this.config.easyBonus * this.config.intervalModifier;
|
|
171
|
+
repetitions++;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
newInterval = Math.min(Math.round(newInterval), this.config.maxInterval);
|
|
175
|
+
newInterval = Math.max(1, newInterval);
|
|
176
|
+
return {
|
|
177
|
+
interval: newInterval,
|
|
178
|
+
easeFactor: newEaseFactor,
|
|
179
|
+
repetitions,
|
|
180
|
+
nextReviewAt: this.addDays(now, newInterval),
|
|
181
|
+
learningStep,
|
|
182
|
+
isGraduated: true,
|
|
183
|
+
isRelearning,
|
|
184
|
+
lapses
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
addMinutes(date, minutes) {
|
|
188
|
+
return new Date(date.getTime() + minutes * 60 * 1000);
|
|
189
|
+
}
|
|
190
|
+
addDays(date, days) {
|
|
191
|
+
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
var srsEngine = new SRSEngine;
|
|
195
|
+
export {
|
|
196
|
+
srsEngine,
|
|
197
|
+
SRSEngine,
|
|
198
|
+
DEFAULT_SRS_CONFIG
|
|
199
|
+
};
|