#モデル概要
FSRS モデルは、評価と経過時間から新しい記憶状態を求め、忘却曲線で想起確率を推定し、記憶状態と目標保持率から基本間隔を計算します。
このセクションの FSRS-3 から FSRS-6 の用語と数式は awesome-fsrs Wiki、特に The Algorithm を基準に確認しています。TypeScript API とフレームワークの責務境界は ts-fsrs 自体に基づきます。
モデルが担うのは、記憶状態の更新、忘却曲線の計算、基本間隔の計算です。scheduler は復習処理を統括し、chrono は日時と経過時間を扱い、middleware は学習ステップ、ファジング、最大間隔、キュー状態のポリシーを適用します。
このプロンプトをコピーすると、AI Agent がスケジューリングポリシーを混在させずに Model 契約へ従います。
#共通の記号
| 記号 | 意味 |
|---|---|
| 難易度。1(最も簡単)から 10(最も難しい)までです。 | |
| FSRS-3〜6 の安定度(日数)。 のとき想起確率は 90% です。 | |
| 日後に思い出せる確率の推定値です。 | |
| 評価。1 は Again、2 は Hard、3 は Good、4 は Easy です。 | |
| 前回の復習からの経過日数です。 | |
nextInterval が使う目標保持率です。 | |
| 安定度 と目標保持率 から求める基本間隔(日数)。middleware のポリシーを適用する前の値です。 | |
| 、 | モデルが許容する安定度の下限と上限です。 |
| 、 | モデルが許容する難易度の下限と上限です。 |
| 学習済みのモデル重みです。 |
FSRS-7 は実装のスナップショットを基に別途説明します。速い安定度を追加し、混合曲線を使って小数の日数の間隔を返します。上表の安定度の 90% 定義と I(r, S) の記法は FSRS-3〜6 に適用されます。
#バージョン別モデルを使う
DefaultScheduler は既定で FSRS-7 を使い、version オプションで対応する FSRS バージョンを選択できます。各バージョンのモデルを直接作成して呼び出すこともできます。以下の例では FSRS-6 を使います。
import { type Rating = 0 | 1 | 2 | 3 | 4
const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating } from 'ts-fsrs'
import { const FSRS6_DEFAULT_WEIGHTS: number[] FSRS6_DEFAULT_WEIGHTS , const FSRS6Model: Model<{
readonly name: "fsrs-6";
readonly config: SRSSchema<{
input: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
output: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
}>;
readonly memoryState: SRSSchema<{
input: {
stability: number;
difficulty: number;
};
output: {
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS6Algorithm;
}>
FSRS6Model } from 'ts-fsrs/models/fsrs-6'
const const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model = const FSRS6Model: Model<{
readonly name: "fsrs-6";
readonly config: SRSSchema<{
input: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
output: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
}>;
readonly memoryState: SRSSchema<{
input: {
stability: number;
difficulty: number;
};
output: {
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS6Algorithm;
}>
FSRS6Model .Model<{ readonly name: "fsrs-6"; readonly config: SRSSchema<{ input: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; output: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; }>; readonly memoryState: SRSSchema<{ input: { stability: number; difficulty: number; }; output: { stability: number; difficulty: number; }; }>; readonly algorithm: FSRS6Algorithm; }>.create: (ctx: {
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
} | {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly migrate?: boolean;
readonly clip?: boolean;
readonly check?: boolean;
readonly bypass?: boolean;
}) => ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
create ({
config: {
weights: const FSRS6_DEFAULT_WEIGHTS: number[] FSRS6_DEFAULT_WEIGHTS ,
enableShortTerm: true,
numRelearningSteps: 1, // Constrains weights; middleware runs the steps.
},
})
const memoryState = const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model .ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.step: (input: ModelStepInput<{
stability: number;
difficulty: number;
}>) => {
stability: number;
difficulty: number;
}
step ({
ModelStepInput<{ stability: number; difficulty: number; }>.memoryState: Readonly<{
stability: number;
difficulty: number;
}> | null
memoryState : null,
ModelStepInput<{ stability: number; difficulty: number; }>.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good,
ModelStepInput<{ stability: number; difficulty: number; }>.elapsedDays: number elapsedDays : 0,
})
const desiredRetention = 0.9
const interval = const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model .ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.nextInterval: (memoryState: Readonly<{
stability: number;
difficulty: number;
}>, desiredRetention: number) => number
nextInterval (memoryState, desiredRetention)
const retrievabilityAtInterval = const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model .ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.forgettingCurve: (memoryState: Readonly<{
stability: number;
difficulty: number;
}>, elapsedDays: number) => number
forgettingCurve (memoryState, interval)
console.log(
JSON.stringify(
{
memoryState,
desiredRetention,
interval,
retrievabilityAtInterval,
},
null,
2
)
)
この例では、まず目標保持率 90% に対応する復習間隔を計算し、次に forgettingCurve でその間隔における想起確率を推定します。FSRS-6 の nextInterval は整数の日数を返すため、結果は 90% とわずかに異なる場合があります。
FSRS-6 の設定では、enableShortTerm がモデルの同日安定度更新を制御します。numRelearningSteps は重みのクリッピングと検査にだけ使われ、再学習ステップのスケジューリングは引き続き middleware が担います。
#モデルを定義する
defineModel は具体的な設定、記憶状態、アルゴリズムの型を保ち、schema、既定値、モデルコアのファクトリーを一つの定義にまとめます。defineModel 自体は設定を解析しません。
以下では SM-2 の派生版 を実装し、モデルの定義方法を示します。
import {
function clamp(value: number, min: number, max: number): number clamp ,
function defineModel<const Name extends string | symbol, const ConfigSchema extends AnySchema, const MemoryStateSchema extends AnyObjectSchema, const Algorithm>(definition: {
readonly name: Name;
readonly schema: ModelSchema<{
readonly config: ConfigSchema;
readonly memoryState: MemoryStateSchema;
}>;
readonly defaultValue: {
readonly memoryState: (ctx: {
readonly config: SchemaOutput<ConfigSchema>;
}) => SchemaInput<MemoryStateSchema>;
};
readonly create: ModelCreate<{
readonly config: ConfigSchema;
readonly memoryState: MemoryStateSchema;
}, Algorithm>;
}): Model<{
readonly name: Name;
readonly config: ConfigSchema;
readonly memoryState: MemoryStateSchema;
readonly algorithm: Algorithm;
}>
defineModel ,
type interface ModelForwardInput<MemoryState extends object> ModelForwardInput ,
type interface ModelStepInput<MemoryState extends object> ModelStepInput ,
type Rating = 0 | 1 | 2 | 3 | 4
const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating ,
} from 'ts-fsrs'
import { z } from 'zod'
const configSchema = z.object({
weights: z.tuple([
z.number().min(0.01).max(100),
z.number().min(0.01).max(100),
z.number().min(1.3).max(10),
z.number().nonnegative(),
z.number().min(5),
z.number(),
]),
})
const memoryStateSchema = z.object({
interval: z.number().min(0).max(36500),
easeFactor: z.number().min(1.3).max(10),
repetitions: z.number().int().nonnegative(),
})
type type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState = z.output<typeof memoryStateSchema>
const const SM2Model: Model<{
readonly name: "sm2";
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
readonly algorithm: null;
}>
SM2Model = defineModel<"sm2", z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>, z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>, null>(definition: {
readonly name: "sm2";
readonly schema: ModelSchema<{
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
...;
}, z.core.$strip>;
}>;
readonly defaultValue: {
...;
};
readonly create: ModelCreate<...>;
}): Model<...>
defineModel ({
name: 'sm2',
schema: ModelSchema<{
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
}>
schema : { ModelSchema<{ readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; }>.config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>
config : configSchema, ModelSchema<{ readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; }>.memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>
memoryState : memoryStateSchema },
defaultValue: {
memoryState: ({ config }) => ({
interval: 0,
easeFactor: config.weights[2],
repetitions: 0,
}),
},
create: ModelCreate<{
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
}, null>
create ({ config }) {
const parsed = configSchema.ZodType<any, any, $ZodObjectInternals<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>>.parse(data: unknown, params?: z.core.ParseContext<z.core.$ZodIssue>): {
weights: [number, number, number, number, number, number];
}
parse (config)
const w = parsed.weights
const const step: ({ memoryState, rating }: ModelStepInput<MemoryState>) => {
interval: number;
easeFactor: number;
repetitions: number;
}
step = ({ memoryState, rating }: interface ModelStepInput<MemoryState extends object> ModelStepInput <type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >) => {
const previous = memoryState ?? {
interval: 0,
easeFactor: parsed.weights[2],
repetitions: 0,
}
// Again / Hard / Good / Easy -> SM-2 quality 2 / 3 / 4 / 5.
const quality = rating + 1
const repetitions = rating > const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Again ? previous.repetitions + 1 : 0
const interval = function clamp(value: number, min: number, max: number): number clamp (
repetitions <= 1
? w[0]
: repetitions === 2
? w[1]
: previous.interval * previous.easeFactor,
0.01,
36500
)
const easeFactor = function clamp(value: number, min: number, max: number): number clamp (
previous.easeFactor - w[3] * (quality - w[4]) ** 2 + w[5],
1.3,
10
)
return { interval, easeFactor, repetitions }
}
const const forward: ({ history, initialState, }: ModelForwardInput<MemoryState>) => {
interval: number;
easeFactor: number;
repetitions: number;
}[]
forward = ({
history: readonly ModelReview[] history ,
initialState,
}: interface ModelForwardInput<MemoryState extends object> ModelForwardInput <type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >) => {
const const states: {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states : type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState [] = []
let memoryState = initialState ?? null
for (const const review: ModelReview review of history: readonly ModelReview[] history ) {
memoryState = const step: ({ memoryState, rating }: ModelStepInput<MemoryState>) => {
interval: number;
easeFactor: number;
repetitions: number;
}
step ({
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.memoryState: Readonly<{
interval: number;
easeFactor: number;
repetitions: number;
}> | null
memoryState ,
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.rating: 1 | 2 | 3 | 4 rating : const review: ModelReview review .ModelReview.rating: 1 | 2 | 3 | 4 rating ,
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.elapsedDays: number elapsedDays : const review: ModelReview review .ModelReview.deltaT: number deltaT ,
})
const states: {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states .push(memoryState)
}
return const states: {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states
}
return {
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.config: {
weights: [number, number, number, number, number, number];
}
config : parsed,
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.bounds: ModelBoundsShape<{
interval: number;
easeFactor: number;
repetitions: number;
}>
bounds : {
iMin: 0.01,
iMax: 36500,
eMin: 1.3,
eMax: 10,
rMin: 0,
rMax: Infinity,
},
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.algorithm: null algorithm : null,
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.step: (input: ModelStepInput<{
interval: number;
easeFactor: number;
repetitions: number;
}>) => {
interval: number;
easeFactor: number;
repetitions: number;
}
step ,
// Invert the benchmark curve to satisfy the Model interval contract.
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.nextInterval: (memoryState: Readonly<{
interval: number;
easeFactor: number;
repetitions: number;
}>, desiredRetention: number) => number
nextInterval : (state: Readonly<type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >, desiredRetention: number) =>
Math.max(
1,
Math.round(
(state.interval * Math.log(desiredRetention)) / Math.log(0.9)
)
),
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forgettingCurve: (memoryState: Readonly<{
interval: number;
easeFactor: number;
repetitions: number;
}>, elapsedDays: number) => number
forgettingCurve : (state: Readonly<type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >, elapsedDays: number) =>
0.9 ** (elapsedDays / state.interval),
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forward: (input: ModelForwardInput<{
interval: number;
easeFactor: number;
repetitions: number;
}>) => readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
forward ,
}
},
})
const const model: ModelCore<{
readonly config: {
weights: [number, number, number, number, number, number];
};
readonly memoryState: {
interval: number;
easeFactor: number;
repetitions: number;
};
readonly algorithm: null;
}>
model = const SM2Model: Model<{
readonly name: "sm2";
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
readonly algorithm: null;
}>
SM2Model .Model<{ readonly name: "sm2"; readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; readonly algorithm: null; }>.create: (ctx: {
readonly config: {
weights: [number, number, number, number, number, number];
} | {
weights: [number, number, number, number, number, number];
};
readonly migrate?: boolean;
readonly clip?: boolean;
readonly check?: boolean;
readonly bypass?: boolean;
}) => ModelCore<{
readonly config: {
weights: [number, number, number, number, number, number];
};
readonly memoryState: {
interval: number;
easeFactor: number;
repetitions: number;
};
readonly algorithm: null;
}>
create ({
config: { weights: [1, 6, 2.5, 0.02, 7, 0.18] },
})
const const states: readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states = const model: ModelCore<{
readonly config: {
weights: [number, number, number, number, number, number];
};
readonly memoryState: {
interval: number;
easeFactor: number;
repetitions: number;
};
readonly algorithm: null;
}>
model .ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forward: (input: ModelForwardInput<{
interval: number;
easeFactor: number;
repetitions: number;
}>) => readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
forward ({
ModelForwardInput<{ interval: number; easeFactor: number; repetitions: number; }>.history: readonly ModelReview[] history : [
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 0 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 1 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 6 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Again, ModelReview.deltaT: number deltaT : 15 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 1 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 1 },
],
})
console.log(JSON.stringify(const states: readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states , null, 2)) // Intervals: 1, 6, 15, 1, 1, 6 days.
Schema は public 境界を記述し、scheduler が schema を合成するときに使われます。create を直接呼び出す場合、具体的な実装は必要な解析と正規化を済ませてから config、bounds、algorithm、step、nextInterval、forgettingCurve、forward を返す必要があります。このコアは決定論的に保ち、スケジューリングポリシーを含めません。
呼び出し側が対象モデルの migrate、clip、schema 検証、範囲検査を明示的に完了している場合に限り、FSRS6Model.create({ config, bypass: true }) を使えます。create 時のこれらの処理と freeze は省略されます。ユーザー入力、ストレージ、ネットワークから得た未検証の設定には bypass を使わないでください。