From 82a788bd1f9cb1c83c6eb7544c588ad84270a66c Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Thu, 12 Sep 2024 11:15:48 +0800 Subject: [PATCH 1/7] compute retrievability for all states --- __tests__/FSRSV5.test.ts | 4 ++-- src/fsrs/fsrs.ts | 11 ++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/__tests__/FSRSV5.test.ts b/__tests__/FSRSV5.test.ts index a9d1b19..1445057 100644 --- a/__tests__/FSRSV5.test.ts +++ b/__tests__/FSRSV5.test.ts @@ -123,10 +123,10 @@ describe('FSRS V5 ', () => { describe('get retrievability', () => { const fsrs = new FSRS({}) - test('return undefined for non-review cards', () => { + test('return 0% for new cards', () => { const card = createEmptyCard() const now = new Date() - const expected = undefined + const expected = "0%" expect(fsrs.get_retrievability(card, now)).toBe(expected) }) diff --git a/src/fsrs/fsrs.ts b/src/fsrs/fsrs.ts index 659fa61..832615e 100644 --- a/src/fsrs/fsrs.ts +++ b/src/fsrs/fsrs.ts @@ -204,16 +204,13 @@ export class FSRS extends FSRSAlgorithm { */ get_retrievability( card: CardInput | Card, - now: DateInput, + now?: DateInput, format: T = true as T - ): undefined | (T extends true ? string : number) { + ): (T extends true ? string : number) { const processedCard = TypeConvert.card(card) - now = TypeConvert.time(now) - if (processedCard.state !== State.Review) { - return undefined - } + now = now ? TypeConvert.time(now) : new Date() const t = Math.max(now.diff(processedCard.last_review as Date, 'days'), 0) - const r = this.forgetting_curve(t, +processedCard.stability.toFixed(2)) + const r = processedCard.state !== State.New ? this.forgetting_curve(t, +processedCard.stability.toFixed(2)) : 0 return (format ? `${(r * 100).toFixed(2)}%` : r) as T extends true ? string : number From 0e57fa796f0783ea0c8fad532a443987a965dd9f Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Thu, 12 Sep 2024 11:22:00 +0800 Subject: [PATCH 2/7] Update FSRSV5.test.ts --- __tests__/FSRSV5.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__tests__/FSRSV5.test.ts b/__tests__/FSRSV5.test.ts index 1445057..3ea1f9a 100644 --- a/__tests__/FSRSV5.test.ts +++ b/__tests__/FSRSV5.test.ts @@ -133,8 +133,8 @@ describe('get retrievability', () => { test('return retrievability percentage for review cards', () => { const card = createEmptyCard('2023-12-01 04:00:00') const sc = fsrs.repeat(card, '2023-12-01 04:05:00') - const r = [undefined, undefined, undefined, '90.26%'] - const r_number = [undefined, undefined, undefined, 0.90260891] + const r = ['100.00%', '100.00%', '100.00%', '90.26%'] + const r_number = [1, 1, 1, 0.90260891] Grades.forEach((grade, index) => { expect(fsrs.get_retrievability(sc[grade].card, sc[grade].card.due)).toBe( r[index] From 8d6d9d1582d7e37a357542436759e270447b8180 Mon Sep 17 00:00:00 2001 From: Jarrett Ye Date: Thu, 12 Sep 2024 11:42:45 +0800 Subject: [PATCH 3/7] pass the test --- __tests__/FSRSV5.test.ts | 4 ++-- src/fsrs/fsrs.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/__tests__/FSRSV5.test.ts b/__tests__/FSRSV5.test.ts index 3ea1f9a..a5ebfc8 100644 --- a/__tests__/FSRSV5.test.ts +++ b/__tests__/FSRSV5.test.ts @@ -123,10 +123,10 @@ describe('FSRS V5 ', () => { describe('get retrievability', () => { const fsrs = new FSRS({}) - test('return 0% for new cards', () => { + test('return 0.00% for new cards', () => { const card = createEmptyCard() const now = new Date() - const expected = "0%" + const expected = "0.00%" expect(fsrs.get_retrievability(card, now)).toBe(expected) }) diff --git a/src/fsrs/fsrs.ts b/src/fsrs/fsrs.ts index 832615e..12799a1 100644 --- a/src/fsrs/fsrs.ts +++ b/src/fsrs/fsrs.ts @@ -209,7 +209,7 @@ export class FSRS extends FSRSAlgorithm { ): (T extends true ? string : number) { const processedCard = TypeConvert.card(card) now = now ? TypeConvert.time(now) : new Date() - const t = Math.max(now.diff(processedCard.last_review as Date, 'days'), 0) + const t = processedCard.state !== State.New ? Math.max(now.diff(processedCard.last_review as Date, 'days'), 0) : 0 const r = processedCard.state !== State.New ? this.forgetting_curve(t, +processedCard.stability.toFixed(2)) : 0 return (format ? `${(r * 100).toFixed(2)}%` : r) as T extends true ? string From 970c9ae96bfb98468ad138e0afd84a5e067bd094 Mon Sep 17 00:00:00 2001 From: ishiko Date: Thu, 12 Sep 2024 20:16:33 +0800 Subject: [PATCH 4/7] add loop test --- __tests__/FSRSV5.test.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/__tests__/FSRSV5.test.ts b/__tests__/FSRSV5.test.ts index a5ebfc8..48bda75 100644 --- a/__tests__/FSRSV5.test.ts +++ b/__tests__/FSRSV5.test.ts @@ -126,7 +126,7 @@ describe('get retrievability', () => { test('return 0.00% for new cards', () => { const card = createEmptyCard() const now = new Date() - const expected = "0.00%" + const expected = '0.00%' expect(fsrs.get_retrievability(card, now)).toBe(expected) }) @@ -144,6 +144,23 @@ describe('get retrievability', () => { ).toBe(r_number[index]) }) }) + + test('loop Again', () => { + const fsrs = new FSRS({}) + let card = createEmptyCard() + let now = new Date() + let i = 0 + while (i < 10 ** 3) { + card = fsrs.next(card, now, Rating.Again).card + now = card.due + i++ + + const r = fsrs.get_retrievability(card, now, false) + console.debug(`Loop ${i}: s:${card.stability} r:${r} `) + + expect(r).not.toBeNaN() + } + }) }) describe('fsrs.next method', () => { From 86ae10f9a91cc30447c66920d5079b8268f9924f Mon Sep 17 00:00:00 2001 From: ishiko Date: Thu, 12 Sep 2024 20:34:06 +0800 Subject: [PATCH 5/7] fake the system time --- __tests__/FSRSV5.test.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/__tests__/FSRSV5.test.ts b/__tests__/FSRSV5.test.ts index 48bda75..b7380d9 100644 --- a/__tests__/FSRSV5.test.ts +++ b/__tests__/FSRSV5.test.ts @@ -134,7 +134,7 @@ describe('get retrievability', () => { const card = createEmptyCard('2023-12-01 04:00:00') const sc = fsrs.repeat(card, '2023-12-01 04:05:00') const r = ['100.00%', '100.00%', '100.00%', '90.26%'] - const r_number = [1, 1, 1, 0.90260891] + const r_number = [1, 1, 1, 0.9026208] Grades.forEach((grade, index) => { expect(fsrs.get_retrievability(sc[grade].card, sc[grade].card.due)).toBe( r[index] @@ -145,6 +145,22 @@ describe('get retrievability', () => { }) }) + test('fake the current system time', () => { + const card = createEmptyCard('2023-12-01 04:00:00') + const sc = fsrs.repeat(card, '2023-12-01 04:05:00') + const r = ['100.00%', '100.00%', '100.00%', '90.26%'] + const r_number = [1, 1, 1, 0.9026208] + jest.useFakeTimers() + Grades.forEach((grade, index) => { + jest.setSystemTime(sc[grade].card.due) + expect(fsrs.get_retrievability(sc[grade].card)).toBe(r[index]) + expect(fsrs.get_retrievability(sc[grade].card, undefined, false)).toBe( + r_number[index] + ) + }) + jest.useRealTimers() + }) + test('loop Again', () => { const fsrs = new FSRS({}) let card = createEmptyCard() @@ -157,7 +173,7 @@ describe('get retrievability', () => { const r = fsrs.get_retrievability(card, now, false) console.debug(`Loop ${i}: s:${card.stability} r:${r} `) - + expect(r).not.toBeNaN() } }) From aaa6844761d1b93a43c35844c1b34720a88dc76a Mon Sep 17 00:00:00 2001 From: ishiko Date: Thu, 12 Sep 2024 20:39:55 +0800 Subject: [PATCH 6/7] Change the toFixed precision from 2 to 8 --- src/fsrs/fsrs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fsrs/fsrs.ts b/src/fsrs/fsrs.ts index 12799a1..3910dbf 100644 --- a/src/fsrs/fsrs.ts +++ b/src/fsrs/fsrs.ts @@ -210,7 +210,7 @@ export class FSRS extends FSRSAlgorithm { const processedCard = TypeConvert.card(card) now = now ? TypeConvert.time(now) : new Date() const t = processedCard.state !== State.New ? Math.max(now.diff(processedCard.last_review as Date, 'days'), 0) : 0 - const r = processedCard.state !== State.New ? this.forgetting_curve(t, +processedCard.stability.toFixed(2)) : 0 + const r = processedCard.state !== State.New ? this.forgetting_curve(t, +processedCard.stability.toFixed(8)) : 0 return (format ? `${(r * 100).toFixed(2)}%` : r) as T extends true ? string : number From 04d28796d9affd19cc7663e4929fc9a6cf3ae210 Mon Sep 17 00:00:00 2001 From: ishiko Date: Thu, 12 Sep 2024 20:44:13 +0800 Subject: [PATCH 7/7] bump version to 4.3.0 --- package.json | 2 +- src/fsrs/default.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 42c3d60..0507a4c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ts-fsrs", - "version": "4.2.0", + "version": "4.3.0", "description": "ts-fsrs is a versatile package based on TypeScript that supports ES modules, CommonJS, and UMD. It implements the Free Spaced Repetition Scheduler (FSRS) algorithm, enabling developers to integrate FSRS into their flashcard applications to enhance the user learning experience.", "main": "dist/index.cjs", "umd": "dist/index.umd.js", diff --git a/src/fsrs/default.ts b/src/fsrs/default.ts index ffc484b..6c5fb97 100644 --- a/src/fsrs/default.ts +++ b/src/fsrs/default.ts @@ -11,7 +11,7 @@ export const default_w = [ export const default_enable_fuzz = false export const default_enable_short_term = true -export const FSRSVersion: string = 'v4.2.0 using FSRS V5.0' +export const FSRSVersion: string = 'v4.3.0 using FSRS V5.0' export const generatorParameters = ( props?: Partial