Skip to content

⚠️ 2026-08-18 更新(cross-end/24):本文多处提到的 bin/patch_lemmatizer_assets.py已删除。资产修正层现在是 src-tauri/examples/build_dict.rs 的 §2.4 corrections + §2.5 force_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.dbsrc-tauri/assets/reading_vocab.dbassets/databases/reading_vocab.db
surface_to_base.jsonsrc-tauri/assets/nlp/surface_to_base.jsonassets/nlp/surface_to_base.json3.2M / 140,370 条
base_forms.jsonsrc-tauri/assets/nlp/base_forms.jsonassets/nlp/base_forms.json1.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 生成侧(核心)

  1. tools/vocabulary_builder_v3/config.yamlpaths: 下新增 lemmatizer_base_forms: ../../assets/nlp/base_forms.jsonlemmatizer_surface_to_base 已存在)。
  2. 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 依赖)。
  3. 重建 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' = transferSELECT 1 FROM lemma_base_forms WHERE base='as' 命中。
    • 记录新 SHA256(更新 baseline_sha256 锚点)。

2.2 RVH 运行时侧(让 Dart 改从 db 读)

  1. lib/core/nlp/lemmatizer_flutter_loader.dart:当前并行 rootBundle.loadString 两个 JSON → 改为从已就绪的 reading_vocab.db SELECT 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)。
  2. pubspec.yaml(line 130):从 assets: 列表移除 assets/nlp/(停止打包)。 保留磁盘上的 assets/nlp/*.json 文件——pipeline 仍读它们当构建输入(config 路径指向此处),只是 app 不再 bundle。
  3. CLI / 测试 wiringbin/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 与契约。

  1. src-tauri/src/commands/lemmatizer.rsSURFACE_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!
  2. src-tauri/assets/nlp/*.json:从 include_str! 解绑后不再打包;文件保留(RB cargo run --example build_dict 的产物 + 交给 pipeline 的输入)。
  3. scripts/sync-rvh-vocabulary.sh:放宽「恰好 1 表 = vocabulary」断言(TABLE_COUNT -eq 1)→ 允许 vocabulary + lemma_surface_to_base + lemma_base_forms + lemma_meta(按白名单或计数 ==4 校验)。
  4. src-tauri/src/db/helpers.rs:bump VOCABULARY_SEED_VERSION 触发装机用户重灌(拿到带新表的 db)。
    • 确认 helpers.rs 的 reseed merge SQL 仍只 ATTACH 读 vocabulary 表;新三张表是 lemmatizer 运行时直接读的,不进 notebook merge 流程。

4. 落地顺序(防 byte-equal 漂移 / 防装机用户拉空)

  1. ✅ 对齐表契约(本文件 §1)——已定。
  2. RVH:改 config + generate_db → 出带三张表的新 db → 改 Dart loader + main() 顺序 → 移除 pubspec 资产 → RVH 端验证 lemmatize 回归。
  3. RB:先放宽 sync-rvh-vocabulary.sh 断言 → 跑同步脚本拉新 db → bump VOCABULARY_SEED_VERSION → 改 lemmatizer.rs 加载层 + 解绑 include_str → RB 端验证。
    • 顺序关键:先放宽断言再同步,否则同步脚本因「表数 ≠ 1」中止。
  4. 两端各跑 lemmatize 回归用例确认行为零变化。

5. 验证清单(两端各做)

  • [ ] db 表数 = 4(vocabulary + 三张 lemma_*);行数 140,370 / 101,646。
  • [ ] lemmatize() 行为零变化transferred→transfermice→mousepicked→pickpassed→passtrying→tryas→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 资产)。