#遷移至 v6 與 FSRS-7
套件版本與模型版本是兩回事:ts-fsrs v5 已使用 FSRS-6。先將應用程式與儲存紀錄遷移至 v6 API,繼續使用 FSRS-6;再單獨遷移模型。本文對應本站的 v6 API,安裝時應鎖定與本文介面一致的 v6 版本。
使用 defineScheduler 與明確的模型入口。遷移後不得使用 fsrs()、FSRS、createEmptyCard、generatorParameters、舊排程器類別與策略,或從根入口匯出的舊 Card、CardInput、ReviewLog、FSRSParameters 等 legacy 型別。部分符號仍為相容性而匯出,不代表它們是 v6 的整合方式。下文舊名稱僅供靜態對照;可執行範例不使用 legacy API 或型別。
複製給具有儲存庫存取權限的 Agent:先確認遷移目標,再整理變更 TODO List;使用者確認清單後才實作與驗證。
#1. 遷移至 v6 API,保留 FSRS-6
#定義排程策略
defineScheduler({ model, chrono }) 建立可重用的定義;.use(...) 組合中介軟體並傳回新定義;.create({ config }) 驗證設定並建立獨立排程器實例。在程式層級保留定義,依使用者或牌組設定建立實例。
單純的定義不包含全部 v5 排程策略。以下沿用目前標準預設的中介軟體順序:目標保留率、模糊、統計、已排程天數、學習步驟、最大間隔、單調間隔。請保留實際設定與 FSRS-6 權重。使用 dateChrono 時,fractionalDays: false 以 UTC 日曆日計算間隔;僅遷移 API 時,不應未經驗證就同時改變時間策略。
import { const dateChrono: 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;
};
}>;
};
}>
dateChrono , function defineScheduler<const M extends AnyModel, const C extends AnyChrono>(definition: {
readonly model: M;
readonly chrono: C;
}): CustomScheduler<SchedulerNameOf<M>, SchedulerEnvFor<M, C, readonly []>, M, C>
defineScheduler , 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'
import {
const schedulerDesiredRetentionMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly desiredRetention: number | Record<Grade, number>;
};
output: {
readonly desiredRetention: Record<Grade, number>;
};
}>;
}>
schedulerDesiredRetentionMiddleware ,
const schedulerFuzzingMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>
schedulerFuzzingMiddleware ,
const schedulerLearningStepsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval?: number;
readonly allowModelOverride?: boolean;
};
output: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval: number;
readonly allowModelOverride: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
}>
schedulerLearningStepsMiddleware ,
const schedulerMaximumIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after learning steps so explicit steps can keep their exact delay.
schedulerMaximumIntervalMiddleware ,
const schedulerMonotonicIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after fuzzing and learning steps, when present.
schedulerMonotonicIntervalMiddleware ,
const schedulerScheduledDaysMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly fractionalDays?: boolean | undefined;
};
output: {
readonly fractionalDays: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
}>
Register before interval middleware so it records their final value on unwind.
schedulerScheduledDaysMiddleware ,
const schedulerStatsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly clearStatsOnForget?: boolean | undefined;
};
output: {
readonly clearStatsOnForget: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly reps: number;
readonly lapses: number;
};
output: {
readonly reps: number;
readonly lapses: number;
};
}>;
}>
schedulerStatsMiddleware ,
} from 'ts-fsrs/middlewares'
import { 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 , const migrateFSRS6Parameters: (parameters?: readonly number[]) => number[] migrateFSRS6Parameters } from 'ts-fsrs/models/fsrs-6'
// Keep the definition at application scope; create an instance per config.
export const const definition: CustomScheduler<"fsrs-6", ExtendSchedulerEnv<SchedulerEnvFor<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;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition = defineScheduler<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;
}>, Chrono<...>>(definition: {
...;
}): CustomScheduler<...>
defineScheduler ({
model: 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;
}>
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 ,
chrono: 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;
};
}>;
};
}>
chrono : const dateChrono: 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;
};
}>;
};
}>
dateChrono ,
}).CustomScheduler<"fsrs-6", SchedulerEnvFor<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; }>, Chrono<...>, readonly []>, Model<...>, Chrono<...>>.use: <readonly [Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly desiredRetention: number | Record<Grade, number>;
};
output: {
readonly desiredRetention: Record<Grade, number>;
};
}>;
}>, Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>, ... 4 more ..., Middleware<...>]>(middlewares_0: Middleware<...>, middlewares_1: Middleware<...>, middlewares_2: Middleware<...>, middlewares_3: Middleware<...>, middlewares_4: Middleware<...>, middlewares_5: Middleware<...>, middlewares_6: Middleware<...>) => CustomScheduler<...>
Returns a new scheduler definition extended with the supplied middleware.
The current scheduler and any cores created from it remain unchanged.
use (
const schedulerDesiredRetentionMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly desiredRetention: number | Record<Grade, number>;
};
output: {
readonly desiredRetention: Record<Grade, number>;
};
}>;
}>
schedulerDesiredRetentionMiddleware ,
const schedulerFuzzingMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>
schedulerFuzzingMiddleware ,
const schedulerStatsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly clearStatsOnForget?: boolean | undefined;
};
output: {
readonly clearStatsOnForget: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly reps: number;
readonly lapses: number;
};
output: {
readonly reps: number;
readonly lapses: number;
};
}>;
}>
schedulerStatsMiddleware ,
const schedulerScheduledDaysMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly fractionalDays?: boolean | undefined;
};
output: {
readonly fractionalDays: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
}>
Register before interval middleware so it records their final value on unwind.
schedulerScheduledDaysMiddleware ,
const schedulerLearningStepsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval?: number;
readonly allowModelOverride?: boolean;
};
output: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval: number;
readonly allowModelOverride: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
}>
schedulerLearningStepsMiddleware ,
const schedulerMaximumIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after learning steps so explicit steps can keep their exact delay.
schedulerMaximumIntervalMiddleware ,
const schedulerMonotonicIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after fuzzing and learning steps, when present.
schedulerMonotonicIntervalMiddleware
)
export const const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<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;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler = const definition: CustomScheduler<"fsrs-6", ExtendSchedulerEnv<SchedulerEnvFor<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;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition .CustomScheduler<"fsrs-6", ExtendSchedulerEnv<SchedulerEnvFor<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; }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>.create: (ctx: {
readonly config: Omit<SchedulerConfigInput<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;
}>, Chrono<...>, readonly []>, "fractionalDays" | ... 8 more ... | "clearStatsOnForget"> & {
...;
};
}) => SchedulerCore<...>
create ({
config: Omit<SchedulerConfigInput<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;
}>, Chrono<...>, readonly []>, "fractionalDays" | ... 8 more ... | "clearStatsOnForget"> & {
...;
}
config : {
weights: function migrateFSRS6Parameters(parameters?: readonly number[]): number[] migrateFSRS6Parameters (), // Pass the user's existing FSRS-6 weights here.
fractionalDays: false,
enableShortTerm: true,
numRelearningSteps: 1, // Keep equal to relearningSteps.length.
desiredRetention: 0.9,
learningSteps: ['1m', '10m'],
relearningSteps: ['10m'],
enableFuzz: false,
maximumInterval: 36500,
clearStatsOnForget: false, // Match v5 forget's default counter behavior.
},
})
export type type Card = {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card = ReturnType<typeof const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<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;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler .SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<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; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>.newCard: (options?: (Omit<{
readonly now?: Date | undefined;
} & {
readonly cardId?: string | number | undefined;
}, "now"> & {
readonly now?: Date | undefined;
}) | undefined) => {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
newCard >
export type type Revlog = {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
Revlog = ReturnType<typeof const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<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;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler .SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<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; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>.review: (input: {
readonly card: Readonly<Omit<Readonly<Omit<Readonly<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep?: number | undefined;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>;
readonly grade: Grade;
readonly now?: Date | undefined;
}) => {
...;
}
review >['revlog']
// An application's stored v5 record, not a legacy library type.
type type StoredV5Card = {
due: string;
last_review?: string | null;
stability: number;
difficulty: number;
state: State;
scheduled_days: number;
learning_steps: number;
reps: number;
lapses: number;
}
StoredV5Card = {
due: string
last_review?: string | null
stability: number
difficulty: number
state: type State = 0 | 1 | 2 | 3 State
scheduled_days: number
learning_steps: number
reps: number
lapses: number
}
export function function convertCard(stored: StoredV5Card, cardId: Card["cardId"]): Card convertCard (
stored: type StoredV5Card = {
due: string;
last_review?: string | null;
stability: number;
difficulty: number;
state: State;
scheduled_days: number;
learning_steps: number;
reps: number;
lapses: number;
}
StoredV5Card ,
cardId: type Card = {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card ['cardId']
): type Card = {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card {
const scheduleStatus = {
[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]: 'learning',
} as const
return const definition: CustomScheduler<"fsrs-6", ExtendSchedulerEnv<SchedulerEnvFor<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;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition .CustomScheduler<"fsrs-6", ExtendSchedulerEnv<SchedulerEnvFor<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; }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>.schema: SchedulerSchema<ExtendSchedulerEnv<SchedulerEnvFor<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;
}>, Chrono<...>, readonly []>, readonly [...]>>
schema .SchedulerSchema<ExtendSchedulerEnv<SchedulerEnvFor<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; }>, Chrono<...>, readonly []>, readonly [...]>>.card: SRSSchema<{
input: Readonly<Omit<Readonly<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep?: number | undefined;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>;
output: Mutable<...>;
}>
card .SRSSchema<{ input: Readonly<Omit<Readonly<{ stability: number; difficulty: number; } & { dueAt: Date; lastReviewAt: Date | null; } & SchedulerCoreFields$1<"new" | "learning" | "review">> & { ...; }, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>; output: Mutable<...>; }>.parse(input: unknown): Mutable<Omit<Mutable<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep: number;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>
parse ({
cardId,
dueAt: new Date(stored.due),
lastReviewAt:
stored.state === const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .New || stored.last_review == null
? null
: new Date(stored.last_review),
stability: stored.stability,
difficulty: stored.difficulty,
state: stored.state,
scheduleStatus: scheduleStatus[stored.state],
scheduledDays: stored.scheduled_days,
learningStep: stored.learning_steps,
reps: stored.reps,
lapses: stored.lapses,
})
}
export const storedCard = {
due: '2026-01-03T00:00:00.000Z',
last_review: '2026-01-01T00:00:00.000Z',
stability: 2,
difficulty: 5,
state: const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .Review,
scheduled_days: 2,
learning_steps: 0,
reps: 3,
lapses: 0,
} satisfies type StoredV5Card = {
due: string;
last_review?: string | null;
stability: number;
difficulty: number;
state: State;
scheduled_days: number;
learning_steps: number;
reps: number;
lapses: number;
}
StoredV5Card
const const card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card = function convertCard(stored: StoredV5Card, cardId: Card["cardId"]): Card convertCard (storedCard, 'card-1')
// An overdue review: previous review, original due date and now are distinct.
const now = new Date('2026-01-04T00:00:00.000Z')
const const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result = const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<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;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler .SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<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; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>.review: (input: {
readonly card: Readonly<Omit<Readonly<Omit<Readonly<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep?: number | undefined;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>;
readonly grade: Grade;
readonly now?: Date | undefined;
}) => {
...;
}
review ({ card, grade: const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, now })
const const restored: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
restored = const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<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;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler .SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<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; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>.rollback: (input: {
readonly card: Mutable<Omit<Mutable<Omit<Mutable<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep: number;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>;
readonly revlog: Mutable<...>;
}) => {
...;
}
rollback ({
card: Mutable<Omit<Mutable<Omit<Mutable<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep: number;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>
card : const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card ,
revlog: Mutable<Omit<Mutable<Omit<Mutable<{
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
reviewTime: Date;
} & SchedulerCoreFields$1<"new" | "learning" | "review"> & {
readonly rating: Grade;
}> & {
readonly cardId: string | number;
readonly learningStep: number;
readonly scheduledDays: number;
}, "rating" | keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review"> & {
readonly rating: Grade;
}>, "rating" | keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...> & {
readonly rating: Grade;
}>
revlog : const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog ,
})
console.log(
JSON.stringify(
{
cardId: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card .cardId,
rating: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog .rating,
reps: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
card .reps,
revlog: {
lastReviewAt: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog .lastReviewAt,
dueAt: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog .dueAt,
reviewTime: const result: {
card: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
};
revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
};
}
result .revlog: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
learningStep: number;
scheduledDays: number;
reviewTime: Date;
state: State;
scheduleStatus: "new" | "learning" | "review";
rating: Grade;
}
revlog .reviewTime,
},
rollbackDueAt: const restored: {
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
restored .dueAt,
},
null,
2
)
)
區域型別 StoredV5Card 描述應用程式的儲存格式,並非從 legacy 函式庫匯入。以資料庫既有的穩定 ID 作為 cardId,另外保留應用程式自有欄位。組合後的卡片 schema 會驗證轉換後的日期與欄位;其他應用程式欄位也應在讀取儲存資料的邊界驗證。
#設定對照
| v5 設定 | v6 設定 | 遷移說明 |
|---|---|---|
w | weights | 此階段保留使用者的 FSRS-6 權重;範例僅為方便執行才使用預設值。 |
request_retention | desiredRetention | 需要目標保留率中介軟體。 |
maximum_interval | maximumInterval | 需要最大間隔中介軟體;單調間隔策略也使用此上限。 |
enable_fuzz | enableFuzz | 需要模糊中介軟體,保留舊設定。 |
enable_short_term | enableShortTerm | 對 FSRS-6,同時影響模型短期行為與明確的學習步驟。 |
learning_steps / relearning_steps | learningSteps / relearningSteps | 保留設定的步驟陣列與既有學習進度。 |
| 重新學習步驟數量 | numRelearningSteps | FSRS-6 需設為 relearningSteps.length。 |
forget(..., reset_count) | clearStatsOnForget | 改為實例設定;v5 預設 false,v6 統計中介軟體預設 true。 |
#卡片、紀錄與操作
| v5 儲存欄位 | v6 欄位或處理方式 |
|---|---|
due | 卡片的 dueAt,反序列化為 Date。 |
elapsed_days / 紀錄 last_elapsed_days | 已移除,使用所選 chrono 由時間戳記計算經過天數;FSRS-7 重播不要使用這些舊的取整值。 |
last_review | lastReviewAt,缺少或 null 統一為 null。 |
scheduled_days | scheduledDays,由中介軟體提供。 |
learning_steps | learningStep,由中介軟體提供。 |
stability、difficulty、state、reps、lapses | API 遷移時保留;統計欄位需要中介軟體。 |
| 應用程式紀錄 ID | cardId,卡片與紀錄使用同一 ID。 |
| 無對應欄位 | scheduleStatus:New → new,Learning/Relearning → learning,Review → review。 |
複習紀錄 review | reviewTime,即實際複習時間,而非卡片到期時間。 |
結果 { card, log } | { card, revlog }。 |
如範例所示,從排程器推導型別。卡片與紀錄欄位取決於模型、chrono 及中介軟體。自訂 v5 策略需改寫為對應的中介軟體 hook,不能只改名或繼續繼承舊排程器。
| v5 操作(僅供對照) | v6 操作 |
|---|---|
createEmptyCard(now) | scheduler.newCard({ now, cardId }) |
f.next(card, now, grade) | scheduler.review({ card, now, grade }) |
f.repeat(card, now) | scheduler.preview({ card, now }) 傳回可迭代物件,透過 Symbol.iterator 提供 iterator,可用 for...of 遍歷。每個候選項包含 { grade, card, revlog },透過 candidate.grade 選取,不再以評分鍵索引物件。 |
f.rollback(card, log) | scheduler.rollback({ card, revlog }),使用傳回的 v6 紀錄。 |
f.forget(card, now, reset_count) | scheduler.forget({ card, now });傳回卡片,而非 { card, log },手動操作紀錄由應用程式保存。 |
f.get_retrievability(card, now, false) | 對有 lastReviewAt 的已學習卡片,呼叫 scheduler.model.forgettingCurve(card, scheduler.chrono.difference(card.lastReviewAt, now)),傳回數值;新卡片另外處理,百分比由應用程式格式化。 |
review() 傳回新資料,應同時保存 result.card 與 result.revlog。新紀錄保存復原所需的複習前欄位,包含 dueAt、lastReviewAt。不能只將舊紀錄的 due 改名就用於 v6 復原:舊紀錄可能缺少必要的複習前快照。保留原始歷史,僅轉換評分與實際複習時間用於模型重播。跨版本復原需要獨立的應用程式快照或復原邊界。
即使模型與設定相同,也不保證所有學習步驟或模糊情境的間隔與 v5 完全一致。切換寫入前,應檢查 New、Learning、Review、Relearning 的代表卡片。DefaultScheduler 是另一個預設入口,但預設模型為 FSRS-7;若在此階段採用它,應明確指定 FSRS-6。
#為什麼紀錄中的三個時間不能直接改名
dateChrono 的欄位名稱是 reviewTime,不是 reviewAt。以逾期複習為例:卡片上次於 1 月 1 日複習,原訂 1 月 3 日到期,實際於 1 月 4 日複習。
| 含義 | v5 一般複習紀錄 | v6 revlog |
|---|---|---|
| 上次複習時間:1 月 1 日 | due(舊實作寫入 card.last_review || card.due) | lastReviewAt |
| 原訂到期時間:1 月 3 日 | 此已學習卡片沒有另外記錄 | dueAt |
| 本次實際複習時間:1 月 4 日 | review | reviewTime |
lastReviewAt 表示本次複習之前的上次複習時間,輸入與輸出的型別有所不同:
- 輸入
DateRevlogInputFields中是可選的lastReviewAt?: Date | null。省略或傳入undefined時,schema 會將其正規化為null;dueAt與reviewTime仍必須是有效的Date。 - 輸出
DateRevlogFields中始終存在lastReviewAt: Date | null。正常的已學習卡片紀錄保存複習前的card.lastReviewAt;沒有上次複習的新卡片,首次複習紀錄中的值為null。 null不會自動推算缺少的上次複習時間。舊紀錄缺少此資訊時,通過 schema 驗證不代表能精確復原;不要以dueAt或reviewTime代替它。
複習傳回的卡片中,lastReviewAt 已變成 1 月 4 日,dueAt 是新計算的到期時間;紀錄保存的是複習前的時間。使用這張卡片與 v6 紀錄復原後,dueAt 恢復為 1 月 3 日,lastReviewAt 恢復為 1 月 1 日,提前或逾期複習皆然。這正是 chrono 重構時分開「計算經過天數的時間」與「供復原使用的原到期時間」的原因。上面的可執行範例會印出這三個欄位。
不能一律執行 oldLog.due → revlog.dueAt:一般 v5 已學習卡片紀錄的 due 通常是上次複習時間;沒有 last_review 的首次複習才記錄原到期時間,Manual/遺忘紀錄又有各自的語意。v5 重設後的 New 卡片也可能帶有舊 last_review,因此卡片轉換範例對 State.New 統一輸出 lastReviewAt: null。
只重建模型記憶時,僅映射 rating 與 review → reviewTime 即可。若要跨版本精確復原,需要原始複習前卡片快照(包含兩個時間欄位與中介軟體欄位),否則應設立復原邊界。不能以 review 或重新計算的間隔補造過去未記錄的到期時間,尤其是在提前複習、學習步驟、模糊或手動調整之後。
#2. 從 FSRS-6 遷移至 FSRS-7
#權重與狀態屬於各自的模型
FSRS-6 使用 21 個權重,FSRS-7 需要獨立的 34 個權重。使用 FSRS7_DEFAULT_WEIGHTS,或重新訓練 FSRS-7 權重。不要替 FSRS-6 參數補值,也不要重用其訓練權重。migrateFSRS7Parameters 僅為空輸入提供預設值,不會轉換舊模型的參數配置。
FSRS-7 記憶狀態包含 stability、stabilityFast 與 difficulty,三個欄位都應透過歷史重建。重建結果僅用於更新目前 card 的記憶狀態(memoryState);歷史 revlog 僅作為唯讀輸入,不得修改、覆寫或補寫其中的任何資料,包括過去記錄的記憶狀態。
不得為既有卡片直接設定 stabilityFast = 0.8 * stability,也不能保留 FSRS-6 的穩定性、難度後只新增快分量。模型初始化使用的係數不是已演化記憶狀態的遷移公式。
設定 fractionalDays: true,保留兩次複習相隔十分鐘等小數天數。FSRS-7 的快軌跡是模型固有部分:安裝學習步驟中介軟體後,enableShortTerm: false 僅關閉明確的學習步驟,不會關閉快軌跡。FSRS-7 模型沒有與 FSRS-6 的 numRelearningSteps 對應的設定項目。
#FSRS-7 的預設步驟與學習狀態
DefaultScheduler 對 FSRS-7 與 FSRS-3~6 一樣預設使用學習步驟 ['1m', '10m'] 與重新學習步驟 ['10m'],並開啟 allowModelOverride,讓較長的模型間隔覆寫目前步驟。明確傳入的步驟不會被預設值覆寫。enableShortTerm 仍預設 true,控制明確的步驟,不控制 FSRS-7 快軌跡或依間隔劃分的學習狀態。
註冊 schedulerLearningStepsMiddleware 後,狀態由最終排程間隔決定,即使步驟關閉、為空或已完成:
| 複習前狀態 | 最終間隔小於 graduatingInterval(預設一天) | 最終間隔至少 graduatingInterval |
|---|---|---|
| New / Learning | Learning,scheduleStatus: learning | Review,scheduleStatus: review |
| Review / Relearning | Relearning,scheduleStatus: learning | Review,scheduleStatus: review |
設定了步驟時,沒有適用步驟的 Hard、Good、Easy 至少在 graduatingInterval 之後到期,因此走完步驟,以及 Review 卡片按 Hard、Good 或 Easy,都會進入 Review。開啟 allowModelOverride 時,使用模型間隔的 Again 總是進入 Learning/Relearning,即使其間隔被限制為 graduatingInterval。
沒有設定步驟時,不能假設「沒有 Learning」、「只有 Again 才進入 Relearning」或「Easy 一定進入 Review」。一般來說,Easy 跳過明確的步驟,Good 僅在存在適用的下一步驟時推進,時長為 0 的步驟視為沒有步驟。learningStep 只在卡片留在 Learning/Relearning 時保留下一步驟,卡片進入 Review 時重設為 0;它是步驟索引,不是複習次數。
這些轉換屬於中介軟體,不屬於 FSRS7Model。只組合模型與 chrono 的排程器仍傳回 Review,即使間隔小於一天。遷移應用程式佇列、狀態篩選與統計時,需要同步調整依賴舊行為的判斷。
「重播記憶,保留排程安排」一節的完整遷移範例明確保留既有學習步驟,不依賴預設值。若產品決定完全採用模型驅動的步驟,可明確設定 learningSteps: []、relearningSteps: [];這不等於移除學習步驟中介軟體。改變步驟策略應單獨驗證,不能在卡片欄位遷移時順便執行。
#遷移回憶機率呼叫
使用 scheduler.model.forgettingCurve(state, elapsedDays) 取得數值回憶機率,或 scheduler.model.algorithm.curve(elapsedDays, state) 檢查快慢分量。兩者皆需要完整 FSRS-7 狀態:stability、stabilityFast、difficulty,不能只傳 FSRS-6 的單一穩定性值。
也可以從 ts-fsrs/models/fsrs-7 匯入獨立函式 curve,呼叫 curve(weights, elapsedDays, state),不必建立演算法實例;weights 必須是有效的 34 個 FSRS-7 權重。此函式不驗證權重:儲存或使用者輸入應先通過 schema/參數驗證,不要用它探測權重是否有效。使用已遷移的記憶狀態與同一份 FSRS-7 權重,elapsedDays 保留小數天數。
曲線結果包含 retrievability、derivative、fast、slow、fastWeight、slowWeight 與 total;total = fastWeight + slowWeight 是混合係數之和,不是總回憶機率。替代舊回憶機率呼叫時讀取 retrievability,百分比由應用程式格式化。演算法實例的曲線結果也包含 total。這些曲線介面只計算機率與分量,不遷移卡片、產生紀錄或安排到期時間。
#重播記憶,保留排程安排
以下為可獨立執行的範例。它接收已轉為 v6 結構的 FSRS-6 卡片,將儲存紀錄的 review 轉成 reviewTime,呼叫 Reschedule.replay(),不傳入舊模型的 initialState。遷移時只重建記憶狀態,不重新安排既有卡片的下次複習時間(dueAt)。之後使用者複習該卡片時,再由 FSRS-7 計算新的下次複習時間。ID、上次複習時間、學習進度與統計也保持不變。
import { const dateChrono: 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;
};
}>;
};
}>
dateChrono , function defineScheduler<const M extends AnyModel, const C extends AnyChrono>(definition: {
readonly model: M;
readonly chrono: C;
}): CustomScheduler<SchedulerNameOf<M>, SchedulerEnvFor<M, C, readonly []>, M, C>
defineScheduler , 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'
import {
const schedulerDesiredRetentionMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly desiredRetention: number | Record<Grade, number>;
};
output: {
readonly desiredRetention: Record<Grade, number>;
};
}>;
}>
schedulerDesiredRetentionMiddleware ,
const schedulerFuzzingMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>
schedulerFuzzingMiddleware ,
const schedulerLearningStepsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval?: number;
readonly allowModelOverride?: boolean;
};
output: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval: number;
readonly allowModelOverride: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
}>
schedulerLearningStepsMiddleware ,
const schedulerMaximumIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after learning steps so explicit steps can keep their exact delay.
schedulerMaximumIntervalMiddleware ,
const schedulerMonotonicIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after fuzzing and learning steps, when present.
schedulerMonotonicIntervalMiddleware ,
const schedulerScheduledDaysMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly fractionalDays?: boolean | undefined;
};
output: {
readonly fractionalDays: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
}>
Register before interval middleware so it records their final value on unwind.
schedulerScheduledDaysMiddleware ,
const schedulerStatsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly clearStatsOnForget?: boolean | undefined;
};
output: {
readonly clearStatsOnForget: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly reps: number;
readonly lapses: number;
};
output: {
readonly reps: number;
readonly lapses: number;
};
}>;
}>
schedulerStatsMiddleware ,
} from 'ts-fsrs/middlewares'
import { const FSRS7_DEFAULT_WEIGHTS: number[] FSRS7_DEFAULT_WEIGHTS , const FSRS7Model: Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>
FSRS7Model } from 'ts-fsrs/models/fsrs-7'
import { class Reschedule<Scheduler extends AnySchedulerCore>Replays a review history on top of an existing srs-kit scheduler core.
The scheduler instance keeps the model and chronology coupled, allowing the
review-time, card and memory-state types to be inferred from the selected
core.
Reschedule } from 'ts-fsrs/reschedule'
export const const definition: CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition = defineScheduler<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>>(definition: {
...;
}): CustomScheduler<...>
defineScheduler ({
model: Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>
model : const FSRS7Model: Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>
FSRS7Model ,
chrono: 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;
};
}>;
};
}>
chrono : const dateChrono: 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;
};
}>;
};
}>
dateChrono ,
}).CustomScheduler<"fsrs-7", SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, Model<...>, Chrono<...>>.use: <readonly [Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly desiredRetention: number | Record<Grade, number>;
};
output: {
readonly desiredRetention: Record<Grade, number>;
};
}>;
}>, Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>, ... 4 more ..., Middleware<...>]>(middlewares_0: Middleware<...>, middlewares_1: Middleware<...>, middlewares_2: Middleware<...>, middlewares_3: Middleware<...>, middlewares_4: Middleware<...>, middlewares_5: Middleware<...>, middlewares_6: Middleware<...>) => CustomScheduler<...>
Returns a new scheduler definition extended with the supplied middleware.
The current scheduler and any cores created from it remain unchanged.
use (
const schedulerDesiredRetentionMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly desiredRetention: number | Record<Grade, number>;
};
output: {
readonly desiredRetention: Record<Grade, number>;
};
}>;
}>
schedulerDesiredRetentionMiddleware ,
const schedulerFuzzingMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays?: boolean;
};
output: {
readonly enableFuzz: boolean;
readonly maximumInterval: number;
readonly fractionalDays: boolean;
};
}>;
readonly cardInitInput: SRSSchema<{
input: {
readonly cardId?: string | number;
};
output: {
readonly cardId?: string | number;
};
}>;
readonly card: SRSSchema<{
input: {
readonly cardId: string | number;
readonly reps: number;
};
output: {
readonly cardId: string | number;
readonly reps: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly cardId: string | number;
};
output: {
readonly cardId: string | number;
};
}>;
}>
schedulerFuzzingMiddleware ,
const schedulerStatsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly clearStatsOnForget?: boolean | undefined;
};
output: {
readonly clearStatsOnForget: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly reps: number;
readonly lapses: number;
};
output: {
readonly reps: number;
readonly lapses: number;
};
}>;
}>
schedulerStatsMiddleware ,
const schedulerScheduledDaysMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly fractionalDays?: boolean | undefined;
};
output: {
readonly fractionalDays: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly scheduledDays: number;
};
output: {
readonly scheduledDays: number;
};
}>;
}>
Register before interval middleware so it records their final value on unwind.
schedulerScheduledDaysMiddleware ,
const schedulerLearningStepsMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval?: number;
readonly allowModelOverride?: boolean;
};
output: {
readonly learningSteps: readonly StepUnit[];
readonly relearningSteps: readonly StepUnit[];
readonly enableShortTerm: boolean;
readonly graduatingInterval: number;
readonly allowModelOverride: boolean;
};
}>;
readonly card: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
readonly revlog: SRSSchema<{
input: {
readonly learningStep?: number;
};
output: {
readonly learningStep: number;
};
}>;
}>
schedulerLearningStepsMiddleware ,
const schedulerMaximumIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after learning steps so explicit steps can keep their exact delay.
schedulerMaximumIntervalMiddleware ,
const schedulerMonotonicIntervalMiddleware: Middleware<symbol, {
readonly config: SRSSchema<{
input: {
readonly maximumInterval?: number;
};
output: {
readonly maximumInterval: number;
};
}>;
}>
Register after fuzzing and learning steps, when present.
schedulerMonotonicIntervalMiddleware
)
export const const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler = const definition: CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition .CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>.create: (ctx: {
readonly config: Omit<SchedulerConfigInput<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, "fractionalDays" | ... 8 more ... | "clearStatsOnForget"> & {
...;
};
}) => SchedulerCore<...>
create ({
config: Omit<SchedulerConfigInput<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, "fractionalDays" | ... 8 more ... | "clearStatsOnForget"> & {
...;
}
config : {
weights: const FSRS7_DEFAULT_WEIGHTS: number[] FSRS7_DEFAULT_WEIGHTS ,
fractionalDays: true,
enableShortTerm: true, // Controls learning steps, not the model's fast trace.
desiredRetention: 0.9,
// Preserve existing steps explicitly instead of inheriting preset defaults.
learningSteps: ['1m', '10m'],
relearningSteps: ['10m'],
enableFuzz: false,
maximumInterval: 36500,
clearStatsOnForget: false,
},
})
type type Card = {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card = ReturnType<typeof const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler .SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>.newCard: (options?: (Omit<{
readonly now?: Date | undefined;
} & {
readonly cardId?: string | number | undefined;
}, "now"> & {
readonly now?: Date | undefined;
}) | undefined) => {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
newCard >
type type PreviousCard = {
state: State;
scheduleStatus: "new" | "learning" | "review";
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
}
PreviousCard = Omit<type Card = {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card , 'stabilityFast'>
type type StoredReview = {
rating: Rating;
review: string;
}
StoredReview = { rating: type Rating = 0 | 1 | 2 | 3 | 4 Rating ; review: string }
// completeHistory means all reviews since creation or the latest memory reset.
// Decide this from application records, not from the number of rows alone.
export function function migrateMemory(card: PreviousCard, logs: readonly StoredReview[], completeHistory: boolean): Card migrateMemory (
card: type PreviousCard = {
state: State;
scheduleStatus: "new" | "learning" | "review";
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
}
PreviousCard ,
logs: readonly type StoredReview = {
rating: Rating;
review: string;
}
StoredReview [],
completeHistory: boolean
): type Card = {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
Card {
if (card.state === const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .New) {
const { stability, stabilityFast, difficulty } = const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler .SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>.newCard: (options?: (Omit<{
readonly now?: Date | undefined;
} & {
readonly cardId?: string | number | undefined;
}, "now"> & {
readonly now?: Date | undefined;
}) | undefined) => {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
newCard ({
cardId: card.cardId,
})
return const definition: CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition .CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>.schema: SchedulerSchema<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>
schema .SchedulerSchema<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>.card: SRSSchema<{
input: Readonly<Omit<Readonly<{
stabilityFast: number;
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep?: number | undefined;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>;
output: Mutable<...>;
}>
card .SRSSchema<{ input: Readonly<Omit<Readonly<{ stabilityFast: number; stability: number; difficulty: number; } & { dueAt: Date; lastReviewAt: Date | null; } & SchedulerCoreFields$1<"new" | "learning" | "review">> & { ...; }, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>; output: Mutable<...>; }>.parse(input: unknown): Mutable<Omit<Mutable<{
stabilityFast: number;
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep: number;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>
parse ({
...card,
stability,
stabilityFast,
difficulty,
})
}
if (!completeHistory)
throw new Error('Keep this card on FSRS-6: incomplete history')
const history = logs
.filter(({ rating }) => rating !== const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Manual)
.map(({ rating, review }) => ({
rating,
reviewTime: new Date(review),
}))
// replay validates dates/ratings and sorts the non-manual history.
// Empty or Manual-only histories throw instead of clearing memory.
const { memoryState } = new new Reschedule<SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>>(scheduler: SchedulerCore<...>): Reschedule<...>
Replays a review history on top of an existing srs-kit scheduler core.
The scheduler instance keeps the model and chronology coupled, allowing the
review-time, card and memory-state types to be inferred from the selected
core.
Reschedule (const scheduler: SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>
scheduler ).Reschedule<SchedulerCore<SchedulerCoreEnv<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>, ModelCore<...>, ChronoCore<...>>>.replay(input: ReplayInput<{
stabilityFast: number;
stability: number;
difficulty: number;
}, Date>, options?: RescheduleOptions): ReplayResult<{
stabilityFast: number;
stability: number;
difficulty: number;
}>
Rebuilds model memory from the review history and returns the memory state
after each review.
replay ({ ReplayInput<{ stabilityFast: number; stability: number; difficulty: number; }, Date>.history: readonly RescheduleReview<Date>[] history })
const latestReview = history.reduce(
(latest, { reviewTime }) => Math.max(latest, reviewTime.getTime()),
-Infinity
)
if (card.lastReviewAt?.getTime() !== latestReview) {
throw new Error(
'Keep this card on FSRS-6: history does not match lastReviewAt'
)
}
// Only the model memory changes; dueAt, counters and learning progress survive.
return const definition: CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>
definition .CustomScheduler<"fsrs-7", ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>, Model<...>, Chrono<...>>.schema: SchedulerSchema<ExtendSchedulerEnv<SchedulerEnvFor<Model<{
readonly name: "fsrs-7";
readonly config: SRSSchema<{
input: {
readonly weights: readonly number[];
};
output: {
readonly weights: readonly number[];
};
}>;
readonly memoryState: SRSSchema<{
input: {
stabilityFast: number;
stability: number;
difficulty: number;
};
output: {
stabilityFast: number;
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS7Algorithm;
}>, Chrono<...>, readonly []>, readonly [...]>>
schema .SchedulerSchema<ExtendSchedulerEnv<SchedulerEnvFor<Model<{ readonly name: "fsrs-7"; readonly config: SRSSchema<{ input: { readonly weights: readonly number[]; }; output: { readonly weights: readonly number[]; }; }>; readonly memoryState: SRSSchema<{ input: { stabilityFast: number; stability: number; difficulty: number; }; output: { stabilityFast: number; stability: number; difficulty: number; }; }>; readonly algorithm: FSRS7Algorithm; }>, Chrono<...>, readonly []>, readonly [...]>>.card: SRSSchema<{
input: Readonly<Omit<Readonly<{
stabilityFast: number;
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep?: number | undefined;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>;
output: Mutable<...>;
}>
card .SRSSchema<{ input: Readonly<Omit<Readonly<{ stabilityFast: number; stability: number; difficulty: number; } & { dueAt: Date; lastReviewAt: Date | null; } & SchedulerCoreFields$1<"new" | "learning" | "review">> & { ...; }, keyof SchedulerCoreFields$1<...>> & SchedulerCoreFields$1<...>>; output: Mutable<...>; }>.parse(input: unknown): Mutable<Omit<Mutable<{
stabilityFast: number;
stability: number;
difficulty: number;
} & {
dueAt: Date;
lastReviewAt: Date | null;
} & SchedulerCoreFields$1<"new" | "learning" | "review">> & {
readonly cardId: string | number;
readonly reps: number;
readonly learningStep: number;
readonly scheduledDays: number;
readonly lapses: number;
}, keyof SchedulerCoreFields$1<"new" | "learning" | "review">> & SchedulerCoreFields$1<"new" | "learning" | "review">>
parse ({ ...card, ...memoryState })
}
export const previousCard: type PreviousCard = {
state: State;
scheduleStatus: "new" | "learning" | "review";
cardId: string | number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
}
PreviousCard = {
cardId: 'card-1',
dueAt: new Date('2026-01-05T00:00:00.000Z'),
lastReviewAt: new Date('2026-01-02T00:00:00.000Z'),
stability: 2,
difficulty: 5,
state: const State: Readonly<{
readonly New: 0;
readonly Learning: 1;
readonly Review: 2;
readonly Relearning: 3;
}>
State .Review,
scheduleStatus: 'review',
scheduledDays: 3,
learningStep: 0,
reps: 3,
lapses: 0,
}
export const storedHistory: type StoredReview = {
rating: Rating;
review: string;
}
StoredReview [] = [
{ rating: const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, review: '2026-01-01T00:00:00.000Z' },
{ rating: const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, review: '2026-01-01T00:10:00.000Z' },
{ rating: const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, review: '2026-01-02T00:00:00.000Z' },
]
const const migrated: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
migrated = function migrateMemory(card: PreviousCard, logs: readonly StoredReview[], completeHistory: boolean): Card migrateMemory (previousCard, storedHistory, true)
console.log(
JSON.stringify(
{
model: 'FSRS-7',
cardId: const migrated: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
migrated .cardId,
dueAt: const migrated: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
migrated .dueAt,
reps: const migrated: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
migrated .reps,
stabilityFast: const migrated: {
cardId: string | number;
stabilityFast: number;
stability: number;
difficulty: number;
dueAt: Date;
lastReviewAt: Date | null;
reps: number;
learningStep: number;
scheduledDays: number;
lapses: number;
state: State;
scheduleStatus: "new" | "learning" | "review";
}
migrated .stabilityFast,
},
null,
2
)
)
| 卡片或歷史情況 | 處理方式 |
|---|---|
新卡片(State.New),含主動重設後的卡片 | 使用 FSRS-7 預設記憶狀態,保留排程與統計。 |
| 已學習卡片,具有從建立或最近一次記憶重設以來的完整歷史 | 使用 FSRS-7 權重重播,保存重建狀態並保留排程。 |
| 歷史缺少或遭截斷 | 卡片及其對應設定繼續使用 FSRS-6;不得默默清零,也不能僅憑列數宣稱歷史完整。 |
| 已學習卡片的歷史為空或僅有 Manual | 重播拋錯,卡片繼續使用 FSRS-6。 |
日期或評分無效,或最後一次複習與 lastReviewAt 不符 | 停止該卡片遷移,先核對紀錄。 |
replay() 依時間排序有效複習並過濾 Rating.Manual,但不會解讀手動遺忘或重設事件。應用程式必須選取最近一次記憶重設之後的完整歷史,排除更早的紀錄;僅手動調整到期時間不等於重設記憶。同一時間戳記的複習應保留紀錄順序。使用 dateChrono 時,時間需反序列化為有效的 Date。第一筆保留的複習從未初始化的記憶狀態開始,經過天數為零。
範例中的歷史完整旗標由應用程式判斷,最後時間戳記相符並不能證明更早的複習沒有缺少。遷移失敗的卡片應依保存的模型版本繼續路由至 FSRS-6,不要將它們傳給 FSRS-7。
#如果確實需要重新安排到期時間
new Reschedule(scheduler).reschedule({ history, initialCard }) 會透過整套排程策略重播,傳回 { card, results },可能改變到期時間、學習進度與統計。應以保留原 cardId、使用第一筆複習時間的 FSRS-7 新卡片作為 initialCard,不能在既有的已學習卡片上再重播完整歷史。產生的紀錄是重新計算的結果,不應替換原始歷史。本文預設使用 replay(),遷移時不重新安排既有卡片的下次複習時間。
#保存與驗收
- 備份卡片、原始紀錄與設定。為每張卡片或可靠關聯的設定保存模型版本,以及重播實際使用的權重。
- 增加
stabilityFast與 v6 欄位的儲存,將重建記憶、模型版本及對應權重/設定參照一併提交,避免混用 FSRS-6 狀態與 FSRS-7 權重。保留應用程式 ID 與中繼資料。 - 保持原始紀錄不變,並建立復原邊界:舊模型紀錄不得直接用於 FSRS-7
rollback()。新的 FSRS-7 紀錄應保存stabilityFast與複習前欄位。 - 以代表卡片驗證複習、預覽、復原、同日複習、學習步驟及設定的模糊行為。僅遷移記憶時,斷言到期時間、ID、學習進度與統計不變。處理失敗後再切換對應卡片的模型。