-
-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* type.ts -> types.ts * add interface * add type convert * refactor scheduler * fix lapses * remove scheduler.ts * fix record log * transforming node_modules * remove ISchedulerLifecycle type * impl basic schduler * add test * update jest.config.js * use TypeConvert.time
- Loading branch information
Showing
13 changed files
with
619 additions
and
333 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { | ||
createEmptyCard, | ||
FSRSAlgorithm, | ||
generatorParameters, | ||
Grade, | ||
Rating, | ||
} from '../../src/fsrs' | ||
import BasicScheduler from '../../src/fsrs/impl/basic_schduler' | ||
|
||
describe('basic schduler', () => { | ||
const params = generatorParameters() | ||
const algorithm = new FSRSAlgorithm(params) | ||
const now = new Date() | ||
|
||
it('[State.New]exist', () => { | ||
const card = createEmptyCard(now) | ||
const basicScheduler = new BasicScheduler(card, now, algorithm) | ||
const preview = basicScheduler.preview() | ||
const again = basicScheduler.review(Rating.Again) | ||
const hard = basicScheduler.review(Rating.Hard) | ||
const good = basicScheduler.review(Rating.Good) | ||
const easy = basicScheduler.review(Rating.Easy) | ||
expect(preview).toEqual({ | ||
[Rating.Again]: again, | ||
[Rating.Hard]: hard, | ||
[Rating.Good]: good, | ||
[Rating.Easy]: easy, | ||
}) | ||
}) | ||
it('[State.New]invalid grade', () => { | ||
const card = createEmptyCard(now) | ||
const basicScheduler = new BasicScheduler(card, now, algorithm) | ||
expect(() => basicScheduler.review('invalid' as unknown as Grade)).toThrow( | ||
'Invalid grade' | ||
) | ||
}) | ||
|
||
it('[State.Learning]exist', () => { | ||
const cardByNew = createEmptyCard(now) | ||
const { card } = new BasicScheduler(cardByNew, now, algorithm).review( | ||
Rating.Again | ||
) | ||
const basicScheduler = new BasicScheduler(card, now, algorithm) | ||
|
||
const preview = basicScheduler.preview() | ||
const again = basicScheduler.review(Rating.Again) | ||
const hard = basicScheduler.review(Rating.Hard) | ||
const good = basicScheduler.review(Rating.Good) | ||
const easy = basicScheduler.review(Rating.Easy) | ||
expect(preview).toEqual({ | ||
[Rating.Again]: again, | ||
[Rating.Hard]: hard, | ||
[Rating.Good]: good, | ||
[Rating.Easy]: easy, | ||
}) | ||
}) | ||
it('[State.Learning]invalid grade', () => { | ||
const cardByNew = createEmptyCard(now) | ||
const { card } = new BasicScheduler(cardByNew, now, algorithm).review( | ||
Rating.Again | ||
) | ||
const basicScheduler = new BasicScheduler(card, now, algorithm) | ||
expect(() => basicScheduler.review('invalid' as unknown as Grade)).toThrow( | ||
'Invalid grade' | ||
) | ||
}) | ||
|
||
it('[State.Review]exist', () => { | ||
const cardByNew = createEmptyCard(now) | ||
const { card } = new BasicScheduler(cardByNew, now, algorithm).review( | ||
Rating.Easy | ||
) | ||
const basicScheduler = new BasicScheduler(card, now, algorithm) | ||
|
||
const preview = basicScheduler.preview() | ||
const again = basicScheduler.review(Rating.Again) | ||
const hard = basicScheduler.review(Rating.Hard) | ||
const good = basicScheduler.review(Rating.Good) | ||
const easy = basicScheduler.review(Rating.Easy) | ||
expect(preview).toEqual({ | ||
[Rating.Again]: again, | ||
[Rating.Hard]: hard, | ||
[Rating.Good]: good, | ||
[Rating.Easy]: easy, | ||
}) | ||
}) | ||
it('[State.Review]invalid grade', () => { | ||
const cardByNew = createEmptyCard(now) | ||
const { card } = new BasicScheduler(cardByNew, now, algorithm).review( | ||
Rating.Easy | ||
) | ||
const basicScheduler = new BasicScheduler(card, now, algorithm) | ||
expect(() => basicScheduler.review('invalid' as unknown as Grade)).toThrow( | ||
'Invalid grade' | ||
) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,17 @@ | ||
/** @type {import('ts-jest').JestConfigWithTsJest} */ | ||
module.exports = { | ||
preset: "ts-jest", | ||
testEnvironment: "node", | ||
testMatch: ["**/__tests__/*.js?(x)", "**/__tests__/*.ts?(x)"], | ||
preset: 'ts-jest', | ||
testEnvironment: 'node', | ||
testMatch: [ | ||
'**/__tests__/*.ts?(x)', | ||
'**/__tests__/**/*.ts?(x)', | ||
], | ||
collectCoverage: true, | ||
coverageReporters: ["text", "cobertura"], | ||
coverageReporters: ['text', 'cobertura'], | ||
coverageThreshold: { | ||
global: { | ||
lines: 80, | ||
}, | ||
}, | ||
}; | ||
transformIgnorePatterns: ['/node_modules/(?!(module-to-transform)/)'], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { FSRSAlgorithm } from './algorithm' | ||
import { TypeConvert } from './convert' | ||
import { | ||
type Card, | ||
type RecordLog, | ||
type Grade, | ||
type RecordLogItem, | ||
State, | ||
Rating, | ||
type ReviewLog, | ||
type CardInput, | ||
type DateInput, | ||
} from './models' | ||
import type { IScheduler } from './types' | ||
|
||
export abstract class AbstractScheduler implements IScheduler { | ||
protected last: Card | ||
protected current: Card | ||
protected review_time: Date | ||
protected next: Map<Grade, RecordLogItem> = new Map() | ||
protected algorithm: FSRSAlgorithm | ||
|
||
constructor( | ||
card: CardInput | Card, | ||
now: DateInput, | ||
algorithm: FSRSAlgorithm | ||
) { | ||
this.algorithm = algorithm | ||
|
||
this.last = TypeConvert.card(card) | ||
this.current = TypeConvert.card(card) | ||
this.review_time = TypeConvert.time(now) | ||
this.init() | ||
} | ||
|
||
private init() { | ||
const { state, last_review } = this.current | ||
let interval = 0 // card.state === State.New => 0 | ||
if (state !== State.New && last_review) { | ||
interval = this.review_time.diff(last_review as Date, 'days') | ||
} | ||
this.current.last_review = this.review_time | ||
this.current.elapsed_days = interval | ||
this.current.reps += 1 | ||
this.initSeed() | ||
} | ||
|
||
public preview(): RecordLog { | ||
return { | ||
[Rating.Again]: this.review(Rating.Again), | ||
[Rating.Hard]: this.review(Rating.Hard), | ||
[Rating.Good]: this.review(Rating.Good), | ||
[Rating.Easy]: this.review(Rating.Easy), | ||
} satisfies RecordLog | ||
} | ||
public review(grade: Grade): RecordLogItem { | ||
const { state } = this.last | ||
switch (state) { | ||
case State.New: | ||
return this.newState(grade) | ||
case State.Learning: | ||
case State.Relearning: | ||
return this.learningState(grade) | ||
case State.Review: { | ||
const item = this.reviewState(grade) | ||
if (!item) { | ||
throw new Error('Invalid grade') | ||
} | ||
return item | ||
} | ||
} | ||
} | ||
|
||
protected abstract newState(grade: Grade): RecordLogItem | ||
|
||
protected abstract learningState(grade: Grade): RecordLogItem | ||
|
||
protected abstract reviewState(grade: Grade): RecordLogItem | ||
|
||
private initSeed() { | ||
const time = this.review_time.getTime() | ||
const reps = this.current.reps | ||
const mul = this.current.difficulty * this.current.stability | ||
this.algorithm.seed = `${time}_${reps}_${mul}` | ||
} | ||
|
||
protected buildLog(rating: Grade): ReviewLog { | ||
const { last_review, due, elapsed_days } = this.last | ||
|
||
return { | ||
rating: rating, | ||
state: this.current.state, | ||
due: last_review || due, | ||
stability: this.current.stability, | ||
difficulty: this.current.difficulty, | ||
elapsed_days: this.current.elapsed_days, | ||
last_elapsed_days: elapsed_days, | ||
scheduled_days: this.current.scheduled_days, | ||
review: this.review_time, | ||
} satisfies ReviewLog | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.