Skip to content

Commit ea6368c

Browse files
committed
docs(spec): v0.11 全系列设计文档(组模型到组结算)
1 parent 05b237e commit ea6368c

1 file changed

Lines changed: 247 additions & 0 deletions

File tree

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# v0.11 大目标版本系列设计:笔记组与学习循环
2+
3+
> 状态:已批准(用户授权全自主执行,2026-08-22)
4+
> 依据:[v4 路线图](../../Foresight/v4-roadmap-build-order-analysis.md)(已裁决采纳)·
5+
> [需求池 v0.11 区段](../../product/requirements-pool.md)(REQ-195~198)·
6+
> [pain-points-v4](../../product/pain-points-v4.md) §7.4/§7.5/§11/附录B
7+
> 范围:v0.11.0(组基建·容器侧)→ v0.11.1(feed 进料)→ v0.11.2(学习循环统一)→ v0.11.3(组结算)
8+
9+
## 一、总架构
10+
11+
统一产物层「笔记组」:一切采集产物归入组,组是唯一容器;容器地形(课程)与
12+
feed 地形(碎片)只是组的两种形成方式(v4 §7.4 融合点)。自底向上四层:
13+
14+
```
15+
原子层:note_groups/fragments/flashcards schema + 路由/调度/结算纯函数(TDD)
16+
业务层:db_note_groups.rs / group_route.rs / spacy scheduler / settlement
17+
系统层:commands_groups.rs / commands_fragments.rs / commands_flashcards.rs
18+
UI 层:NotesPage 组侧栏 + 组卡片(路由可见可改)+ 复习面(最小化)+ 结算向导
19+
```
20+
21+
硬约束(写入每层实现):
22+
- 提取优先:复习"先回忆再看",无被动重读模式
23+
- 弹性承诺:周契约,断签不清零(review_logs 无 streak 字段)
24+
- 动机App化防御:UI 最小化,引擎先有真牙
25+
- 碎片身份诚实:fragments 独立表,不与 notes 混装
26+
- feed 功能开关默认关;路由误判可见可改
27+
28+
## 二、v0.11.0 笔记组基建·容器侧(REQ-195~198)
29+
30+
### 2.1 数据模型(db_migrations.rs 惯例:建表幂等 + ensure_column)
31+
32+
```sql
33+
CREATE TABLE IF NOT EXISTS note_groups (
34+
id INTEGER PRIMARY KEY AUTOINCREMENT,
35+
name TEXT NOT NULL,
36+
terrain TEXT NOT NULL DEFAULT 'container', -- container/feed(管线分叉前提)
37+
kind TEXT NOT NULL DEFAULT 'standalone', -- course 课程组/topic 主题组/standalone 独立组
38+
domain_tag TEXT, -- DomainKind kebab-case(主题组必填)
39+
source TEXT NOT NULL DEFAULT 'route', -- route/series/manual
40+
series_key TEXT, -- series_detect 系列名(课程组幂等键)
41+
route_reason TEXT, -- 路由理由 JSON(命中信号明细,REQ-198)
42+
route_overridden INTEGER NOT NULL DEFAULT 0, -- 用户改判标记(修改即记忆)
43+
created_at INTEGER NOT NULL,
44+
updated_at INTEGER NOT NULL
45+
);
46+
CREATE UNIQUE INDEX IF NOT EXISTS idx_groups_series
47+
ON note_groups(series_key) WHERE series_key IS NOT NULL;
48+
CREATE INDEX IF NOT EXISTS idx_groups_domain ON note_groups(domain_tag);
49+
-- notes 补列(ensure_column 幂等;ON DELETE SET NULL——删组不断笔记资产)
50+
ALTER TABLE notes ADD COLUMN group_id INTEGER REFERENCES note_groups(id) ON DELETE SET NULL;
51+
CREATE INDEX IF NOT EXISTS idx_notes_group ON notes(group_id);
52+
```
53+
54+
### 2.2 结构密度路由纯函数(group_route.rs,TDD golden 用例先行)
55+
56+
复用 vote_detect/vote_tier 加权投票范式(每信号一票 → 计票 → 置信):
57+
58+
```rust
59+
pub struct GroupRouteSignals {
60+
pub has_series: bool, // series_detect 命中 → 直接课程组(不进投票)
61+
pub chapter_density: f32, // 章节数/小时(chapter_detect 产出)
62+
pub glossary_terms: usize, // 术语表条目数(glossary 产出)
63+
pub ocr_text_density: f32, // OCR 文本块密度 0..1
64+
pub profile_unknown: bool, // 形态 unknown(四维解耦信号)
65+
pub tier_rich: bool, // 画面档 rich/mid(video_tier_detect)
66+
pub domain_kind: Option<DomainKind>, // 领域命中(归主题组的前提)
67+
}
68+
pub enum RouteAction { OwnGroup, TopicGroup, NeedConfirm }
69+
pub struct GroupRouteDecision {
70+
pub action: RouteAction,
71+
pub reasons: Vec<String>, // 命中信号明细(前端路由理由展示)
72+
}
73+
pub fn route_group(signals: &GroupRouteSignals) -> GroupRouteDecision;
74+
```
75+
76+
投票规则(附录B 判据:高密度自成一组、低密度归主题组):
77+
- 高密度票:chapter_density ≥ 2.0/h、glossary_terms ≥ 5、ocr_text_density ≥ 0.5、tier_rich
78+
- 低密度票:profile_unknown、chapter_density < 0.5、glossary_terms == 0
79+
- 高密度 ≥2 票 → OwnGroup;低密度 ≥2 票且 domain 命中 → TopicGroup;
80+
票数不足/高低冲突 → NeedConfirm(诚实不乱判,vote_tier 同口径)
81+
- 零信号 → OwnGroup 兜底(会话产物默认独立成组,不埋进主题组——埋没比空洞更伤)
82+
83+
### 2.3 容器侧组化接线(commands_session_note.rs convert_to_note 内)
84+
85+
笔记落库后(单一落库出口):
86+
1. `extract_series(session.title)` 命中 → 课程组(series_key 幂等查找/创建,
87+
kind=course,source=series)——兑现 REQ-078 语义升级
88+
2. 无系列 → 收集信号跑 `route_group`:
89+
- OwnGroup → 独立组(name=笔记标题)
90+
- TopicGroup → 按 domain_tag 查/建主题组(kind=topic,terrain=container)
91+
- NeedConfirm → 独立组 + route_reason 标记待确认(UI 高亮)
92+
3. `notes.group_id` 回写;route_reason 落组(REQ-198 可见)
93+
94+
### 2.4 Commands(commands_groups.rs)+ UI
95+
96+
- `list_note_groups`(含组内笔记数)/ `get_note_group` / `create_topic_group`
97+
- `override_group_route(group_id, kind, domain_tag)`——改判即记忆
98+
(route_overridden=1,改判记录进 route_reason,REQ-198)
99+
- `move_note_to_group(note_id, group_id)` / `rename_note_group`
100+
- 前端:NotesPage 左侧栏新增「组」区段(按 v4 §7.5 容器组在每日/会话面语义内);
101+
组卡片显示路由理由明细 + 待确认徽标 + 一键改判(v0.9.0 检测卡交互哲学延续);
102+
笔记列表支持按组过滤
103+
104+
## 三、v0.11.1 feed 进料口(功能开关默认关)
105+
106+
### 3.1 fragments 原料层(独立表——碎片不是笔记,身份诚实)
107+
108+
```sql
109+
CREATE TABLE IF NOT EXISTS fragments (
110+
id INTEGER PRIMARY KEY AUTOINCREMENT,
111+
text TEXT NOT NULL,
112+
image_path TEXT, -- 剪贴板图片(存 data_dir/fragments/)
113+
domain_tag TEXT, -- detect_domain 自动归组依据
114+
group_id INTEGER REFERENCES note_groups(id) ON DELETE SET NULL,
115+
source TEXT NOT NULL DEFAULT 'manual', -- manual/clipboard
116+
created_at INTEGER NOT NULL
117+
);
118+
```
119+
120+
### 3.2 功能开关与捕获
121+
122+
- `feature_flags.json`(应用数据目录):`{ "feed_capture": false }`——默认关,
123+
SettingsPage 开关;关闭时捕获 command 拒绝执行(不信任前端隐藏)
124+
- `capture_fragment(text, image_b64?)`:文本 ≤2000 字;图片解码白名单格式
125+
落 data_dir/fragments/;`detect_domain` 命中标题词 → 查/建 terrain=feed
126+
主题组并挂 group_id;未命中 → 无组(结算面兜底归组)
127+
- DomainTag 归组粒度对齐领域(契约一:拒绝大类)
128+
129+
### 3.3 UI
130+
131+
SettingsPage 开关 + NotesPage 组侧栏显示 feed 主题组(仅开关开启时);
132+
快速捕获输入框(组侧栏顶部,文本粘贴即捕获;图片走剪贴板)。
133+
134+
## 四、v0.11.2 学习循环统一(最大新建块)
135+
136+
### 4.1 调度器(FSRS 主 / SM-2 降级,接口不变)
137+
138+
- 优先引入 `fsrs = "6"` crate(纯算法零网络,FSRS-6;补 ADR-018);
139+
若构建环境受限 → 手写 `sm2.rs` 纯函数兜底(EF 系数版,调度接口一致)
140+
- 统一接口(scheduler.rs):
141+
142+
```rust
143+
pub enum Rating { Again, Hard, Good, Easy }
144+
pub struct CardState { pub stability: f32, pub difficulty: f32,
145+
pub reps: u32, pub lapses: u32, pub last_review_ms: u64 }
146+
pub struct ScheduleOutcome { pub next_state: CardState, pub due_at_ms: u64 }
147+
pub fn schedule(state: &CardState, rating: Rating, now_ms: u64) -> ScheduleOutcome;
148+
```
149+
150+
- 弹性承诺:无 streak 概念;欠账卡仅按 due_at 排序呈现,不追债不清零
151+
152+
### 4.2 数据模型
153+
154+
```sql
155+
CREATE TABLE IF NOT EXISTS flashcards (
156+
id INTEGER PRIMARY KEY AUTOINCREMENT,
157+
group_id INTEGER NOT NULL REFERENCES note_groups(id) ON DELETE CASCADE,
158+
note_id INTEGER REFERENCES notes(id) ON DELETE SET NULL,
159+
fragment_id INTEGER REFERENCES fragments(id) ON DELETE SET NULL,
160+
front TEXT NOT NULL, back TEXT NOT NULL,
161+
kind TEXT NOT NULL DEFAULT 'fact', -- 内容分型预埋:fact 先做
162+
state_json TEXT NOT NULL, -- CardState 序列化
163+
due_at INTEGER NOT NULL,
164+
created_at INTEGER NOT NULL
165+
);
166+
CREATE INDEX IF NOT EXISTS idx_cards_due ON flashcards(due_at);
167+
CREATE TABLE IF NOT EXISTS review_logs (
168+
id INTEGER PRIMARY KEY AUTOINCREMENT,
169+
card_id INTEGER NOT NULL REFERENCES flashcards(id) ON DELETE CASCADE,
170+
rating TEXT NOT NULL, -- again/hard/good/easy
171+
reviewed_at INTEGER NOT NULL
172+
);
173+
CREATE TABLE IF NOT EXISTS metrics_events (
174+
id INTEGER PRIMARY KEY AUTOINCREMENT,
175+
kind TEXT NOT NULL, -- card_reviewed/fragment_upgraded/
176+
-- group_settled/self_test_done
177+
payload_json TEXT NOT NULL DEFAULT '{}',
178+
created_at INTEGER NOT NULL
179+
);
180+
```
181+
182+
### 4.3 组→闪卡生成(本地规则版先行)
183+
184+
- `generate_group_cards(group_id)`:组内笔记的词汇表块术语(structure_note
185+
## 词汇表 行解析)+ glossary 候选 → front=术语 / back=术语首次出现的上下文句
186+
(锚点回链);fragment 文本 >2 句时首句为 front 其余为 back(提取优先)
187+
- AI 生成为可选增强:复用 ai_client 授权/成本/审计全套,默认关
188+
- 生成幂等:同 group+front 查重跳过
189+
190+
### 4.4 复习面(UI 最小化)+ 埋点
191+
192+
- NotesPage 组卡片「复习」入口 → 复习队列(due_at ≤ now,按组过滤):
193+
先显示 front → 点击「回忆完成」→ 展开 back → 四档评分(Again/Hard/Good/Easy)
194+
- 主题自测最轻形态:组内随机 5 卡快答(仅评分不展示对错叙事——N14 防御)
195+
- 每次评分落 review_logs + metrics_events(card_reviewed);
196+
fragment→card 落 metrics_events(fragment_upgraded)——Phase 4 门控数据从第一天记
197+
198+
## 五、v0.11.3 组结算机制(防沼泽仪式)
199+
200+
### 5.1 触发纯函数(settlement.rs)
201+
202+
```rust
203+
pub struct SettlementSignals { pub item_count: usize, pub oldest_at_ms: u64,
204+
pub last_settled_at_ms: Option<u64>, pub now_ms: u64 }
205+
pub fn settlement_due(s: &SettlementSignals) -> bool;
206+
// 阈值:item_count ≥ 50;或 周期:≥90 天未结算且 item_count ≥ 20
207+
```
208+
209+
### 5.2 结算向导(用户可见仪式,三步)
210+
211+
1. **核心提炼**:组内词汇表/高频术语汇总为「组核心」笔记(本地规则;
212+
AI 精修可选,复用 AiRefine 协议)
213+
2. **重复合并**:碎片/卡文本 Jaccard 去重(复用 asr_dedupe 归一化口径),
214+
展示合并对由用户确认(不静默删)
215+
3. **低价值归档**:无卡绑定且 180 天未访问的碎片标记 archived
216+
(fragments 补 status 列;不删除——可恢复)
217+
218+
### 5.3 settlements 记录
219+
220+
```sql
221+
CREATE TABLE IF NOT EXISTS settlements (
222+
id INTEGER PRIMARY KEY AUTOINCREMENT,
223+
group_id INTEGER NOT NULL REFERENCES note_groups(id) ON DELETE CASCADE,
224+
stats_json TEXT NOT NULL, -- 提炼条数/合并对数/归档数
225+
created_at INTEGER NOT NULL
226+
);
227+
-- fragments 补列(ensure_column):status TEXT NOT NULL DEFAULT 'active'
228+
```
229+
230+
结算完成落 metrics_events(group_settled)——北极星组成之一。
231+
232+
## 六、测试策略
233+
234+
- 纯函数全 TDD(AAA + golden 用例):group_route(高/低/冲突/零信号/系列直判)、
235+
schedule(四档评分间隔单调性、Again 重置)、settlement_due(阈值/周期边界)、
236+
卡片生成幂等、去重合并
237+
- 数据层用 `:memory:` 库(db_notes_tests 同款 mem_db 范式)
238+
- command 层仅参数校验与编排,无业务逻辑(AGENTS.md §6)
239+
- 模型文件零依赖(调度器纯算法;FSRS crate 无模型资产)
240+
241+
## 七、版本交付与提交纪律
242+
243+
每个小版本独立交付闭环(代码 + 测试 + 需求池状态同步 + 版本文档 + 提交):
244+
- v0.11.0:`feat(groups): ...` 3-5 个原子提交(原子层/接线/commands/UI/文档)
245+
- v0.11.1 同上;v0.11.2 启动时补 ADR-018(fsrs 依赖引入)
246+
- v0.11.3 收尾全系列;需求池 REQ-195~198 状态逐版推进
247+
- 单文件 ≤300 行(超限即拆 + 豁免登记);所有新模块含 @ai-context 注释

0 commit comments

Comments
 (0)