--- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/index.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # はじめに `ts-fsrs` は、FSRS を使用した間隔反復システムを構築するための TypeScript ツールキットです。宣言的 API はモデル、時刻体系、任意の middleware を再利用可能なスケジューラー定義に組み合わせます。 ## 構成要素 **モデル**は FSRS アルゴリズムとそのパラメーターです。`FSRS7Model` が現行版で、FSRS-3 から FSRS-6 もそれらで蓄積した復習履歴のために利用できます。 **時刻体系**は時間の表現方法を決めます。`dateChrono` は `Date`、`numericChrono` は日単位の数値、`temporalInstantChrono` は `Temporal.Instant` を使います。 **Middleware** はスケジューリングのポリシーを担います。例えば `schedulerLearningStepsMiddleware`、`schedulerFuzzingMiddleware`、`schedulerMaximumIntervalMiddleware`、`schedulerLeechMiddleware` があります。スケジューラーを作成する前に `.use(...)` で組み合わせます。 `defineScheduler` はこれらを 1 つの定義にまとめます。`.create()` は設定を検証して独立したスケジューラーを返すため、1 つの定義から複数のスケジューラーを作れます。 For your Agent TypeScript アプリに ts-fsrs を組み込む Prompt をコピー 私の TypeScript アプリに ts-fsrs を組み込んでください。 1. ドキュメント索引 https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt とクイックスタート https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/quick-start.md を読む。まず既に ts-fsrs を使用しているか確認する。v5 → v6 または FSRS-6 → FSRS-7 の更新では、移行ガイド https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/migration.md を必ず読み、その実装・検証手順に従って既存のカードとログを保持し、移行時にカードの次回復習日時は変更しない。以下のインストールと新規カードの例は新規導入の場合のみ実行する。 2. `ts-fsrs` package をインストールする。 3. `defineScheduler`、`FSRS7Model`、`dateChrono` で FSRS-7 スケジューラーを作成する。 4. 新しいカードを作成し、`Rating.Good` で復習する。 5. 実装を最小限に保ち、現在の ts-fsrs public API を使用する。 次は[クイックスタート](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/quick-start.md)で、package をインストールしてブラウザーでスケジューラーを実行します。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/quick-start.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # クイックスタート 既存アプリケーションの更新は [v5 → v6 と FSRS-7 への移行ガイド](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/migration.md)を参照してください。 ## インストール `ts-fsrs` には Node.js 24 以降が必要です。 ```sh [npm] npm install ts-fsrs ``` ```sh [yarn] yarn add ts-fsrs ``` ```sh [pnpm] pnpm add ts-fsrs ``` ```sh [bun] bun add ts-fsrs ``` ```sh [deno] deno add npm:ts-fsrs ``` FSRS パラメーターの学習や復習ログ CSV の変換が必要な場合にのみ、`@open-spaced-repetition/binding` を追加でインストールします。 ## DefaultScheduler から始める `DefaultScheduler()` は FSRS スケジューリングを最短で導入する方法です。既定で FSRS-7 を使用し、目標保持率、統計、学習ステップ、最大間隔、単調間隔、スケジュール日数の標準ポリシーを含みます。ファジングはオプションで有効にできます。 ```ts twoslash file="../../snippets/run-code/default-scheduler.ts" import { DefaultScheduler, Rating } from 'ts-fsrs' const now = new Date('2026-01-01T00:00:00.000Z') const scheduler = await DefaultScheduler({ version: 'FSRS-7', desiredRetention: 0.9, enableShortTerm: true, learningSteps: ['1m', '10m'], relearningSteps: ['10m'], enableFuzz: false, maximumInterval: 36_500, }) const card = scheduler.newCard({ cardId: 'quick-start', now }) const result = scheduler.review({ card, grade: Rating.Good, now }) console.log( JSON.stringify( { state: result.card.state, dueAt: result.card.dueAt, stability: result.card.stability, stabilityFast: result.card.stabilityFast, difficulty: result.card.difficulty, rating: result.revlog.rating, }, null, 2 ) ) ``` ▶ コードを実行 この例では一般的な既定値を明示しています。製品に合わせて `desiredRetention`、学習・再学習ステップ、ファジング、`maximumInterval` を変更できます。`weights` を省略すると、選択した FSRS バージョンのプリセット重みが使われます。 ## スケジューラーを作成する モデルや時刻体系を選ぶ場合、または middleware を自分で組み合わせる場合は `defineScheduler` を使います。再利用可能な definition を作成し、`.create()` が設定を検証して独立したスケジューラーを返します。 `chrono` は、時間の表現方法、カードや復習ログへの保存方法、スケジューリング計算での扱い方を定義し、JavaScript の `Date`、Temporal、数値による時間表現を利用できるようにします。 `chrono.normalize` は `ChronoTimeNormalizer` スキーマを使い、カードや復習ログの時刻フィールドを `{ previous, current }` に変換して、スケジューリングとロールバックに使用します。 ```ts twoslash file="../../snippets/run-code/define-scheduler.ts" import { dateChrono, defineScheduler, Rating } from 'ts-fsrs' import { FSRS7_DEFAULT_WEIGHTS, FSRS7Model } from 'ts-fsrs/models/fsrs-7' // Reuse this definition as the program's scheduling preset. const schedulerDefinition = defineScheduler({ model: FSRS7Model, chrono: dateChrono, }) // Create an independent scheduler when a user's config is available. const scheduler = schedulerDefinition.create({ config: { weights: FSRS7_DEFAULT_WEIGHTS, fractionalDays: true, }, }) const now = new Date('2026-01-01T00:00:00.000Z') const card = scheduler.newCard({ now }) const result = scheduler.review({ card, grade: Rating.Good, now }) console.log( JSON.stringify( { state: result.card.state, dueAt: result.card.dueAt, stability: result.card.stability, difficulty: result.card.difficulty, stabilityFast: result.card.stabilityFast, rating: result.revlog.rating, }, null, 2 ) ) ``` ▶ コードを実行 :::note スケジューラー定義を再利用する `defineScheduler(...)` の結果は、`.use(...)` で組み合わせた middleware を含めて、プログラム共通のプリセットとして保持します。ユーザーやコレクションにスケジューラーが必要になった時点で `schedulerDefinition.create({ config })` を呼び出してください。呼び出すたびに設定が検証され、独立したインスタンスが返ります。 ::: コードブロックのコピーボタンは元のソースをコピーします。専用 Worker でそのまま実行することもできます。固定した `now` により出力を再現できます。 - `newCard()` はスケジューラー既定のカードを作成します。 - `review()` は新しいカードと復習ログを返し、入力カードを変更しません。 - 再利用する definition に `.use(...)` でポリシー middleware を追加してから、ユーザーごとのスケジューラーを作成します。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/migration.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # v6 と FSRS-7 への移行 パッケージとモデルのバージョンは別です。**ts-fsrs v5 は既に FSRS-6 を使用しています**。まず FSRS-6 を維持したままアプリケーションと保存データを v6 API に移行し、その後モデルを移行します。このガイドは本サイトの v6 API に対応しています。記載されたインターフェースを提供する v6 バージョンを指定してください。 :::warning 移行後は legacy API を使用しないでください `defineScheduler` と明示的なモデルのエントリポイントを使用してください。移行後のコードでは `fsrs()`、`FSRS`、`createEmptyCard`、`generatorParameters`、旧スケジューラーのクラスや strategy、ルートから export される旧 `Card`、`CardInput`、`ReviewLog`、`FSRSParameters` などの legacy 型を使わないでください。互換性のために export が残っていても、v6 の推奨インターフェースではありません。以下の旧名称は比較用です。実行可能なサンプルは legacy API や型を使用しません。 ::: For your Agent Agent に移行を実装してもらう Prompt をコピー 現在のアプリケーションを ts-fsrs v6(defineScheduler)へ移行してください。まず希望する移行先を質問し、明確な回答を待ってください。モデル更新を既定とせず、回答前にコードやデータを変更しないでください。 A. ts-fsrs v6 への更新のみ行い、FSRS-6 と既存の重み・記憶状態を維持する。 B. ts-fsrs v6 に更新し、モデルも FSRS-6 から FSRS-7 に更新する。 ユーザーが移行先を選んだら、まず手順 1 を読み取り専用で実行し、その移行先に必要な変更点を整理してください。実行順の TODO List を - [ ] 形式で提示し、各項目に対象ファイル・モジュール、現在の動作と変更内容、データへの影響、検証方法を記載してください。ユーザーにリストの承認を求め、明確な回答を待ってください。移行先の選択だけでは実行リストの承認になりません。リスト承認前にコード・設定・データを変更したり、書き込みを伴う移行コマンドを実行したりしないでください。 TODO List の承認後に、その範囲のコード、必要なデータ移行スクリプト、テストを実装し、完了した項目にチェックを付けてください。リスト外の変更が必要になった場合は、リストを更新して再度確認を得てから追加部分を実行してください。A の場合は手順 6–8 と手順 9 の FSRS-7 専用検証を省略し、stabilityFast の追加や履歴リプレイによる FSRS-7 状態の再構築をしないでください。B の場合は該当するすべての手順を実行してください。 1. 移行ガイド:https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/migration.md、スケジューラーガイド:https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/scheduler/index.md、FSRS-7 ガイド:https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-7.md を読んでください。リポジトリの規則、固定された依存バージョン、すべてのスケジュール呼び出し、設定、カードとログの保存形式を調べ、実際の開始地点を確認してください。完了済みの段階は省略し、既存の v6/FSRS-7 実装をダウングレードしないでください。 2. まず v6 API を移行し、B の場合のみ続けてモデルを更新して、各段階を検証してください。 defineScheduler({ model: FSRS6Model, chrono })、.use(...)、.create({ config }) で API を移行し、実際の FSRS-6 の重み、時間方針、学習ステップ、ファジング、統計を維持してください。定義だけでは旧方針を網羅しないため、ガイドに沿って必要な middleware を組み合わせ、型は新スケジューラーから推論してください。 3. 移行後のコードでは legacy API・型を使わないでください。fsrs()、FSRS、createEmptyCard、generatorParameters、旧スケジューラー/strategy、ルートから import する旧 Card、CardInput、ReviewLog、FSRSParameters が対象です。恒久的な互換レイヤーを追加せず、変更を絞り、無関係な作業を保持してください。 4. カード、設定、メソッド呼び出しを移行し、固定 cardId とアプリケーションのメタデータを維持して、デシリアライズした時刻を検証してください。scheduleStatus、State.New の lastReviewAt: null、フィールド名、{ card, revlog } の戻り値、clearStatsOnForget のデフォルト値の違いを明示的に扱ってください。 5. ログの時刻を区別してください。v6 の revlog.lastReviewAt は復習前の前回復習時刻、dueAt は復習前の期日、reviewTime は今回の実際の復習時刻です(reviewAt ではありません)。通常の v5 ログの due は last_review || due に由来するため、一律に dueAt へ改名してはいけません。元のスナップショットがなければ、正確な取り消しを保証する v6 ログを捏造しないでください。 6. B の場合のみ:次に FSRS7Model と、独立した 34 個のデフォルト重みまたは再学習した FSRS-7 の重みを使用し、fractionalDays: true にしてください。FSRS-6 の重みを流用したり、0.8 * stability で stabilityFast を補ったりしてはいけません。enableShortTerm が制御するのは明示的な学習ステップであり、FSRS-7 の高速トレースではありません。DefaultScheduler は FSRS-7 で learningSteps: ['1m', '10m']、relearningSteps: ['10m']、allowModelOverride: true を既定値として使います。既定値に頼らず、既存の学習ステップを明示して維持してください。最終間隔が 1 日未満/以上、ステップ無効/空/完了、middleware なしの状態境界を検証してください。独立した curve(weights, elapsedDays, state) は ts-fsrs/models/fsrs-7 から import し、重みを事前検証して retrievability を読んでください。total は混合係数の和です。model.forgettingCurve でも数値の想起確率を取得できます。 7. B の場合のみ:学習済みカードでは作成時または最新の記憶リセット以降の完全な履歴を選び、rating と実際の reviewTime を対応付け、旧モデルの initialState を渡さず Reschedule.replay() で stability、stabilityFast、difficulty を再構築してください。再構築した memoryState は現在の card だけに適用してください。過去の revlog は読み取り専用の入力とし、履歴データの変更・上書き・後付けをしないでください。移行時は現在のカードの記憶状態だけを更新し、既存カードの次回復習日時(dueAt)は変更しないでください。そのカードを次に復習した時点で、FSRS-7 が新しい次回復習日時を計算します。lastReviewAt、cardId、学習進捗、統計は維持してください。Manual の除外はリセットの解釈ではなく、行数や最後の時刻だけでは完全性を証明できません。 8. B の場合のみ:新規カードは FSRS-7 の記憶デフォルト値で初期化してください。学習済みカードの履歴が欠落、切り詰め、空、Manual のみ、または不整合なら明示的にエラー/スキップとし、FSRS-6 の状態と設定を維持してモデルバージョンで振り分けてください。暗黙に記憶を初期化しないでください。元のログを保持し、モデル、重み/設定、記憶状態を一緒に保存し、モデル間の取り消し境界を設けてください。 9. 保存データを変更する場合はバックアップ、ドライラン、復旧手順を用意し、本番データの変更は既存の運用手順に従ってください。旧データ変換、4 種類のカード状態、復習/プレビュー/取り消し/忘却、前倒し・延滞復習の 3 時刻、小数の同日間隔、Manual/リセット境界、履歴不足、期日/ID/学習進捗/統計の維持を対象としたテストを追加して実行してください。 10. 型チェック、関連テスト、ビルドを実行し、変更ファイル、実行したコマンドと結果、未移行データと理由を報告してください。検証済みと未実行を区別し、移行先と TODO List の両方について、ユーザーの明確な承認が必須です。その他の不足情報はまずリポジトリで調べ、データの正確性に関わる未解決事項だけをユーザーに質問してください。 ## 1. FSRS-6 を維持して v6 API に移行する ### スケジュール方針を定義する `defineScheduler({ model, chrono })` は再利用可能な定義を作成します。`.use(...)` は middleware を組み合わせた新しい定義を返し、`.create({ config })` は設定を検証して独立したスケジューラーを作成します。定義はアプリケーション全体で保持し、ユーザーやデッキの設定に応じてインスタンスを作成してください。 定義だけでは **v5 のすべてのスケジュール方針は含まれません**。以下は現在の標準プリセットと同じ順序で middleware を組み合わせます。目標保持率、ファジング、統計、予定日数、学習ステップ、最大間隔、単調間隔の順です。実際の設定と FSRS-6 の重みを引き継いでください。`dateChrono` の `fractionalDays: false` は UTC 暦日の差を使います。API だけを移行する段階では、影響を検証せずに時間の計算方針まで変えないでください。 ```ts twoslash file="../../snippets/run-code/migration-v6.ts" fold height=520 import { dateChrono, defineScheduler, Rating, State } from 'ts-fsrs' import { schedulerDesiredRetentionMiddleware, schedulerFuzzingMiddleware, schedulerLearningStepsMiddleware, schedulerMaximumIntervalMiddleware, schedulerMonotonicIntervalMiddleware, schedulerScheduledDaysMiddleware, schedulerStatsMiddleware, } from 'ts-fsrs/middlewares' import { FSRS6Model, migrateFSRS6Parameters } from 'ts-fsrs/models/fsrs-6' // Keep the definition at application scope; create an instance per config. export const definition = defineScheduler({ model: FSRS6Model, chrono: dateChrono, }).use( schedulerDesiredRetentionMiddleware, schedulerFuzzingMiddleware, schedulerStatsMiddleware, schedulerScheduledDaysMiddleware, schedulerLearningStepsMiddleware, schedulerMaximumIntervalMiddleware, schedulerMonotonicIntervalMiddleware ) export const scheduler = definition.create({ config: { weights: migrateFSRS6Parameters(), // Pass the user's existing FSRS-6 weights here. fractionalDays: false, enableShortTerm: true, numRelearningSteps: 1, // Keep equal to relearningSteps.length. desiredRetention: 0.9, learningSteps: ['1m', '10m'], relearningSteps: ['10m'], enableFuzz: false, maximumInterval: 36500, clearStatsOnForget: false, // Match v5 forget's default counter behavior. }, }) export type Card = ReturnType export type Revlog = ReturnType['revlog'] // An application's stored v5 record, not a legacy library type. type StoredV5Card = { due: string last_review?: string | null stability: number difficulty: number state: State scheduled_days: number learning_steps: number reps: number lapses: number } export function convertCard( stored: StoredV5Card, cardId: Card['cardId'] ): Card { const scheduleStatus = { [State.New]: 'new', [State.Learning]: 'learning', [State.Review]: 'review', [State.Relearning]: 'learning', } as const return definition.schema.card.parse({ cardId, dueAt: new Date(stored.due), lastReviewAt: stored.state === State.New || stored.last_review == null ? null : new Date(stored.last_review), stability: stored.stability, difficulty: stored.difficulty, state: stored.state, scheduleStatus: scheduleStatus[stored.state], scheduledDays: stored.scheduled_days, learningStep: stored.learning_steps, reps: stored.reps, lapses: stored.lapses, }) } export const storedCard = { due: '2026-01-03T00:00:00.000Z', last_review: '2026-01-01T00:00:00.000Z', stability: 2, difficulty: 5, state: State.Review, scheduled_days: 2, learning_steps: 0, reps: 3, lapses: 0, } satisfies StoredV5Card const card = convertCard(storedCard, 'card-1') // An overdue review: previous review, original due date and now are distinct. const now = new Date('2026-01-04T00:00:00.000Z') const result = scheduler.review({ card, grade: Rating.Good, now }) const restored = scheduler.rollback({ card: result.card, revlog: result.revlog, }) console.log( JSON.stringify( { cardId: result.card.cardId, rating: result.revlog.rating, reps: result.card.reps, revlog: { lastReviewAt: result.revlog.lastReviewAt, dueAt: result.revlog.dueAt, reviewTime: result.revlog.reviewTime, }, rollbackDueAt: restored.dueAt, }, null, 2 ) ) ``` ▶ コードを実行 ローカルの `StoredV5Card` 型はアプリケーションの保存形式を表し、legacy ライブラリからの import ではありません。データベースの既存の固定 ID を `cardId` に渡し、アプリケーション固有のフィールドは別途保持してください。合成されたカード schema が変換後の日付とフィールドを検証します。その他のアプリケーション固有のフィールドも、保存データを読み込む境界で検証してください。 ### 設定の対応表 | v5 の設定 | v6 の設定 | 移行時の注意 | | ------------------------------------- | ----------------------------------- | ----------------------------------------------------------------- | | `w` | `weights` | この段階ではユーザーの FSRS-6 の重みを維持します。サンプルは実行できるようにデフォルト値を使用します。 | | `request_retention` | `desiredRetention` | 目標保持率の middleware が必要です。 | | `maximum_interval` | `maximumInterval` | 最大間隔の middleware が必要です。単調間隔の方針もこの上限を使います。 | | `enable_fuzz` | `enableFuzz` | ファジングの middleware が必要です。既存の設定を維持します。 | | `enable_short_term` | `enableShortTerm` | FSRS-6 ではモデルの短期動作と明示的な学習ステップの両方に影響します。 | | `learning_steps` / `relearning_steps` | `learningSteps` / `relearningSteps` | 設定済みのステップ配列と学習進捗を維持します。 | | 再学習ステップ数 | `numRelearningSteps` | FSRS-6 では `relearningSteps.length` に設定します。 | | `forget(..., reset_count)` | `clearStatsOnForget` | インスタンスの設定になりました。v5 のデフォルトは `false`、v6 の統計 middleware は `true` です。 | ### カード、ログ、操作 | v5 の保存フィールド | v6 のフィールドまたは処理 | | ------------------------------------------------ | -------------------------------------------------------------------------------- | | `due` | カードの `dueAt`。`Date` にデシリアライズします。 | | `elapsed_days` / ログの `last_elapsed_days` | 削除されました。選択した chrono とタイムスタンプから経過日数を計算し、FSRS-7 のリプレイに古い丸め済みの値を使わないでください。 | | `last_review` | `lastReviewAt`。未設定と null は `null` に統一します。 | | `scheduled_days` | middleware が提供する `scheduledDays`。 | | `learning_steps` | middleware が提供する `learningStep`。 | | `stability`、`difficulty`、`state`、`reps`、`lapses` | API 移行時に維持します。統計には middleware が必要です。 | | アプリケーションのレコード ID | `cardId`。カードとログで同じ ID を使います。 | | 対応するフィールドなし | `scheduleStatus`:New → `new`、Learning/Relearning → `learning`、Review → `review`。 | | 復習ログの `review` | `reviewTime`。カードの期日ではなく実際の復習時刻です。 | | 結果 `{ card, log }` | `{ card, revlog }`。 | サンプルのようにスケジューラーから型を推論してください。カードとログのフィールドはモデル、chrono、middleware によって変わります。独自の v5 strategy は対応する [middleware](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/middleware.md) の hook に書き換える必要があります。名前の変更や旧スケジューラーの継承だけでは移行できません。 | v5 の操作(比較用) | v6 の操作 | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `createEmptyCard(now)` | `scheduler.newCard({ now, cardId })` | | `f.next(card, now, grade)` | `scheduler.review({ card, now, grade })` | | `f.repeat(card, now)` | `scheduler.preview({ card, now })` は `Symbol.iterator` で iterator を提供する iterable を返し、`for...of` で走査できます。各候補には `{ grade, card, revlog }` が含まれます。評点をキーにしたオブジェクトへのアクセスではなく、`candidate.grade` で選択します。 | | `f.rollback(card, log)` | 返された v6 ログを使って `scheduler.rollback({ card, revlog })`。 | | `f.forget(card, now, reset_count)` | `scheduler.forget({ card, now })`。`{ card, log }` ではなくカードを返します。手動操作はアプリケーションで記録します。 | | `f.get_retrievability(card, now, false)` | `lastReviewAt` のある学習済みカードでは `scheduler.model.forgettingCurve(card, scheduler.chrono.difference(card.lastReviewAt, now))`。戻り値は数値です。新規カードは別途処理し、パーセント表示はアプリケーションで整形します。 | `review()` は新しいデータを返します。`result.card` と `result.revlog` の両方を保存してください。新しいログは `dueAt`、`lastReviewAt` など、取り消しに必要な復習前のフィールドを保持します。旧ログの `due` を改名するだけで v6 の取り消しに使わないでください。旧ログには復習前のスナップショットが不足している場合があります。元の履歴は維持し、モデルのリプレイ用には評点と実際の復習時刻だけを変換します。バージョンをまたぐ取り消しには、アプリケーションの独立したスナップショットか取り消し境界が必要です。 モデルと設定が同じでも、すべての学習ステップやファジングで v5 と同一の間隔になる保証はありません。書き込みを切り替える前に New、Learning、Review、Relearning の代表カードを検証してください。[DefaultScheduler](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/scheduler/default-scheduler.md) も利用できますが、デフォルトモデルは FSRS-7 です。この段階で使う場合は FSRS-6 を明示してください。 ### ログの 3 つの時刻は単なる改名ではない `dateChrono` のフィールド名は **`reviewTime` であり、`reviewAt` ではありません**。前回の復習が 1 月 1 日、元の期日が 1 月 3 日、実際の復習が 1 月 4 日という延滞の例を考えます。 | 意味 | v5 の通常の復習ログ | v6 の `revlog` | | ------------------ | ------------------------------------------------ | -------------- | | 前回の復習時刻:1 月 1 日 | `due`(旧実装は `card.last_review \|\| card.due` を記録) | `lastReviewAt` | | 元の期日:1 月 3 日 | この学習済みカードでは独立した記録なし | `dueAt` | | 今回の実際の復習時刻:1 月 4 日 | `review` | `reviewTime` | `lastReviewAt` は**今回の復習より前の、前回の復習時刻**です。入力と出力で型が異なります。 - 入力の `DateRevlogInputFields` では `lastReviewAt?: Date | null` と省略可能です。省略または `undefined` の場合、schema は `null` に正規化します。`dueAt` と `reviewTime` は有効な `Date` が必須です。 - 出力の `DateRevlogFields` には `lastReviewAt: Date | null` が必ず存在します。通常の学習済みカードのログには復習前の `card.lastReviewAt` を記録し、前回の復習がない新規カードの初回ログでは `null` になります。 - `null` から欠落した前回復習時刻を復元することはできません。旧ログにその情報がなければ、schema の検証に通っても正確な取り消しは保証されません。`dueAt` や `reviewTime` で代用しないでください。 復習が返すカードの `lastReviewAt` は 1 月 4 日になり、`dueAt` は新たな期日になります。一方、ログは**復習前**の時刻を保持します。そのカードと v6 ログで取り消すと、`dueAt` は 1 月 3 日、`lastReviewAt` は 1 月 1 日に戻ります。前倒しや延滞でも同様です。経過日数の計算に使う時刻と、取り消しのために保存する期日を分離したのが chrono リファクタリングの理由です。上のサンプルは 3 フィールドを出力します。 一律に `oldLog.due → revlog.dueAt` と変換してはいけません。通常の v5 学習済みカードの `due` は前回の復習時刻です。`last_review` のない初回復習では元の期日になり、Manual/忘却のログには別の意味があります。リセット済みの v5 New カードにも古い `last_review` が残る場合があるため、カード変換サンプルでは `State.New` の `lastReviewAt` を `null` にします。 モデルの記憶だけを再構築する場合は、`rating` と `review → reviewTime` の変換で十分です。バージョンをまたいで正確に取り消すには、両方の時刻と middleware フィールドを含む元の復習前カードのスナップショットが必要です。それがなければ取り消し境界を設けてください。前倒し復習、学習ステップ、ファジング、手動調整があると、`review` や再計算した間隔から未記録の期日を復元することはできません。 ## 2. FSRS-6 から FSRS-7 に移行する ### 重みと状態はモデル固有 FSRS-6 は 21 個、FSRS-7 は独立した **34 個の重み**を使用します。`FSRS7_DEFAULT_WEIGHTS` を使うか、[FSRS-7 の重みを再学習](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/optimizer.md)してください。FSRS-6 のベクトルに値を追加したり、その学習済み重みを流用したりしないでください。`migrateFSRS7Parameters` は空の入力にデフォルト値を提供しますが、旧モデルのパラメーター配置を変換するものではありません。 FSRS-7 の記憶状態は `stability`、`stabilityFast`、`difficulty` です。3 つとも履歴から再構築してください。**再構築した結果は現在の card の記憶状態(memoryState)だけに適用します。過去の revlog は読み取り専用の入力として扱い、記録済みの記憶状態を含め、データの変更・上書き・後付けをしてはいけません。** **学習済みカードに `stabilityFast = 0.8 * stability` を設定してはいけません**。FSRS-6 の安定性と難易度を維持したまま高速成分だけを追加する方法も使えません。モデル初期化時の係数は、更新を重ねた記憶状態の移行式ではありません。 `fractionalDays: true` にして、10 分間隔の復習などを小数日数のまま保持してください。FSRS-7 の高速トレースはモデル固有です。学習ステップの middleware を使用した場合、`enableShortTerm: false` が無効にするのは明示的な学習ステップだけで、高速トレースは無効になりません。FSRS-7 モデルには FSRS-6 の `numRelearningSteps` に対応する設定はありません。 ### FSRS-7 のデフォルトステップと学習状態 `DefaultScheduler` は FSRS-7 でも FSRS-3~6 と同じ学習ステップ `['1m', '10m']` と再学習ステップ `['10m']` をデフォルトとし、`allowModelOverride` を有効にして、より長いモデル間隔で現在のステップを上書きします。明示した設定はデフォルト値で上書きされません。`enableShortTerm` は引き続き `true` がデフォルトで、明示的なステップを制御します。FSRS-7 の高速トレースや間隔による状態分類は無効にしません。 `schedulerLearningStepsMiddleware` が登録されている場合、ステップが無効・空・完了済みでも、**最終スケジュール間隔**で状態が決まります。 | 復習前の状態 | 最終間隔が `graduatingInterval`(デフォルト 1 日)未満 | 最終間隔が `graduatingInterval` 以上 | | ------------------- | --------------------------------------- | ------------------------------- | | New / Learning | Learning、`scheduleStatus: learning` | Review、`scheduleStatus: review` | | Review / Relearning | Relearning、`scheduleStatus: learning` | Review、`scheduleStatus: review` | ステップを設定している場合、適用されるステップがない Hard、Good、Easy は少なくとも `graduatingInterval` 後に予定されます。そのため、ステップを終えたときや、`Review` のカードに Hard、Good、Easy を付けたときは常に `Review` になります。`allowModelOverride` が有効な場合、モデルの間隔を使う Again は、間隔が `graduatingInterval` に制限されても常に `Learning`/`Relearning` になります。 ステップを設定していない場合、Learning がなくなる、Again だけが Relearning を生む、Easy は必ず Review になる、という前提は使えません。一般に、Easy は明示的なステップをスキップし、Good は適用可能な次のステップがある場合のみ進み、長さ 0 のステップはステップなしとして扱います。`learningStep` はカードが Learning/Relearning に留まる間だけ次のステップを保持し、カードが Review に移ると `0` に戻ります。これはステップ番号であり、復習回数ではありません。 状態遷移は middleware の責務で、`FSRS7Model` の責務ではありません。モデルと chrono だけのスケジューラーは、間隔が 1 日未満でも Review を返します。旧動作を前提としたキュー、状態フィルター、統計の処理も更新してください。 [「記憶をリプレイし、スケジュールを維持する」の完全な移行サンプル](#記憶をリプレイしスケジュールを維持する)は、デフォルトに頼らず**既存の学習ステップを明示して維持**します。製品として完全にモデル主導のステップを選ぶ場合は `learningSteps: []` と `relearningSteps: []` を明示してください。学習ステップ middleware を削除するという意味ではありません。方針の変更はカードフィールドの移行と分けて検証してください。 ### 想起確率の呼び出しを移行する `scheduler.model.forgettingCurve(state, elapsedDays)` で数値の想起確率を取得し、`scheduler.model.algorithm.curve(elapsedDays, state)` で両成分を確認できます。どちらも `stability`、`stabilityFast`、`difficulty` を含む FSRS-7 の完全な状態が必要で、FSRS-6 の安定性 1 値だけは渡せません。 または `ts-fsrs/models/fsrs-7` から独立した `curve` を import し、`curve(weights, elapsedDays, state)` を呼び出せます。アルゴリズムのインスタンスは不要です。有効な 34 個の FSRS-7 の重みを渡してください。**この関数は重みを検証しません**。保存データやユーザー入力は事前に schema・パラメーターチェックで検証し、`curve` の呼び出しを妥当性チェックに使わないでください。再構築した記憶状態と同じ FSRS-7 の重みを使い、`elapsedDays` の小数部分を保持してください。 曲線の戻り値は `retrievability`、`derivative`、`fast`、`slow`、`fastWeight`、`slowWeight`、`total` を含みます。`total = fastWeight + slowWeight` は混合係数の和であり、全体の想起確率ではありません。旧確率呼び出しの代わりには `retrievability` を使い、パーセント表示はアプリケーションで整形してください。アルゴリズムインスタンスの曲線結果にも `total` が含まれます。これらの API は確率と成分の計算用であり、カード移行、ログ生成、期日の設定は行いません。 ### 記憶をリプレイし、スケジュールを維持する 以下は単独で実行できます。v6 形式に変換済みの FSRS-6 カードを受け取り、保存ログの `review` を `reviewTime` に変換して `Reschedule.replay()` を呼びます。旧モデルの `initialState` は渡しません。**移行時は記憶状態だけを再構築し、既存カードの次回復習日時(`dueAt`)は変更しません**。そのカードを次に復習した時点で、FSRS-7 が新しい次回復習日時を計算します。ID、最終復習時刻、学習進捗、統計も維持されます。 ```ts twoslash file="../../snippets/run-code/migration-fsrs7.ts" fold height=520 import { dateChrono, defineScheduler, Rating, State } from 'ts-fsrs' import { schedulerDesiredRetentionMiddleware, schedulerFuzzingMiddleware, schedulerLearningStepsMiddleware, schedulerMaximumIntervalMiddleware, schedulerMonotonicIntervalMiddleware, schedulerScheduledDaysMiddleware, schedulerStatsMiddleware, } from 'ts-fsrs/middlewares' import { FSRS7_DEFAULT_WEIGHTS, FSRS7Model } from 'ts-fsrs/models/fsrs-7' import { Reschedule } from 'ts-fsrs/reschedule' export const definition = defineScheduler({ model: FSRS7Model, chrono: dateChrono, }).use( schedulerDesiredRetentionMiddleware, schedulerFuzzingMiddleware, schedulerStatsMiddleware, schedulerScheduledDaysMiddleware, schedulerLearningStepsMiddleware, schedulerMaximumIntervalMiddleware, schedulerMonotonicIntervalMiddleware ) export const scheduler = definition.create({ config: { weights: FSRS7_DEFAULT_WEIGHTS, fractionalDays: true, enableShortTerm: true, // Controls learning steps, not the model's fast trace. desiredRetention: 0.9, // Preserve existing steps explicitly instead of inheriting preset defaults. learningSteps: ['1m', '10m'], relearningSteps: ['10m'], enableFuzz: false, maximumInterval: 36500, clearStatsOnForget: false, }, }) type Card = ReturnType type PreviousCard = Omit type StoredReview = { rating: Rating; review: string } // completeHistory means all reviews since creation or the latest memory reset. // Decide this from application records, not from the number of rows alone. export function migrateMemory( card: PreviousCard, logs: readonly StoredReview[], completeHistory: boolean ): Card { if (card.state === State.New) { const { stability, stabilityFast, difficulty } = scheduler.newCard({ cardId: card.cardId, }) return definition.schema.card.parse({ ...card, stability, stabilityFast, difficulty, }) } if (!completeHistory) throw new Error('Keep this card on FSRS-6: incomplete history') const history = logs .filter(({ rating }) => rating !== Rating.Manual) .map(({ rating, review }) => ({ rating, reviewTime: new Date(review), })) // replay validates dates/ratings and sorts the non-manual history. // Empty or Manual-only histories throw instead of clearing memory. const { memoryState } = new Reschedule(scheduler).replay({ history }) const latestReview = history.reduce( (latest, { reviewTime }) => Math.max(latest, reviewTime.getTime()), -Infinity ) if (card.lastReviewAt?.getTime() !== latestReview) { throw new Error( 'Keep this card on FSRS-6: history does not match lastReviewAt' ) } // Only the model memory changes; dueAt, counters and learning progress survive. return definition.schema.card.parse({ ...card, ...memoryState }) } export const previousCard: PreviousCard = { cardId: 'card-1', dueAt: new Date('2026-01-05T00:00:00.000Z'), lastReviewAt: new Date('2026-01-02T00:00:00.000Z'), stability: 2, difficulty: 5, state: State.Review, scheduleStatus: 'review', scheduledDays: 3, learningStep: 0, reps: 3, lapses: 0, } export const storedHistory: StoredReview[] = [ { rating: Rating.Good, review: '2026-01-01T00:00:00.000Z' }, { rating: Rating.Good, review: '2026-01-01T00:10:00.000Z' }, { rating: Rating.Good, review: '2026-01-02T00:00:00.000Z' }, ] const migrated = migrateMemory(previousCard, storedHistory, true) console.log( JSON.stringify( { model: 'FSRS-7', cardId: migrated.cardId, dueAt: migrated.dueAt, reps: migrated.reps, stabilityFast: migrated.stabilityFast, }, null, 2 ) ) ``` ▶ コードを実行 | カード・履歴の状態 | 処理 | | ---------------------------------------- | ---------------------------------------------------- | | 新規カード(`State.New`)。意図的にリセットしたカードも含む | FSRS-7 のデフォルト記憶状態を設定し、スケジュールと統計を維持します。 | | 作成時または最新の記憶リセット以降の完全な履歴がある学習済みカード | FSRS-7 の重みでリプレイし、スケジュールを維持して再構築した状態を保存します。 | | 履歴の欠落・切り詰めがある | カードと対応設定を FSRS-6 に残します。暗黙に初期化したり、行数だけで完全性を判断したりしません。 | | 学習済みカードの履歴が空、または Manual のみ | リプレイは例外を投げます。カードは FSRS-6 に残します。 | | 不正な日付・評点、または最後の復習が `lastReviewAt` と一致しない | そのカードの移行を止め、記録を確認します。 | `replay()` は有効な復習を時刻順に並べ、`Rating.Manual` を除外します。ただし手動の忘却・リセットイベントは解釈しません。アプリケーションが**最新の記憶リセット以降**の完全な履歴を選び、それ以前の復習を除外する必要があります。期日を手動調整しただけなら記憶リセットではありません。同時刻の復習は記録順を維持してください。`dateChrono` ではデシリアライズ後の時刻は有効な `Date` である必要があります。最初の復習は未初期化の記憶状態から、経過日数ゼロで開始します。 サンプルの履歴完全性フラグはアプリケーションで判断します。最後の時刻が一致していても、それ以前の復習が欠落していない証明にはなりません。移行できないカードは保存されたモデルバージョンに応じて FSRS-6 に振り分け、FSRS-7 に渡さないでください。 ### 意図的に期日も再計算する場合 `new Reschedule(scheduler).reschedule({ history, initialCard })` はスケジュール方針全体を通してリプレイし、`{ card, results }` を返します。期日、学習進捗、統計が変わる場合があります。`initialCard` には、既存の `cardId` と最初の復習時刻を指定した **FSRS-7 の新規カード**を使ってください。学習済みカードに完全な履歴を重ねてリプレイしてはいけません。生成されるログは再計算結果であり、元の履歴の置き換えではありません。このガイドでは `replay()` を標準とし、移行時に既存カードの次回復習日時を変更しません。 ## 保存と検証 1. カード、元のログ、設定をバックアップします。モデルバージョンと実際にリプレイに使った重みを、カードまたは確実に関連付けられた設定に保存します。 2. `stabilityFast` と v6 フィールドを保存できるようにし、再構築した記憶、モデルバージョン、対応する重み・設定の参照を一緒に更新します。FSRS-6 の状態と FSRS-7 の重みを混在させず、アプリケーションの ID とメタデータを維持します。 3. 元のログを保持し、取り消しの境界を設けます。旧モデルのログを FSRS-7 の `rollback()` に直接渡してはいけません。新しい FSRS-7 ログには `stabilityFast` と復習前のフィールドを保存します。 4. 代表カードで復習、プレビュー、取り消し、同日復習、学習ステップ、設定したファジングを検証します。記憶だけの移行では期日、ID、学習進捗、統計が変わらないことを確認し、失敗を解消してから該当カードのモデルを切り替えます。 モデルの詳細は [FSRS-7](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-7.md)、API の構成は[スケジューラー定義](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/scheduler.md)、[Chrono](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/chrono.md)、[middleware](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/middleware.md)を参照してください。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/architecture.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # アーキテクチャ ts-fsrs v6 のスケジューリングアーキテクチャでは、記憶モデル、時間処理、スケジューリングポリシーを Model、Chrono、Middleware に分離し、Scheduler がそれらの実行を調整します。 ## 責務の分担 | 要素 | 責務 | | ---------- | -------------------------------------------------------------------------------- | | Model | `step` で記憶状態を更新し、`nextInterval` で基準の間隔を計算します。FSRS-6 の記憶状態には安定性と難易度が含まれます。 | | Chrono | 時刻の表現と時間演算を定義します。`dateChrono` は JavaScript の `Date` を使い、時間差の計算や日時の加減算を提供します。 | | Middleware | 目標保持率、学習ステップ、最大間隔などのポリシーを追加し、config、card、revlog の schema を拡張できます。 | | Scheduler | schema とデフォルト値を組み合わせ、設定と入力を検証し、`newCard`、`review`、`preview`、`rollback` などを実行します。 | 安定性や難易度などの Model の記憶状態は、New や Learning などのカード状態とは異なります。カードの学習状態とスケジューリングポリシーは Scheduler と Middleware が共同で処理します。 カードの選択、返された card と revlog の保存、ユーザーとコレクションの管理はアプリケーションが担当します。スケジューラは結果を自動で永続化しません。 ## スケジューラの定義とインスタンス `defineScheduler({ model, chrono })` は定義を作成します。`.use(...)` は追加した middleware と推論された型を持つ新しい定義を返し、元の定義も再利用できます。`.create({ config })` はユーザー設定を検証し、独立したスケジューラインスタンスを作成します。 :::note 定義全体を再利用する `.use(...)` による組み合わせを含む定義をプログラム単位のプリセットとして保持し、ユーザーやコレクションの設定が決まった時点でインスタンスを作成します。 ::: ```ts twoslash file="../../snippets/run-code/architecture.ts" import { dateChrono, defineScheduler, Rating } from 'ts-fsrs' import { schedulerDesiredRetentionMiddleware, schedulerMaximumIntervalMiddleware, } from 'ts-fsrs/middlewares' import { FSRS6_DEFAULT_WEIGHTS, FSRS6Model } from 'ts-fsrs/models/fsrs-6' // Keep the model, chronology, and policies as a reusable program preset. const schedulerDefinition = defineScheduler({ model: FSRS6Model, chrono: dateChrono, }).use(schedulerDesiredRetentionMiddleware, schedulerMaximumIntervalMiddleware) // Supply each user's configuration when creating their scheduler. const scheduler = schedulerDefinition.create({ config: { weights: FSRS6_DEFAULT_WEIGHTS, enableShortTerm: true, numRelearningSteps: 1, desiredRetention: 0.9, maximumInterval: 1, }, }) const now = new Date('2026-01-01T00:00:00.000Z') const card = scheduler.newCard({ now }) const result = scheduler.review({ card, grade: Rating.Good, now }) console.log( JSON.stringify( { originalState: card.state, state: result.card.state, dueAt: result.card.dueAt, stability: result.card.stability, rating: result.revlog.rating, }, null, 2 ) ) ``` ▶ コードを実行 この例では最大間隔を意図的に 1 日に制限します。モデルが計算する安定性は `2.3065` のままですが、最大間隔のポリシーが期限を 1 月 2 日に変更します。元のカードは New のままで、返されたカードは Review になります。この最小プリセットには学習ステップ、統計、fuzzing は含まれません。 ## review のデータの流れ 1. `card`、`grade`、`now` を `review` に渡します。スケジューラは入力を検証し、Chrono で経過時間を計算します。 2. Middleware がコア処理を囲んで実行されます。目標保持率の middleware は間隔の計算前に保持率を設定します。 3. コア処理が次の記憶状態を計算し、必要に応じてモデルを呼び出して基準の間隔を求めます。Middleware は最終的なスケジュール間隔を指定または調整できます。 4. チェーン全体が戻った後、スケジューラは Chrono を使って復習時刻と最終的な間隔から `dueAt` を算出し、新しい card と revlog を返します。Card は次の記憶状態を持ち、revlog は復習前の記憶状態と今回の評価・復習時刻を保持します。 ## Middleware の順序 `.use(A, B)` の各 review handler が `next()` を呼び出す場合、次の順序で実行されます。 `A の next() 前 → B の next() 前 → コア処理 → B の next() 後 → A の next() 後` `next()` より前のコードは処理を準備し、その後のコードは下流の結果を受け取ります。このため、登録順序によって最後に適用されるポリシーが変わります。この例ではコア処理の前に目標保持率を設定し、その後で最大間隔を制限します。学習ステップを追加する場合は、最大間隔の middleware を学習ステップの middleware より後に登録します。学習ステップの middleware は戻りの段階で設定された学習間隔を復元し、日単位の間隔ポリシーによる上書きを防ぎます。 カードのライフサイクルは[状態遷移](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/state-transitions.md)、数式は [FSRS-6](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-6.md) を参照してください。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/state-transitions.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # 状態遷移 `State` はカードの現在の FSRS 学習段階を表します。`scheduler.newCard()` で作成したカードの初期状態は `New` です。復習するたびに、`DefaultScheduler` は FSRS モデルの結果と学習ステップのポリシーを組み合わせて、次の状態と次回予定時刻を決定します。 ![DefaultScheduler の状態遷移図](/ts-fsrs/static/svg/state-transitions.ja-JP.ce720065bd.svg) 図と表は、明示的なステップと間隔ミドルウェアの処理後の最終間隔に基づく遷移を示します。評価は間隔に影響しますが、評価だけでは次の状態は決まりません。 `graduatingInterval`(既定値 `1`、単位は日)は、`Review` のカードの最短間隔です。最終間隔が `graduatingInterval` より短ければ `Learning`/`Relearning` に入り、または留まります。`graduatingInterval` 以上なら `Review` に移ります。ステップが無効または空で、モデルが間隔を決める場合も同じです。 ただし、Again でモデルの間隔が明示的なステップを上書きした場合は例外です。最終間隔が `graduatingInterval` に達しても、カードは `Learning`/`Relearning` に留まります。図は間隔による一般的な状態遷移を示しており、この例外が優先されます。 長さがゼロのステップ(`0m` や秒単位の四捨五入でゼロになる長さ)はステップなしとして扱います。負の長さは指定できません。 ## 4 つの状態 | 状態 | 意味 | 既定のキュー | | ------------ | ------------------------------------------------------ | ---------- | | `New` | 新規カード、または `scheduler.forget()` でリセットされたカードです。 | `new` | | `Learning` | 初回学習中で、再度復習が必要なカードです。間隔は通常 `graduatingInterval` 未満です。 | `learning` | | `Review` | 短期学習段階ではないカードです。間隔は `graduatingInterval` 以上です。 | `review` | | `Relearning` | 再学習中で、再度復習が必要な既習カードです。間隔は通常 `graduatingInterval` 未満です。 | `learning` | ## 評価による状態遷移 評価によって、明示的なステップの間隔を使うかどうかが決まります。次の表は `enableShortTerm: true` で、現在の状態に対応するステップ一覧が空でない場合を示します。`New`/`Learning` には `learningSteps`、`Review`/`Relearning` には `relearningSteps` を使います。その一覧が空なら、もう一方の一覧にステップがあっても、卒業の下限を適用せずモデルの間隔を使います。 以下の卒業動作は `maximumInterval >= graduatingInterval` で、他のミドルウェアが間隔を `graduatingInterval` 未満に短縮しないことを前提とします。 | 評価 | `New` / `Learning` / `Relearning` | `Review` | | ------- | -------------------------------------------------------------------- | --------------------------------------------------- | | `Again` | 最初のステップに戻り、`learningStep` を `0` に戻します。 | 最初の再学習ステップを使い、`learningStep` を `0` に戻します。 | | `Hard` | 現在の `learningStep` を維持し、Hard 用のステップ間隔を使います。 | モデルの間隔を使い(`graduatingInterval` 以上)、`Review` に留まります。 | | `Good` | 次のステップがあれば進めます。なければモデルの間隔(`graduatingInterval` 以上)で `Review` に卒業します。 | モデルの間隔を使い(`graduatingInterval` 以上)、`Review` に留まります。 | | `Easy` | 明示的なステップを飛ばし、モデルの間隔(`graduatingInterval` 以上)で `Review` に卒業します。 | モデルの間隔を使い(`graduatingInterval` 以上)、`Review` に留まります。 | Hard 用の間隔は最初の 2 ステップの平均で、ステップが 1 つならその 1.5 倍です。ステップが空、または `enableShortTerm: false` の場合は、すべての評価がモデルの間隔を使い、下の状態の割り当てだけが適用されます。`learningStep` はカードが `Learning`/`Relearning` に留まる間だけ次のステップを保持し、カードが `Review` に移ると `0` に戻ります。 `allowModelOverride` が有効な場合(`DefaultScheduler` では FSRS-7 の既定)、モデルの間隔が現在のステップより長ければ、スケジューラーはモデルの間隔を使います。Again ではモデルの間隔が `graduatingInterval` までに制限され、モデルの間隔を使う Again は常に `Learning`/`Relearning` に入ります。Hard と Good では、間隔が `graduatingInterval` に達すると、残りのステップを終えずに `Review` に卒業します。モデルの間隔は、他の間隔ミドルウェアで調整されることがあります。 `maximumInterval < graduatingInterval` の場合、上限によって卒業できないことがあります。例えば、`graduatingInterval: 2` と `maximumInterval: 1` を設定すると、学習ステップ完了後のモデルの間隔が 1 日に制限されるため、カードは `Learning` に留まります。 間隔が決まった後、`schedulerLearningStepsMiddleware` が次の表に従って状態を決めます。ただし、上記の Again によるモデル上書きの例外が優先されます。表は状態遷移を示し、各評価の間隔選択ルールが同じという意味ではありません。 | 現在の状態 | 最終間隔 \< `graduatingInterval` | 最終間隔 ≥ `graduatingInterval` | | ------------ | ---------------------------- | --------------------------- | | `New` | `Learning` | `Review` | | `Learning` | `Learning` | `Review` | | `Review` | `Relearning` | `Review` | | `Relearning` | `Relearning` | `Review` | 適用される明示的なステップがある場合は、`allowModelOverride` がより長いモデル間隔を選ばない限り、そのステップに設定された間隔を保ちます。`graduatingInterval` 以上のステップでも、カードは `Review` に移ります。 :::warning Learning 状態にはミドルウェアが必要です `DefaultScheduler` は `schedulerLearningStepsMiddleware` を登録し、FSRS-7 を含め `enableShortTerm` の既定値は `true` です。FSRS-7 では、この設定は明示的な `learningSteps` と `relearningSteps` を制御し、モデル固有の短期記憶や間隔による状態の割り当てを無効にはしません。`enableShortTerm: false` でも、モデルの間隔が `graduatingInterval` 未満なら `Learning` または `Relearning` に入ります。`defineScheduler` でこの状態遷移を使うには `.use(schedulerLearningStepsMiddleware)` を登録してください。FSRS-7 モデルだけを組み合わせ、このミドルウェアを登録しない場合、間隔が 1 日未満でも `state` は `Review`、`scheduleStatus` は `review` のままです。モデルは記憶状態と間隔を計算し、学習状態への遷移はミドルウェアが担当します。FSRS-7 のスケジューリング後に `Learning`/`Relearning` にするには、`schedulerLearningStepsMiddleware` を登録するか、状態遷移を担う独自のミドルウェアを実装してください。 ::: `Rating.Manual` は復習の評価ではありません。`review()` には `Again`、`Hard`、`Good`、`Easy` のいずれかを渡します。 ## 状態遷移を実行する 以下の実行可能な例では、既定の FSRS-7 モデルを次の設定で使います。 ```ts const scheduler = await DefaultScheduler({ learningSteps: ['1m', '10m'], relearningSteps: ['10m'], enableFuzz: false, allowModelOverride: false, }) ``` `graduatingInterval` は既定値の **1 日**を使います。カードの初期状態は `New`、`learningStep: 0` です。`learningStep` はゼロ始まりのステップ番号で、復習回数ではありません。学習と再学習の進捗は同じフィールドで管理します。 `allowModelOverride: false` の場合、例では次のように遷移します。 | 現在の状態 | 現在の learningStep | 評価 | 遷移後の状態 | 遷移後の learningStep | 間隔 | | ---------- | ---------------- | ----- | ---------- | ----------------- | ----- | | New | 0 | Again | Learning | 0 | 1 分 | | Learning | 0 | Good | Learning | 1 | 10 分 | | Learning | 1 | Hard | Learning | 1 | 5.5 分 | | Learning | 1 | Again | Learning | 0 | 1 分 | | Learning | 1 | Good | Review | 0 | 1 日 | | Review | 0 | Again | Relearning | 0 | 10 分 | | Relearning | 0 | Good | Review | 0 | 1 日 | `learningStep: 1` の Hard、Again、Good の行は、同じカードに対する別々の評価であり、連続した復習ではありません。再学習の分岐は、Easy で `Review` に移った別のカードから始まります。 `allowModelOverride` を無効にすると、設定したステップの間隔を保ちます。有効な場合、より長いモデルの間隔が明示的なステップを置き換えます。例えば、仮にモデルの間隔が 30 分なら、10 分の Good ステップは 30 分に延びます。モデルの間隔が短ければ、そのステップは変わりません。`allowModelOverride: false` でも、ステップ完了後や適用されるステップがない場合はモデルの間隔を使います。 以下の完全な例は公開の `DefaultSchedulerCard`、`Grade`、`State` 型を使い、間隔を分単位で小数点以下 2 桁に丸めて表示します。 :::note dueAt と now は別の時刻です `dueAt` はカードの復習予定時刻、`now` は実際の復習時刻です。通常、`now` は `dueAt` 以降である必要があります。この例では両者を混同せずに出力を再現できるよう、各カードを `dueAt` の 30 秒後に復習します。 ::: ```ts twoslash file="../../snippets/run-code/state-transitions.ts" import { DefaultScheduler, type DefaultSchedulerCard, type Grade, Rating, State, } from 'ts-fsrs' const scheduler = await DefaultScheduler({ learningSteps: ['1m', '10m'], relearningSteps: ['10m'], enableFuzz: false, allowModelOverride: false, }) const stateNames: Record = { [State.New]: 'New', [State.Learning]: 'Learning', [State.Review]: 'Review', [State.Relearning]: 'Relearning', } const reviewAfterDue = (card: DefaultSchedulerCard, grade: Grade) => { const reviewedAt = new Date(card.dueAt.getTime() + 30_000) return scheduler.review({ card, grade, now: reviewedAt }).card } const newCard = scheduler.newCard({ cardId: 'state-transitions', now: new Date('2026-01-01T00:00:00.000Z'), }) const learning = reviewAfterDue(newCard, Rating.Again) const nextLearningStep = reviewAfterDue(learning, Rating.Good) const afterLearningSteps = reviewAfterDue(nextLearningStep, Rating.Good) const repeated = reviewAfterDue(nextLearningStep, Rating.Hard) const restarted = reviewAfterDue(nextLearningStep, Rating.Again) const reviewed = reviewAfterDue(newCard, Rating.Easy) const relearning = reviewAfterDue(reviewed, Rating.Again) const afterRelearningSteps = reviewAfterDue(relearning, Rating.Good) for (const [from, rating, to] of [ [newCard, 'Again', learning], [learning, 'Good', nextLearningStep], [nextLearningStep, 'Hard', repeated], [nextLearningStep, 'Again', restarted], [nextLearningStep, 'Good', afterLearningSteps], [newCard, 'Easy', reviewed], [reviewed, 'Again', relearning], [relearning, 'Good', afterRelearningSteps], ] as const) { console.log( `${stateNames[from.state]} --${rating}--> ${stateNames[to.state]}; ` + `learningStep: ${from.learningStep} -> ${to.learningStep}; ` + `interval: ${(to.scheduledDays * 1440).toFixed(2)}m` ) } ``` ▶ コードを実行 ## Card、revlog、記憶状態 - 入力 `card` は復習前の状態です。`review()` は入力を変更しません。 - `result.card` には次の `state`、次の FSRS 記憶状態(FSRS-7 では `stability`、`stabilityFast`、`difficulty`)、新しい `dueAt` が入ります。 - `result.revlog` は、選んだ評価と復習時刻に加えて、以前の状態と記憶状態を記録します。rollback もこのスナップショットからカードを復元します。 - `scheduleStatus` はカードが属するスケジューリングキューを決めます。既定では `New` が `new`、2 つの学習状態が `learning`、`Review` が `review` に対応します。middleware は 4 つの FSRS 状態を変えずに `suspended` などを追加できます。 学習フェーズの説明には `card.state`、スケジューリングキューの絞り込みには `card.scheduleStatus` を使います。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/llms.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # LLM 本サイトの各ページは Markdown 版も公開され、更新情報は Atom フィードとしても公開されます。そのため Agent やフィードリーダーがレンダリング済みの HTML を解析する必要はありません。 For your Agent ドキュメントに基づいて ts-fsrs の質問に答える Prompt をコピー ts-fsrs のドキュメントを唯一の信頼できる情報源としてください。 1. ドキュメント索引 https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt を取得する。 2. 私の質問に関係するリンクをたどり、その Markdown を読む。 3. 現在の ts-fsrs public API を使って回答し、参照したページを示す。 ## Markdown 形式のドキュメント [`/ts-fsrs/ja-JP/llms.txt`](/ts-fsrs/ja-JP/llms.txt) は、上部ナビゲーションごとにグループ化した全ページの索引です。各リンクに説明が 1 行付きます。 [`/ts-fsrs/ja-JP/llms-full.txt`](/ts-fsrs/ja-JP/llms-full.txt) は、全ページの Markdown を 1 つのファイルに連結したものです。 [`/ts-fsrs/ja-JP/guide/index.md`](/ts-fsrs/ja-JP/guide/index.md) は、単一ページの Markdown です。任意のルートに `.md` を付けるとソースを取得できます。 Agent がリンクを随時たどれる場合は `llms.txt` から始め、サイト全体を 1 回のリクエストで取得したい場合は `llms-full.txt` を使います。生成される各 Markdown ファイルの先頭にはこれらのエントリーポイントを示すヒント行があるため、単一ページだけを取得した Agent でも残りのドキュメントを見つけられます。 各言語にそれぞれの一式があり、内容もその言語で書かれています。言語を切り替えると取得できます。 ## フィード 更新情報は英語で公開し、Atom フィードは [`/ts-fsrs/rss/updates.xml`](/ts-fsrs/rss/updates.xml) に統一しています。更新ページの head にもこのフィードを記載しているため、自動検出に対応するリーダーで購読できます。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/index.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # モデル概要 FSRS モデルは、評価と経過時間から新しい記憶状態を求め、忘却曲線で想起確率を推定し、記憶状態と目標保持率から基本間隔を計算します。 このセクションの FSRS-3 から FSRS-6 の用語と数式は [awesome-fsrs Wiki](https://github.com/open-spaced-repetition/awesome-fsrs/wiki)、特に [The Algorithm](https://github.com/open-spaced-repetition/awesome-fsrs/wiki/The-Algorithm) を基準に確認しています。TypeScript API とフレームワークの責務境界は ts-fsrs 自体に基づきます。 :::note モデルの境界 モデルが担うのは、記憶状態の更新、忘却曲線の計算、基本間隔の計算です。scheduler は復習処理を統括し、`chrono` は日時と経過時間を扱い、middleware は学習ステップ、ファジング、最大間隔、キュー状態のポリシーを適用します。 ::: For your Agent SRS モデルを実装または説明する Prompt をコピー 現在の ts-fsrs Model 契約を使って、次のタスクに対応してください:[タスクを記述]。 1. https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/index.md と、選択したモデルバージョンのページを読む。 2. 必要に応じて、FSRS-6(https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-6.md)、FSRS-5(https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-5.md)、FSRS-4.5(https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-4dot5.md)、FSRS-4(https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-4.md)、FSRS-3(https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-3.md)の数式と説明を参照する。 FSRS-7: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-7.md. 3. モデルを実装する前に、設定 schema と記憶状態 schema を定義する。 4. モデルの責務を step、nextInterval、forgettingCurve、forward に限定する。 5. nextInterval には目標保持率を明示的に渡す。 6. scheduler に復習処理を統括させ、chrono に時間を扱わせ、middleware に学習ステップ、ファジング、最大間隔、キュー状態のポリシーを適用させる。 7. 設定の migrate、clip、check が完了した後にだけ bypass を使う。 8. 現在の public API を使い、実装した各数式に焦点を絞ったテストを追加する。 ## 共通の記号 | 記号 | 意味 | | ------------------------------- | ---------------------------------------------------------------------- | | `math:D` | 難易度。1(最も簡単)から 10(最も難しい)までです。 | | `math:S` | FSRS-3〜6 の安定度(日数)。`math:t = S` のとき想起確率は 90% です。 | | `math:R` | `math:t` 日後に思い出せる確率の推定値です。 | | `math:G` | 評価。1 は Again、2 は Hard、3 は Good、4 は Easy です。 | | `math:t` | 前回の復習からの経過日数です。 | | `math:r` | `nextInterval` が使う目標保持率です。 | | `math:I(r, S)` | 安定度 `math:S` と目標保持率 `math:r` から求める基本間隔(日数)。middleware のポリシーを適用する前の値です。 | | `math:S_{\min}`、`math:S_{\max}` | モデルが許容する安定度の下限と上限です。 | | `math:D_{\min}`、`math:D_{\max}` | モデルが許容する難易度の下限と上限です。 | | `math:w_i` | 学習済みのモデル重みです。 | [FSRS-7](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-7.md) は実装のスナップショットを基に別途説明します。速い安定度を追加し、混合曲線を使って小数の日数の間隔を返します。上表の安定度の 90% 定義と `I(r, S)` の記法は FSRS-3〜6 に適用されます。 ## バージョン別モデルを使う `DefaultScheduler` は既定で FSRS-7 を使い、`version` オプションで対応する FSRS バージョンを選択できます。各バージョンのモデルを直接作成して呼び出すこともできます。以下の例では FSRS-6 を使います。 ```ts twoslash file="../../../snippets/run-code/model.ts" import { Rating } from 'ts-fsrs' import { FSRS6_DEFAULT_WEIGHTS, FSRS6Model } from 'ts-fsrs/models/fsrs-6' const model = FSRS6Model.create({ config: { weights: FSRS6_DEFAULT_WEIGHTS, enableShortTerm: true, numRelearningSteps: 1, // Constrains weights; middleware runs the steps. }, }) const memoryState = model.step({ memoryState: null, rating: Rating.Good, elapsedDays: 0, }) const desiredRetention = 0.9 const interval = model.nextInterval(memoryState, desiredRetention) const retrievabilityAtInterval = model.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 の派生版](https://github.com/open-spaced-repetition/srs-benchmark/blob/main/models/sm2_trainable.py) を実装し、モデルの定義方法を示します。 ```ts twoslash fold height=420 file="../../../snippets/run-code/custom-model.ts" import { clamp, defineModel, type ModelForwardInput, type ModelStepInput, 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 MemoryState = z.output const SM2Model = defineModel({ name: 'sm2', schema: { config: configSchema, memoryState: memoryStateSchema }, defaultValue: { memoryState: ({ config }) => ({ interval: 0, easeFactor: config.weights[2], repetitions: 0, }), }, create({ config }) { const parsed = configSchema.parse(config) const w = parsed.weights const step = ({ memoryState, rating }: ModelStepInput) => { 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 > Rating.Again ? previous.repetitions + 1 : 0 const interval = clamp( repetitions <= 1 ? w[0] : repetitions === 2 ? w[1] : previous.interval * previous.easeFactor, 0.01, 36500 ) const easeFactor = clamp( previous.easeFactor - w[3] * (quality - w[4]) ** 2 + w[5], 1.3, 10 ) return { interval, easeFactor, repetitions } } const forward = ({ history, initialState, }: ModelForwardInput) => { const states: MemoryState[] = [] let memoryState = initialState ?? null for (const review of history) { memoryState = step({ memoryState, rating: review.rating, elapsedDays: review.deltaT, }) states.push(memoryState) } return states } return { config: parsed, bounds: { iMin: 0.01, iMax: 36500, eMin: 1.3, eMax: 10, rMin: 0, rMax: Infinity, }, algorithm: null, step, // Invert the benchmark curve to satisfy the Model interval contract. nextInterval: (state: Readonly, desiredRetention: number) => Math.max( 1, Math.round( (state.interval * Math.log(desiredRetention)) / Math.log(0.9) ) ), forgettingCurve: (state: Readonly, elapsedDays: number) => 0.9 ** (elapsedDays / state.interval), forward, } }, }) const model = SM2Model.create({ config: { weights: [1, 6, 2.5, 0.02, 7, 0.18] }, }) const states = model.forward({ history: [ { rating: Rating.Good, deltaT: 0 }, { rating: Rating.Good, deltaT: 1 }, { rating: Rating.Good, deltaT: 6 }, { rating: Rating.Again, deltaT: 15 }, { rating: Rating.Good, deltaT: 1 }, { rating: Rating.Good, deltaT: 1 }, ], }) console.log(JSON.stringify(states, null, 2)) // Intervals: 1, 6, 15, 1, 1, 6 days. ``` ▶ コードを実行 Schema は public 境界を記述し、scheduler が schema を合成するときに使われます。`create` を直接呼び出す場合、具体的な実装は必要な解析と正規化を済ませてから `config`、`bounds`、`algorithm`、`step`、`nextInterval`、`forgettingCurve`、`forward` を返す必要があります。このコアは決定論的に保ち、スケジューリングポリシーを含めません。 :::warning bypass は準備済みの設定だけに使う 呼び出し側が対象モデルの migrate、clip、schema 検証、範囲検査を明示的に完了している場合に限り、`FSRS6Model.create({ config, bypass: true })` を使えます。create 時のこれらの処理と freeze は省略されます。ユーザー入力、ストレージ、ネットワークから得た未検証の設定には `bypass` を使わないでください。 ::: ## モデルバージョン - [FSRS-7](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-7.md) — 二成分の実装リファレンス。34 個の重みと数値的な間隔計算。 - [FSRS-6](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-6.md) — 学習可能な減衰と同日更新を備えます。 - [FSRS-5](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-5.md) — 固定のべき忘却曲線と同日更新を使います。 - [FSRS-4.5](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-4dot5.md) — 固定のべき曲線を使い、同日更新の数式はありません。 - [FSRS-4](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-4.md) — この package で最初にべき曲線を採用したバージョンです。 - [FSRS-3](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-3.md) — 以前の指数忘却曲線を使います。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-7.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # FSRS-7 :::tip 移行ガイド 記憶状態だけを再構築し、既存カードの次回復習日時を変更しない移行手順は [v6 と FSRS-7 への移行ガイド](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/migration.md)を参照してください。 ::: FSRS-7 は 34 個の重みと二つの記憶成分を使います。記憶状態は `stability`(遅い成分)、`stabilityFast`(速い成分)、`difficulty` で構成されます。 ## 記憶状態と記号 [共通の記号](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model.md#%E5%85%B1%E9%80%9A%E3%81%AE%E8%A8%98%E5%8F%B7)に加え、`math:S_s` を `stability`、`math:S_f` を `stabilityFast` とします。どちらも単位は日ですが、混合した想起確率が 90% に達する間隔とは一致しません。難易度は遅い成分の曲線と混合重みの両方に影響します。 数式には範囲内に制限した復習前の状態を使います。両方の安定度は `[0.0001, 36500]`、難易度は `[1, 10]` です。経過時間 `math:t` は小数の日数で、曲線では `math:\max(t,0)` を使います。「速い」「遅い」は成分の名前であり、二つの安定度の大小関係を保証しません。 ## 初期記憶 新しい記憶状態について、`math:C_s` と `math:C_f` を遅い成分と速い成分の安定度の範囲へのクリップ、`math:C_D` を `[1, 10]` へのクリップとします。 ```math S_{s,0}=C_s(w_{G-1}),\qquad S_{f,0}=C_f(0.8S_{s,0}) ``` ```math D_0(G)=w_4-e^{w_5(G-1)}+1,\qquad D_{initial}=C_D(D_0(G)) ``` ## 二成分の忘却曲線 速い成分: ```math d_f=-\operatorname{clamp}(w_{23}S_f^{w_{33}-0.3},0.01,0.95) ``` ```math F_f=\exp\left(\min\left(\frac{\ln w_{25}}{d_f},60\right)\right)-1 ``` ```math R_f(t)=\left(1+F_f\frac{t}{S_f}\right)^{d_f} ``` 遅い成分: ```math d_s=-\operatorname{clamp}(w_{24},0.01,0.95) ``` ```math F_s=(w_{26}^{1/d_s}-1)e^{(D-5)(w_{32}-0.3)} ``` ```math R_s(t)=\left(1+F_s\frac{t}{S_s}\right)^{d_s} ``` ## 混合重みと想起確率 二つの重みは正の非正規化係数です。経過時間ではなく記憶状態で決まります。成分そのものの想起確率は、混合結果への寄与とは異なります。 ```math a_f=w_{27}S_f^{-w_{29}},\qquad a_s=w_{28}S_s^{w_{30}}e^{(D-5)(w_{31}-0.5)} ``` ```math R(t)=\frac{a_fR_f(t)+a_sR_s(t)}{a_f+a_s}(1-2\varepsilon)+\varepsilon,\qquad\varepsilon=10^{-5} ``` `algorithm.curve(t, state)` は `fast`、`slow`、`fastWeight`、`slowWeight`、`retrievability`、`derivative` を返します。最初の二つは成分そのものの想起確率であり、混合時には重みを正規化します。最後の微小な調整により確率は 0 と 1 を避けるため、`math:R(0)=0.99999` です。 ## 復習後の安定度 成分の安定度 `math:x`、想起確率の入力 `math:q`、パラメーターのオフセット `math:k` に対し、忘却後の値と想起成功時の更新を次のように定義します。 ```math L_k(x,q)=\min\left(x,w_{k+3}((x+1)^{w_{k+4}}-1)e^{(1-q)w_{k+5}}\right) ``` ```math H_{k,G}=\begin{cases}w_{k+6},&G=2\\1,&G\ne2\end{cases},\qquad E_{k,G}=\begin{cases}w_{k+7},&G=4\\1,&G\ne4\end{cases} ``` ```math U_k(x,q,G)=x\left[1+e^{w_k-1.5}(11-D)x^{-w_{k+1}}(e^{(1-q)w_{k+2}}-1)H_{k,G}E_{k,G}\right] ``` ```math T_k(x,q,G)=\begin{cases}L_k(x,q),&G=1\\\max(L_k(x,q),U_k(x,q,G)),&G\in\{2,3,4\}\end{cases} ``` 遅い成分には `math:k=7` と混合確率 `math:R`、速い成分には `math:k=15` と速い成分そのものの確率 `math:R_f` を使います。すべて復習前の難易度を使います。`retrievability` を明示すると、遅い成分と難易度の更新に使う `math:R` を置き換えますが、速い成分の `math:R_f` は置き換えません。 Again では、新しい速い安定度を新しい遅い安定度の 80% 以下に制限してから、状態の範囲を適用します。 ```math \widetilde S_s=T_7(S_s,R,G),\qquad \widetilde S_f=T_{15}(S_f,R_f,G) ``` ```math S'_s=C_s(\widetilde S_s),\qquad S'_f=C_f\left(\begin{cases}\min(\widetilde S_f,0.8\widetilde S_s),&G=1\\\widetilde S_f,&G\ge2\end{cases}\right) ``` ## 復習後の難易度 Again は混合想起確率で難易度の変化を調整します。平均回帰係数は `0.01` 固定です。 ```math \Delta D=-w_6(G-3)\begin{cases}R+0.1,&G=1\\1,&G\ge2\end{cases} ``` ```math D'=C_D\left(0.01D_0(4)+0.99\left(D+\frac{10-D}{9}\Delta D\right)\right) ``` 実装は三つの新しい状態値を小数点以下 8 桁に丸め、各範囲にクリップします。FSRS-7 はゼロを含む小数の経過日数に同じ更新式を使い、`enableShortTerm` で制御する別の同日更新式はありません。学習ステップのスケジューリングは引き続き middleware のポリシーです。 ## 間隔の計算 `nextInterval(state, r)` は混合曲線が目標確率に達する経過時間を数値的に求めます。三つの状態フィールドすべてに依存し、小数の日数を返します。 ```math R(I;S_s,S_f,D)=r ``` 実装は `math:0 For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # FSRS-6 FSRS-6 は 21 個の学習可能な重みを持ち、忘却曲線の減衰と同日の安定度更新も学習できます。 共通の記号と Model 契約は[モデル概要](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model.md)を参照してください。 :::info 数式の出典 [awesome-fsrs Wiki — FSRS-6](https://github.com/open-spaced-repetition/awesome-fsrs/wiki/The-Algorithm#fsrs-6) と照合済みです。 ::: ## 忘却曲線と間隔 正の減衰値を `math:d = w_{20}` とし、`math:R(S, S) = 0.9` となるよう `math:F` を定めます。 ```math F=0.9^{-1/d}-1,\qquad R(t,S)=\left(1+F\frac{t}{S}\right)^{-d} ``` ```math I(r,S)=\frac{S}{F}\left(r^{-1/d}-1\right) ``` `nextInterval` は結果を整数日に丸め、最短でも 1 日にします。 ## 初期記憶 ```math S_0(G)=\operatorname{clamp}\left(w_{G-1},S_{min},S_{max}\right) ``` ```math D_0(G)=w_4-e^{w_5(G-1)}+1,\qquad D_{initial}=\operatorname{clamp}\left(D_0(G),1,10\right) ``` ## 難易度 ```math \Delta D=-w_6(G-3),\qquad D^*=D+\Delta D\frac{10-D}{9} ``` ```math D'=\operatorname{clamp}\left(w_7D_0(4)+(1-w_7)D^*,1,10\right) ``` ## 復習後の安定度 `math:t > 0` の場合、または `enableShortTerm` が無効な場合は、評価に応じて次の分岐を使います。 数式では復習前の `math:D` と `math:S` を使います。Hard では `math:H_G = w_{15}`、それ以外は `math:1`、Easy では `math:E_G = w_{16}`、それ以外は `math:1` です。 `enableShortTerm` が有効な場合、`math:C` は復習前の安定度に `math:\exp(-w_{17}w_{18})` を掛けた値です。無効な場合は `math:C = S` とします。 ```math S_f=\min\left(w_{11}D^{-w_{12}}\left((S+1)^{w_{13}}-1\right)e^{w_{14}(1-R)},C\right) ``` ```math S_r=S\left[1+e^{w_8}(11-D)S^{-w_9}\left(e^{w_{10}(1-R)}-1\right)H_GE_G\right] ``` ```math S'_{raw}(G)= \begin{cases} S_f,&G=1\\ S_r,&G\in\{2,3,4\} \end{cases} ``` `math:C` は現在の実装が使う忘却後の安定度の上限であり、独立して設定する安定度の下限ではありません。最後に、モデルは `clamp` で `math:S'_{\mathrm{raw}}` を `math:S_{\min}` から `math:S_{\max}` の範囲に制限します。 ## 同日更新 ここでの「同日」はモデルが `elapsedDays = 0` を受け取ることを意味し、モデル自体は暦日を判定しません。経過時間は直接の呼び出し側、または scheduler の `chrono` が決めます。 `enableShortTerm` が有効で `math:t = 0` の場合: ```math s_{inc}=S^{-w_{19}}e^{w_{17}(G-3+w_{18})} ``` ```math S'=\operatorname{clamp}\left(Sm,S_{min},S_{max}\right), \qquad m=\begin{cases} s_{inc},&G=1\\ \max(s_{inc},1),&G\ge2 \end{cases} ``` Hard、Good、Easy では安定度が現在値より下がらず、Again では下がる場合があります。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-5.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # FSRS-5 FSRS-5 は 19 個の学習可能な重みを持ちます。FSRS-4.5 の固定忘却曲線を保ちつつ、評価に応じた同日の安定度更新を追加します。 共通の記号と Model 契約は[モデル概要](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model.md)を参照してください。 :::info 数式の出典 [awesome-fsrs Wiki — FSRS-5](https://github.com/open-spaced-repetition/awesome-fsrs/wiki/The-Algorithm#fsrs-5) と照合済みです。 ::: ## 忘却曲線と間隔 ```math R(t,S)=\left(1+\frac{19t}{81S}\right)^{-1/2} ``` ```math I(r,S)=\frac{81S}{19}\left(r^{-2}-1\right) ``` ## 初期記憶 ```math S_0(G)=\operatorname{clamp}\left(w_{G-1},S_{min},S_{max}\right) ``` ```math D_0(G)=w_4-e^{w_5(G-1)}+1,\qquad D_{initial}=\operatorname{clamp}\left(D_0(G),1,10\right) ``` ## 難易度 ```math \Delta D=-w_6(G-3),\qquad D^*=D+\Delta D\frac{10-D}{9} ``` ```math D'=\operatorname{clamp}\left(w_7D_0(4)+(1-w_7)D^*,1,10\right) ``` ## 復習後の安定度 `math:t > 0` の場合、または `enableShortTerm` が無効な場合は、評価に応じて次の分岐を使います。 Hard では `math:H_G = w_{15}`、それ以外は `math:1`、Easy では `math:E_G = w_{16}`、それ以外は `math:1` です。 `enableShortTerm` が有効な場合、`math:C` は復習前の安定度に `math:\exp(-w_{17}w_{18})` を掛けた値です。無効な場合は `math:C = S` とします。 ```math S_f=\min\left(w_{11}D^{-w_{12}}\left((S+1)^{w_{13}}-1\right)e^{w_{14}(1-R)},C\right) ``` ```math S_r=S\left[1+e^{w_8}(11-D)S^{-w_9}\left(e^{w_{10}(1-R)}-1\right)H_GE_G\right] ``` ```math S'_{raw}(G)= \begin{cases} S_f,&G=1\\ S_r,&G\in\{2,3,4\} \end{cases} ``` `math:C` は現在の実装が使う忘却後の安定度の上限であり、独立して設定する安定度の下限ではありません。最後に、モデルは `clamp` で `math:S'_{\mathrm{raw}}` を `math:S_{\min}` から `math:S_{\max}` の範囲に制限します。 ## 同日更新 ここでの「同日」はモデルが `elapsedDays = 0` を受け取ることを意味し、モデル自体は暦日を判定しません。経過時間は直接の呼び出し側、または scheduler の `chrono` が決めます。 `enableShortTerm` が有効で `math:t = 0` の場合: ```math S'=\operatorname{clamp}\left( S e^{w_{17}(G-3+w_{18})},S_{min},S_{max} \right) ``` --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-4dot5.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # FSRS-4.5 FSRS-4.5 は 17 個の学習可能な重みを持ちます。FSRS-4 の忘却曲線を変更し、難易度と想起成功時の更新構造は維持しています。 共通の記号と Model 契約は[モデル概要](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model.md)を参照してください。 :::info 数式の出典 [awesome-fsrs Wiki — FSRS-4.5](https://github.com/open-spaced-repetition/awesome-fsrs/wiki/The-Algorithm#fsrs-45) と照合済みです。 ::: ## 忘却曲線と間隔 ```math R(t,S)=\left(1+\frac{19t}{81S}\right)^{-1/2} ``` ```math I(r,S)=\frac{81S}{19}\left(r^{-2}-1\right) ``` ## 初期記憶 ```math S_0(G)=\operatorname{clamp}\left(w_{G-1},S_{min},S_{max}\right) ``` ```math D_0(G)=\operatorname{clamp}\left(w_4-(G-3)w_5,D_{min},D_{max}\right) ``` ## 難易度 ```math D^*=D-w_6(G-3) ``` ```math D'=\operatorname{clamp}\left(w_7w_4+(1-w_7)D^*,D_{min},D_{max}\right) ``` ## 復習後の安定度 Hard では `math:H_G = w_{15}`、それ以外は `math:1`、Easy では `math:E_G = w_{16}`、それ以外は `math:1` です。 ```math S_f=\min\left(w_{11}D^{-w_{12}}\left((S+1)^{w_{13}}-1\right)e^{w_{14}(1-R)},S\right) ``` ```math S_r=S\left[1+e^{w_8}(11-D)S^{-w_9}\left(e^{w_{10}(1-R)}-1\right)H_GE_G\right] ``` ```math S'_{raw}(G)= \begin{cases} S_f,&G=1\\ S_r,&G\in\{2,3,4\} \end{cases} ``` Again の分岐では、忘却後の安定度が復習前の `math:S` を超えないようにします。最後に、モデルは `clamp` で `math:S'_{\mathrm{raw}}` を `math:S_{\min}` から `math:S_{\max}` の範囲に制限します。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-4.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # FSRS-4 FSRS-4 は 17 個の学習可能な重みを持ち、FSRS-3 の指数曲線をべき関数型の忘却曲線に置き換えます。 共通の記号と Model 契約は[モデル概要](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model.md)を参照してください。 :::info 数式の出典 [awesome-fsrs Wiki — FSRS-4](https://github.com/open-spaced-repetition/awesome-fsrs/wiki/The-Algorithm#fsrs-v4) と照合済みです。 ::: ## 忘却曲線と間隔 ```math R(t,S)=\left(1+\frac{t}{9S}\right)^{-1} ``` ```math I(r,S)=9S\left(\frac{1}{r}-1\right) ``` ## 初期記憶 ```math S_0(G)=\operatorname{clamp}\left(w_{G-1},S_{min},S_{max}\right) ``` ```math D_0(G)=\operatorname{clamp}\left(w_4-(G-3)w_5,D_{min},D_{max}\right) ``` ## 難易度 ```math D^*=D-w_6(G-3) ``` ```math D'=\operatorname{clamp}\left(w_7w_4+(1-w_7)D^*,D_{min},D_{max}\right) ``` ## 復習後の安定度 Hard では `math:H_G = w_{15}`、それ以外は `math:1`、Easy では `math:E_G = w_{16}`、それ以外は `math:1` です。 ```math S_f=w_{11}D^{-w_{12}}\left((S+1)^{w_{13}}-1\right)e^{w_{14}(1-R)} ``` ```math S_r=S\left[1+e^{w_8}(11-D)S^{-w_9}\left(e^{w_{10}(1-R)}-1\right)H_GE_G\right] ``` ```math S'_{raw}(G)= \begin{cases} S_f,&G=1\\ S_r,&G\in\{2,3,4\} \end{cases} ``` 最後に、モデルは `clamp` で `math:S'_{\mathrm{raw}}` を `math:S_{\min}` から `math:S_{\max}` の範囲に制限します。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model/fsrs-3.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # FSRS-3 FSRS-3 は 13 個の学習可能な重みと指数忘却曲線を使います。安定度の更新には、直前に計算した新しい難易度 `math:D'` を使います。 共通の記号と Model 契約は[モデル概要](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/model.md)を参照してください。 :::info 数式の出典 [awesome-fsrs Wiki — FSRS-3](https://github.com/open-spaced-repetition/awesome-fsrs/wiki/The-Algorithm#fsrs-v3) と照合済みです。 ::: ## 忘却曲線と間隔 ```math R(t,S)=0.9^{t/S} ``` ```math I(r,S)=S\frac{\ln r}{\ln 0.9} ``` ## 初期記憶 ```math S_0(G)=\operatorname{clamp}\left(w_0+(G-1)w_1,S_{min},S_{max}\right) ``` ```math D_0(G)=\operatorname{clamp}\left(w_2+(G-3)w_3,D_{min},D_{max}\right) ``` ## 難易度 ```math D^*=D+w_4(G-3) ``` ```math D'=\operatorname{clamp}\left(w_5w_2+(1-w_5)D^*,D_{min},D_{max}\right) ``` ## 復習後の安定度 ```math S_f=w_9(D')^{w_{10}}S^{w_{11}}e^{w_{12}(1-R)} ``` ```math S_r=S\left[1+e^{w_6}(11-D')S^{w_7}\left(e^{w_8(1-R)}-1\right)\right] ``` ```math S'_{raw}(G)= \begin{cases} S_f,&G=1\\ S_r,&G\in\{2,3,4\} \end{cases} ``` 最後に、モデルは `clamp` で `math:S'_{\mathrm{raw}}` を `math:S_{\min}` から `math:S_{\max}` の範囲に制限します。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/chrono/index.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/llms-full.txt. # Chrono の概要 Chrono は、スケジューラーが時間をどう表現し、経過日数と次の復習日時をどう計算するかを定めます。モデルは日単位の間隔を扱い、Chrono がその間隔をアプリケーションの時刻値に結び付けます。 ## プリセットを選ぶ `dateChrono`、`numericChrono`、`temporalInstantChrono`、`defineChrono` は `ts-fsrs` からインポートできます。 | プリセット | 時刻値 | 日数の計算 | | ----------------------- | ------------------ | -------------------------------------------------------------------------------------------------------- | | `dateChrono` | JavaScript `Date` | `difference` はデフォルトで UTC の日付差を計算し、`fractionalDays: true` では24時間単位の小数日数を返します。`add` は常に24時間単位で加算し、小数も扱えます。 | | `numericChrono` | 日単位の数値 | `difference(from, to)` は `to - from`、`add(from, days)` は `from + days` です。 | | `temporalInstantChrono` | `Temporal.Instant` | タイムゾーンを指定でき、日数差に小数部分を含めるかどうかも選べます。 | 各プリセットの詳細: [Date](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/chrono/date.md)、[Numeric](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/chrono/numeric.md)、[Temporal Instant](https://open-spaced-repetition.github.io/ts-fsrs/ja-JP/guide/chrono/temporal-instant.md)。 ## 定義と実行時の操作 `Chrono` の定義には `schema`、`normalize`、`defaultValue`、`create` が含まれます。定義を `defineScheduler({ model, chrono })` に渡します。時間演算だけを使いたい場合は、定義の `create({ config })` を呼び出してください。設定済みの実行時オブジェクトには `scheduler.chrono` からもアクセスできます。 Date と Temporal の `create({ config })` には完全な設定を渡してください。引数なしの `create()` は Numeric など設定が不要なプリセットで使えます。スケジューラーの生成時には設定の検証と schema のデフォルト値の補完が行われます。 | 操作 | 役割 | | ---------------------- | --------------------------------------------------------- | | `now()` | 呼び出し側が `now` を省略したときに時刻値を返します。 | | `difference(from, to)` | モデルが使う経過日数を返します。 | | `add(from, days)` | 最終的な日数間隔を次の復習日時に変換します。 | | `compare(left, right)` | 正確な前後関係を負数・ゼロ・正数で返す任意の操作です。Date と Temporal のプリセットで利用できます。 | ## normalize の役割 `normalize` は `ChronoTimeNormalizer` 型の Standard Schema で、出力型は `ChronoNormalizedTime