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, and this page is available as Markdown at https://open-spaced-repetition.github.io/ts-fsrs/zh-TW/guide/state-transitions.md.
  • 繁體中文
  • 狀態轉換

    State 表示卡片目前所處的 FSRS 學習階段。透過 scheduler.newCard() 建立的卡片初始狀態為 New。每次複習時,DefaultScheduler 會結合 FSRS 模型結果與學習步驟策略,決定下一個狀態和到期時間。

    DefaultScheduler 狀態轉換流程圖

    圖表按顯式步驟和間隔中介軟體處理後的最終排程間隔分類。評分會影響間隔,但不能單獨決定下一個狀態。

    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 / RelearningReview
    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
    NewLearningReview
    LearningLearningReview
    ReviewRelearningReview
    RelearningRelearningReview

    有適用的顯式步驟時,排程器保留該步驟設定的時長,除非 allowModelOverride 選用了較長的模型間隔。達到 graduatingInterval 的步驟仍會讓卡片進入 Review。

    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 模型和以下設定:

    const scheduler = await DefaultScheduler({
      learningSteps: ['1m', '10m'],
      relearningSteps: ['10m'],
      enableFuzz: false,
      allowModelOverride: false,
    })

    graduatingInterval 使用預設值 1 天。卡片初始狀態為 New,learningStep: 0。learningStep 是從零開始的步驟索引,不是複習次數;學習和重新學習都使用同一個欄位記錄步驟進度。

    設定 allowModelOverride: false 時,範例產生以下轉換:

    目前狀態目前 learningStep評分轉換後狀態轉換後 learningStep間隔
    New0AgainLearning01 分鐘
    Learning0GoodLearning110 分鐘
    Learning1HardLearning15.5 分鐘
    Learning1AgainLearning01 分鐘
    Learning1GoodReview01 天
    Review0AgainRelearning010 分鐘
    Relearning0GoodReview01 天

    learningStep: 1 的 Hard、Again 和 Good 行是對同一張卡片的不同評分分支,並非連續複習。重新學習分支從另一張透過 Easy 進入 Review 的卡片開始。

    關閉 allowModelOverride 會保留設定的步驟間隔。開啟後,較長的模型間隔可以取代顯式步驟。例如,假設模型間隔為 30 分鐘,原本 10 分鐘的 Good 步驟就會延長到 30 分鐘;如果模型間隔較短,則該步驟維持不變。即使設定 allowModelOverride: false,排程器在步驟完成後或沒有適用步驟時仍會使用模型間隔。

    下方完整範例使用公開的 DefaultSchedulerCard、Grade 和 State 型別,輸出間隔以分鐘顯示,保留兩位小數。

    dueAt 不是 now

    dueAt 表示卡片應從何時起複習,now 是實際複習時間,通常應滿足 now >= dueAt。為了讓輸出可以重複驗證且不混淆兩者,本例會在每張卡片的 dueAt 後 30 秒進行複習。

    import {
      
    function DefaultScheduler<Version extends DefaultSchedulerVersion = "FSRS-7">(options?: DefaultSchedulerOptions<Version>): Promise<DefaultScheduler<Version>>
    type DefaultScheduler<Version extends DefaultSchedulerVersion = "FSRS-7"> = (Version extends "FSRS-7" ? Omit<SchedulerCore<SchedulerCoreEnv<SchedulerEnvFor<FSRS7DefaultSchedulerModel, Chrono<{
        readonly time: SRSSchema<{
            input: Date;
            output: Date;
        }>;
        readonly config: SRSSchema<{
            input: {
                readonly fractionalDays?: boolean | undefined;
            };
            output: {
                readonly fractionalDays: boolean;
            };
        }>;
        readonly fields: {
            readonly card: SRSSchema<{
                input: {
                    dueAt: Date;
                    lastReviewAt?: (Date | null) | undefined;
                };
                output: {
                    dueAt: Date;
                    lastReviewAt: Date | null;
                };
            }>;
            readonly revlog: SRSSchema<{
                input: {
                    dueAt: Date;
                    lastReviewAt?: (Date | null) | undefined;
                    reviewTime: Date;
                };
                output: {
                    dueAt: Date;
                    lastReviewAt: Date | null;
                    reviewTime: Date;
                };
            }>;
        };
    }>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>, "definition"> : Omit<...> & {
        ...;
    }) & {
        ...;
    }
    DefaultScheduler
    ,
    type
    type DefaultSchedulerCard<Version extends DefaultSchedulerVersion = "FSRS-7"> = Version extends "FSRS-7" ? {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    } : {
        cardId: string | number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    DefaultSchedulerCard
    ,
    type
    type Grade = 1 | 2 | 3 | 4
    Grade
    ,
    type Rating = 0 | 1 | 2 | 3 | 4
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    ,
    type State = 0 | 1 | 2 | 3
    const State: Readonly<{
        readonly New: 0;
        readonly Learning: 1;
        readonly Review: 2;
        readonly Relearning: 3;
    }>
    State
    ,
    } from 'ts-fsrs' const
    const scheduler: DefaultScheduler<"FSRS-7">
    scheduler
    = await
    DefaultScheduler<"FSRS-7">(options?: DefaultSchedulerOptions<"FSRS-7"> | undefined): Promise<DefaultScheduler<"FSRS-7">>
    DefaultScheduler
    ({
    DefaultSchedulerOptions<"FSRS-7">.learningSteps?: readonly (`${number}d` | `${number}m` | `${number}h`)[] | undefined

    Defaults to ['1m', '10m'].

    learningSteps
    : ['1m', '10m'],
    DefaultSchedulerOptions<"FSRS-7">.relearningSteps?: readonly (`${number}d` | `${number}m` | `${number}h`)[] | undefined

    Defaults to ['10m'].

    relearningSteps
    : ['10m'],
    DefaultSchedulerOptions<Version extends DefaultSchedulerVersion = DefaultSchedulerVersion>.enableFuzz?: boolean | undefined
    enableFuzz
    : false,
    DefaultSchedulerOptions<Version extends DefaultSchedulerVersion = DefaultSchedulerVersion>.allowModelOverride?: boolean | undefined

    Let longer model intervals override learning steps; defaults to true for FSRS-7 only.

    allowModelOverride
    : false,
    }) const stateNames: Record<
    type State = 0 | 1 | 2 | 3
    State
    , string> = {
    [
    const State: Readonly<{
        readonly New: 0;
        readonly Learning: 1;
        readonly Review: 2;
        readonly Relearning: 3;
    }>
    State
    .New]: 'New',
    [
    const State: Readonly<{
        readonly New: 0;
        readonly Learning: 1;
        readonly Review: 2;
        readonly Relearning: 3;
    }>
    State
    .Learning]: 'Learning',
    [
    const State: Readonly<{
        readonly New: 0;
        readonly Learning: 1;
        readonly Review: 2;
        readonly Relearning: 3;
    }>
    State
    .Review]: 'Review',
    [
    const State: Readonly<{
        readonly New: 0;
        readonly Learning: 1;
        readonly Review: 2;
        readonly Relearning: 3;
    }>
    State
    .Relearning]: 'Relearning',
    } const
    const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewAfterDue
    = (
    card: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    card
    :
    type DefaultSchedulerCard<Version extends DefaultSchedulerVersion = "FSRS-7"> = Version extends "FSRS-7" ? {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    } : {
        cardId: string | number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    DefaultSchedulerCard
    , grade:
    type Grade = 1 | 2 | 3 | 4
    Grade
    ) => {
    const reviewedAt = new Date(
    card: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    card
    .dueAt.getTime() + 30_000)
    return
    const scheduler: DefaultScheduler<"FSRS-7">
    scheduler
    .
    review: (input: {
        readonly card: Readonly<Omit<Readonly<{
            stabilityFast: number;
            stability: number;
            difficulty: number;
        } & {
            dueAt: Date;
            lastReviewAt: Date | null;
        } & {
            readonly cardId: string | number;
            readonly reps: number;
            readonly lapses: number;
            readonly scheduledDays: number;
            readonly learningStep?: number | undefined;
        } & SchedulerCoreFields$1<"review" | "new" | "learning">>, keyof SchedulerCoreFields$1<"review" | "new" | "learning">> & SchedulerCoreFields$1<"review" | "new" | "learning">>;
        readonly grade: Grade;
        readonly now?: Date | undefined;
    }) => {
        ...;
    }
    review
    ({ card, grade, now: reviewedAt }).
    card: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    card
    } const
    const newCard: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    newCard
    =
    const scheduler: DefaultScheduler<"FSRS-7">
    scheduler
    .
    newCard: (options?: ({
        readonly cardId?: string | number | undefined;
    } & {
        readonly now?: Date | undefined;
    }) | undefined) => {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    newCard
    ({
    cardId: 'state-transitions', now: new Date('2026-01-01T00:00:00.000Z'), }) const
    const learning: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    learning
    =
    const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewAfterDue
    (
    const newCard: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    newCard
    ,
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Again)
    const
    const nextLearningStep: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    nextLearningStep
    =
    const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewAfterDue
    (
    const learning: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    learning
    ,
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Good)
    const
    const afterLearningSteps: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    afterLearningSteps
    =
    const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewAfterDue
    (
    const nextLearningStep: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    nextLearningStep
    ,
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Good)
    const
    const repeated: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    repeated
    =
    const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewAfterDue
    (
    const nextLearningStep: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    nextLearningStep
    ,
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Hard)
    const
    const restarted: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    restarted
    =
    const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewAfterDue
    (
    const nextLearningStep: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    nextLearningStep
    ,
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Again)
    const
    const reviewed: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewed
    =
    const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewAfterDue
    (
    const newCard: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    newCard
    ,
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Easy)
    const
    const relearning: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    relearning
    =
    const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewAfterDue
    (
    const reviewed: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewed
    ,
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Again)
    const
    const afterRelearningSteps: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    afterRelearningSteps
    =
    const reviewAfterDue: (card: DefaultSchedulerCard, grade: Grade) => {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewAfterDue
    (
    const relearning: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    relearning
    ,
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Good)
    for (const [
    const from: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    } | {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    from
    , rating,
    const to: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    to
    ] of [
    [
    const newCard: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    newCard
    , 'Again',
    const learning: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    learning
    ],
    [
    const learning: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    learning
    , 'Good',
    const nextLearningStep: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    nextLearningStep
    ],
    [
    const nextLearningStep: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    nextLearningStep
    , 'Hard',
    const repeated: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    repeated
    ],
    [
    const nextLearningStep: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    nextLearningStep
    , 'Again',
    const restarted: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    restarted
    ],
    [
    const nextLearningStep: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    nextLearningStep
    , 'Good',
    const afterLearningSteps: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    afterLearningSteps
    ],
    [
    const newCard: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    newCard
    , 'Easy',
    const reviewed: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewed
    ],
    [
    const reviewed: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    reviewed
    , 'Again',
    const relearning: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    relearning
    ],
    [
    const relearning: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    relearning
    , 'Good',
    const afterRelearningSteps: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    afterRelearningSteps
    ],
    ] as
    type const = readonly [readonly [{
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }, "Again", {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }], readonly [{
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }, "Good", {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }], ... 5 more ..., readonly [...]]
    const
    ) {
    console.log( `${stateNames[
    const from: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    } | {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    from
    .state]} --${rating}--> ${stateNames[
    const to: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    to
    .state]}; ` +
    `learningStep: ${
    const from: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    } | {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    from
    .learningStep} -> ${
    const to: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    to
    .learningStep}; ` +
    `interval: ${(
    const to: {
        cardId: string | number;
        stabilityFast: number;
        stability: number;
        difficulty: number;
        dueAt: Date;
        lastReviewAt: Date | null;
        reps: number;
        lapses: number;
        scheduledDays: number;
        learningStep: number;
        state: State;
        scheduleStatus: "review" | "new" | "learning";
    }
    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 篩選排程佇列。