diff --git a/HANDOFF.md b/HANDOFF.md index f95f1617..5f156bac 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -2,9 +2,9 @@ ## 0. 元信息 -- 更新:2026-11-14 / Claude Code(**air 本机**)。阶段:**已发布 1.3.0 / build 43**(tag `v1.3.0`,Release、站点已上线); +- 更新:2026-11-15 / Claude Code(**air 本机**)。阶段:**已发布 1.3.0 / build 43**(tag `v1.3.0`,Release、站点已上线); air 与 pro 均已装发版产物(执行 `9cb738c9…`),30/30 样本、性能、内存、IT-22 A 档 / N1–N5 全部 PASS。 -- 分支:`main` = `343ce01`(PR #313 统一拆字标准 v1 已合入);`dev` = `05f25d1` + 后续文档。 +- 分支:`main` = `7014346`(PR #314 86 专注 + 标准变体已合入);`dev` = 本轮 D2 字形别名(ADR-359/报告 285)待开 PR。 - 版本:`Cargo.toml` = **1.3.0**、`packaging/build-number` = **43**;最新 tag **`v1.3.0`**。**下次正式发版前必须重新绑定候选身份。** - 最近验证:`make verify` PASS(528 产物零网络符号)、`make macos-offline-tests` **67 文件 / 335 例 PASS**、`build.sh --strict` PASS、 `tools/check-level3-codes.py` PASS(3,362 条)。 @@ -33,7 +33,7 @@ | **B0 schema v3(报告 243)** | 占位组件 `null` + 键位推断;修掉 D3 字根按字符解析(空格混入组件、BOM/CRLF)缺陷。 | | **13% 结构不一致归因(报告 242)** | 66/512 是两套**分类口径**差异(独体字判杂合、拆法不同、包围),不是数据错误;处置=五笔识别码为权威、IDS 只作校验、差异归档。 | | **corpus 数据来源调研(`docs/research/corpus-data-sources.md`)** | 主选 `yi-bai/ids`(MIT)+ Unihan 交叉校验;**不采用** Make Me a Hanzi 的 Arphic 图形、Hanzi Writer、KanjiVG、字典抓取。 | -| **B0 现状(schema v7,2026-11-15)** | 1,000 字:`full` **916** / `partial` **80** / `unknown` **4**。3,000 字:`full` **2,578** / `partial` **268** / `unknown` **154**;运行时同。标准 v1(ADR-358)下 `full` 必须通过标准字根/键位校验;98/新世纪仅预留 profile,不实现;报告 280–284。 | +| **B0 现状(schema v7,2026-11-15)** | 1,000 字:`full` **928** / `partial` **68** / `unknown` **4**。3,000 字:`full` **2,614** / `partial` **232** / `unknown` **154**;运行时同。标准 v1(ADR-358)+ D2 字形别名归一化(ADR-359)下 `full` 必须通过标准字根/键位校验;98/新世纪仅预留 profile,不实现;报告 280–285。 | ### 2.2 发布线(2026-11-05,1.0.0 → 1.1.0/build 38) @@ -67,7 +67,7 @@ 初版清单误用 D3 BMP PUA,已更正为 D2 补充区;可定位 79 个 PUA,按 `(PUA, 键位)` 核验 66 条, 状态 `verified 58 / partial 8 / unresolved 13`。B0 1,000 字 `full` 735→919;运行时 3,000 字 `full` 1,904→2,454; D3 不再作 `componentsSource`;未解决项仍以 `?:键` 占位,不猜。 -- **P1 · 数据与方案**:corpus M2-1 调研 ✅、N1-d ✅、N2/N3/N4 ✅(ADR-355);余项是覆盖扩量(86.3%→98%)、13 个语境歧义 PUA、P1-5 拆字说明同步、多方案码表(TBD-2)。 +- **P1 · 数据与方案**:corpus M2-1 调研 ✅、N1-d ✅、N2/N3/N4 ✅(ADR-355)、cjk-decomp 扩量 ✅(ADR-357)、标准 v1 ✅(ADR-358)、D2 字形别名 ✅(ADR-359);余项是覆盖扩量(87.1%→98%,受许可数据限制)、13 个语境歧义 PUA、P1-5 拆字说明同步、多方案码表(TBD-2,98/新世纪暂不实现)。 - **P2 · 有证据才做**:排序观察期、位置记忆、冷首键、拼音后端评估、n-gram(均写触发条件)。 - **明确不做**:云/账号/广告/统计/剪贴板/语音;平台扩展单独立项,不与 macOS 同批。 @@ -132,6 +132,11 @@ make macos-offline-tests ## 9. 会话日志(最近) +- 2026-11-15 / Claude Code(air):**D2 字形别名归一化(ADR-359,报告 285)**。逐条复查标准 v1 的 + 73 条 `positionMismatch`,把 D2 用假名/注音符号代替部件的同形写法归一化(`リ→刂`、`ㄗ→卩`、`ク→夕`), + 另补 `彑`(X)到标准字根清单、`ノ` 作撇笔画。B0 1,000 字 `full` 916→**928**、`partial` 80→**68**; + 3,000 字 `full` 2,578→**2,614**、`partial` 268→**232**;运行时同,**零降级**(36 条 partial→full)。 + 剩余 partial 多为 D2 视觉拆法(如 `交` 记 `亠八乂`,86 取 `六乂`),需 canonical 根序数据,不猜。 - 2026-11-15 / Claude Code(air):**PR #313 已合入 main(343ce01)**。统一拆字标准 v1 落地; B0 3,000 字 `full` 2,578 / `partial` 268 / `unknown` 154;1,000 字 `full` 916。 98/新世纪复用校验逻辑,但需各自的字根/码表 profile。 diff --git a/ROADMAP.md b/ROADMAP.md index 83a4ceb3..6242a3c8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -336,7 +336,7 @@ ADR-147:Terminal正常实际提交“你”及wq前缀确认后的failure/INT/ 交叉校验;不采用无许可五笔字根字体。N1-d 已更正并接入:3,000 字可定位 79 个 D2 补充区 PUA, 按 `(PUA, 键位)` 核验 66 条(verified 58 / partial 8 / unresolved 13);运行时 full 1,904→2,454。 N2/N3/N4 已落地(ADR-355/356,报告 281/282):56 个成字字根真实笔顺、结构覆盖 2,766 条、8 个 D2 漏拆补齐; - 统一拆字标准 v1(ADR-358)后 B0 full 2,578(85.9%)、运行时 full 2,578,仍未达 ≥98%; + 统一拆字标准 v1(ADR-358)+ D2 字形别名(ADR-359)后 B0/runtime full 2,614(87.1%),仍未达 ≥98%; 剩余 154 个 unknown;标准要求 full 逐组件通过标准字根/键位校验;98/新世纪仅预留 profile,当前不实现。 验收=高频 3,000 字 ≥98% 可解释、许可齐全、体积/延迟实测、模块内可独立构建。 不属于发布阻塞;若要优化,先用 phase 钩子确认宿主侧占比,再决定是否立门禁。 diff --git a/data/generated/B0-runtime.tsv b/data/generated/B0-runtime.tsv index 61152ca2..120690d5 100644 --- a/data/generated/B0-runtime.tsv +++ b/data/generated/B0-runtime.tsv @@ -214,7 +214,7 @@ 四 lhng full 四:l lh 子 bbbb full bb 书 nnhy full 乛:n 𠃌:n 丨:h 丶:y -仍 wen partial 亻:w 乃:e we +仍 wen full 亻:w 乃:e N|折|左右 we 难 cwyg full 又:c 亻:w 隹:y G|横|左右 cw 西 sghg full 西:s 东 aii full 七:a 小:i I|捺|杂合 ai @@ -300,12 +300,12 @@ 条 tsu full 夂:t 木:s U|捺|上下 ts 剑 wgij full 人:w 一:g 丷:i 刂:j 神 pyjh full 礻:p 丶:y 日:j 丨:h -动 fcln partial 二:f 厶:c 力:l +动 fcln full 二:f 厶:c 力:l N|折|左右 湖 ideg full 氵:i 古:d 月:e G|横|左右 河 iskg full 氵:i 丁:s 口:k G|横|左右 白 rrrr full 官 pnhn partial 宀:p N|折|左右 pn -师 jgmh partial +师 jgmh full 刂:j 一:g 冂:m 丨:h 仅 wcy full 亻:w 又:c Y|捺|左右 教 ftbt full 土:f 丿:t 孑:b 夂:t 哪 kvfb full 口:k 刀:v 二:f 阝:b kv @@ -463,7 +463,7 @@ 线 xgt full 纟:x 戋:g T|撇|左右 xg 忽 qrnu partial 勹:q U|捺|上下 七 agn full 七:a ag -久 qyi partial I|捺|杂合 qy +久 qyi full 夕:q 乀:y I|捺|杂合 qy 史 kqi full 口:k 乂:q I|捺|杂合 kq 辖 lpdk full 车:l 宀:p 三:d 口:k 英 amdu full 艹:a ㄇ:m 大:d U|捺|上下 @@ -481,9 +481,9 @@ 八 wty full 八:w 喜 fkuk full 士:f 口:k 䒑:u 口:k 论 ywxn full 讠:y 人:w 匕:x N|折|左右 -伤 wtln partial 亻:w 𠂉:t 力:l +伤 wtln full 亻:w 𠂉:t 力:l N|折|左右 架 lksu full 力:l 口:k 木:s U|捺|上下 -归 jvg partial G|横|左右 jv +归 jvg full 刂:j 彐:v G|横|左右 jv 退 vepi partial 彐:v I|捺|杂合 游 iytb full 氵:i 方:y 𠂉:t 子:b 实 pudu full 宀:p 丷:u 大:d U|捺|上下 pu @@ -621,7 +621,7 @@ 球 gfiy partial 王:g 守 pfu full 宀:p 寸:f U|捺|上下 pf 俄 wtrt full 亻:w 丿:t 扌:r T|撇|左右 -功 aln partial 工:a 力:l al +功 aln full 工:a 力:l N|折|左右 al 端 umdj full 立:u 山:m 丆:d 〢:j 散 aety full 龷:a 月:e 夂:t Y|捺|左右 义 yqi full 丶:y 乂:q I|捺|杂合 yq @@ -696,7 +696,7 @@ 微 tmgt full 彳:t 山:m 一:g 夂:t 虎 hamv full 虍:h 七:a 几:m V|折|杂合 ha 布 dmhj full 𠂇:d 冂:m 丨:h J|竖|上下 -节 abj partial 艹:a J|竖|上下 ab +节 abj full 艹:a 卩:b J|竖|上下 ab 升 tak full 丿:t 廾:a K|竖|杂合 依 wyey full 亻:w 亠:y 衣:e Y|捺|左右 苦 adf full 艹:a 古:d F|横|上下 @@ -716,7 +716,7 @@ 广 yygt full 广:y 怒 vcnu full 女:v 又:c 心:n U|捺|上下 孙 biy full 孑:b 小:i Y|捺|左右 bi -绣 xten partial 纟:x 禾:t 乃:e +绣 xten full 纟:x 禾:t 乃:e N|折|左右 版 thgc full 丿:t 丨:h 一:g 又:c 坏 fgiy partial 土:f 丆:g Y|捺|左右 组 xegg full 纟:x 月:e 一:g G|横|左右 @@ -901,7 +901,7 @@ 钟 qkhh full 钅:q 口:k 丨:h H|竖|左右 咬 kuqy partial 口:k 糖 oyvk full 米:o 广:y 彐:v 口:k -劝 cln partial 又:c 力:l cl +劝 cln full 又:c 力:l N|折|左右 cl 宜 pegf full 宀:p 月:e 一:g F|横|上下 腰 esvg full 月:e 覀:s 女:v G|横|左右 杩 scg full 木:s 马:c 一:g @@ -975,9 +975,9 @@ 饮 qnqw full ⺈:q 乚:n ⺈:q 人:w 牌 thgf full 丿:t 丨:h 一:g 十:f 爹 wqqq full 八:w 乂:q 夕:q 夕:q -劲 caln partial ス:c 工:a 力:l +劲 caln full ス:c 工:a 力:l N|折|左右 碗 dpqb full 石:d 宀:p 夕:q ⺋:b -爷 wqbj partial 八:w 乂:q J|竖|上下 +爷 wqbj full 八:w 乂:q 卩:b J|竖|上下 魔 yssc full 广:y 木:s 木:s 厶:c 君 vtkd full 彐:v 丿:t 口:k D|横|杂合 孔 bnn full 孑:b 乚:n N|折|左右 @@ -1011,8 +1011,8 @@ 毁 vamc full 臼:v 工:a 几:m 又:c va 屾 mmh unknown 蒙 apge partial 艹:a 冖:p E|撇|杂合 -切 avn partial 七:a 刀:v av -防 byn partial 阝:b 方:y by +切 avn full 七:a 刀:v N|折|左右 av +防 byn full 阝:b 方:y N|折|左右 by 尖 idu full 小:i 大:d U|捺|上下 id 踢 khjr full 口:k 止:h 日:j R|撇|上下 卡 hhu full 上:h 卜:h U|捺|上下 @@ -1215,7 +1215,7 @@ 猜 qtge full 犭:q ノ:t 主:g 月:e 终 xtuy full 纟:x 夂:t 丷:u Y|捺|左右 票 sfiu full 覀:s 二:f 小:i U|捺|上下 -粉 owvn partial 米:o 八:w 刀:v ow +粉 owvn full 米:o 八:w 刀:v N|折|左右 ow 泡 iqnn full 氵:i 勹:q 巳:n N|折|左右 继 xonn full 纟:x 米:o 乚:n N|折|左右 xo 沪 iynt full 氵:i 丶:y 尸:n ノ:t @@ -1240,7 +1240,7 @@ 递 uxhp full 丷:u 弓:x 丨:h 辶:p 庆 ydi full 广:y 大:d I|捺|杂合 yd 品 kkkf full 口:k 口:k 口:k F|横|上下 -扔 ren partial 扌:r 乃:e re +扔 ren full 扌:r 乃:e N|折|左右 re 握 rngf full 扌:r 尸:n 一:g 土:f 煮 ftjo full 土:f 丿:t 日:j 灬:o 挤 ryjh full 扌:r 文:y 刂:j H|竖|左右 @@ -1281,7 +1281,7 @@ 鍏 qnhh unknown 搜 rvhc full 扌:r 臼:v 丨:h 又:c 忍 vynu full 刀:v 丶:y 心:n U|捺|上下 -助 egln partial 月:e 一:g 力:l +助 egln full 月:e 一:g 力:l N|折|左右 炸 othf full 火:o 𠂉:t 丨:h 二:f 鎴 qthn unknown 傻 wtlt full 亻:w 丿:t 囗:l 夂:t @@ -1294,7 +1294,7 @@ 程 tkgg full 禾:t 口:k 王:g G|横|左右 窝 pwkw full 宀:p 八:w 口:k 人:w 测 imjh full 氵:i 贝:m 刂:j H|竖|左右 -抛 rvln partial 扌:r 九:v 力:l +抛 rvln full 扌:r 九:v 力:l N|折|左右 壳 fpmb full 士:f 冖:p 几:m B|折|上下 律 tvfh full 彳:t 彐:v 二:f 丨:h 库 ylk full 广:y 车:l K|竖|杂合 @@ -1310,7 +1310,7 @@ 剅 gkuj unknown 兽 ulgk full 丷:u 田:l 一:g 口:k 济 iyjh full 氵:i 文:y 刂:j H|竖|左右 -缘 xxey partial 纟:x Y|捺|左右 +缘 xxey full 纟:x 彑:x 豕:e Y|捺|左右 档 sivg full 木:s ⺌:i 彐:v G|横|左右 si 湾 iyox full 氵:i 亠:y 亦:o 弓:x 湿 ijog full 氵:i 日:j 业:o 一:g @@ -1462,7 +1462,7 @@ 荷 awsk full 艹:a 亻:w 丁:s 口:k 愣 nlyn full 忄:n 罒:l 方:y N|折|左右 灌 iaky full 氵:i 艹:a 口:k Y|捺|左右 -盼 hwvn partial 目:h 八:w 刀:v +盼 hwvn full 目:h 八:w 刀:v N|折|左右 润 iugg full 氵:i 门:u 王:g G|横|左右 臂 nkue full 尸:n 口:k 立:u 月:e 捏 rjfg full 扌:r 日:j 土:f G|横|左右 @@ -1489,7 +1489,7 @@ 翅 fcnd full 十:f 又:c 羽:n D|横|杂合 锌 quh full 钅:q 辛:u H|竖|左右 钉 qsh full 钅:q 丁:s H|竖|左右 qs -卫 bgd partial D|横|杂合 bg +卫 bgd full 卩:b 一:g D|横|杂合 bg 察 pwfi full 宀:p 癶:w 二:f 小:i 湁 irwk full 氵:i 扌:r 人:w 口:k 窗 pwtq full 宀:p 八:w 丿:t 夕:q @@ -1550,11 +1550,11 @@ 欠 qwu full ⺈:q 人:w U|捺|上下 qw 镜 qujq full 钅:q 立:u 日:j 儿:q 召 vkf full 刀:v 口:k F|横|上下 -拐 rkln partial 扌:r 口:k 力:l +拐 rkln full 扌:r 口:k 力:l N|折|左右 戒 aak full 戈:a 廾:a K|竖|杂合 仇 wvn full 亻:w 九:v N|折|左右 瞒 hagw full 目:h 艹:a 一:g 人:w -砌 davn partial 石:d 七:a 刀:v +砌 davn full 石:d 七:a 刀:v N|折|左右 乾 fjtn full 十:f 早:j 𠂉:t 乙:n 郑 udbh full 䒑:u 大:d 阝:b H|竖|左右 莲 alpu full 艹:a 车:l 辶:p U|捺|上下 @@ -1622,7 +1622,7 @@ 淡 iooy full 氵:i 火:o 火:o Y|捺|左右 io 吊 kmhj full 口:k 冂:m 丨:h J|竖|上下 涂 iwty partial 氵:i 人:w -访 yyn partial 讠:y 方:y +访 yyn full 讠:y 方:y N|折|左右 抚 rfqn full 扌:r 二:f 儿:q N|折|左右 诺 yadk full 讠:y 艹:a 𠂇:d 口:k 弃 ycaj full 亠:y 厶:c 廾:a J|竖|上下 @@ -1744,7 +1744,7 @@ 蹲 khuf full 口:k 止:h 丷:u 寸:f 戳 nwya full 羽:n 亻:w 隹:y 戈:a 耐 dmjf full 丆:d 冂:m 〢:j 寸:f -劫 fcln partial 土:f 厶:c 力:l +劫 fcln full 土:f 厶:c 力:l N|折|左右 堝 fmnk unknown 池 ibn full 氵:i 也:b N|折|左右 ib 哎 kaqy full 口:k 艹:a 乂:q Y|捺|左右 @@ -1767,7 +1767,7 @@ 鏂 qakk unknown 丘 rgd full 斤:r 一:g D|横|杂合 皱 qvhc full ⺈:q 彐:v 𠂇:h 又:c -奶 ven partial 女:v 乃:e ve +奶 ven full 女:v 乃:e N|折|左右 ve 歪 gigh partial 丆:g 齿 hwbj full 止:h 人:w 凵:b J|竖|上下 卞 yhu full 亠:y 卜:h U|捺|上下 @@ -1974,7 +1974,7 @@ 鮠 qoqb unknown 诰 ytfk full 讠:y 丿:t 土:f 口:k 援 refc partial 扌:r 爫:e -坊 fyn partial 土:f 方:y +坊 fyn full 土:f 方:y N|折|左右 彭 fkue full 士:f 口:k 䒑:u 彡:e 洒 isg full 氵:i 西:s G|横|左右 is 瓙 gfno unknown @@ -2038,7 +2038,7 @@ 侠 wguw partial 亻:w 一:g 丷:u 恰 nwgk full 忄:n 人:w 一:g 口:k 笛 tmf full ⺮:t 由:m F|横|上下 -扮 rwvn partial 扌:r 八:w 刀:v +扮 rwvn full 扌:r 八:w 刀:v N|折|左右 奕 yodu full 亠:y 亦:o 大:d U|捺|上下 渝 iwgj full 氵:i 人:w 一:g 刂:j 宴 pjvf full 宀:p 日:j 女:v F|横|上下 @@ -2191,7 +2191,7 @@ 扛 rag full 扌:r 工:a G|横|左右 摊 rcwy full 扌:r 又:c 亻:w Y|捺|左右 途 wtpi partial 人:w -幼 xln partial ㄠ:x 力:l +幼 xln full ㄠ:x 力:l N|折|左右 谏 ygli full 讠:y 一:g 罒:l I|捺|杂合 鏅 qwhe full 金:q 亻:w 丨:h 月:e 嶈 mnhf unknown @@ -2297,7 +2297,7 @@ 勮 hael unknown 悲 djdn full 三:d 〢:j 三:d 心:n 渔 iqgg full 氵:i 鱼:q 一:g G|横|左右 -帅 jmhh partial H|竖|左右 +帅 jmhh full 刂:j 冂:m 丨:h H|竖|左右 熸 oaqj unknown 藻 aiks full 艹:a 氵:i 口:k 木:s 宏 pdcu full 宀:p 𠂇:d 厶:c U|捺|上下 @@ -2358,7 +2358,7 @@ 耙 dicn full 三:d 小:i 巴:c N|折|左右 膳 eudk full 月:e 丷:u 丰:d 口:k 桩 syfg full 木:s 广:y 土:f G|横|左右 -狮 qtjh partial 犭:q ノ:t +狮 qtjh full 犭:q ノ:t 刂:j 丨:h 诚 ydnt full 讠:y 厂:d 𠃌:n T|撇|左右 鐫 qwym unknown 啗 kqvg full 口:k ⺈:q 臼:v G|横|左右 @@ -2407,7 +2407,7 @@ 薪 ausr full 艹:a 立:u 木:s 斤:r 警 aqky full 艹:a 勹:q 口:k 言:y 塌 fjng full 土:f 日:j 羽:n G|横|左右 -楞 slyn partial 木:s 罒:l 方:y sl +楞 slyn full 木:s 罒:l 方:y N|折|左右 sl 茅 acbt partial 艹:a マ:c 桶 sceh full 木:s マ:c 用:e H|竖|左右 欏 slxy unknown @@ -2588,7 +2588,7 @@ 刨 qnjh full 勹:q 巳:n 刂:j H|竖|左右 咏 kyni full 口:k 丶:y 𠃌:n I|捺|杂合 瘤 uqyl full 疒:u 卯:q 丶:y 田:l -仿 wyn partial 亻:w 方:y +仿 wyn full 亻:w 方:y N|折|左右 吟 kwyn full 口:k 人:w 丶:y 乛:n 眠 hnan partial 目:h コ:n 鐩 quep unknown @@ -2769,7 +2769,7 @@ 弧 xrcy full 弓:x ⺁:r 厶:c Y|捺|左右 掀 rrqw full 扌:r 斤:r ⺈:q 人:w 梭 scwt full 木:s 厶:c 八:w 夂:t -勋 kmln partial 口:k 贝:m 力:l +勋 kmln full 口:k 贝:m 力:l N|折|左右 拈 rhkg full 扌:r 卜:h 口:k G|横|左右 韬 fnhv full 二:f 𠃌:n 丨:h 臼:v 叮 ksh full 口:k 丁:s H|竖|左右 @@ -2795,7 +2795,7 @@ 撬 rtfn full 扌:r 丿:t 二:f 乚:n 妄 ynvf full 亠:y 乚:n 女:v F|横|上下 淫 ietf full 氵:i 爫:e 丿:t 士:f -纺 xyn partial 纟:x 方:y xy +纺 xyn full 纟:x 方:y N|折|左右 xy 驮 cdy full 马:c 大:d Y|捺|左右 犁 tjrh full 禾:t 刂:j 𠂉:r 丨:h 碍 djgf full 石:d 日:j 一:g 寸:f @@ -2841,7 +2841,7 @@ 纨 xvyy full 纟:x 九:v 丶:y Y|捺|左右 柟 smfg unknown 箍 trah full ⺮:t 扌:r 匚:a 丨:h -篆 txeu partial ⺮:t U|捺|上下 +篆 txeu full ⺮:t 彑:x 豕:e U|捺|上下 蚌 jdhh full 虫:j 三:d 丨:h H|竖|左右 氯 rnvi full 𠂉:r ⺄:n 彐:v 氺:i 窦 pwfd full 宀:p 八:w 十:f 大:d diff --git a/data/learning/wubi86-root-inventory.tsv b/data/learning/wubi86-root-inventory.tsv index 7924cd0e..4e37c99d 100644 --- a/data/learning/wubi86-root-inventory.tsv +++ b/data/learning/wubi86-root-inventory.tsv @@ -223,6 +223,7 @@ ヒ x standard ㄠ x standard 匕 x key-roots +彑 x standard 幺 x key-roots 弓 x key-roots 毌 x standard diff --git a/data/sources.toml b/data/sources.toml index 9ad7d8f8..301d4dca 100644 --- a/data/sources.toml +++ b/data/sources.toml @@ -442,9 +442,18 @@ id = "wubi-root-inventory" path = "data/learning/wubi86-root-inventory.tsv" source_ids = ["D1", "D2", "D14"] license = "CC-BY-SA-4.0" -sha256 = "e13254aa3b0e4c2829edf103b3acdaeb207a175f0cc33e30531ba6091e551074" +sha256 = "ce091f95967c7a7c7486323550e8b9ad2abe2346fa4c1e678355bb4dbb797343" generator = "manual/standard v1(ADR-358/报告 284)" -notes = "五笔86 统一拆字标准 v1 的标准字根清单:240 条 root/key(官方字根 + 标准变体);full 必须逐组件键位匹配;98/新世纪需各自 profile,不能直接套用" +notes = "五笔86 统一拆字标准 v1 的标准字根清单:241 条 root/key(官方字根 + 标准变体);full 必须逐组件键位匹配;98/新世纪需各自 profile,不能直接套用" + +[[derived]] +id = "wubi-d2-glyph-aliases" +path = "modules/hanzi-structure/corpus/d2-glyph-aliases.tsv" +source_ids = ["D2"] +license = "CC-BY-SA-4.0" +sha256 = "d9b33de7ac6c2087cbf172c07c47bcff35ed1457603cb507b97789cf84ef23cf" +generator = "manual(ADR-359/报告 285)" +notes = "D2 字形别名→标准字根:リ→刂(j)、ㄗ→卩(b)、ク→夕(q);只做同形归一化,不新增字根/键位,归一化后仍须通过标准 v1 校验" [[derived]] id = "pua-inventory" @@ -460,9 +469,9 @@ id = "b0-runtime" path = "data/generated/B0-runtime.tsv" source_ids = ["D1", "D2", "D5", "D10", "D17", "D17-bis", "D18", "D19", "D24"] license = "CC-BY-SA-4.0" -sha256 = "b41e61a9f6fbbd3a665c73e9155c1264623d2dc4564572d35c23267e04ab7659" +sha256 = "148f41688186809525fa3c84f28d99427f3d1ca036f4744094b4de2bca21b258" generator = "modules/hanzi-structure/tools/build_b0.py --limit 3000 --runtime-out --pua-supplementary --manual-decomp" -notes = "ADR-311 v2 学习区运行时资源:字\t全码\t质量\t字根(形:键)\t识别码(字母|末笔|结构)\t简码(一/二);3,000 字约 116.6 KB(40 B/字,预算 512 KiB);full 2,578 / partial 268 / unknown 154;无 PUA;只读提示,不改候选顺序;N1-d 补充区映射(ADR-353)+ N4 补全(ADR-355)+ cjk-decomp 覆盖(ADR-357)" +notes = "ADR-311 v2 学习区运行时资源:字\t全码\t质量\t字根(形:键)\t识别码(字母|末笔|结构)\t简码(一/二);3,000 字约 117.9 KB(40 B/字,预算 512 KiB);full 2,614 / partial 232 / unknown 154;无 PUA;只读提示,不改候选顺序;N1-d 补充区映射(ADR-353)+ N4 补全(ADR-355)+ cjk-decomp 覆盖(ADR-357)+ D2 字形别名(ADR-359)" # 键位字根分布(modules/keymap):从 b0-runtime 派生,供练习/学习界面画键位图 [[derived]] diff --git a/docs/reviews/285-d2-glyph-aliases.md b/docs/reviews/285-d2-glyph-aliases.md new file mode 100644 index 00000000..b5fb546e --- /dev/null +++ b/docs/reviews/285-d2-glyph-aliases.md @@ -0,0 +1,82 @@ +# 285 — D2 字形别名归一化(ADR-359) + +> 2026-11-15。**只归一化同形替代,不放松标准;零降级。** + +## 1. 问题 + +标准 v1 下 B0 1,000 字有 73 条 `positionMismatch`。逐条复查后发现三类成因: + +| 成因 | 例子 | 处理 | +|---|---|---| +| D2 用假名/注音符号代汉字部件 | `リ`→`刂`、`ㄗ`→`卩`、`ク`→`夕` | **同形归一化**(本次) | +| D2 是视觉拆法,不是 86 根序 | `交` 记作 `亠八乂`,86 取 `六乂` | 保持 `partial`,不猜 | +| 占位 PUA 未识别 | `被` 的第 2 位 | 保持 `partial`,等许可数据 | + +## 2. 做法 + +`modules/hanzi-structure/corpus/d2-glyph-aliases.tsv`(项目自编 CC-BY-SA-4.0): + +```text +alias canonical evidence +リ 刂 师 归 帅 狮 筛 蛳 岿(7 例) +ㄗ 卩 节 爷 卫 疖 栉(5 例) +ク 夕 久 玖 柩 灸 疚(5 例) +``` + +`build_b0.py --glyph-aliases` 在 `decode()` 的非 PUA 分支归一化;归一化后仍走标准 v1 +的字根清单与码位校验,**别名本身不能把条目送进 `full`**。 + +同时: + +- `彑` → X 加入标准字根清单(`彖 xeu`、`椽 sxey`、`喙 kxey`、`篆 txeu` 等 7 例); +- `ノ` 作为撇笔画加入 `STROKES`,让「末位识别码槽是可读笔画」的判定覆盖 D2 的假名写法。 + +## 3. 结果 + +| 指标 | 前(报告 284) | 后 | +|---|---:|---:| +| B0 1,000 `full` | 916 | **928** | +| B0 1,000 `partial` | 80 | **68** | +| B0 1,000 `standardMismatchEntries` | 73 | **61** | +| B0 3,000 `full` | 2,578 | **2,614** | +| B0 3,000 `partial` | 268 | **232** | +| 运行时 `full` | 2,578 | **2,614** | +| 运行时体积 | 120,460 B | 120,785 B | + +**零降级**:3,000 字运行时逐条比对,36 条全部 `partial → full`,没有 `full → partial`。 + +## 4. 逐条核验 + +| 字 | 全码 | 组件 | 依据 | +|---|---|---|---| +| 师 | jgmh | 刂 一 冂 丨 | 刂=j、一=g、冂=m、丨=h,四根无识别码 | +| 归 | jvg | 刂 彐 | 刂=j、彐=v + 识别 g(横+左右) | +| 节 | abj | 艹 卩 | 艹=a、卩=b + 识别 j(竖+上下) | +| 爷 | wqbj | 八 乂 卩 | 八=w、乂=q、卩=b + 识别 j(竖+上下) | +| 久 | qyi | 夕 乀 | 夕=q、乀=y + 识别 i(捺+杂合) | +| 仍 | wen | 亻 乃 | 亻=w、乃=e + 识别 n(折+左右) | +| 动 | fcln | 二 厶 力 | 二=f、厶=c、力=l + 识别 n(力末笔取折) | +| 功 | aln | 工 力 | 工=a、力=l + 识别 n | +| 劝 | cln | 又 力 | 又=c、力=l + 识别 n | +| 劲 | caln | ス 工 力 | ス=c、工=a、力=l + 识别 n | +| 伤 | wtln | 亻 𠂉 力 | 亻=w、𠂉=t、力=l + 识别 n | +| 绣 | xten | 纟 禾 乃 | 纟=x、禾=t、乃=e + 识别 n | + +`力/刀/乃` 末笔按五笔 86 惯例取「折」;`ス` 是 86 标准变体(C 键),已在字根清单中。 + +## 5. 边界 + +- 归一化只接受**同形替代**;形近而非同一部件的 D2 字形不塞进清单。 +- 剩余 232 条 partial 主要是 D2 视觉拆法、占位 PUA、以及 154 条 `unknown`(繁体/生僻,无许可拆字源)。 +- 标准 v1 不承诺 100% 覆盖;本轮不引入任何新外部数据源。 + +## 6. 验证 + +- `validate_b0.py` PASS(1,000 条,full 无 `positionMismatch`) +- `test-hanzi-structure-b0.py` 新增 `test_d2_glyph_aliases_are_normalized` +- `make macos-offline-tests`、`make verify` + +## 7. 关联 + +ADR-359、报告 284、`docs/standards/wubi-decomposition.md`、`d2-glyph-aliases.tsv`、 +`data/learning/wubi86-root-inventory.tsv`、`data/sources.toml`。 diff --git a/docs/standards/wubi-decomposition.md b/docs/standards/wubi-decomposition.md index 5e79cfe4..c0a4856c 100644 --- a/docs/standards/wubi-decomposition.md +++ b/docs/standards/wubi-decomposition.md @@ -52,14 +52,14 @@ modules/hanzi-structure/schemes/ ## 4. 当前 86 profile 的落地 -- 字根清单:`data/learning/wubi86-root-inventory.tsv`(240 条 root/key,含标准变体); -- 拆字来源:D2(MIT)、D24 cjk-decomp(Apache-2.0,树切分+键位/结构校验); +- 字根清单:`data/learning/wubi86-root-inventory.tsv`(241 条 root/key,含标准变体); +- 拆字来源:D2(MIT,含 `corpus/d2-glyph-aliases.tsv` 同形归一化)、D24 cjk-decomp(Apache-2.0,树切分+键位/结构校验); - 结构:识别码 > CDP/IDS;CDP 通过 `檢字表.連接符號` 提供 1/2/3 结构; - 笔数:D18 真实笔顺(56 成字字根)+ CDP/Unihan 笔数校验; - 验收:`validate_b0.py` 强制 full 条目 `positionMismatch` 为空;B0 report 记录 `scheme`/`standardVersion`/`standardMismatchEntries`。 ## 5. 结果与边界 -标准 v1 落地后,B0 3,000 字 `full` 2,578 / `partial` 268 / `unknown` 154;1,000 字 `full` 916 / `partial` 80 / `unknown` 4。相比未加标准的 2,759,减少的条目主要是 D2 组件不在标准字根清单、或末位识别码槽/键位不能自洽的条目;它们被如实降为 `partial`,不再“看起来 full”。 +标准 v1 落地后,B0 3,000 字 `full` 2,614 / `partial` 232 / `unknown` 154;1,000 字 `full` 928 / `partial` 68 / `unknown` 4(ADR-359 字形别名归一化后)。相比未加标准的 2,759,减少的条目主要是 D2 组件不在标准字根清单、或末位识别码槽/键位不能自洽的条目;它们被如实降为 `partial`,不再“看起来 full”。 全量 27,823 的 canonical 五笔拆字仍受外部数据限制:CDP/cjk-decomp/IDS 是结构部件,不是五笔码位拆字;D3/inputx 不能用/不采用。标准的作用是**保证已判 full 的每条都可审计**,而不是承诺 100% 覆盖。 diff --git "a/docs/\345\206\263\347\255\226\350\256\260\345\275\225.md" "b/docs/\345\206\263\347\255\226\350\256\260\345\275\225.md" index e47db01d..92ca3318 100644 --- "a/docs/\345\206\263\347\255\226\350\256\260\345\275\225.md" +++ "b/docs/\345\206\263\347\255\226\350\256\260\345\275\225.md" @@ -3939,3 +3939,27 @@ - **状态**:采纳(2026-11-15)。 - **关联**:ADR-309/310/353/355/356/357、报告 281–283、`docs/standards/wubi-decomposition.md`、 `data/learning/wubi86-root-inventory.tsv`、`validate_b0.py`。 + +### ADR-359:D2 字形别名归一化(假名/注音符号 → 标准字根) + +- **背景**:标准 v1(ADR-358)把「组件不在标准字根清单」的字降为 `partial`。逐条复查 1,000 字语料的 + 73 条 `positionMismatch` 后发现,其中一部分不是拆法错误,而是 **D2 用假名/注音符号代替汉字部件** + (源文件缺字所致):`リ` 代 `刂`、`ㄗ` 代 `卩`、`ク` 代 `夕`。这些字形与目标部件同形,键位也一致。 +- **决策**: + 1. 新增 `modules/hanzi-structure/corpus/d2-glyph-aliases.tsv`(项目自编 CC-BY-SA-4.0): + 只做**同形归一化**(`リ→刂`、`ㄗ→卩`、`ク→夕`),**不新增字根、不改变键位**。 + 2. `build_b0.py` 新增 `--glyph-aliases`;归一化只作用于 D2 的非 PUA 可读字形,之后仍走标准 v1 + 的字根清单与码位校验——归一化本身不能把条目送进 `full`。 + 3. 另把 **`彑`(X 键)** 加入标准字根清单(`彖 xeu`、`椽 sxey`、`喙 kxey`、`篆 txeu` 等 7 例码位佐证), + `ノ` 作为撇笔画加入 `STROKES`,供末位识别码槽判定;二者都已有 D3 只读 oracle 之外的码位证据。 + 4. 别名表登记为 derived `wubi-d2-glyph-aliases`;B0 report 记录其 SHA-256。 +- **结果**:B0 1,000 字 `full` 916→**928**、`partial` 80→**68**(`standardMismatchEntries` 73→61); + 3,000 字 `full` 2,578→**2,614**、`partial` 268→**232**;运行时同步。**零降级**(36 条全部 partial→full), + 逐条核验:`师 jgmh`、`归 jvg`、`节 abj`、`爷 wqbj`、`久 qyi`、`仍 wen`、`动 fcln`、`功 aln`、 + `劝 cln`、`劲 caln`、`伤 wtln`、`绣 xten`。 +- **边界**:只处理同形替代,不把「形近而非同一部件」的 D2 字形硬塞进清单;剩余 232 条 partial 多数是 + D2 的视觉拆法(如 `交` 记作 `亠八乂`,而 86 取 `六乂`),需 canonical 根序数据才能提升,**不猜**。 +- **98/新世纪适用性**:别名表是 86 profile 的数据,不外推到其它方案;逻辑(别名归一化 → 标准校验)通用。 +- **状态**:采纳(2026-11-15)。 +- **关联**:ADR-358、报告 284/285、`docs/standards/wubi-decomposition.md`、`d2-glyph-aliases.tsv`、 + `wubi86-root-inventory.tsv`、`data/sources.toml`。 diff --git "a/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" "b/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" index d0c78f3c..2dc40866 100644 --- "a/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" +++ "b/docs/\345\276\205\345\212\236\344\270\216\347\247\257\345\216\213.md" @@ -26,7 +26,8 @@ 状态 `verified 58 / partial 8 / unresolved 13`。B0 1,000 字 `full` 735→919,运行时 3,000 字 `full` 1,904→2,454; D3 不再作 `componentsSource`;未解决项仍以 `?:键` 占位,不猜。 - **N2/N3/N4 已完成**(ADR-355,报告 281):56 个成字字根真实笔顺、结构覆盖 2,766/3,000、8 个 D2 漏拆补齐;B0 schema v6。 - 运行时 `full` 2,578(85.9%);B0 `full` 2,578(85.9%),仍未达第二阶段 ≥98% 覆盖门槛。 + 统一拆字标准 v1(ADR-358)与 D2 字形别名归一化(ADR-359)后,运行时/B0 `full` 2,614(87.1%); + 剩余 232 partial / 154 unknown,受许可 canonical 拆字数据限制,仍未达第二阶段 ≥98% 覆盖门槛。 ## 2. 待办 @@ -49,7 +50,7 @@ | P1-1 | ~~三级简码专项~~ ✅ 2026-11-13 入表(ADR-345,D17-ter 3,362 条);练习题型暂未加 | 权威表来自 D9;排除 42 条撞码;核心表 +40 KB;全部简码条目首选验证通过 | M | — | — | | P1-2 | ~~corpus M2-1:来源与许可调研~~ ✅ 2026-11-14(ADR-353,报告 279);主候选中央研究院「漢字構形資料庫」(GPL-3/GFDL/CC BY-SA 2.5 TW)+ cjk-decomp(MIT/Apache-2.0)交叉校验;未下载数据 | `docs/research/root-glyph-sources.md`;官方站点证书过期需离线核对 | S | — | ✅ 已调研 | | P1-3 | **字根字形身份库(N1)**:N1-d 已解掉可定位的 D2 补充区 PUA;剩余 13 个未解决项与字形绘制 | ADR-353:N1-d 核验表已接入(报告 280);剩余 13 个(如 U+F005A 氏/卯、U+F001B 才/亅、U+F0145 乚/匕)保持占位。后续才需要 Academia Sinica / 自绘;不再需要全量自绘 251 个 | M–L | N1-d ✅ | 数据入库 | -| P1-4 | ~~真笔顺(N2)/ 结构标注(N3)/ 完整拆字(N4)~~ ✅ 2026-11-14(ADR-355/356,报告 281/282) | D18 56 个成字字根真实笔顺;结构覆盖 2,766/3,000(识别码优先/IDS 参考,146 条冲突记录);8 个 D2 漏拆字补成 full。标准 v1 后 B0 full 2,554(85.1%)、运行时 2,554(85.1%);剩余 154 unknown;D24 cjk-decomp(Apache-2.0)已接入,未用 D3 作来源 | M–L | P1-3 ✅ | 数据入库 | +| P1-4 | ~~真笔顺(N2)/ 结构标注(N3)/ 完整拆字(N4)~~ ✅ 2026-11-14(ADR-355/356,报告 281/282) | D18 56 个成字字根真实笔顺;结构覆盖 2,766/3,000(识别码优先/IDS 参考,146 条冲突记录);8 个 D2 漏拆字补成 full。标准 v1 + 字形别名后 B0/runtime full 2,614(87.1%);剩余 232 partial / 154 unknown;D24 cjk-decomp(Apache-2.0)已接入,未用 D3 作来源 | M–L | P1-3 ✅ | 数据入库 | | P1-5 | **拆字说明扩充** | B0 已有 2,759 条 `full` 结构化解释(运行时 2,602);D14 旧表仍是 104 条,尚未同步/扩写 | L | P1-4 ✅ | 数据入库 | | P1-6 | **多方案支持**(五笔 98 / 郑码 / 仓颉) | **当前不实现**:先把 86 做透;标准 v1 已留 `schemes/*.toml` profile 扩展点,98/新世纪需各自字根/码表许可数据 | M(含数据) | 用户暂缓 | 暂缓 | | P1-7 | ~~客户端自动回读缺口~~ ✅ 2026-11-14 关闭 | 用户决定收敛外部依赖:Safari/Chrome/Xcode 自动化、探针、fixture 与客户端矩阵已移除;验收只保留系统自带 TextEdit(质量样本)与自建 imetype 窗口 | — | — | 已闭环 | diff --git a/modules/hanzi-structure/corpus/B0-report.json b/modules/hanzi-structure/corpus/B0-report.json index cbc30e9a..d8b1134e 100644 --- a/modules/hanzi-structure/corpus/B0-report.json +++ b/modules/hanzi-structure/corpus/B0-report.json @@ -4,10 +4,10 @@ "allCodes": false, "scheme": "wubi86", "standardVersion": "wubi86-1.0", - "standardMismatchEntries": 73, + "standardMismatchEntries": 61, "quality": { - "full": 916, - "partial": 80, + "full": 928, + "partial": 68, "unknown": 4 }, "classes": { @@ -16,7 +16,7 @@ "key-name": 23, "None": 4 }, - "withRecognition": 642, + "withRecognition": 649, "componentsSource": { "D18": 50, "D2": 913, @@ -30,14 +30,14 @@ "withWritingStrokes": 42, "structureCoverage": 989, "structureSource": { - "recognition": 642, - "IDS": 347 + "recognition": 649, + "IDS": 340 }, "structureConflicts": 65, "manualDecomp": 50, "withIds": 990, "idsFilled": 1, - "positionMismatchEntries": 73, + "positionMismatchEntries": 61, "inputs": { "codes": { "path": "../../data/generated/wubi86-single.tsv", @@ -93,9 +93,13 @@ }, "root_inventory": { "path": "../../data/learning/wubi86-root-inventory.tsv", - "sha256": "e13254aa3b0e4c2829edf103b3acdaeb207a175f0cc33e30531ba6091e551074" + "sha256": "ce091f95967c7a7c7486323550e8b9ad2abe2346fa4c1e678355bb4dbb797343" + }, + "glyph_aliases": { + "path": "corpus/d2-glyph-aliases.tsv", + "sha256": "d9b33de7ac6c2087cbf172c07c47bcff35ed1457603cb507b97789cf84ef23cf" } }, "generator": "modules/hanzi-structure/tools/build_b0.py", - "command": "python3 tools/build_b0.py --codes ../../data/generated/wubi86-single.tsv --radicals ../../data/sources/wubi-lex-86.txt --pua-map corpus/pua-map.json --pua-supplementary corpus/pua-supplementary.tsv --pua-d2-bmp corpus/pua-d2-bmp.tsv --pua-context corpus/pua-context.tsv --root-strokes ../../data/learning/wubi86-root-strokes.tsv --manual-decomp ../../data/learning/wubi86-complete-decomp.tsv --coverage-decomp ../../data/learning/wubi86-cjkdecomp-coverage.tsv --root-inventory ../../data/learning/wubi86-root-inventory.tsv --readable-keys ../../data/learning/wubi86-readable-keys.tsv --ids ../../data/sources/ids_lv1.txt --short1 ../../data/sources/wubi86-level1-codes.tsv --short2 ../../data/sources/wubi86-level2-codes.tsv --pinyin ../../data/generated/pinyin-chars.tsv --limit 1000 --out-dir corpus" + "command": "python3 tools/build_b0.py --codes ../../data/generated/wubi86-single.tsv --radicals ../../data/sources/wubi-lex-86.txt --pua-map corpus/pua-map.json --pua-supplementary corpus/pua-supplementary.tsv --pua-d2-bmp corpus/pua-d2-bmp.tsv --pua-context corpus/pua-context.tsv --coverage-decomp ../../data/learning/wubi86-cjkdecomp-coverage.tsv --root-inventory ../../data/learning/wubi86-root-inventory.tsv --glyph-aliases corpus/d2-glyph-aliases.tsv --root-strokes ../../data/learning/wubi86-root-strokes.tsv --manual-decomp ../../data/learning/wubi86-complete-decomp.tsv --readable-keys ../../data/learning/wubi86-readable-keys.tsv --ids ../../data/sources/ids_lv1.txt --short1 ../../data/sources/wubi86-level1-codes.tsv --short2 ../../data/sources/wubi86-level2-codes.tsv --pinyin ../../data/generated/pinyin-chars.tsv --limit 1000 --out-dir corpus" } diff --git a/modules/hanzi-structure/corpus/B0.json b/modules/hanzi-structure/corpus/B0.json index f7d66352..a539903f 100644 --- a/modules/hanzi-structure/corpus/B0.json +++ b/modules/hanzi-structure/corpus/B0.json @@ -8103,34 +8103,34 @@ "componentsSource": "D2", "components": [ "亻", - "乃", - "ノ" + "乃" ], "keys": [ "w", - "e", - "n" + "e" ], "strokes": null, "strokesSource": null, "strokesKind": null, "structure": "左右", - "structureSource": "IDS", + "structureSource": "recognition", "structureConflict": false, "ids": "⿰亻乃", "idsStructure": "左右", - "positionMismatch": [ - 3 - ], - "recognition": null, + "positionMismatch": [], + "recognition": { + "key": "n", + "stroke": "折", + "structure": "左右", + "because": "2 个字根不足四码:末笔折 + 结构左右 ⇒ 识别码 n" + }, "short1": null, "short2": "we", "pinyin": "reng", "frequency": 18627, - "quality": "partial", + "quality": "full", "notes": [ - "标准字根/键位不符:第 3 个(ノ,键 n)", - "字根已够四码,无识别码" + "末位为识别码槽(可读字形),已丢弃" ] }, { @@ -11382,35 +11382,35 @@ "components": [ "二", "厶", - "力", - "ノ" + "力" ], "keys": [ "f", "c", - "l", - "n" + "l" ], "strokes": null, "strokesSource": null, "strokesKind": null, "structure": "左右", - "structureSource": "IDS", + "structureSource": "recognition", "structureConflict": false, "ids": "⿰云力", "idsStructure": "左右", - "positionMismatch": [ - 4 - ], - "recognition": null, + "positionMismatch": [], + "recognition": { + "key": "n", + "stroke": "折", + "structure": "左右", + "because": "3 个字根不足四码:末笔折 + 结构左右 ⇒ 识别码 n" + }, "short1": null, "short2": null, "pinyin": "dong", "frequency": 12509, - "quality": "partial", + "quality": "full", "notes": [ - "标准字根/键位不符:第 4 个(ノ,键 n)", - "字根已够四码,无识别码" + "末位为识别码槽(可读字形),已丢弃" ] }, { @@ -11573,7 +11573,7 @@ "class": "compound", "componentsSource": "D2", "components": [ - "リ", + "刂", "一", "冂", "丨" @@ -11592,17 +11592,14 @@ "structureConflict": false, "ids": "⿰リ帀", "idsStructure": "左右", - "positionMismatch": [ - 1 - ], + "positionMismatch": [], "recognition": null, "short1": null, "short2": null, "pinyin": "shi", "frequency": 12194, - "quality": "partial", + "quality": "full", "notes": [ - "标准字根/键位不符:第 1 个(リ,键 j)", "字根已够四码,无识别码" ] }, @@ -17606,7 +17603,7 @@ "class": "compound", "componentsSource": "D2", "components": [ - "ク", + "夕", "乀" ], "keys": [ @@ -17621,9 +17618,7 @@ "structureConflict": false, "ids": "⿸丿ス", "idsStructure": "杂合", - "positionMismatch": [ - 1 - ], + "positionMismatch": [], "recognition": { "key": "i", "stroke": "捺", @@ -17634,10 +17629,8 @@ "short2": "qy", "pinyin": "jiu", "frequency": 7645, - "quality": "partial", - "notes": [ - "标准字根/键位不符:第 1 个(ク,键 q)" - ] + "quality": "full", + "notes": [] }, { "char": "史", @@ -18295,35 +18288,35 @@ "components": [ "亻", "𠂉", - "力", - "ノ" + "力" ], "keys": [ "w", "t", - "l", - "n" + "l" ], "strokes": null, "strokesSource": null, "strokesKind": null, "structure": "左右", - "structureSource": "IDS", + "structureSource": "recognition", "structureConflict": false, "ids": "⿰亻𱐩", "idsStructure": "左右", - "positionMismatch": [ - 4 - ], - "recognition": null, + "positionMismatch": [], + "recognition": { + "key": "n", + "stroke": "折", + "structure": "左右", + "because": "3 个字根不足四码:末笔折 + 结构左右 ⇒ 识别码 n" + }, "short1": null, "short2": null, "pinyin": "shang", "frequency": 7363, - "quality": "partial", + "quality": "full", "notes": [ - "标准字根/键位不符:第 4 个(ノ,键 n)", - "字根已够四码,无识别码" + "末位为识别码槽(可读字形),已丢弃" ] }, { @@ -18373,7 +18366,7 @@ "class": "compound", "componentsSource": "D2", "components": [ - "リ", + "刂", "彐" ], "keys": [ @@ -18388,9 +18381,7 @@ "structureConflict": false, "ids": "⿰リ𫜹", "idsStructure": "左右", - "positionMismatch": [ - 1 - ], + "positionMismatch": [], "recognition": { "key": "g", "stroke": "横", @@ -18401,10 +18392,8 @@ "short2": "jv", "pinyin": "gui", "frequency": 7299, - "quality": "partial", - "notes": [ - "标准字根/键位不符:第 1 个(リ,键 j)" - ] + "quality": "full", + "notes": [] }, { "char": "退", @@ -23627,34 +23616,34 @@ "componentsSource": "D2", "components": [ "工", - "力", - "ノ" + "力" ], "keys": [ "a", - "l", - "n" + "l" ], "strokes": null, "strokesSource": null, "strokesKind": null, "structure": "左右", - "structureSource": "IDS", + "structureSource": "recognition", "structureConflict": false, "ids": "⿰工力", "idsStructure": "左右", - "positionMismatch": [ - 3 - ], - "recognition": null, + "positionMismatch": [], + "recognition": { + "key": "n", + "stroke": "折", + "structure": "左右", + "because": "2 个字根不足四码:末笔折 + 结构左右 ⇒ 识别码 n" + }, "short1": null, "short2": "al", "pinyin": "gong", "frequency": 5641, - "quality": "partial", + "quality": "full", "notes": [ - "标准字根/键位不符:第 3 个(ノ,键 n)", - "字根已够四码,无识别码" + "末位为识别码槽(可读字形),已丢弃" ] }, { @@ -26505,7 +26494,7 @@ "componentsSource": "D2", "components": [ "艹", - "ㄗ" + "卩" ], "keys": [ "a", @@ -26519,9 +26508,7 @@ "structureConflict": false, "ids": "⿱艹𰆊", "idsStructure": "上下", - "positionMismatch": [ - 2 - ], + "positionMismatch": [], "recognition": { "key": "j", "stroke": "竖", @@ -26532,10 +26519,8 @@ "short2": "ab", "pinyin": "jie", "frequency": 4822, - "quality": "partial", - "notes": [ - "标准字根/键位不符:第 2 个(ㄗ,键 b)" - ] + "quality": "full", + "notes": [] }, { "char": "升", @@ -27275,35 +27260,35 @@ "components": [ "纟", "禾", - "乃", - "ノ" + "乃" ], "keys": [ "x", "t", - "e", - "n" + "e" ], "strokes": null, "strokesSource": null, "strokesKind": null, "structure": "左右", - "structureSource": "IDS", + "structureSource": "recognition", "structureConflict": false, "ids": "⿰纟秀", "idsStructure": "左右", - "positionMismatch": [ - 4 - ], - "recognition": null, + "positionMismatch": [], + "recognition": { + "key": "n", + "stroke": "折", + "structure": "左右", + "because": "3 个字根不足四码:末笔折 + 结构左右 ⇒ 识别码 n" + }, "short1": null, "short2": null, "pinyin": "xiu", "frequency": 4645, - "quality": "partial", + "quality": "full", "notes": [ - "标准字根/键位不符:第 4 个(ノ,键 n)", - "字根已够四码,无识别码" + "末位为识别码槽(可读字形),已丢弃" ] }, { @@ -34401,34 +34386,34 @@ "componentsSource": "D2", "components": [ "又", - "力", - "ノ" + "力" ], "keys": [ "c", - "l", - "n" + "l" ], "strokes": null, "strokesSource": null, "strokesKind": null, "structure": "左右", - "structureSource": "IDS", + "structureSource": "recognition", "structureConflict": false, "ids": "⿰又力", "idsStructure": "左右", - "positionMismatch": [ - 3 - ], - "recognition": null, + "positionMismatch": [], + "recognition": { + "key": "n", + "stroke": "折", + "structure": "左右", + "because": "2 个字根不足四码:末笔折 + 结构左右 ⇒ 识别码 n" + }, "short1": null, "short2": "cl", "pinyin": "quan", "frequency": 3543, - "quality": "partial", + "quality": "full", "notes": [ - "标准字根/键位不符:第 3 个(ノ,键 n)", - "字根已够四码,无识别码" + "末位为识别码槽(可读字形),已丢弃" ] }, { @@ -37255,35 +37240,35 @@ "components": [ "ス", "工", - "力", - "ノ" + "力" ], "keys": [ "c", "a", - "l", - "n" + "l" ], "strokes": null, "strokesSource": null, "strokesKind": null, "structure": "左右", - "structureSource": "IDS", + "structureSource": "recognition", "structureConflict": false, "ids": "⿰𢀖力", "idsStructure": "左右", - "positionMismatch": [ - 4 - ], - "recognition": null, + "positionMismatch": [], + "recognition": { + "key": "n", + "stroke": "折", + "structure": "左右", + "because": "3 个字根不足四码:末笔折 + 结构左右 ⇒ 识别码 n" + }, "short1": null, "short2": null, "pinyin": "jin", "frequency": 3175, - "quality": "partial", + "quality": "full", "notes": [ - "标准字根/键位不符:第 4 个(ノ,键 n)", - "字根已够四码,无识别码" + "末位为识别码槽(可读字形),已丢弃" ] }, { @@ -37334,7 +37319,7 @@ "components": [ "八", "乂", - "ㄗ" + "卩" ], "keys": [ "w", @@ -37349,9 +37334,7 @@ "structureConflict": false, "ids": "⿱父𰆊", "idsStructure": "上下", - "positionMismatch": [ - 3 - ], + "positionMismatch": [], "recognition": { "key": "j", "stroke": "竖", @@ -37362,10 +37345,8 @@ "short2": null, "pinyin": "ye", "frequency": 3172, - "quality": "partial", - "notes": [ - "标准字根/键位不符:第 3 个(ㄗ,键 b)" - ] + "quality": "full", + "notes": [] }, { "char": "魔", diff --git a/modules/hanzi-structure/corpus/d2-glyph-aliases.tsv b/modules/hanzi-structure/corpus/d2-glyph-aliases.tsv new file mode 100644 index 00000000..2c65ad9f --- /dev/null +++ b/modules/hanzi-structure/corpus/d2-glyph-aliases.tsv @@ -0,0 +1,7 @@ +# D2 字形别名 → 标准字根字形(项目自编,CC-BY-SA-4.0;ADR-359) +# D2 用部分假名/注音符号代替汉字部件(字体缺字所致),这里只做**同形归一化**, +# 不新增字根、不改变键位;归一化后仍须通过标准 v1 的字根清单与键位校验。 +# alias canonical evidence +リ 刂 D2 以片假名リ 代立刀旁;样本 师 归 帅 狮 筛 蛳 岿(7 例) +ㄗ 卩 D2 以注音符号ㄗ 代卩;样本 节 爷 卫 疖 栉(5 例) +ク 夕 D2 以片假名ク 代夕;样本 久 玖 柩 灸 疚(5 例) diff --git a/modules/hanzi-structure/provenance/B0.md b/modules/hanzi-structure/provenance/B0.md index 376b190c..5a589aec 100644 --- a/modules/hanzi-structure/provenance/B0.md +++ b/modules/hanzi-structure/provenance/B0.md @@ -15,8 +15,9 @@ | 读音与字频 | D5/D10 | mozillazg/pinyin-data + fjsjy/jieba(经 pinyin-core 生成) | MIT | `data/generated/pinyin-chars.tsv` | `70536b58ffdfe5d6…` | | 补充区 PUA 字根 | N1-d | 项目核验:D2 位序 + 标准五笔86 字根表 + OFL 参考字形 | CC-BY-SA-4.0 | `corpus/pua-supplementary.tsv` | `6d01786ad42e2573…` | | D2 BMP / 逐字语境 PUA | N1-d 扩展 | 项目核验:D2 位序 + 码位 + 标准字根 | CC-BY-SA-4.0 | `corpus/pua-d2-bmp.tsv`、`corpus/pua-context.tsv` | `4457e7e9c0eaaa42…` / `3419271ee78bb607…` | -| 完整拆字(覆盖扩量) | D24 | amake/cjk-decomp(Apache-2.0;仅构建期读取) | Apache-2.0 / 编排 CC-BY-SA-4.0 | `data/learning/wubi86-cjkdecomp-coverage.tsv` | `22c439581feb938b…` | -| 标准字根清单 | D1/D2/D14 | 项目标准 v1(ADR-358) | CC-BY-SA-4.0 | `data/learning/wubi86-root-inventory.tsv` | `e13254aa3b0e4c28…` | +| 完整拆字(覆盖扩量) | D24 | amake/cjk-decomp(Apache-2.0;仅构建期读取) | Apache-2.0 / 编排 CC-BY-SA-4.0 | `data/learning/wubi86-cjkdecomp-coverage.tsv` | `9144257482619890…` | +| 标准字根清单 | D1/D2/D14 | 项目标准 v1(ADR-358/359) | CC-BY-SA-4.0 | `data/learning/wubi86-root-inventory.tsv` | `ce091f95967c7a7c…` | +| D2 字形别名 | D2 归一化 | 项目自编同形归一化(ADR-359) | CC-BY-SA-4.0 | `corpus/d2-glyph-aliases.tsv` | `d9b33de7ac6c2087…` | | 成字字根真实笔顺 / 完整拆字 | D18 | 项目自编(Unihan kTotalStrokes 校验笔数) | CC-BY-SA-4.0 | `data/learning/wubi86-root-strokes.tsv`、`wubi86-complete-decomp.tsv` | `7b0892dbb4934871…` / `391052b5602479b5…` | 完整 SHA-256(生成时记录): @@ -57,11 +58,11 @@ }, "coverage_decomp": { "path": "../../data/learning/wubi86-cjkdecomp-coverage.tsv", - "sha256": "22c439581feb938bf836340b05d70ad04d90c20226eb38290f909b81ed3fa88b" + "sha256": "914425748261989073c17b2486185917005858889da7169d9cb8bb9e04fcf9d5" }, "root_inventory": { "path": "../../data/learning/wubi86-root-inventory.tsv", - "sha256": "e13254aa3b0e4c2829edf103b3acdaeb207a175f0cc33e30531ba6091e551074" + "sha256": "ce091f95967c7a7c7486323550e8b9ad2abe2346fa4c1e678355bb4dbb797343" }, "root_strokes": { "path": "../../data/learning/wubi86-root-strokes.tsv", @@ -70,6 +71,10 @@ "manual_decomp": { "path": "../../data/learning/wubi86-complete-decomp.tsv", "sha256": "391052b5602479b5fb2ce33b6813f9d1104ca50e58824368bde98db2c42ce240" + }, + "glyph_aliases": { + "path": "corpus/d2-glyph-aliases.tsv", + "sha256": "d9b33de7ac6c2087cbf172c07c47bcff35ed1457603cb507b97789cf84ef23cf" } } ``` @@ -94,8 +99,8 @@ { "schemaVersion": 7, "quality": { - "full": 916, - "partial": 80, + "full": 928, + "partial": 68, "unknown": 4 }, "classes": { @@ -109,23 +114,23 @@ "D18": 50, "D24": 10 }, - "withRecognition": 642, + "withRecognition": 649, "withStrokes": 42, "withWritingStrokes": 42, "structureCoverage": 989, - "structureSource": {"recognition": 642, "IDS": 347}, + "structureSource": {"recognition": 649, "IDS": 340}, "structureConflicts": 65, "manualDecomp": 50, "withIds": 990, "placeholderEntries": 23, - "standardMismatchEntries": 82 + "standardMismatchEntries": 61 } ``` **运行时资源(3,000 字,`data/generated/B0-runtime.tsv`,ADR-311 v2)**: -`full` 2,578 / `partial` 268 / `unknown` 154;120,460 字节(≈40 B/字,预算 512 KiB); +`full` 2,614 / `partial` 232 / `unknown` 154;120,785 字节(≈40 B/字,预算 512 KiB); `componentsSource` 仅 D2(2,675)/D18(64)/D24(83);无 PUA。N3 结构覆盖 2,832 条(冲突 157);占位 57 条; -`standardMismatchEntries` 268。标准 v1 下 1,000 字 `full` 916 / `partial` 80 / `unknown` 4。 +`standardMismatchEntries` 232。标准 v1 下 1,000 字 `full` 928 / `partial` 68 / `unknown` 4。 ## 4. 复现 @@ -143,6 +148,7 @@ python3 tools/build_b0.py --codes ../../data/generated/wubi86-single.tsv \ --root-inventory ../../data/learning/wubi86-root-inventory.tsv \ --root-strokes ../../data/learning/wubi86-root-strokes.tsv \ --manual-decomp ../../data/learning/wubi86-complete-decomp.tsv \ + --glyph-aliases corpus/d2-glyph-aliases.tsv \ --readable-keys ../../data/learning/wubi86-readable-keys.tsv \ --ids ../../data/sources/ids_lv1.txt \ --short1 ../../data/sources/wubi86-level1-codes.tsv \ @@ -158,6 +164,7 @@ python3 tools/build_b0.py --codes ../../data/generated/wubi86-single.tsv \ --root-inventory ../../data/learning/wubi86-root-inventory.tsv \ --root-strokes ../../data/learning/wubi86-root-strokes.tsv \ --manual-decomp ../../data/learning/wubi86-complete-decomp.tsv \ + --glyph-aliases corpus/d2-glyph-aliases.tsv \ --readable-keys ../../data/learning/wubi86-readable-keys.tsv \ --ids ../../data/sources/ids_lv1.txt \ --short1 ../../data/sources/wubi86-level1-codes.tsv \ @@ -172,9 +179,10 @@ python3 tests/validate_b0.py --corpus corpus/B0.json --report corpus/B0-report.j ## 5. 已知缺口 -- `partial` 87 条:主要是**含占位组件**(字形未识别)的条目(IDS 已补齐 1 条;D18 补 8 个完整拆字;D24 覆盖拆字 64 条;PUA 映射补齐其余);合体字的「组件数 ↔ 码长」已 100% 对齐(报告 244)。讲解时按 partial 降级表述,占位显示为 `?`; -- `unknown` 8 条:缺全码或缺拆字数据; +- 3,000 字 `partial` 232 条:主要是 D2 视觉拆法(如 `交` 记作 `亠八乂`,86 取 `六乂`)、占位组件与「识别码槽被当成字根」的条目;D18 补 8 个完整拆字、D24 补 83 条、PUA/别名归一化补其余。讲解时按 partial 降级表述,占位显示为 `?`(报告 285); +- 3,000 字 `unknown` 154 条:缺全码或缺拆字数据(多为繁体/生僻字,无许可 canonical 拆字源); - 笔画/笔顺、字根图形、几何资源**不在 B0**(属 ADR-189 的 corpus/assets 阶段); - `corpus/pua-map.json`:由 `tools/decode_pua.py` 从 D2/D3 对齐学出的 **D3 BMP PUA**→字根映射(184 个可确认),只用于生成阶段; - `corpus/pua-supplementary.tsv`:N1-d 人工核验的 **D2 补充区 PUA**→字根表(66 条按键位 + 62 条 `*` 兜底);`pua-inventory.tsv` 列出 3,000 字里可定位的 79 个补充区 PUA(verified 64 / partial 2 / unresolved 13);另有 `pua-d2-bmp.tsv`(3 条)与 `pua-context.tsv`(34 条逐字语境),未解决项不填猜测字形(ADR-353/356、报告 280/282)。 +- `corpus/d2-glyph-aliases.tsv`:D2 的假名/注音同形替代归一化(`リ→刂`、`ㄗ→卩`、`ク→夕`),不新增字根/键位(ADR-359、报告 285)。 - 产物本身不含任何私有区字形。 diff --git a/modules/hanzi-structure/tools/build_b0.py b/modules/hanzi-structure/tools/build_b0.py index ce05b79b..4f19816f 100755 --- a/modules/hanzi-structure/tools/build_b0.py +++ b/modules/hanzi-structure/tools/build_b0.py @@ -34,7 +34,7 @@ } # 单笔画可读字形(D2 的成字字根后续字形) # 注意:'〇' 在 D2 里是占位字形(不是笔画),不能算进首/次/末笔 -STROKES = {'一': '横', '丨': '竖', '丿': '撇', '丶': '捺', '乙': '折', '乛': '折', '亅': '竖'} +STROKES = {'一': '横', '丨': '竖', '丿': '撇', 'ノ': '撇', '丶': '捺', '乙': '折', '乛': '折', '亅': '竖'} # 笔画键 ↔ 名称 ↔ 规范字形(成字字根取码用;这是"取码笔顺",不是笔顺教学数据) STROKE_KEY = {'g': '横', 'h': '竖', 't': '撇', 'y': '捺', 'n': '折'} STROKE_GLYPH = {'g': '一', 'h': '丨', 't': '丿', 'y': '丶', 'n': '乙'} @@ -130,6 +130,15 @@ def load_coverage_decomp(path: Path) -> dict[str, list[str]]: return load_manual_decomp(path) +def load_glyph_aliases(path: Path) -> dict[str, str]: + """D2 字形别名 → 标准字根字形(ADR-359;同形归一化,不改键位)。""" + out: dict[str, str] = {} + for parts in read_rows(path, 3): + if len(parts) >= 2 and len(parts[0]) == 1 and len(parts[1]) == 1: + out[parts[0]] = parts[1] + return out + + def load_pua_context(path: Path) -> dict[tuple[str, str, str], str]: """PUA 语境表:字 + PUA + 键位 → 字根(同一 PUA 在不同字里指不同部件)。""" out: dict[tuple[str, str, str], str] = {} @@ -193,7 +202,8 @@ def decode(glyphs: list[str], pua_map: dict[str, str], supp_map: dict[tuple[str, str], str] | None = None, d2_bmp_map: dict[tuple[str, str], str] | None = None, char: str | None = None, - context_map: dict[tuple[str, str, str], str] | None = None) -> tuple[list[str | None], int]: + context_map: dict[tuple[str, str, str], str] | None = None, + aliases: dict[str, str] | None = None) -> tuple[list[str | None], int]: """把一组字形解码成可读字形;解不出的私有区字形变成占位组件 None。 - BMP 私有区(D3 字形):查 ``pua_map``(D2/D3 对齐学出)。 @@ -231,7 +241,7 @@ def decode(glyphs: list[str], pua_map: dict[str, str], out.append(None) # 占位:不许把 PUA 写进产物 unknown += 1 else: - out.append(g) + out.append(aliases.get(g, g) if aliases else g) return out, unknown @@ -332,7 +342,8 @@ def build(char: str, code: str, glyphs: list[str], short1: dict, short2: dict, d2_bmp_map: dict[tuple[str, str], str] | None = None, context_map: dict[tuple[str, str, str], str] | None = None, coverage_decomp: dict[str, list[str]] | None = None, - root_inventory: dict[str, set[str]] | None = None) -> dict: + root_inventory: dict[str, set[str]] | None = None, + aliases: dict[str, str] | None = None) -> dict: pua_map = pua_map or {} supp_map = supp_map or {} root_strokes = root_strokes or {} @@ -427,7 +438,7 @@ def finish(record: dict) -> dict: entry['componentsSource'] = 'D1' entry['quality'] = 'full' return finish(entry) - readable_strokes = ([g for g in decode(glyphs, pua_map)[0][1:] + readable_strokes = ([g for g in decode(glyphs, pua_map, aliases=aliases)[0][1:] if isinstance(g, str) and g in STROKES] if glyphs[0] == char else []) if readable_strokes: @@ -461,7 +472,7 @@ def finish(record: dict) -> dict: keys2 = None decoded2, unknown2 = (decode(glyphs, pua_map, drop_tail_pua=per_position, keys=keys2, supp_map=supp_map, d2_bmp_map=d2_bmp_map, - char=char, context_map=context_map) + char=char, context_map=context_map, aliases=aliases) if glyphs else ([], 0)) # 标准 v1:D2 与全码等长时,末位可能是「可读的识别码字形」(如 我 的末位 丶)。 @@ -631,6 +642,8 @@ def main() -> int: help='D24 cjk-decomp 覆盖拆字(可选)') parser.add_argument('--root-inventory', help='五笔86 标准字根清单(ADR-358,可选)') + parser.add_argument('--glyph-aliases', + help='D2 字形别名→标准字根字形(ADR-359,可选)') parser.add_argument('--limit', type=int, default=1000) parser.add_argument('--all-codes', action='store_true', help='覆盖全码表全部字(27,823),忽略 --limit') @@ -657,6 +670,8 @@ def main() -> int: paths['coverage_decomp'] = Path(args.coverage_decomp) if args.root_inventory: paths['root_inventory'] = Path(args.root_inventory) + if args.glyph_aliases: + paths['glyph_aliases'] = Path(args.glyph_aliases) for name, path in paths.items(): if not path.is_file(): print(f'缺少输入文件 --{name} {path}', file=sys.stderr) @@ -683,6 +698,7 @@ def main() -> int: context_map = load_pua_context(Path(args.pua_context)) if args.pua_context else {} coverage_decomp = load_coverage_decomp(Path(args.coverage_decomp)) if args.coverage_decomp else {} root_inventory = load_root_inventory(Path(args.root_inventory)) if args.root_inventory else {} + aliases = load_glyph_aliases(Path(args.glyph_aliases)) if args.glyph_aliases else {} if args.all_codes: ranked = sorted(((char, 0) for char in codes), @@ -721,7 +737,7 @@ def main() -> int: root_strokes=root_strokes, manual_decomp=manual_decomp, d2_bmp_map=d2_bmp_map, context_map=context_map, coverage_decomp=coverage_decomp, - root_inventory=root_inventory)) + root_inventory=root_inventory, aliases=aliases)) entries.sort(key=lambda e: (-e['frequency'], e['char'])) stats: dict[str, int] = {} diff --git a/platforms/macos/tests/test-hanzi-structure-b0.py b/platforms/macos/tests/test-hanzi-structure-b0.py index 549e9ff6..77f49fd4 100644 --- a/platforms/macos/tests/test-hanzi-structure-b0.py +++ b/platforms/macos/tests/test-hanzi-structure-b0.py @@ -216,6 +216,30 @@ def test_n4_d2_gaps_have_manual_decomposition(self): self.assertEqual(entry['componentsSource'], 'D18', char) self.assertEqual(entry['quality'], 'full', char) + def test_d2_glyph_aliases_are_normalized(self): + """ADR-359:D2 的假名/注音字形别名归一化为标准字根,产物里不得再出现别名。""" + import json + aliases = {} + for line in (MODULE / 'corpus/d2-glyph-aliases.tsv').read_text(encoding='utf-8').splitlines(): + if line and not line.startswith('#'): + alias, canonical, _evidence = line.split('\t') + aliases[alias] = canonical + self.assertTrue(aliases, '别名表不能为空') + entries = json.loads((MODULE / 'corpus/B0.json').read_text(encoding='utf-8')) + for entry in entries: + for glyph in entry['components']: + self.assertFalse(isinstance(glyph, str) and glyph in aliases, + f"{entry['char']}: 组件仍是 D2 别名 {glyph}") + expected = { + '师': ['刂', '一', '冂', '丨'], '归': ['刂', '彐'], '节': ['艹', '卩'], + '爷': ['八', '乂', '卩'], '久': ['夕', '乀'], '仍': ['亻', '乃'], + } + by_char = {e['char']: e for e in entries} + for char, components in expected.items(): + entry = by_char[char] + self.assertEqual(entry['components'], components, char) + self.assertEqual(entry['quality'], 'full', char) + def test_provenance_records_hashes(self): text = (MODULE / 'provenance/B0.md').read_text(encoding='utf-8') for marker in ('D2', 'D17-bis', 'SHA-256', 'CC-BY-SA-4.0'):