--- url: https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/index.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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` 將這些項目組合成 definition。`.create()` 會驗證設定並回傳獨立的排程器,因此同一個 definition 可以建立多個排程器。 For your Agent 在 TypeScript 應用程式中整合 ts-fsrs 複製提示詞 在我的 TypeScript 應用程式中整合 ts-fsrs。 1. 閱讀文件索引 https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt 與快速開始 https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/quick-start.md。先檢查專案是否已使用 ts-fsrs:若涉及 v5 → v6 或 FSRS-6 → FSRS-7 升級,必須閱讀遷移指南 https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/migration.md,依其步驟與驗收要求完成遷移,保留既有卡片與紀錄,遷移時不重新安排卡片的下次複習時間;以下安裝與新卡範例僅用於首次整合。 2. 安裝 `ts-fsrs` package。 3. 使用 `defineScheduler`、`FSRS7Model` 與 `dateChrono` 建立 FSRS-7 排程器。 4. 建立一張新卡片,並使用 `Rating.Good` 完成一次複習。 5. 保持實作最小化,並使用目前 ts-fsrs 公開 API。 接下來前往[快速開始](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/quick-start.md),安裝 package 並在瀏覽器中執行排程器。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/quick-start.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # 快速開始 既有應用程式升級請參閱 [v5 → v6 與 FSRS-7 遷移指南](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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` schema,將卡片或複習日誌的時間欄位轉換為 `{ 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/zh-TW/guide/migration.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # 遷移至 v6 與 FSRS-7 套件版本與模型版本是兩回事:**ts-fsrs v5 已使用 FSRS-6**。先將應用程式與儲存紀錄遷移至 v6 API,繼續使用 FSRS-6;再單獨遷移模型。本文對應本站的 v6 API,安裝時應鎖定與本文介面一致的 v6 版本。 :::warning 遷移後的程式碼不得使用 legacy API 使用 `defineScheduler` 與明確的模型入口。遷移後不得使用 `fsrs()`、`FSRS`、`createEmptyCard`、`generatorParameters`、舊排程器類別與策略,或從根入口匯出的舊 `Card`、`CardInput`、`ReviewLog`、`FSRSParameters` 等 legacy 型別。部分符號仍為相容性而匯出,不代表它們是 v6 的整合方式。下文舊名稱僅供靜態對照;可執行範例不使用 legacy API 或型別。 ::: For your Agent 讓 Agent 完成遷移 複製提示詞 請協助我將目前應用程式遷移至 ts-fsrs v6(defineScheduler)。先詢問我選擇哪一個目標,並等待明確回答,不要預設升級模型,也不要在回答前修改程式碼或資料: A. 僅升級至 ts-fsrs v6,繼續使用 FSRS-6,保留既有 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/zh-TW/guide/migration.md,以及排程器說明:https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/scheduler/index.md、FSRS-7 說明:https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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,以及從根入口匯入的舊 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。請明確保留原學習步驟,不要依賴預設值;驗證最終間隔小於一天/至少一天、關閉/空/完成步驟及無中介軟體時的狀態邊界。獨立 curve(weights, elapsedDays, state) 從 ts-fsrs/models/fsrs-7 匯入,預先驗證權重並讀取 retrievability;total 是混合權重之和。也可使用 model.forgettingCurve 取得數值回憶機率。 7. 僅選擇 B 時:對已學習卡片選取從建立或最近一次記憶重設以來的完整歷史,映射 rating 與實際 reviewTime,以 Reschedule.replay() 重建 stability、stabilityFast、difficulty,不傳入舊模型 initialState。重建結果僅更新目前 card 的 memoryState;歷史 revlog 僅作為唯讀輸入,不得修改、覆寫或補寫任何歷史資料。遷移時只更新目前卡片的記憶狀態,不重新安排既有卡片的下次複習時間(dueAt);之後複習時再由 FSRS-7 計算新的時間。lastReviewAt、cardId、學習進度與統計保持不變。過濾 Manual 不等於處理記憶重設,完整性不能僅憑列數或最後時間戳記判斷。 8. 僅選擇 B 時:新卡使用 FSRS-7 預設記憶狀態;已學習卡片的歷史缺少、截斷、為空、僅含 Manual 或不一致時,必須明確報錯/跳過,保留 FSRS-6 狀態與設定並依模型版本路由,不默默清零。保留原始紀錄;模型、權重設定與記憶狀態一起保存,設定跨模型復原邊界。 9. 若需修改保存資料,提供備份、試執行與回復方式,正式環境資料變更遵循儲存庫既有流程。加入並實際執行聚焦測試:舊資料轉換、四種卡片狀態、複習/預覽/復原/遺忘、提前與逾期複習的三個時間欄位、同日小數間隔、Manual 與重設邊界、缺少歷史處理,以及遷移前後到期時間/ID/學習進度/統計不變。 10. 完成型別檢查、相關測試與建置,報告修改檔案、實際執行的命令與結果、尚未遷移的資料及原因。區分已驗證結果與未執行步驟;遷移目標與 TODO List 均必須經使用者明確確認。除此之外,其他資訊不足時先檢查儲存庫,僅就無法自行確定且影響資料正確性的事項詢問使用者。 ## 1. 遷移至 v6 API,保留 FSRS-6 ### 定義排程策略 `defineScheduler({ model, chrono })` 建立可重用的定義;`.use(...)` 組合中介軟體並傳回新定義;`.create({ config })` 驗證設定並建立獨立排程器實例。在程式層級保留定義,依使用者或牌組設定建立實例。 單純的定義**不包含全部 v5 排程策略**。以下沿用目前標準預設的中介軟體順序:目標保留率、模糊、統計、已排程天數、學習步驟、最大間隔、單調間隔。請保留實際設定與 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 函式庫匯入。以資料庫既有的穩定 ID 作為 `cardId`,另外保留應用程式自有欄位。組合後的卡片 schema 會驗證轉換後的日期與欄位;其他應用程式欄位也應在讀取儲存資料的邊界驗證。 ### 設定對照 | v5 設定 | v6 設定 | 遷移說明 | | ------------------------------------- | ----------------------------------- | ---------------------------------------- | | `w` | `weights` | 此階段保留使用者的 FSRS-6 權重;範例僅為方便執行才使用預設值。 | | `request_retention` | `desiredRetention` | 需要目標保留率中介軟體。 | | `maximum_interval` | `maximumInterval` | 需要最大間隔中介軟體;單調間隔策略也使用此上限。 | | `enable_fuzz` | `enableFuzz` | 需要模糊中介軟體,保留舊設定。 | | `enable_short_term` | `enableShortTerm` | 對 FSRS-6,同時影響模型短期行為與明確的學習步驟。 | | `learning_steps` / `relearning_steps` | `learningSteps` / `relearningSteps` | 保留設定的步驟陣列與既有學習進度。 | | 重新學習步驟數量 | `numRelearningSteps` | FSRS-6 需設為 `relearningSteps.length`。 | | `forget(..., reset_count)` | `clearStatsOnForget` | 改為實例設定;v5 預設 `false`,v6 統計中介軟體預設 `true`。 | ### 卡片、紀錄與操作 | v5 儲存欄位 | v6 欄位或處理方式 | | ------------------------------------------------ | -------------------------------------------------------------------------------- | | `due` | 卡片的 `dueAt`,反序列化為 `Date`。 | | `elapsed_days` / 紀錄 `last_elapsed_days` | 已移除,使用所選 chrono 由時間戳記計算經過天數;FSRS-7 重播不要使用這些舊的取整值。 | | `last_review` | `lastReviewAt`,缺少或 null 統一為 `null`。 | | `scheduled_days` | `scheduledDays`,由中介軟體提供。 | | `learning_steps` | `learningStep`,由中介軟體提供。 | | `stability`、`difficulty`、`state`、`reps`、`lapses` | API 遷移時保留;統計欄位需要中介軟體。 | | 應用程式紀錄 ID | `cardId`,卡片與紀錄使用同一 ID。 | | 無對應欄位 | `scheduleStatus`:New → `new`,Learning/Relearning → `learning`,Review → `review`。 | | 複習紀錄 `review` | `reviewTime`,即實際複習時間,而非卡片到期時間。 | | 結果 `{ card, log }` | `{ card, revlog }`。 | 如範例所示,從排程器推導型別。卡片與紀錄欄位取決於模型、chrono 及中介軟體。自訂 v5 策略需改寫為對應的[中介軟體](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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,可用 `for...of` 遍歷。每個候選項包含 `{ grade, card, revlog }`,透過 `candidate.grade` 選取,不再以評分鍵索引物件。 | | `f.rollback(card, log)` | `scheduler.rollback({ card, revlog })`,使用傳回的 v6 紀錄。 | | `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/zh-TW/guide/scheduler/default-scheduler.md) 是另一個預設入口,但預設模型為 FSRS-7;若在此階段採用它,應明確指定 FSRS-6。 ### 為什麼紀錄中的三個時間不能直接改名 `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 重構時分開「計算經過天數的時間」與「供復原使用的原到期時間」的原因。上面的可執行範例會印出這三個欄位。 不能一律執行 `oldLog.due → revlog.dueAt`:一般 v5 已學習卡片紀錄的 `due` 通常是上次複習時間;沒有 `last_review` 的首次複習才記錄原到期時間,Manual/遺忘紀錄又有各自的語意。v5 重設後的 New 卡片也可能帶有舊 `last_review`,因此卡片轉換範例對 `State.New` 統一輸出 `lastReviewAt: null`。 只重建模型記憶時,僅映射 `rating` 與 `review → reviewTime` 即可。若要跨版本精確復原,需要原始複習前卡片快照(包含兩個時間欄位與中介軟體欄位),否則應設立復原邊界。不能以 `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/zh-TW/guide/optimizer.md)。不要替 FSRS-6 參數補值,也不要重用其訓練權重。`migrateFSRS7Parameters` 僅為空輸入提供預設值,不會轉換舊模型的參數配置。 FSRS-7 記憶狀態包含 `stability`、`stabilityFast` 與 `difficulty`,三個欄位都應透過歷史重建。**重建結果僅用於更新目前 card 的記憶狀態(memoryState);歷史 revlog 僅作為唯讀輸入,不得修改、覆寫或補寫其中的任何資料,包括過去記錄的記憶狀態。** **不得為既有卡片直接設定 `stabilityFast = 0.8 * stability`**,也不能保留 FSRS-6 的穩定性、難度後只新增快分量。模型初始化使用的係數不是已演化記憶狀態的遷移公式。 設定 `fractionalDays: true`,保留兩次複習相隔十分鐘等小數天數。FSRS-7 的快軌跡是模型固有部分:安裝學習步驟中介軟體後,`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`(預設一天) | 最終間隔至少 `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 總是進入 `Learning`/`Relearning`,即使其間隔被限制為 `graduatingInterval`。 沒有設定步驟時,不能假設「沒有 Learning」、「只有 Again 才進入 Relearning」或「Easy 一定進入 Review」。一般來說,Easy 跳過明確的步驟,Good 僅在存在適用的下一步驟時推進,時長為 0 的步驟視為沒有步驟。`learningStep` 只在卡片留在 Learning/Relearning 時保留下一步驟,卡片進入 Review 時重設為 `0`;它是步驟索引,不是複習次數。 這些轉換屬於中介軟體,不屬於 `FSRS7Model`。只組合模型與 chrono 的排程器仍傳回 Review,即使間隔小於一天。遷移應用程式佇列、狀態篩選與統計時,需要同步調整依賴舊行為的判斷。 [「重播記憶,保留排程安排」一節的完整遷移範例](#重播記憶保留排程安排)**明確保留既有學習步驟**,不依賴預設值。若產品決定完全採用模型驅動的步驟,可明確設定 `learningSteps: []`、`relearningSteps: []`;這不等於移除學習步驟中介軟體。改變步驟策略應單獨驗證,不能在卡片欄位遷移時順便執行。 ### 遷移回憶機率呼叫 使用 `scheduler.model.forgettingCurve(state, elapsedDays)` 取得數值回憶機率,或 `scheduler.model.algorithm.curve(elapsedDays, state)` 檢查快慢分量。兩者皆需要完整 FSRS-7 狀態:`stability`、`stabilityFast`、`difficulty`,不能只傳 FSRS-6 的單一穩定性值。 也可以從 `ts-fsrs/models/fsrs-7` 匯入獨立函式 `curve`,呼叫 `curve(weights, elapsedDays, state)`,不必建立演算法實例;`weights` 必須是有效的 34 個 FSRS-7 權重。**此函式不驗證權重**:儲存或使用者輸入應先通過 schema/參數驗證,不要用它探測權重是否有效。使用已遷移的記憶狀態與同一份 FSRS-7 權重,`elapsedDays` 保留小數天數。 曲線結果包含 `retrievability`、`derivative`、`fast`、`slow`、`fastWeight`、`slowWeight` 與 `total`;`total = fastWeight + slowWeight` 是混合係數之和,不是總回憶機率。替代舊回憶機率呼叫時讀取 `retrievability`,百分比由應用程式格式化。演算法實例的曲線結果也包含 `total`。這些曲線介面只計算機率與分量,不遷移卡片、產生紀錄或安排到期時間。 ### 重播記憶,保留排程安排 以下為可獨立執行的範例。它接收已轉為 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 }`,可能改變到期時間、學習進度與統計。應以保留原 `cardId`、使用第一筆複習時間的 **FSRS-7 新卡片**作為 `initialCard`,不能在既有的已學習卡片上再重播完整歷史。產生的紀錄是重新計算的結果,不應替換原始歷史。本文預設使用 `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/zh-TW/guide/model/fsrs-7.md),API 組成見[排程器定義](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/scheduler.md)、[Chrono](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/chrono.md) 與[中介軟體](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/middleware.md)。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/architecture.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # 架構 ts-fsrs v6 的排程架構圍繞 Model、Chrono、Middleware 和 Scheduler 組織,將記憶模型、時間處理與排程策略分開,並由 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 ) ) ``` ▶ 執行程式碼 範例刻意將最大間隔設為一天。模型仍計算出 `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/zh-TW/guide/state-transitions.md),數學細節見 [FSRS-6 公式](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-6.md)。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/state-transitions.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # 狀態轉換 `State` 表示卡片目前所處的 FSRS 學習階段。透過 `scheduler.newCard()` 建立的卡片初始狀態為 `New`。每次複習時,`DefaultScheduler` 會結合 FSRS 模型結果與學習步驟策略,決定下一個狀態和到期時間。 ![DefaultScheduler 狀態轉換流程圖](/ts-fsrs/static/svg/state-transitions.zh-TW.eed5cf8279.svg) 圖表按顯式步驟和間隔中介軟體處理後的最終排程間隔分類。評分會影響間隔,但不能單獨決定下一個狀態。 `graduatingInterval`(預設 `1`,單位為天)是卡片處於 `Review` 時的最短間隔。最終間隔短於 `graduatingInterval` 時進入或維持 `Learning`/`Relearning`;達到 `graduatingInterval` 時進入 `Review`。步驟被停用或為空、由模型提供間隔時,也適用這項規則。 有一個例外:Again 使用模型間隔覆蓋顯式步驟時,即使最終間隔達到 `graduatingInterval`,卡片仍維持 `Learning`/`Relearning`。流程圖呈現的是一般的間隔對應規則,此例外優先於圖中的規則。 零時長步驟(如 `0m`,或按秒四捨五入後為零的時長)視為沒有步驟,不支援負數時長。 ## 四種狀態 | 狀態 | 含義 | 預設佇列 | | ------------ | ------------------------------------------------- | ---------- | | `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` | 存在下一步時推進;否則畢業進入 `Review`,使用模型間隔,但不短於 `graduatingInterval`。 | 使用模型間隔,但不短於 `graduatingInterval`;維持 `Review`。 | | `Easy` | 跳過顯式步驟,畢業進入 `Review`,使用模型間隔,但不短於 `graduatingInterval`。 | 使用模型間隔,但不短於 `graduatingInterval`;維持 `Review`。 | Hard 步驟間隔為前兩個步驟時長的平均值;只有一個步驟時,為該步驟時長的 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`,且 `enableShortTerm` 預設為 `true`,FSRS-7 也一樣。對於 FSRS-7,此開關控制顯式 `learningSteps` 和 `relearningSteps`,不關閉模型內在的短期記憶,也不控制依間隔進行的狀態映射。即使 `enableShortTerm: false`,模型間隔小於 `graduatingInterval` 時仍會進入 `Learning` 或 `Relearning`。使用 `defineScheduler` 時,需註冊 `.use(schedulerLearningStepsMiddleware)` 才會套用此狀態映射;只組合 FSRS-7 模型而未註冊此中介軟體時,即使排程間隔小於一天,`state` 仍為 `Review`,`scheduleStatus` 仍為 `review`。模型負責記憶狀態和間隔計算,學習狀態轉換由中介軟體負責。如果要讓 FSRS-7 排程後顯示 `Learning`/`Relearning`,請掛載 `schedulerLearningStepsMiddleware`,或自行實作負責狀態轉換的 middleware。 ::: `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` 型別,輸出間隔以分鐘顯示,保留兩位小數。 :::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`,兩個學習狀態對應 `learning`,`Review` 對應 `review`;middleware 可以加入 `suspended` 等佇列狀態,而不會改變四種 FSRS 狀態。 用 `card.state` 解釋學習階段,用 `card.scheduleStatus` 篩選排程佇列。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/llms.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # LLM 本站每個頁面都會同步發布 Markdown 版本,更新區也提供 Atom 訂閱源,因此 Agent 與 RSS 閱讀器無須解析渲染後的 HTML。 For your Agent 依據文件回答 ts-fsrs 問題 複製提示詞 請將 ts-fsrs 文件視為唯一可信來源。 1. 取得文件索引 https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt。 2. 跟進與問題相關的連結,閱讀對應 Markdown。 3. 使用目前 ts-fsrs 公開 API 回答,並註明參考的頁面。 ## Markdown 格式的文件 [`/ts-fsrs/zh-TW/llms.txt`](/ts-fsrs/zh-TW/llms.txt) 是依頂端導覽分組的全站頁面索引,每個連結都附有一行說明。 [`/ts-fsrs/zh-TW/llms-full.txt`](/ts-fsrs/zh-TW/llms-full.txt) 將所有頁面的 Markdown 合併成單一檔案。 [`/ts-fsrs/zh-TW/guide/index.md`](/ts-fsrs/zh-TW/guide/index.md) 是單一頁面的 Markdown。在任意路由後加上 `.md` 即可取得其來源檔案。 如果 Agent 可以依需求跟進連結,請從 `llms.txt` 開始;如果需要一次取得全站內容,則使用 `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/zh-TW/guide/model/index.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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 模型 複製提示詞 使用目前 ts-fsrs Model 契約完成以下任務:[描述任務]。 1. 閱讀 https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/index.md 以及所選模型版本的頁面。 2. 可依需要對照 FSRS-6(https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-6.md)、FSRS-5(https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-5.md)、FSRS-4.5(https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-4dot5.md)、FSRS-4(https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-4.md)和 FSRS-3(https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-3.md)的公式與說明。 FSRS-7: https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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. 使用目前公開 API,並為每個實作的公式加入聚焦測試。 ## 共用符號 | 符號 | 含義 | | ------------------------------- | --------------------------------------------------------------- | | `math:D` | 難度,範圍為 1(最簡單)至 10(最困難)。 | | `math:S` | FSRS-3 至 FSRS-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/zh-TW/guide/model/fsrs-7.md) 依據其實作快照另行說明:它增加快穩定性,使用混合曲線並回傳小數天間隔。上表中穩定性的 90% 定義和 `I(r, S)` 記法適用於 FSRS-3 至 FSRS-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、預設值與模型核心工廠組織為一個定義。它本身不會解析設定: 以下透過實作一個 [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 描述公開邊界,並在 scheduler 組合 schema 時使用。直接呼叫 `create` 時,具體實作必須先完成所需的解析和正規化,再回傳 `config`、`bounds`、`algorithm`、`step`、`nextInterval`、`forgettingCurve` 和 `forward`。該核心應保持確定性,且不包含排程策略。 :::warning bypass 僅用於已處理的設定 只有呼叫方已明確完成該模型的 migrate、clip、schema 驗證和範圍檢查後,才能使用 `FSRS6Model.create({ config, bypass: true })`。它會略過這些 create 階段的處理以及凍結。不要對來自使用者、儲存空間或網路的原始設定使用 `bypass`。 ::: ## 模型版本 - [FSRS-7](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-7.md) — 雙分量實作參考,34 個權重與數值求間隔。 - [FSRS-6](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-6.md) — 支援可訓練衰減與同日更新。 - [FSRS-5](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-5.md) — 使用固定冪函數遺忘曲線,並支援同日更新。 - [FSRS-4.5](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-4dot5.md) — 使用固定冪函數曲線,不包含同日公式。 - [FSRS-4](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-4.md) — 本專案中第一個使用冪函數曲線的版本。 - [FSRS-3](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-3.md) — 使用較早的指數遺忘曲線。 --- url: https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model/fsrs-7.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # FSRS-7 :::tip 遷移指南 只重建記憶狀態、不重新安排下次複習時間的遷移步驟見 [v6 與 FSRS-7 遷移指南](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/migration.md)。 ::: FSRS-7 使用 34 個權重和兩個記憶分量。記憶狀態包含 `stability`(慢分量)、`stabilityFast`(快分量)和 `difficulty`。 ## 記憶狀態與符號 沿用[共用符號](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/model.md#%E5%85%B1%E7%94%A8%E7%AC%A6%E8%99%9F),以 `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) ``` 實作將三個新狀態值四捨五入至小數點後八位,再裁剪到各自邊界。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/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # FSRS-6 FSRS-6 有 21 個可訓練權重,包括可訓練的遺忘曲線衰減和同日穩定性參數。 共用符號和 Model 契約請參閱[模型概覽](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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:F`,使 `math:R(S, S) = 0.9`: ```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` 會將結果四捨五入為整數天,並確保至少為一天。 ## 初始記憶 ```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/zh-TW/guide/model/fsrs-5.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # FSRS-5 FSRS-5 有 19 個可訓練權重。它保留 FSRS-4.5 的固定遺忘曲線,並增加依評分變化的同日穩定性更新。 共用符號和 Model 契約請參閱[模型概覽](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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/zh-TW/guide/model/fsrs-4dot5.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # FSRS-4.5 FSRS-4.5 有 17 個可訓練權重。它修改了 FSRS-4 的遺忘曲線,同時保留相同的難度和成功回憶更新結構。 共用符號和 Model 契約請參閱[模型概覽](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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/zh-TW/guide/model/fsrs-4.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # FSRS-4 FSRS-4 有 17 個可訓練權重,並使用冪函數遺忘曲線取代 FSRS-3 的指數曲線。 共用符號和 Model 契約請參閱[模型概覽](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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/zh-TW/guide/model/fsrs-3.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # FSRS-3 FSRS-3 有 13 個可訓練權重,並使用指數遺忘曲線。其穩定性更新使用剛計算出的新難度 `math:D'`。 共用符號和 Model 契約請參閱[模型概覽](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/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/zh-TW/guide/chrono/index.md --- > For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/llms-full.txt. # Chrono 概覽 Chrono 決定排程器如何表示時間、計算經過的天數,以及確定下次到期時間。模型使用以天為單位的間隔,Chrono 負責把這些間隔與應用程式中的時間值連接起來。 ## 選擇預設 從 `ts-fsrs` 匯入 `dateChrono`、`numericChrono`、`temporalInstantChrono` 和 `defineChrono`。 | 預設 | 時間值 | 天數運算 | | ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------ | | `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/zh-TW/guide/chrono/date.md)、[Numeric](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/chrono/numeric.md)、[Temporal Instant](https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/chrono/temporal-instant.md)。 ## 定義與執行時操作 `Chrono` 定義包含 `schema`、`normalize`、`defaultValue` 和 `create`。將定義傳給 `defineScheduler({ model, chrono })`;需要單獨進行時間運算時,呼叫定義的 `create({ config })`。排程器也透過 `scheduler.chrono` 提供設定完成的執行時物件。 Date 和 Temporal 需要向 `create({ config })` 傳入完整設定;只有 Numeric 等不需要設定的預設可以無參數呼叫 `create()`。透過排程器建立實例時,會驗證設定並補齊 schema 預設值。 | 操作 | 含義 | | ---------------------- | --------------------------------------------- | | `now()` | 呼叫端省略 `now` 時提供時間值。 | | `difference(from, to)` | 回傳模型使用的經過天數。 | | `add(from, days)` | 將最終的天數間隔轉換為到期時間。 | | `compare(left, right)` | 可選的精確時間比較,回傳負數、零或正數;Date 和 Temporal 預設均提供此操作。 | ## normalize 的作用 `normalize` 是一個 `ChronoTimeNormalizer` Standard Schema,輸出型別為 `ChronoNormalizedTime