清洗管线重构为严格三层架构:
- tango_analyser.py(底层:单词分析)
- task_processor.py(中层:文件 I/O、桶管理)
- workflow.py(顶层:状态机、任务推进)
- 移除旧的 batch_processor.py
新增配置文件系统:
- config.py:TOML 配置,相对路径相对配置文件目录解析
- 查找优先级 --config > cwd > 项目根 > ~ > 默认值
- count=None 语义为处理到文件末尾
项目目录整理:
- 根脚本归档到 scripts/analysis 与 scripts/legacy
- 文档归档到 docs/{design,history,analysis}
- 临时报告移到 reports/(已 gitignore)
文档质量:
- 新增 .markdownlint.json 与 scripts/mdlint.cmd
- 修复全部 14 个 md 文件的 markdownlint 警告
测试:74 passed(8 cleaner + 31 workflow + 6 tango + 15 validator + 14 config)
389 lines
12 KiB
Markdown
389 lines
12 KiB
Markdown
# Japanese Cleaner - 日语词表清洗工具
|
||
|
||
工程化的日语词表清洗流水线,自动化处理汉字-假名-拼音对齐。采用**三层架构 + 任务化**:
|
||
清晰的职责分层,每次清洗是一个跨会话、有状态、可并存的任务。
|
||
|
||
> 完整协作规范见 [`WORKFLOW.md`](WORKFLOW.md)。
|
||
|
||
## 架构设计
|
||
|
||
**三层职责分离**(自底向上):
|
||
|
||
| 层 | 模块 | 职责 | 对外接口 |
|
||
| --- | --- | --- | --- |
|
||
| **底层 · 单词级** | `tango_analyser.py` | 接收单个词条 `(kanji, kana)`,输出 `AnalysisResult`(格式化行 + 状态分类)。封装 Classifier / Aligner / PinyinMaker,不碰文件、不知道"桶"。 | `TangoAnalyser.analyze()` |
|
||
| **中间层 · 文件级** | `task_processor.py` | 持有 Task(因此知道所有文件路径),读源文件/review 文件 → 调底层 → 按状态分流写入桶文件;合并单批到权威库。**只搬文件,不改任务状态**。 | `TaskProcessor.process_source()` / `.process_review()` / `.merge_final()` |
|
||
| **工作流层 · 编排级** | `workflow.py` | 给定任务判断走到哪一步、下一步做什么,**唯一接口 `run()`** 推进任务;更新任务状态机 `created → reviewing/ready → merged`。 | `CleanerWorkflow.run()` |
|
||
|
||
**任务模型**:
|
||
|
||
- `Task` / `TaskConfig` / `TaskState` — 配置 + 状态机 + 独立目录
|
||
- `TaskManager` — 任务 CRUD,多任务并存
|
||
|
||
**设计原则**:
|
||
|
||
- 底层不知道文件/桶,只返回状态枚举(`AnalysisStatus`)
|
||
- 中间层持有路径、负责 I/O,但不决策"该做什么"
|
||
- 工作流层只做决策/编排,委托中间层执行文件操作
|
||
|
||
## 功能特性
|
||
|
||
- **三层架构**:职责清晰,底层可独立测试,工作流可编排复杂逻辑
|
||
- **配置文件管理**:TOML 格式,支持相对/绝对路径,多项目友好
|
||
- **任务化**:每次清洗是独立任务(独立配置 + 独立临时文件 + 状态机),多任务可并存
|
||
- **两层分离**:单批临时工作区 / 最终只增去重的权威库
|
||
- **数据源只读**:处理时不修改原文件,支持任意切片(起始行 + 条数)
|
||
- **严格校验**:输入输出条数自动校验
|
||
- **半自动协作**:自动分流 + 人工 review + 改代码重跑
|
||
- **工作流推进**:`run()` 一键推进,能自动处理就处理,需人工就提示
|
||
- **单元测试**:73 个测试全覆盖(底层单词分析 / 文件处理 / 工作流编排 / 配置管理)
|
||
|
||
## 数据目录布局
|
||
|
||
```text
|
||
data/
|
||
├── db/ # 权威成品库(只增不删 + 去重)
|
||
│ ├── vocabulary.txt # 所有批次累积的成品
|
||
│ └── skipped.txt # 所有批次累积的跳过项
|
||
├── sources/ # 原始数据源(只读)
|
||
│ └── xinbiaori_1.txt
|
||
└── backup/ # 备份
|
||
└── *.backup.txt
|
||
|
||
tasks/
|
||
└── {task_id}/ # 每个任务独立目录
|
||
├── task.json # 任务配置 + 状态
|
||
├── auto_done.txt # 单批结果
|
||
├── skip.txt
|
||
└── review_*.txt # 待确认桶(pinyin/split/verb/special)
|
||
```
|
||
|
||
## 入口方式
|
||
|
||
本项目的清洗流程**需要人工介入**(review 循环:改字典/规则 → 重跑),因此
|
||
**推荐用测试驱动**协作(见 [`tests/test_cleaner_workflow.py`](tests/test_cleaner_workflow.py)),
|
||
命令行只是便捷入口。
|
||
|
||
三种等价的命令行入口:
|
||
|
||
```bash
|
||
jclean <cmd> ... # 安装后的 console 命令(推荐)
|
||
python -m pl_japanese.cleaner.cli <cmd> ... # 模块调用(无需 console 入口)
|
||
python scripts/clean.py <cmd> ... # 兼容薄壳(未安装包时)
|
||
```
|
||
|
||
安装(开发模式,注册 `jclean` 命令):
|
||
|
||
```bash
|
||
pip install -e .
|
||
```
|
||
|
||
## 配置文件
|
||
|
||
支持 TOML 格式配置文件,路径可以是相对路径(相对配置文件所在目录)或绝对路径。
|
||
|
||
### 生成示例配置
|
||
|
||
```bash
|
||
jclean init-config
|
||
# 生成 jclean.toml,编辑后自动生效
|
||
```
|
||
|
||
### 配置文件示例
|
||
|
||
```toml
|
||
# jclean.toml
|
||
|
||
[paths]
|
||
# 任务根目录(存放所有任务的独立目录)
|
||
tasks_root = "tasks"
|
||
|
||
# 权威库(所有任务最终合并的目标)
|
||
vocabulary = "data/db/vocabulary.txt"
|
||
skipped = "data/db/skipped.txt"
|
||
|
||
# 数据源目录(可选)
|
||
sources_dir = "data/sources"
|
||
|
||
[defaults]
|
||
# 创建任务时的默认值
|
||
start_line = 1
|
||
count = 300
|
||
|
||
# 是否在合并前自动备份权威库
|
||
backup_before_merge = true
|
||
backup_dir = "data/backup"
|
||
|
||
[logging]
|
||
# 日志级别:DEBUG / INFO / WARNING / ERROR
|
||
level = "INFO"
|
||
```
|
||
|
||
### 配置文件查找顺序
|
||
|
||
1. 命令行参数 `--config /path/to/config.toml`(最高优先级)
|
||
2. 当前目录 `./jclean.toml` 或 `./.jclean.toml`
|
||
3. 向上查找项目根(遇到 `.git` 停止)
|
||
4. 用户主目录 `~/.jclean.toml`
|
||
5. 硬编码默认值
|
||
|
||
### 路径解析规则
|
||
|
||
- **绝对路径**:直接使用
|
||
- **相对路径**:相对配置文件所在目录
|
||
|
||
**示例**(配置文件在 `/home/user/project/jclean.toml`):
|
||
|
||
```toml
|
||
[paths]
|
||
tasks_root = "tasks" # → /home/user/project/tasks
|
||
vocabulary = "/data/global/vocab.txt" # → /data/global/vocab.txt(绝对)
|
||
sources_dir = "../shared/sources" # → /home/user/shared/sources
|
||
```
|
||
|
||
### 查看当前配置
|
||
|
||
```bash
|
||
jclean show-config
|
||
# 显示当前生效的配置(包含解析后的绝对路径)
|
||
```
|
||
|
||
### 优先级
|
||
|
||
命令行参数 > 配置文件 > 默认值
|
||
|
||
```bash
|
||
# 配置文件中 tasks_root = "tasks"
|
||
# 命令行参数覆盖配置文件
|
||
jclean --tasks-root /custom/tasks create batch1 --source ...
|
||
```
|
||
|
||
## 快速开始
|
||
|
||
### CLI 方式(极简命令)
|
||
|
||
```bash
|
||
# 1. 创建任务(数据源只读,可指定处理范围)
|
||
jclean create batch1 --source data/sources/xinbiaori_1.txt --start 1 --count 300
|
||
|
||
# 2. 工作流推进(自动判断下一步,能做就做,需人工就提示)
|
||
jclean run batch1
|
||
|
||
# 3. 如果有 review 待确认:人工修正字典/规则后,重跑指定桶
|
||
jclean run batch1 --bucket pinyin
|
||
|
||
# 4. 继续推进(review 清零后自动合并)
|
||
jclean run batch1
|
||
|
||
# 5. 完成!
|
||
|
||
# 其他命令
|
||
jclean list # 列举所有任务
|
||
jclean status batch1 # 查看任务状态
|
||
jclean clear batch1 # 清空单批文件
|
||
```
|
||
|
||
### 代码调用(三层 API)
|
||
|
||
#### 工作流层(推荐,高层抽象)
|
||
|
||
```python
|
||
from pl_japanese.cleaner import TaskManager, CleanerWorkflow
|
||
|
||
tm = TaskManager()
|
||
task = tm.create_task(
|
||
task_id='batch1',
|
||
source='data/sources/xinbiaori_1.txt',
|
||
start_line=1,
|
||
count=300,
|
||
)
|
||
|
||
wf = CleanerWorkflow(task)
|
||
|
||
# 自动推进
|
||
result = wf.run()
|
||
print(result['action']) # 'processed' / 'need_human' / 'completed'
|
||
print(result['status']) # 'reviewing' / 'ready' / 'merged'
|
||
print(result['message']) # 处理结果说明
|
||
|
||
# 如果需要人工介入
|
||
if result['action'] == 'need_human':
|
||
print(result['review']) # 待确认的 review 桶摘要
|
||
# 人工修正字典/规则后,重跑指定桶
|
||
wf.processor.process_review('pinyin')
|
||
result = wf.run() # 继续推进
|
||
|
||
# 循环推进直到完成
|
||
while result['action'] != 'completed':
|
||
result = wf.run()
|
||
if result['action'] == 'need_human':
|
||
break
|
||
```
|
||
|
||
#### 中间层(文件操作,精细控制)
|
||
|
||
```python
|
||
from pl_japanese.cleaner import TaskManager, TaskProcessor
|
||
|
||
tm = TaskManager()
|
||
task = tm.load_task('batch1')
|
||
proc = TaskProcessor(task)
|
||
|
||
# 处理源文件
|
||
result = proc.process_source()
|
||
print(result['buckets']) # 各桶条数
|
||
print(result['review_total']) # 需 review 的总数
|
||
|
||
# 重跑某个 review 桶
|
||
result = proc.process_review('pinyin')
|
||
|
||
# 合并(dry-run 校验)
|
||
result = proc.merge_final(dry_run=True)
|
||
|
||
# 查询当前状态
|
||
print(proc.snapshot_counts()) # 各单批桶当前条数
|
||
print(proc.review_total()) # review 总数
|
||
print(proc.final_counts()) # 权威库条数
|
||
```
|
||
|
||
#### 底层(单词分析,测试/调试用)
|
||
|
||
```python
|
||
from pl_japanese.cleaner import TangoAnalyser, AnalysisStatus
|
||
|
||
analyser = TangoAnalyser()
|
||
|
||
# 分析单个词条
|
||
result = analyser.analyze("日本人", "にほんじん")
|
||
print(result.status) # AnalysisStatus.SUCCESS
|
||
print(result.formatted_line) # "日|本|人:に|ほん|じん:ri|ben|ren"
|
||
print(result.is_success) # True
|
||
print(result.needs_review) # False
|
||
|
||
# 需人工确认的词
|
||
result = analyser.analyze("女将", "おかみ")
|
||
print(result.status) # AnalysisStatus.SPLIT_FAILED
|
||
print(result.needs_review) # True
|
||
```
|
||
|
||
## 核心概念
|
||
|
||
### 状态分类(底层)
|
||
|
||
`AnalysisStatus` 枚举(底层单词分析结果):
|
||
|
||
| 状态 | 含义 | 是否需 review |
|
||
| --- | --- | --- |
|
||
| `SUCCESS` | 成功处理,格式化行可直接采用 | ❌ |
|
||
| `SKIP` | 无汉字等,跳过不进成品库 | ❌ |
|
||
| `POLYPHONE` | 含多音字/多解,拼音需人工确认 | ✅ |
|
||
| `SPLIT_FAILED` | 假名无法与汉字对齐分割 | ✅ |
|
||
| `VERB_FORM` | 动词/敬语,需确认形式 | ✅ |
|
||
| `SPECIAL_CASE` | 含字母/片假名/格式异常 | ✅ |
|
||
|
||
### 状态机(工作流层)
|
||
|
||
任务状态流转:
|
||
|
||
```text
|
||
created
|
||
↓ run() → process_source
|
||
reviewing (有 review_* 待确认)
|
||
↓ 人工改代码/字典 + process_review → review 清零
|
||
↓ run() → 检测清零
|
||
ready (可合并)
|
||
↓ run() → merge_final
|
||
merged (完成)
|
||
↓ run()
|
||
completed (终态)
|
||
```
|
||
|
||
### 单批桶(中间层文件分类)
|
||
|
||
| 桶名 | 对应状态 | 含义 |
|
||
| --- | --- | --- |
|
||
| `auto_done.txt` | SUCCESS | 自动处理成功,待合并进 `vocabulary.txt` |
|
||
| `skip.txt` | SKIP | 跳过(无汉字等),待合并进 `skipped.txt` |
|
||
| `review_pinyin.txt` | POLYPHONE | 多音字待确认 |
|
||
| `review_split.txt` | SPLIT_FAILED | 分割失败待人工 |
|
||
| `review_verb.txt` | VERB_FORM | 动词形式待确认 |
|
||
| `review_special.txt` | SPECIAL_CASE | 特殊情况待判断 |
|
||
|
||
### 工作流 `run()` 返回
|
||
|
||
`CleanerWorkflow.run()` 返回 dict,关键字段:
|
||
|
||
| 字段 | 含义 |
|
||
| --- | --- |
|
||
| `action` | `processed`(推进了)/ `need_human`(需人工)/ `completed`(完成)/ `empty`(无内容) |
|
||
| `status` | 推进后的任务状态(`created` / `reviewing` / `ready` / `merged`) |
|
||
| `message` | 人类可读说明 |
|
||
| `review` | (`need_human` 时)待确认的 review 桶摘要 `{bucket: {count, sample}}` |
|
||
| `process` | (`processed` 时)处理结果统计 |
|
||
| `merge` | (`processed` 且合并时)合并结果统计 |
|
||
|
||
## 分类规则
|
||
|
||
核心规则详见 [`src/pl_japanese/cleaner/classifier.py`](src/pl_japanese/cleaner/classifier.py)。
|
||
|
||
**汉字分词(自动)**:
|
||
|
||
- 每个汉字单独一段
|
||
- `~`(通配符)单独一段,匹配任意长假名
|
||
- 其余非汉字连续合并成一段
|
||
|
||
**拼音生成**:
|
||
|
||
- 汉字:查 `WORD_OVERRIDE` 全词覆写 → 查 `KANA_HINT` 假名提示 → pypinyin
|
||
- 多音字检测:pypinyin 返回多个候选 → 标记 `POLYPHONE`
|
||
|
||
**动词处理**:
|
||
|
||
- 假名以 `う段` 结尾 / 含 `~` 标记 / 假名末有 "ます/ません/ました" → 判定为动词
|
||
- 去掉送り仮名(活用后缀)后处理
|
||
|
||
**跳过规则**:
|
||
|
||
- 无汉字 → `SKIP`
|
||
- 纯片假名/字母/符号 → `SKIP`
|
||
|
||
**字典路径**(多音字覆写):
|
||
|
||
- `WORD_OVERRIDE`:全词精确匹配(优先级最高)
|
||
- `KANA_HINT`:`(kanji, kana_prefix)` → pinyin,用于区分假名提示多音字
|
||
|
||
## 测试
|
||
|
||
```bash
|
||
# 运行所有测试(60 个)
|
||
pytest tests/
|
||
|
||
# 只测工作流(31 个)
|
||
pytest tests/test_cleaner_workflow.py -v
|
||
|
||
# 只测底层分析(8 个)
|
||
pytest tests/test_cleaner.py -v
|
||
```
|
||
|
||
测试覆盖:
|
||
|
||
- **底层单词分析**:状态分类、格式输出、needs_review 逻辑
|
||
- **中间层文件处理**:条数铁律、追加模式、去重、格式校验、review 重跑
|
||
- **工作流编排**:状态机推进、人工介入门禁、review 清零自动转 ready、合并授权
|
||
- **任务管理**:CRUD、多任务隔离、持久化
|
||
|
||
## 版本历史
|
||
|
||
- **v0.3.0** (2025-01):三层架构重构 + 配置文件管理
|
||
- 三层职责清晰分离(底层单词分析 / 中间层文件处理 / 工作流编排)
|
||
- 新增 `CleanerWorkflow.run()` 工作流推进
|
||
- CLI 精简(5 个命令,`run` 统一推进)
|
||
- TOML 配置文件支持(相对/绝对路径)
|
||
- 73 个测试全覆盖
|
||
- **v0.2.0** (2025-01):任务化架构,多任务并存,状态机管理
|
||
- **v0.1.0** (2024):初版单批流水线
|
||
|
||
## 许可
|
||
|
||
MIT
|