For AI agents: the complete documentation index is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-CN/llms.txt, the full documentation bundle is available at https://open-spaced-repetition.github.io/ts-fsrs/zh-CN/llms-full.txt, and this page is available as Markdown at https://open-spaced-repetition.github.io/ts-fsrs/zh-CN/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 筛选调度队列。