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/model/index.md.
  • 简体中文
  • 模型概览

    FSRS 模型根据评分和经过时间计算新的记忆状态,通过遗忘曲线估算回忆概率,并根据记忆状态与目标保留率计算基础间隔。

    本节的 FSRS-3 至 FSRS-6 术语与公式依据 awesome-fsrs Wiki 校对,主要参考其中的 The Algorithm。TypeScript API 与框架职责边界以 ts-fsrs 本身为准。

    模型边界

    模型只负责记忆状态更新、遗忘曲线计算和基础间隔计算。scheduler 负责组织复习流程,chrono 负责日期与经过时间,middleware 负责学习步骤、模糊处理、最大间隔限制和队列状态策略。

    For your Agent
    实现或解释 SRS 模型

    复制这段提示词,让 AI Agent 遵守 Model 契约,避免把调度策略混入模型。

    公共符号

    符号含义
    DD难度,范围为 1(最简单)至 10(最困难)。
    SSFSRS-3 至 FSRS-6 的稳定性,单位为天;当 t=St = S 时回忆概率为 90%。
    RR经过 tt 天后能够回忆的估计概率。
    GG评分:1 为 Again、2 为 Hard、3 为 Good、4 为 Easy。
    tt距离上次复习经过的天数。
    rrnextInterval 使用的目标保留率。
    I(r,S)I(r, S)根据稳定性 SS 和目标保留率 rr 计算的基础间隔,单位为天,尚未应用 middleware 策略。
    Smin⁡S_{\min}、Smax⁡S_{\max}模型允许的稳定性下限和上限。
    Dmin⁡D_{\min}、Dmax⁡D_{\max}模型允许的难度下限和上限。
    wiw_i训练得到的模型权重。

    FSRS-7 根据其实现快照单独说明:它增加快稳定性,使用混合曲线并返回小数天间隔。上表中稳定性的 90% 定义和 I(r, S) 记法适用于 FSRS-3 至 FSRS-6。

    使用版本化模型

    DefaultScheduler 默认使用 FSRS-7,可通过 version 选择任一支持的 FSRS 版本。也可以直接创建并调用各版本模型;下面以 FSRS-6 为例:

    import { 
    type Rating = 0 | 1 | 2 | 3 | 4
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    } from 'ts-fsrs'
    import {
    const FSRS6_DEFAULT_WEIGHTS: number[]
    FSRS6_DEFAULT_WEIGHTS
    ,
    const FSRS6Model: Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>
    FSRS6Model
    } from 'ts-fsrs/models/fsrs-6'
    const
    const model: ModelCore<{
        readonly config: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        };
        readonly memoryState: {
            stability: number;
            difficulty: number;
        };
        readonly algorithm: FSRS6Algorithm;
    }>
    model
    =
    const FSRS6Model: Model<{
        readonly name: "fsrs-6";
        readonly config: SRSSchema<{
            input: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
            output: {
                readonly weights: number[];
                readonly enableShortTerm: boolean;
                readonly numRelearningSteps: number;
            };
        }>;
        readonly memoryState: SRSSchema<{
            input: {
                stability: number;
                difficulty: number;
            };
            output: {
                stability: number;
                difficulty: number;
            };
        }>;
        readonly algorithm: FSRS6Algorithm;
    }>
    FSRS6Model
    .
    Model<{ readonly name: "fsrs-6"; readonly config: SRSSchema<{ input: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; output: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; }>; readonly memoryState: SRSSchema<{ input: { stability: number; difficulty: number; }; output: { stability: number; difficulty: number; }; }>; readonly algorithm: FSRS6Algorithm; }>.create: (ctx: {
        readonly config: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        } | {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        };
        readonly migrate?: boolean;
        readonly clip?: boolean;
        readonly check?: boolean;
        readonly bypass?: boolean;
    }) => ModelCore<{
        readonly config: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        };
        readonly memoryState: {
            stability: number;
            difficulty: number;
        };
        readonly algorithm: FSRS6Algorithm;
    }>
    create
    ({
    config: { weights:
    const FSRS6_DEFAULT_WEIGHTS: number[]
    FSRS6_DEFAULT_WEIGHTS
    ,
    enableShortTerm: true, numRelearningSteps: 1, // Constrains weights; middleware runs the steps. }, }) const memoryState =
    const model: ModelCore<{
        readonly config: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        };
        readonly memoryState: {
            stability: number;
            difficulty: number;
        };
        readonly algorithm: FSRS6Algorithm;
    }>
    model
    .
    ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.step: (input: ModelStepInput<{
        stability: number;
        difficulty: number;
    }>) => {
        stability: number;
        difficulty: number;
    }
    step
    ({
    ModelStepInput<{ stability: number; difficulty: number; }>.memoryState: Readonly<{
        stability: number;
        difficulty: number;
    }> | null
    memoryState
    : null,
    ModelStepInput<{ stability: number; difficulty: number; }>.rating: 1 | 2 | 3 | 4
    rating
    :
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Good,
    ModelStepInput<{ stability: number; difficulty: number; }>.elapsedDays: number
    elapsedDays
    : 0,
    }) const desiredRetention = 0.9 const interval =
    const model: ModelCore<{
        readonly config: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        };
        readonly memoryState: {
            stability: number;
            difficulty: number;
        };
        readonly algorithm: FSRS6Algorithm;
    }>
    model
    .
    ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.nextInterval: (memoryState: Readonly<{
        stability: number;
        difficulty: number;
    }>, desiredRetention: number) => number
    nextInterval
    (memoryState, desiredRetention)
    const retrievabilityAtInterval =
    const model: ModelCore<{
        readonly config: {
            readonly weights: number[];
            readonly enableShortTerm: boolean;
            readonly numRelearningSteps: number;
        };
        readonly memoryState: {
            stability: number;
            difficulty: number;
        };
        readonly algorithm: FSRS6Algorithm;
    }>
    model
    .
    ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.forgettingCurve: (memoryState: Readonly<{
        stability: number;
        difficulty: number;
    }>, elapsedDays: number) => number
    forgettingCurve
    (memoryState, interval)
    console.log( JSON.stringify( { memoryState, desiredRetention, interval, retrievabilityAtInterval, }, null, 2 ) )

    示例先根据 90% 的目标保留率计算复习间隔,再通过 forgettingCurve 估算到达该间隔时的回忆概率。由于 FSRS-6 的 nextInterval 返回整数天,结果可能与 90% 略有差异。

    在 FSRS-6 配置中,enableShortTerm 控制模型的同日稳定性更新。numRelearningSteps 只参与权重裁剪与检查;重新学习步骤仍由 middleware 调度。

    定义模型

    defineModel 会保留具体的配置、记忆状态和算法类型,并把 schema、默认值与模型核心工厂组织为一个定义。它本身不会解析配置:

    下面通过实现一个 SM-2 变体,演示如何定义模型。

    import {
      
    function clamp(value: number, min: number, max: number): number
    clamp
    ,
    function defineModel<const Name extends string | symbol, const ConfigSchema extends AnySchema, const MemoryStateSchema extends AnyObjectSchema, const Algorithm>(definition: {
        readonly name: Name;
        readonly schema: ModelSchema<{
            readonly config: ConfigSchema;
            readonly memoryState: MemoryStateSchema;
        }>;
        readonly defaultValue: {
            readonly memoryState: (ctx: {
                readonly config: SchemaOutput<ConfigSchema>;
            }) => SchemaInput<MemoryStateSchema>;
        };
        readonly create: ModelCreate<{
            readonly config: ConfigSchema;
            readonly memoryState: MemoryStateSchema;
        }, Algorithm>;
    }): Model<{
        readonly name: Name;
        readonly config: ConfigSchema;
        readonly memoryState: MemoryStateSchema;
        readonly algorithm: Algorithm;
    }>
    defineModel
    ,
    type
    interface ModelForwardInput<MemoryState extends object>
    ModelForwardInput
    ,
    type
    interface ModelStepInput<MemoryState extends object>
    ModelStepInput
    ,
    type Rating = 0 | 1 | 2 | 3 | 4
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    ,
    } from 'ts-fsrs' import { z } from 'zod' const configSchema = z.object({ weights: z.tuple([ z.number().min(0.01).max(100), z.number().min(0.01).max(100), z.number().min(1.3).max(10), z.number().nonnegative(), z.number().min(5), z.number(), ]), }) const memoryStateSchema = z.object({ interval: z.number().min(0).max(36500), easeFactor: z.number().min(1.3).max(10), repetitions: z.number().int().nonnegative(), }) type
    type MemoryState = {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }
    MemoryState
    = z.output<typeof memoryStateSchema>
    const
    const SM2Model: Model<{
        readonly name: "sm2";
        readonly config: z.ZodObject<{
            weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
        }, z.core.$strip>;
        readonly memoryState: z.ZodObject<{
            interval: z.ZodNumber;
            easeFactor: z.ZodNumber;
            repetitions: z.ZodNumber;
        }, z.core.$strip>;
        readonly algorithm: null;
    }>
    SM2Model
    =
    defineModel<"sm2", z.ZodObject<{
        weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
    }, z.core.$strip>, z.ZodObject<{
        interval: z.ZodNumber;
        easeFactor: z.ZodNumber;
        repetitions: z.ZodNumber;
    }, z.core.$strip>, null>(definition: {
        readonly name: "sm2";
        readonly schema: ModelSchema<{
            readonly config: z.ZodObject<{
                weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
            }, z.core.$strip>;
            readonly memoryState: z.ZodObject<{
                ...;
            }, z.core.$strip>;
        }>;
        readonly defaultValue: {
            ...;
        };
        readonly create: ModelCreate<...>;
    }): Model<...>
    defineModel
    ({
    name: 'sm2',
    schema: ModelSchema<{
        readonly config: z.ZodObject<{
            weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
        }, z.core.$strip>;
        readonly memoryState: z.ZodObject<{
            interval: z.ZodNumber;
            easeFactor: z.ZodNumber;
            repetitions: z.ZodNumber;
        }, z.core.$strip>;
    }>
    schema
    : {
    ModelSchema<{ readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; }>.config: z.ZodObject<{
        weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
    }, z.core.$strip>
    config
    : configSchema,
    ModelSchema<{ readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; }>.memoryState: z.ZodObject<{
        interval: z.ZodNumber;
        easeFactor: z.ZodNumber;
        repetitions: z.ZodNumber;
    }, z.core.$strip>
    memoryState
    : memoryStateSchema },
    defaultValue: { memoryState: ({ config }) => ({ interval: 0, easeFactor: config.weights[2], repetitions: 0, }), },
    create: ModelCreate<{
        readonly config: z.ZodObject<{
            weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
        }, z.core.$strip>;
        readonly memoryState: z.ZodObject<{
            interval: z.ZodNumber;
            easeFactor: z.ZodNumber;
            repetitions: z.ZodNumber;
        }, z.core.$strip>;
    }, null>
    create
    ({ config }) {
    const parsed = configSchema.
    ZodType<any, any, $ZodObjectInternals<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>>.parse(data: unknown, params?: z.core.ParseContext<z.core.$ZodIssue>): {
        weights: [number, number, number, number, number, number];
    }
    parse
    (config)
    const w = parsed.weights const
    const step: ({ memoryState, rating }: ModelStepInput<MemoryState>) => {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }
    step
    = ({ memoryState, rating }:
    interface ModelStepInput<MemoryState extends object>
    ModelStepInput
    <
    type MemoryState = {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }
    MemoryState
    >) => {
    const previous = memoryState ?? { interval: 0, easeFactor: parsed.weights[2], repetitions: 0, } // Again / Hard / Good / Easy -> SM-2 quality 2 / 3 / 4 / 5. const quality = rating + 1 const repetitions = rating >
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Again ? previous.repetitions + 1 : 0
    const interval =
    function clamp(value: number, min: number, max: number): number
    clamp
    (
    repetitions <= 1 ? w[0] : repetitions === 2 ? w[1] : previous.interval * previous.easeFactor, 0.01, 36500 ) const easeFactor =
    function clamp(value: number, min: number, max: number): number
    clamp
    (
    previous.easeFactor - w[3] * (quality - w[4]) ** 2 + w[5], 1.3, 10 ) return { interval, easeFactor, repetitions } } const
    const forward: ({ history, initialState, }: ModelForwardInput<MemoryState>) => {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }[]
    forward
    = ({
    history: readonly ModelReview[]
    history
    ,
    initialState, }:
    interface ModelForwardInput<MemoryState extends object>
    ModelForwardInput
    <
    type MemoryState = {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }
    MemoryState
    >) => {
    const
    const states: {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }[]
    states
    :
    type MemoryState = {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }
    MemoryState
    [] = []
    let memoryState = initialState ?? null for (const
    const review: ModelReview
    review
    of
    history: readonly ModelReview[]
    history
    ) {
    memoryState =
    const step: ({ memoryState, rating }: ModelStepInput<MemoryState>) => {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }
    step
    ({
    ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.memoryState: Readonly<{
        interval: number;
        easeFactor: number;
        repetitions: number;
    }> | null
    memoryState
    ,
    ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.rating: 1 | 2 | 3 | 4
    rating
    :
    const review: ModelReview
    review
    .
    ModelReview.rating: 1 | 2 | 3 | 4
    rating
    ,
    ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.elapsedDays: number
    elapsedDays
    :
    const review: ModelReview
    review
    .
    ModelReview.deltaT: number
    deltaT
    ,
    })
    const states: {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }[]
    states
    .push(memoryState)
    } return
    const states: {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }[]
    states
    } return {
    ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.config: {
        weights: [number, number, number, number, number, number];
    }
    config
    : parsed,
    ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.bounds: ModelBoundsShape<{
        interval: number;
        easeFactor: number;
        repetitions: number;
    }>
    bounds
    : {
    iMin: 0.01, iMax: 36500, eMin: 1.3, eMax: 10, rMin: 0, rMax: Infinity, },
    ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.algorithm: null
    algorithm
    : null,
    ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.step: (input: ModelStepInput<{
        interval: number;
        easeFactor: number;
        repetitions: number;
    }>) => {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }
    step
    ,
    // Invert the benchmark curve to satisfy the Model interval contract.
    ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.nextInterval: (memoryState: Readonly<{
        interval: number;
        easeFactor: number;
        repetitions: number;
    }>, desiredRetention: number) => number
    nextInterval
    : (state: Readonly<
    type MemoryState = {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }
    MemoryState
    >, desiredRetention: number) =>
    Math.max( 1, Math.round( (state.interval * Math.log(desiredRetention)) / Math.log(0.9) ) ),
    ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forgettingCurve: (memoryState: Readonly<{
        interval: number;
        easeFactor: number;
        repetitions: number;
    }>, elapsedDays: number) => number
    forgettingCurve
    : (state: Readonly<
    type MemoryState = {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }
    MemoryState
    >, elapsedDays: number) =>
    0.9 ** (elapsedDays / state.interval),
    ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forward: (input: ModelForwardInput<{
        interval: number;
        easeFactor: number;
        repetitions: number;
    }>) => readonly {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }[]
    forward
    ,
    } }, }) const
    const model: ModelCore<{
        readonly config: {
            weights: [number, number, number, number, number, number];
        };
        readonly memoryState: {
            interval: number;
            easeFactor: number;
            repetitions: number;
        };
        readonly algorithm: null;
    }>
    model
    =
    const SM2Model: Model<{
        readonly name: "sm2";
        readonly config: z.ZodObject<{
            weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
        }, z.core.$strip>;
        readonly memoryState: z.ZodObject<{
            interval: z.ZodNumber;
            easeFactor: z.ZodNumber;
            repetitions: z.ZodNumber;
        }, z.core.$strip>;
        readonly algorithm: null;
    }>
    SM2Model
    .
    Model<{ readonly name: "sm2"; readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; readonly algorithm: null; }>.create: (ctx: {
        readonly config: {
            weights: [number, number, number, number, number, number];
        } | {
            weights: [number, number, number, number, number, number];
        };
        readonly migrate?: boolean;
        readonly clip?: boolean;
        readonly check?: boolean;
        readonly bypass?: boolean;
    }) => ModelCore<{
        readonly config: {
            weights: [number, number, number, number, number, number];
        };
        readonly memoryState: {
            interval: number;
            easeFactor: number;
            repetitions: number;
        };
        readonly algorithm: null;
    }>
    create
    ({
    config: { weights: [1, 6, 2.5, 0.02, 7, 0.18] }, }) const
    const states: readonly {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }[]
    states
    =
    const model: ModelCore<{
        readonly config: {
            weights: [number, number, number, number, number, number];
        };
        readonly memoryState: {
            interval: number;
            easeFactor: number;
            repetitions: number;
        };
        readonly algorithm: null;
    }>
    model
    .
    ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forward: (input: ModelForwardInput<{
        interval: number;
        easeFactor: number;
        repetitions: number;
    }>) => readonly {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }[]
    forward
    ({
    ModelForwardInput<{ interval: number; easeFactor: number; repetitions: number; }>.history: readonly ModelReview[]
    history
    : [
    {
    ModelReview.rating: 1 | 2 | 3 | 4
    rating
    :
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Good,
    ModelReview.deltaT: number
    deltaT
    : 0 },
    {
    ModelReview.rating: 1 | 2 | 3 | 4
    rating
    :
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Good,
    ModelReview.deltaT: number
    deltaT
    : 1 },
    {
    ModelReview.rating: 1 | 2 | 3 | 4
    rating
    :
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Good,
    ModelReview.deltaT: number
    deltaT
    : 6 },
    {
    ModelReview.rating: 1 | 2 | 3 | 4
    rating
    :
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Again,
    ModelReview.deltaT: number
    deltaT
    : 15 },
    {
    ModelReview.rating: 1 | 2 | 3 | 4
    rating
    :
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Good,
    ModelReview.deltaT: number
    deltaT
    : 1 },
    {
    ModelReview.rating: 1 | 2 | 3 | 4
    rating
    :
    const Rating: Readonly<{
        readonly Manual: 0;
        readonly Again: 1;
        readonly Hard: 2;
        readonly Good: 3;
        readonly Easy: 4;
    }>
    Rating
    .Good,
    ModelReview.deltaT: number
    deltaT
    : 1 },
    ], }) console.log(JSON.stringify(
    const states: readonly {
        interval: number;
        easeFactor: number;
        repetitions: number;
    }[]
    states
    , null, 2)) // Intervals: 1, 6, 15, 1, 1, 6 days.

    Schema 描述公共边界,并在 scheduler 组合 schema 时使用。直接调用 create 时,具体实现必须先完成所需的解析和规范化,再返回 config、bounds、algorithm、step、nextInterval、forgettingCurve 和 forward。该核心应保持确定性,且不包含调度策略。

    bypass 仅用于已处理的配置

    只有调用方已经明确完成该模型的 migrate、clip、schema 校验和范围检查后,才能使用 FSRS6Model.create({ config, bypass: true })。它会跳过这些 create 阶段的处理以及冻结。不要对来自用户、存储或网络的原始配置使用 bypass。

    模型版本

    • FSRS-7 — 双分量实现参考,34 个权重与数值求间隔。
    • FSRS-6 — 支持可训练衰减与同日更新。
    • FSRS-5 — 使用固定幂函数遗忘曲线,并支持同日更新。
    • FSRS-4.5 — 使用固定幂函数曲线,不包含同日公式。
    • FSRS-4 — 本项目中首个使用幂函数曲线的版本。
    • FSRS-3 — 使用较早的指数遗忘曲线。