Skip to content

Commit

Permalink
Feat/rollback card (#40)
Browse files Browse the repository at this point in the history
* Doc/wrong annotations

* Fix/missing 'last_review' in the data returned by the 'createEmptyCard' function

* Fix/incorrect log in record_log

* Feat/rollback card utilizing the current card and log

* Test/rollback

* Test/first repeat (#39)

* 3.1.0-beta1
  • Loading branch information
ishiko732 authored Nov 3, 2023
1 parent 474488f commit 86b4cda
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 30 deletions.
46 changes: 46 additions & 0 deletions __tests__/FSRSV4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
generatorParameters,
FSRS,
createEmptyCard,
State,
} from "../src/fsrs";

describe("initial FSRS V4", () => {
Expand Down Expand Up @@ -49,6 +50,7 @@ describe("FSRS V4 AC by py-fsrs", () => {
],
enable_fuzz: false,
});
const grade = [Rating.Again, Rating.Hard, Rating.Good,Rating.Easy];
it("ivl_history", () => {
let card = createEmptyCard();
let now = new Date(2022, 11, 29, 12, 30, 0, 0);
Expand All @@ -70,6 +72,13 @@ describe("FSRS V4 AC by py-fsrs", () => {
];
const ivl_history: number[] = [];
for (const rating of ratings) {
for (const check of grade) {
const rollbackCard = f.rollback(
scheduling_cards[check].card,
scheduling_cards[check].log,
);
expect(rollbackCard).toEqual(card);
}
card = scheduling_cards[rating].card;
const ivl = card.scheduled_days;
ivl_history.push(ivl);
Expand All @@ -81,4 +90,41 @@ describe("FSRS V4 AC by py-fsrs", () => {
0, 5, 16, 43, 106, 236, 0, 0, 12, 25, 47, 85, 147,
]);
});

it("first repeat", () => {
const card = createEmptyCard();
const now = new Date(2022, 11, 29, 12, 30, 0, 0);
const scheduling_cards = f.repeat(card, now);
const grades = [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy];

const stability: number[] = [];
const difficulty: number[] = [];
const elapsed_days: number[] = [];
const scheduled_days: number[] = [];
const reps: number[] = [];
const lapses: number[] = [];
const states: State[] = [];
for (const rating of grades) {
const first_card = scheduling_cards[rating].card;
stability.push(first_card.stability);
difficulty.push(first_card.difficulty);
reps.push(first_card.reps);
lapses.push(first_card.lapses);
elapsed_days.push(first_card.elapsed_days);
scheduled_days.push(first_card.scheduled_days);
states.push(first_card.state);
}
expect(stability).toEqual([1.14, 1.01, 5.44, 14.67]);
expect(difficulty).toEqual([8.4348, 6.8686, 5.3024, 3.7361999999999993]);
expect(reps).toEqual([1, 1, 1, 1]);
expect(lapses).toEqual([1, 0, 0, 0]);
expect(elapsed_days).toEqual([0, 0, 0, 0]);
expect(scheduled_days).toEqual([0, 0, 0, 15]);
expect(states).toEqual([
State.Learning,
State.Learning,
State.Learning,
State.Review,
]);
});
});
41 changes: 41 additions & 0 deletions __tests__/rollback.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { createEmptyCard, fsrs, FSRS, Rating } from "../src/fsrs";

describe("FSRS rollback", () => {
const f: FSRS = fsrs({
w: [
1.14, 1.01, 5.44, 14.67, 5.3024, 1.5662, 1.2503, 0.0028, 1.5489, 0.1763,
0.9953, 2.7473, 0.0179, 0.3105, 0.3976, 0.0, 2.0902,
],
enable_fuzz: false,
});
it("first rollback", () => {
const card = createEmptyCard();
const now = new Date(2022, 11, 29, 12, 30, 0, 0);
const scheduling_cards = f.repeat(card, now);
const grade = [Rating.Again, Rating.Hard, Rating.Good, Rating.Easy];
for (const rating of grade) {
const rollbackCard = f.rollback(
scheduling_cards[rating].card,
scheduling_cards[rating].log,
);
expect(rollbackCard).toEqual(card);
}
});

it("rollback 2", () => {
let card = createEmptyCard();
let now = new Date(2022, 11, 29, 12, 30, 0, 0);
let scheduling_cards = f.repeat(card, now);
card = scheduling_cards["4"].card;
now = card.due;
scheduling_cards = f.repeat(card, now);
const grade = [Rating.Again, Rating.Hard, Rating.Good,Rating.Easy];
for (const rating of grade) {
const rollbackCard = f.rollback(
scheduling_cards[rating].card,
scheduling_cards[rating].log,
);
expect(rollbackCard).toEqual(card);
}
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-fsrs",
"version": "3.1.0-beta0",
"version": "3.1.0-beta1",
"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.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
3 changes: 2 additions & 1 deletion src/fsrs/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const default_w = envParams.FSRS_W || [
];
export const default_enable_fuzz = envParams.FSRS_ENABLE_FUZZ || false;

export const FSRSVersion: string = "3.1.0-beta0";
export const FSRSVersion: string = "3.1.0-beta1";

export const generatorParameters = (props?: Partial<FSRSParameters>): FSRSParameters => {
return {
Expand All @@ -51,6 +51,7 @@ export const createEmptyCard = (now?: Date): Card => {
reps: 0,
lapses: 0,
state: State.New,
last_review: undefined,
};
};

Expand Down
89 changes: 70 additions & 19 deletions src/fsrs/fsrs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { SchedulingCard } from "./index";
import { fixDate, fixState } from "./help";
import { FSRSParameters, Card, State, CardInput, DateInput, RecordLog } from "./models";
import { SchedulingCard } from "./scheduler";
import { fixDate, fixRating, fixState } from "./help";
import {
Card,
CardInput,
DateInput,
FSRSParameters,
Rating,
RecordLog,
ReviewLog,
ReviewLogInput,
State,
} from "./models";
import type { int } from "./type";
import { FSRSAlgorithm } from "./algorithm";

Expand All @@ -9,27 +19,33 @@ export class FSRS extends FSRSAlgorithm {
super(param);
}

preProcess(_card: CardInput, _now: DateInput) {
const card: Card = {
private preProcessCard(_card: CardInput): Card {
return {
..._card,
state: fixState(_card.state),
due: fixDate(_card.due),
last_review: _card.last_review ? fixDate(_card.last_review) : undefined,
};
const now = fixDate(_now);
return { card, now };
}

private preProcessDate(_date: DateInput): Date {
return fixDate(_date);
}

private preProcessLog(_log: ReviewLogInput): ReviewLog {
return {
..._log,
rating: fixRating(_log.rating),
state: fixState(_log.state),
review: fixDate(_log.review),
};
}

repeat = (card: CardInput, now: DateInput): RecordLog => {
const process = this.preProcess(card, now);
card = process.card;
now = process.now;
card.elapsed_days =
card.state === State.New ? 0 : now.diff(card.last_review as Date, "days"); //相距时间
card.last_review = now; // 上次复习时间
card.reps += 1;
const s = new SchedulingCard(card).update_state(card.state);
this.seed = String(card.last_review.getTime()) + String(card.elapsed_days);
card = this.preProcessCard(card);
now = this.preProcessDate(now);
const s = new SchedulingCard(card, now).update_state(card.state);
this.seed = String(now.getTime()) + String(card.reps);
let easy_interval, good_interval, hard_interval;
switch (card.state) {
case State.New:
Expand Down Expand Up @@ -73,9 +89,8 @@ export class FSRS extends FSRSAlgorithm {
};

get_retrievability = (card: Card, now: Date): undefined | string => {
const process = this.preProcess(card, now);
card = process.card;
now = process.now;
card = this.preProcessCard(card);
now = this.preProcessDate(now);
if (card.state !== State.Review) {
return undefined;
}
Expand All @@ -84,4 +99,40 @@ export class FSRS extends FSRSAlgorithm {
(this.current_retrievability(t, card.stability) * 100).toFixed(2) + "%"
);
};

rollback = (card: CardInput, log: ReviewLogInput): Card => {
card = this.preProcessCard(card);
log = this.preProcessLog(log);

let last_due, last_review, last_lapses;
switch (log.state) {
case State.New:
last_due = log.due;
last_review = undefined;
last_lapses = 0;
break;
case State.Learning:
case State.Relearning:
case State.Review:
last_due = log.review;
last_review = log.due;
last_lapses =
card.lapses -
(log.rating === Rating.Again && log.state === State.Review ? 1 : 0);
break;
}

return {
...card,
due: last_due,
stability: log.stability,
difficulty: log.difficulty,
elapsed_days: log.elapsed_days,
scheduled_days: log.scheduled_days,
reps: Math.max(0, card.reps - 1),
lapses: Math.max(0, last_lapses),
state: log.state,
last_review: last_review,
};
};
}
7 changes: 7 additions & 0 deletions src/fsrs/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export enum Rating {
export interface ReviewLog {
rating: Rating;
state: State;
due:Date;
stability: number;
difficulty: number;
elapsed_days: number;
scheduled_days: number;
review: Date;
Expand All @@ -44,6 +47,10 @@ export interface Card {

export type CardInput = Card & { state: StateType | State };
export type DateInput = Date | number | string;
export type ReviewLogInput = ReviewLog & {
rating: RatingType | Rating;
state: StateType | State;
};

export interface FSRSParameters {
request_retention: number;
Expand Down
38 changes: 29 additions & 9 deletions src/fsrs/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@ export class SchedulingCard {
hard: Card;
good: Card;
easy: Card;
last_review: Date;
elapsed_days: number;

private copy(card: Card): Card {
return {
...card,
};
}

constructor(card: Card) {
constructor(card: Card, now: Date) {
this.last_review = card.last_review || card.due;
this.elapsed_days = card.elapsed_days;
card.elapsed_days =
card.state === State.New ? 0 : now.diff(card.last_review as Date, "days"); //相距时间
card.last_review = now; // 上次复习时间
card.reps += 1;
this.again = this.copy(card);
this.hard = this.copy(card);
this.good = this.copy(card);
Expand Down Expand Up @@ -69,8 +77,11 @@ export class SchedulingCard {
log: {
rating: Rating.Again,
state: card.state,
elapsed_days: this.again.scheduled_days,
scheduled_days: card.elapsed_days,
due: this.last_review,
stability: card.stability,
difficulty: card.difficulty,
elapsed_days: this.elapsed_days,
scheduled_days: card.scheduled_days,
review: now,
},
},
Expand All @@ -79,8 +90,11 @@ export class SchedulingCard {
log: {
rating: Rating.Hard,
state: card.state,
elapsed_days: this.hard.scheduled_days,
scheduled_days: card.elapsed_days,
due: this.last_review,
stability: card.stability,
difficulty: card.difficulty,
elapsed_days: this.elapsed_days,
scheduled_days: card.scheduled_days,
review: now,
},
},
Expand All @@ -89,8 +103,11 @@ export class SchedulingCard {
log: {
rating: Rating.Good,
state: card.state,
elapsed_days: this.good.scheduled_days,
scheduled_days: card.elapsed_days,
due: this.last_review,
stability: card.stability,
difficulty: card.difficulty,
elapsed_days: this.elapsed_days,
scheduled_days: card.scheduled_days,
review: now,
},
},
Expand All @@ -99,8 +116,11 @@ export class SchedulingCard {
log: {
rating: Rating.Easy,
state: card.state,
elapsed_days: this.easy.scheduled_days,
scheduled_days: card.elapsed_days,
due: this.last_review,
stability: card.stability,
difficulty: card.difficulty,
elapsed_days: this.elapsed_days,
scheduled_days: card.scheduled_days,
review: now,
},
},
Expand Down

0 comments on commit 86b4cda

Please sign in to comment.