|
| 1 | +# ADR-018: 间隔重复调度器引入 fsrs crate(FSRS-6) |
| 2 | + |
| 3 | +## 状态 |
| 4 | + |
| 5 | +已接受 |
| 6 | + |
| 7 | +## 日期 |
| 8 | + |
| 9 | +2026-08-22 |
| 10 | + |
| 11 | +## 背景 |
| 12 | + |
| 13 | +v0.11.2(学习循环统一)需要间隔重复调度器。2026-08-22 用户裁决选型 FSRS |
| 14 | +(记忆决策:《间隔重复引擎选型 FSRS 取代 SM-2(v0.11.2)》):FSRS-6 是 |
| 15 | +Anki 23.10+ 默认调度器,DSR 三变量模型(难度/稳定性/可提取性)基于遗忘曲线 |
| 16 | +实证研究,按目标留存率反解间隔,大规模真实数据验证优于 SM-2。 |
| 17 | + |
| 18 | +备选降级路径为手写 SM-2 纯函数兜底——触发条件是「依赖引入受限」(本机存在 |
| 19 | +TLS 拦截,部分 CDN 下载会失败)。 |
| 20 | + |
| 21 | +## 决策 |
| 22 | + |
| 23 | +1. 引入 `fsrs = "6"`(crates.io v6.6.1)作为调度器实现: |
| 24 | + - 实测 crates.io 拉取与编译成功(含 6 个轻量传递依赖),降级条件未触发; |
| 25 | + - 仅消费推理 API(`FSRS::new(&[])` 默认参数 + `next_states`),不启用 |
| 26 | + 训练/优化器路径(`--no-default-features` 关闭 experimental_cost_adr); |
| 27 | + - 纯算法零网络依赖,契合本地优先;默认参数开箱即用。 |
| 28 | +2. 目标留存率取 0.9(FSRS 推荐默认);个性化参数优化是复习数据积累后的 |
| 29 | + 远期扩展(数据不出本机)。 |
| 30 | +3. 调度接口(scheduler.rs:`schedule(state, rating, now) -> ScheduleOutcome`) |
| 31 | + 与算法实现解耦——将来若需换算法(或受限环境补 SM-2),接口不变。 |
| 32 | +4. 不保留双实现(YAGNI):依赖引入成功后 SM-2 兜底失去存在理由;接口预留 |
| 33 | + 使后补成本极低。 |
| 34 | + |
| 35 | +## 备选方案 |
| 36 | + |
| 37 | +### 方案 A:手写 SM-2 纯函数(未采用) |
| 38 | +- 优点:零依赖、完全可控。 |
| 39 | +- 缺点:启发式 EF 系数易漂移失真,调度质量系统性弱于 FSRS;用户已裁决弃用。 |
| 40 | + |
| 41 | +### 方案 B:引入 rs-fsrs 轻量调度 crate(未采用) |
| 42 | +- 优点:依赖更少。 |
| 43 | +- 缺点:非 Anki 官方算法主线,维护活跃度与算法版本(FSRS-6)跟进不确定。 |
| 44 | + |
| 45 | +### 方案 C:fsrs 官方 crate(采用) |
| 46 | +- 优点:算法主线权威(Anki 同款)、FSRS-6 默认参数、纯 Rust 编译通过。 |
| 47 | +- 缺点:crate 含训练域代码(未启用),传递依赖 6 个。 |
| 48 | + |
| 49 | +## 后果 |
| 50 | + |
| 51 | +- flashcards.state_json 采用 FSRS MemoryState(stability/difficulty)+ reps/lapses |
| 52 | + 扩展字段的序列化格式;旧格式损坏回退新卡状态(诚实降级)。 |
| 53 | +- 复习日志无 streak 字段(弹性承诺纪律:不追债不清零,N10 归零暴政防御)。 |
| 54 | +- 指标埋点(card_reviewed/fragment_upgraded)随复习链路同批落库——北极星与 |
| 55 | + Phase 4 门控判据从第一天记录。 |
0 commit comments