主题
⚠️ 2026-08-18 更新(cross-end/24):本文多处提到的
bin/patch_lemmatizer_assets.py已删除。资产修正层现在是src-tauri/examples/build_dict.rs的 §2.4corrections+ §2.5force_as_base(生成器自带落地断言),重跑生成器即可字节复现资产。下文凡写 「必须在 patch_lemmatizer_assets.py patch 之后的 JSON 上 bake」的地方,现在读作 「必须在 build_dict 重跑之后的 JSON 上 bake」。lemma 表行数契约也已从 140,370 / 101,646 改为 140,281 / 101,709。
13 — lemmatizer 资产折叠进 reading_vocab.db(RVH 会话提示 / 跨端握手)
物理位置:本文件在 RB 仓库
~/reading-browser/docs/cross-end/13-lemmatizer-fold-handoff.md。 RVH 会话用绝对路径读:Read ~/reading-browser/docs/cross-end/13-lemmatizer-fold-handoff.md。 跨仓库引用一律~-锚定绝对路径(RB→~/reading-browser/...,RVH→~/reading_vocab_helper/...)。创建:2026-06-15 · RB 先行(§9)· 关联 backlog 条目「lemmatizer 资产折叠」
0. 一句话目标
今天两端各自打包并加载 3 个 byte-equal 资产:
| 资产 | RB 路径 | RVH 路径 | 大小 |
|---|---|---|---|
reading_vocab.db | src-tauri/assets/reading_vocab.db | assets/databases/reading_vocab.db | — |
surface_to_base.json | src-tauri/assets/nlp/surface_to_base.json | assets/nlp/surface_to_base.json | 3.2M / 140,370 条 |
base_forms.json | src-tauri/assets/nlp/base_forms.json | assets/nlp/base_forms.json | 1.1M / 101,646 条 |
目标:把后两个 JSON 烤成 reading_vocab.db 里的两张表,两端运行时改从 db 读 lemmatizer Layer 1/2,停止打包 JSON。打包/加载的 byte-equal 资产从 3 → 1。
收益:① 同步面 3→1;② 消灭一类红线 #9 漂移——运行时归一器与「当初归一 PK 用的那份 surface_to_base」装在同一文件里,字节级保证一致;③ app 包/二进制变小。 不解决:lemmatizer Layer 3/4 算法代码(后缀规则 + 裁决 + NFC)两端各一份的对齐义务照旧——折叠只合并数据,不合并代码。
1. 表契约(握手核心 —— RB / RVH 必须按这个 schema 编码)
generate_db 在 reading_vocab.db 里新增三张表(与现有 vocabulary 表并存):
sql
-- Layer 1:屈折 surface → base(原 surface_to_base.json 的 "map")
CREATE TABLE lemma_surface_to_base (
surface TEXT PRIMARY KEY, -- 小写表面形
base TEXT NOT NULL -- 归一 base/lemma
); -- 140,370 行
-- Layer 2:base 形集合(原 base_forms.json 的 "base" 数组)
CREATE TABLE lemma_base_forms (
base TEXT PRIMARY KEY -- 小写 base 形
); -- 101,646 行
-- 出处/版本(原两个 JSON 的 version/count;RB 当前解析 version 字段但 #[allow(dead_code)],保留作 provenance)
CREATE TABLE lemma_meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
); -- 行:('surface_to_base_version', '<原 json version>'),
-- ('base_forms_version', '<原 json version>'),
-- ('surface_to_base_count', '140370'),
-- ('base_forms_count', '101646')确定性纪律:INSERT 必须 ORDER BY 主键(surface / base 升序)写入,保持 generate_db 输出 run-to-run 可复现(对齐既有 baseline_sha256 锚点)。db 是单一生产者(RVH pipeline 产出 → RB 用 sync-rvh-vocabulary.sh 整包复制),所以 RB/RVH byte-equal 由复制保证;确定性是为 pipeline 自身可复现/可审。
2. RVH 会话任务清单
全部在
~/reading_vocab_helper/。必须新会话(CLAUDE.md §9,工具链 = flutter/dart)。
2.1 pipeline 生成侧(核心)
tools/vocabulary_builder_v3/config.yaml:paths:下新增lemmatizer_base_forms: ../../assets/nlp/base_forms.json(lemmatizer_surface_to_base已存在)。tools/vocabulary_builder_v3/bin/generate_db.dart(359 行):在写完vocabulary表后, 读这两个 JSON(路径取自 config),按 §1 DDL 建三张表 + 排序写入。- 注意:必须在
patch_lemmatizer_assets.py已 patch 之后的 JSON 上 bake—— 即as→a映射已删(lemma_surface_to_base不含as这条 key)、base_forms仍含"as"、picked/passed/trying已修正。 - 沿用 generate_db 既有 sqlite3 写法 + pragma,别引入新的非确定性(无时间戳/无 random/无 rowid 依赖)。
- 注意:必须在
- 重建 db:跑 generate_db(或 build_all),核对:
SELECT COUNT(*) FROM sqlite_master WHERE type='table'→ vocabulary + lemma_surface_to_base + lemma_base_forms + lemma_meta(注意 RB 同步脚本断言要随之放宽,见 §3)。- 行数 140,370 / 101,646。
- 抽查
SELECT base FROM lemma_surface_to_base WHERE surface='transferred'=transfer;SELECT 1 FROM lemma_base_forms WHERE base='as'命中。 - 记录新 SHA256(更新
baseline_sha256锚点)。
2.2 RVH 运行时侧(让 Dart 改从 db 读)
lib/core/nlp/lemmatizer_flutter_loader.dart:当前并行rootBundle.loadString两个 JSON → 改为从已就绪的reading_vocab.dbSELECT surface,base FROM lemma_surface_to_base+SELECT base FROM lemma_base_forms, 组装成Map<String,String>/Set<String>喂给Lemmatizer.preloadFromJsonStrings(...)(或新增等价的Lemmatizer.preloadFromMaps({baseForms, surfaceToBase}),避免再走 JSON 序列化)。- 启动顺序坑:
main()里 lemmatizer 加载目前与 db 拷贝是并行的(app_database.dart:377从 rootBundle 拷 db)。 改 db-load 后,lemmatizer 加载必须排在 db 就绪之后——调整main()wiring(db ready → 再 load lemmatizer)。
- 启动顺序坑:
pubspec.yaml(line 130):从assets:列表移除assets/nlp/(停止打包)。 保留磁盘上的assets/nlp/*.json文件——pipeline 仍读它们当构建输入(config 路径指向此处),只是 app 不再 bundle。- CLI / 测试 wiring:
bin/phase0_normalize.dart/test/core/nlp/lemmatizer_test_setup.dart仍可继续用dart:io File读 JSON(它们是 build/test 期,不受 app 打包影响)——不用改,除非你想统一。建议本任务不动,缩小爆炸半径。
2.3 不动的
bin/patch_lemmatizer_assets.py:照旧 patch 输入 JSON(patch 在 bake 之前发生)。wordlist_builder.dart的_lemmatize:照旧读surface_to_base.json当输入做 PK 归一(§ 上轮讨论:lemmatizer 在 pipeline 里只做 PK 归一,不补内容)。
3. RB 侧对侧(在 RB 会话做 —— 列在此让 RVH 对齐顺序)
RVH 会话不要改 RB 文件;这块由 RB 会话负责,写在这里只为对齐 ordering 与契约。
src-tauri/src/commands/lemmatizer.rs:SURFACE_TO_BASE/BASE_FORMS两个LazyLock+include_str!("../../assets/nlp/*.json")→ 改为从 attach 的 reading_vocab.db 一次性 SELECT 进内存 (OnceCell<HashMap>/OnceCell<HashSet>,app setup 拿到 resource 路径后 eager init)。 查词路径不变——仍内存 O(1),只换数据来源;不要改成每次查 query db。去掉两处include_str!。src-tauri/assets/nlp/*.json:从include_str!解绑后不再打包;文件保留(RBcargo run --example build_dict的产物 + 交给 pipeline 的输入)。scripts/sync-rvh-vocabulary.sh:放宽「恰好 1 表 = vocabulary」断言(TABLE_COUNT -eq 1)→ 允许vocabulary + lemma_surface_to_base + lemma_base_forms + lemma_meta(按白名单或计数 ==4 校验)。src-tauri/src/db/helpers.rs:bumpVOCABULARY_SEED_VERSION触发装机用户重灌(拿到带新表的 db)。- 确认 helpers.rs 的 reseed merge SQL 仍只 ATTACH 读
vocabulary表;新三张表是 lemmatizer 运行时直接读的,不进 notebook merge 流程。
- 确认 helpers.rs 的 reseed merge SQL 仍只 ATTACH 读
4. 落地顺序(防 byte-equal 漂移 / 防装机用户拉空)
- ✅ 对齐表契约(本文件 §1)——已定。
- RVH:改 config + generate_db → 出带三张表的新 db → 改 Dart loader + main() 顺序 → 移除 pubspec 资产 → RVH 端验证 lemmatize 回归。
- RB:先放宽
sync-rvh-vocabulary.sh断言 → 跑同步脚本拉新 db → bumpVOCABULARY_SEED_VERSION→ 改lemmatizer.rs加载层 + 解绑 include_str → RB 端验证。- 顺序关键:先放宽断言再同步,否则同步脚本因「表数 ≠ 1」中止。
- 两端各跑 lemmatize 回归用例确认行为零变化。
5. 验证清单(两端各做)
- [ ] db 表数 = 4(vocabulary + 三张 lemma_*);行数 140,370 / 101,646。
- [ ]
lemmatize()行为零变化:transferred→transfer、mice→mouse、picked→pick、passed→pass、trying→try、as→as(patch 特例:surface 无 as→a,靠 base_forms 含 as 自映射返回)。 - [ ] app 不再打包
assets/nlp/*.json(RB 无 include_str / RVH pubspec 无 assets/nlp),启动正常、双击查词归一正常。 - [ ] 装机用户重灌路径:bump 版本后旧用户重灌,lemmatizer 从新 db 读,归一正常。
- [ ] 更新
baseline_sha256锚点;新 db SHA 双端一致(复制保证)。
6. 完成后回写
- RVH:在
~/reading_vocab_helper/CHANGELOG.md+ 自己的跨端日志记一笔;新 db SHA 通报 RB。 - RB(对侧会话):
docs/database-schema.md加这三张表说明 + 红线 #10 措辞补「reading_vocab.db 现含 lemmatizer 表」;docs/vocabulary-domain-knowledge.md§7 注明 lemmatizer 资产已折叠进 db;backlog 条目移除;写14-rb-lemmatizer-fold-confirmation.md收口。 - 红线 #9 措辞更新:byte-equal 要求从「surface_to_base.json / base_forms.json 运行时资产」转为「reading_vocab.db 内 lemma_* 表」;JSON 降级为 pipeline 构建输入(仍需 RB build_dict ↔ RVH 一致,但不再是 shipped runtime 资产)。