# 设计说明

## 1. 数据模型

一个字不是一个平仄值，而是一组读音：

```mbt
pub struct Reading {
  tone : Tone        // Ping / Ze
  group : String     // 平水韵："上平一东"；词林正韵："第四部"
  tone_class : String // 平 / 上 / 去 / 入
}
```

两套韵书独立存放，API 必须显式选择 `RhymeSystem`，避免把诗韵和词韵混用：

- **平水韵**：106 部，10,386 条字-韵记录，8,239 个不同汉字。上平/下平为平声；上/去/入为仄声。
- **词林正韵**：19 部，按平声/仄声/入声分组，5,037 个不同汉字。仄声组把上声、去声合并，入声单列。

数据由 `tools/prepare_data.py` 从固定提交的上游 MIT 仓库生成，输出 `data/*.tsv`、`data/manifest.json` 和 `data_generated.mbt`。补录项（上声三讲、啼、沉、先）在清单中逐条记录。

## 2. 近体诗

### 2.1 模板

五言/七言 × 绝句/律诗 × 平起/仄起 × 首句入韵/不入韵，共 16 套。以七言为例：

```
平起入韵     平平仄仄仄平平 / 仄仄平平仄仄平 / 仄仄平平平仄仄 / 平平仄仄仄平平
平起不入韵   平平仄仄平平仄 / 仄仄平平仄仄平 / 仄仄平平平仄仄 / 平平仄仄仄平平
仄起入韵     仄仄平平仄仄平 / 平平仄仄仄平平 / 平平仄仄平平仄 / 仄仄平平仄仄平
仄起不入韵   仄仄平平平仄仄 / 平平仄仄仄平平 / 平平仄仄平平仄 / 仄仄平平仄仄平
```

律诗 = 同起式的四句模板 + 同起式“首句不入韵”的四句模板，因为第 5 句必须粘第 4 句。

### 2.2 判定

- **固定位**：五言第 2/4/5 字；七言第 2/4/6/7 字。末字固定，因为决定是否押韵。
- **一三五不论**：其余位置不要求合定格；但会在触发孤平、三平调、三仄尾时报告。
- **孤平**：韵句 `平平仄仄平`（五言）或 `仄仄平平仄仄平`（七言）中，除韵脚外只剩一个平声字。实现上检查“该平的位置连续为仄”，并允许本句自救（把后一个仄位改成平）。
- **三平调**：韵句末三字全平，硬错误。
- **三仄尾**：出句末三字全仄，提示级，不判失败。
- **押韵**：所有韵脚（含首句入韵时的首句末字）必须能取到同一个平声韵部。多音字取读音集合交集，找不到交集才报 `RHYME_MISMATCH`。
- **起式选择**：对 4 套模板分别计算固定位不匹配数，取最小者；因此用户不必先声明平起/仄起。若两套同分，保留先出现的一套（平起优先），报告 `template` 字段以便人工复核。

### 2.3 未实现

- 对句相救：出句第 4/6 字拗、对句第 3/5 字救的联合规则未单独建模。此类拗体可能被报为固定位不合。
- 对仗：无法在不引入词性/语义资源的前提下可靠判断，故不检查。
- 仄韵近体诗：模板只覆盖平韵；仄韵诗会被判为韵脚不合。

## 3. 词

### 3.1 词谱

`data/citunes.tsv` 每行一种格式：

```
词牌 <TAB> 字数/韵脚说明 <TAB> 例词作者 <TAB> 平仄串 <TAB> 韵脚下标
```

平仄串用 `p`（平）/ `z`（仄）/ `a`（中）表示；韵脚下标从 0 开始，逗号分隔。内置 817 个词牌、1,275 种格式，每个词牌最多保留 2 种格式；`check` 会在候选格式中选差异最小的一种，并在报告里给出 `template`。

### 3.2 判定

- 只比较汉字序列，忽略标点、换行与空白；长度必须与词谱一致，否则 `CI_LENGTH`。
- 每个字按词林正韵读音集合判定：`p` 要求存在平声读法，`z` 要求存在仄声读法，`a` 不约束。
- 韵脚按“连续同预期声调”切段。段内先按词林正韵部求交；失败再按平水韵同部求交，并在 `notes` 标注“跨部通押”。这解决了《浣溪沙》“杯台回来徊”被词林正韵拆到第三/第五部的问题。
- 入声单独处理：若某韵脚只有入声读法，则该段按入声韵校验，不与上/去混押。

### 3.3 未实现

- 同声调换韵（段与段之间声调相同但换部）无法从词谱数据判断，会按“全段同部”处理。
- 领字、叠句、衬字、摊破等体式未建模；`中` 位不校验，可能放过部分差异。
- 覆盖 817 个词牌（每牌最多 2 体）；如需更多体式，可调大 `MAX_FORMATS` 后重新生成。

## 4. 错误码

| code | 级别 | 含义 |
| --- | --- | --- |
| `SHI_SHAPE` | error | 句数/字数与所选诗体不符，或无法识别 |
| `SHI_TONE` | error | 固定位平仄不合 |
| `GU_PING` | error | 孤平 |
| `SAN_PING` | error | 三平调 |
| `SAN_ZE` | warning | 三仄尾 |
| `RHYME_MISMATCH` | error | 韵脚不在同一平声韵部 |
| `UNKNOWN_CHAR` | warning | 字不在韵表，跳过判定 |
| `CI_LENGTH` | error | 字数与词谱不符 |
| `CI_TONE` | error | 词谱固定位平仄不合 |
| `CI_RHYME` | error | 同段韵脚不同部 |
| `CI_UNKNOWN_TUNE` | error | 内置词谱没有该词牌 |
| `CI_IDENTIFY` | error | 无法匹配任何词牌格式 |

## 5. 确定性与边界

- 所有判定只依赖输入文本与内置数据，不访问网络、不读时钟、不使用随机数；同一输入永远得到同一报告。
- 输入上限未做硬限制；`poem_lines` 线性扫描，单次校验对 10 万字以内的文本足够。词谱匹配是 1,275 次线性比较，未做索引优化；当前规模下单次识别仍在毫秒级。
- 报告只声称“存在合律读法”，不声称“作者本意如此”。多音字、异读、方音差异都可能让判定与作者意图不同。
- 韵表缺失字按未知处理，不猜读音；这是为了不把不确定变成假错误。

## 6. 可扩展点

- 增加词牌体式：脚本已包含上游全部词牌；调大 `tools/prepare_data.py` 的 `MAX_FORMATS` 后重跑。
- 增加韵书：在 `Reading` 层新增 `RhymeSystem` 分支，保持 `common_group` 接口不变。
- 增加“对句相救”：在 `analyze_line` 之后做跨句联合检查，需要把单句求解结果暴露给诗级检查器。
- 增加批注/出处：可在 `Issue` 上扩展 `source` 字段，不影响现有 JSON 消费方。
