Skip to content

13 · lemmatizer 资产折叠进 reading_vocab.db reseed 交接(RVH → RB)

RVH 段二交付:接 RB 表契约 ~/reading-browser/docs/cross-end/13-lemmatizer-fold-handoff.md, 把 surface_to_base.json + base_forms.json 两个打包资产烤进 reading_vocab.db 的三张 lemma_* 表,RVH 运行时改从 db 读 lemmatizer Layer 1/2,停止打包 JSON。 日期:2026-06-15 · RVH 主库 Schema 不变 / 预装库 新增三表(vocabulary 内容 byte-identical) · 优先级:架构收敛(打包/加载资产 3→1,消灭一类红线 #9 漂移)

⚠️ 行数/SHA 已被 v26 取代(2026-08-18)lemma_* 表的表契约(DDL)不变, 但行数会随每次资产修正变动(v26 140,281/101,709 → v27 140,277/101,713 → …)。 当前权威值只在根目录 CLAUDE.md 红线 #5e 维护,本文不再跟随 —— 这几个数字 在 v23/v24/v26/v27 已反复漂移,就地重写只会再漂一次。 (RB build_dict.rs 修正 178 个 surface 的 AGID 古语/残根错映射)。 当前权威值见根目录 CLAUDE.md 红线 #5e;本轮落地见 24-rb-lemmatizer-asset-v26-reseed-handoff.md(RB 交接)+ 25-rvh-lemmatizer-v26-confirmation.md(RVH 回执)。 本文 §1 的契约数字已就地更新;§3 验证回执保留 2026-06-15 当时的实测值不改 (那是真机日志与当时的 db SHA,属 as-built 记录)。


TL;DR(RB 要做的事 —— 全在 RB 会话,本文件只对齐契约 & 报新 SHA)

  1. 先放宽 scripts/sync-rvh-vocabulary.sh 断言TABLE_COUNT -eq 1 → 允许 4 表白名单) → 跑同步脚本拉新 db(顺序关键,否则脚本因「表数 ≠ 1」中止)。
  2. 拉到的 reading_vocab.db 新 SHA = 05facc9bbdadcaccadd3b3a32a1e3c03e42df10485ece5922d352f19c8f249e6 (旧 c81d137492…)。bump VOCABULARY_SEED_VERSION 触发装机用户重灌。
  3. src-tauri/src/commands/lemmatizer.rs:两个 LazyLock + include_str! → 从 attach 的 reading_vocab.db 一次性 SELECT 进内存OnceCell<HashMap> / OnceCell<HashSet>, app setup eager init)。查词路径不变(仍内存 O(1))。去掉两处 include_str!
  4. 确认 helpers.rs reseed merge SQL 仍只 ATTACH 读 vocabulary 表;三张 lemma_* 表是 lemmatizer 运行时直接读的,不进 notebook merge 流程。
  5. 收口写 14-rb-lemmatizer-fold-confirmation.md

RVH 不动 RB 仓库任何文件(段三由 RB 会话做)。


§1 表契约回执(按 RB §1 编码,已验证)

reading_vocab.db 新增三张表与现有 vocabulary 并存:

sql
CREATE TABLE lemma_surface_to_base (
  surface TEXT PRIMARY KEY,   -- 小写表面形
  base    TEXT NOT NULL       -- 归一 base/lemma
);  -- 行数见 CLAUDE.md 红线 #5e(← surface_to_base.json "map")

CREATE TABLE lemma_base_forms (
  base TEXT PRIMARY KEY       -- 小写 base 形
);  -- 行数见 CLAUDE.md 红线 #5e(← base_forms.json "base")

CREATE TABLE lemma_meta (
  key   TEXT PRIMARY KEY,
  value TEXT NOT NULL
);  -- 4 行 provenance

lemma_meta 实际内容(按 key 升序写入):

keyvalue
base_forms_count当前值见 CLAUDE.md 红线 #5e
base_forms_versionagid-2016.01.19
surface_to_base_count当前值见 CLAUDE.md 红线 #5e
surface_to_base_versionagid-2016.01.19

确定性纪律(RB §1):INSERT 按主键升序写入(surfaces..sort() / bases.toSet().toList()..sort()), generate_db 输出 run-to-run 可复现。bake 发生在修正层套用之后的 JSON 上(当时的载体是 patch_lemmatizer_assets.py;该脚本已于 2026-08-18 删除,修正层单一真相源改为 RB src-tauri/examples/build_dict.rs §2.4 corrections / §2.5 force_as_base): as 不在 lemma_surface_to_base(surface 无 as→a 映射)、as lemma_base_forms(靠 base 自映射 返回 as)、picked→pick / passed→pass / trying→try 已修正。


§2 RVH 落地清单(已完成)

生成侧(pipeline)

  • tools/vocabulary_builder_v3/config.yamlpathslemmatizer_base_formslemmatizer_surface_to_base 已存在)。
  • tools/vocabulary_builder_v3/lib/db_generator.dartDbGeneratorsurfaceToBasePath / baseFormsPath 构造参数;generate()_verify(db) 后调 _bakeLemmaTables(db)(建三表 + 排序写入,沿用既有 sqlite3 写法,无时间戳/无 random)。
  • tools/vocabulary_builder_v3/bin/generate_db.dart:从 config 取两路径透传给 DbGenerator
  • 不动patch_lemmatizer_assets.py(patch 在 bake 之前)、wordlist_builder.dart::_lemmatize(仍读 JSON 当 PK 归一输入)。

运行时侧(Flutter)

  • lib/core/nlp/lemmatizer.dart:加 preloadFromMaps({baseForms, surfaceToBase})(从内存 Set/Map 直装, 绕过 JSON 反序列化)。preloadFromJsonStrings 保留(CLI / 测试用)。
  • lib/core/nlp/lemmatizer_flutter_loader.dartloadLemmatizerFromAssets(rootBundle 读 JSON) → loadLemmatizerFromDatabase(从 preinstalled_vocab.db SELECT lemma_surface_to_base / lemma_base_formspreloadFromMaps)。复用预装库导入拷出的 preinstalled_vocab.db缺表自愈(探测无 lemma 表 → 从 bundled asset 重拷兜底)。
  • lib/core/services/startup_initialization_service.dart:新增 Stage 1.5 _initLemmatizer(), 排在 _initDatabase() 之后(串行)。启动顺序坑已解:lemmatizer 从 main() 并行批移走 → db 先于 lemmatizer 就绪。
  • lib/main.dart:从 fast-init Future.wait 移除 loadLemmatizerFromAssets()(四并行 → 三并行)+ 移除 import。
  • pubspec.yaml:移除 assets/nlp/ 打包;JSON 文件留盘(pipeline config 路径仍指向,作构建输入)。

§3 验证回执

结果
db 表数4(vocabulary + lemma_surface_to_base + lemma_base_forms + lemma_meta)
行数vocabulary 18899(不变)/ lemma_surface_to_base 140370 / lemma_base_forms 101646 / lemma_meta 4
db↔JSON 内容比对byte-equal(surface_to_base diff 0 / base_forms diff 0,全量逐行)
vocabulary 内容byte-identical(除 created_at/updated_at 外全列 dump SHA 一致)
lemmatize 抽查transferred→transfer / mice→mouse / picked→pick / passed→pass / trying→tryas 不在 surface 表、在 base 表
lemma-regression.txt行为零变化(as→as base 层 / 其余 dictionary 层)
设备运行时(Android 15,real device)启动序 Database initializedLoaded from db: 140370 surface→base, 101646 base formsInitialization completed in 2862ms,无异常。自愈兜底路径实测命中(既存安装旧 preinstalled_vocab.db 无 lemma 表 → 重拷成功)

新预装库 SHA25605facc9bbdadcaccadd3b3a32a1e3c03e42df10485ece5922d352f19c8f249e6 (旧 c81d137492bd4a20bc14fbb697ad441d6d5868b4531c7e92c12c50be76c0f4ba)。 output/baseline_sha256.txt 锚点已更新。


§4 两点 RVH 特有说明(RB 对齐用)

  1. RVH 主库 schema 不变、_preinstalledVocabVersion 不变lemma_* 表只在预装库里, RVH 运行时通过 preinstalled_vocab.db(asset 的拷贝)直接读,不 merge 进 app 主库(RB 同理:lemma 表是 运行时直读,不进 notebook merge)。vocabulary 内容零变化,故 RVH 端无需 bump 预装版本触发 upsert; 既存安装由 loader 缺表自愈重拷处理(实测命中)。RB 端因 reseed 复制整包 db,仍需 bump VOCABULARY_SEED_VERSION 让装机用户拿到带新表的 db。
  2. 红线 #5e 措辞迁移:byte-equal 要求从「surface_to_base.json / base_forms.json 运行时资产」 转为「reading_vocab.db 内 lemma_* 表」;JSON 降级为 pipeline 构建输入(仍需 RB build_dict ↔ RVH 一致, 但不再是 shipped runtime 资产)。lemmatizer Layer 3/4 算法代码(后缀规则 + 裁决 + NFC)两端各一份的对齐义务照旧——折叠只合并数据,不合并代码。

关联

  • RB 表契约源文档:~/reading-browser/docs/cross-end/13-lemmatizer-fold-handoff.md
  • RB 收口(待 RB 会话写):~/reading-browser/docs/cross-end/14-rb-lemmatizer-fold-confirmation.md
  • RVH CHANGELOG:本批 [Unreleased] / Changed
  • 红线:CLAUDE.md §跨端 Sync 协议红线 #5e(资产 byte-equal,措辞迁移见 §4.2)