japanese/tests/test_two_section_review.py
panli 2e4dcf8980 feat: 新增 jlearn 学习资料生成器 + 日语音变标注体系
新增学习资料生成器模块(learner),从权威库生成多维日语学习资料:
- 拼音/假名/汉字/熟字训四类索引,带拼音↔假名↔汉字交叉跳转
- 逐字音训分类(KANJIDIC2 精确查表 + 启发式回退 + 排序键)
- 音变标注体系:浊化(連濁)、半浊化、促音变(促音便)、连声(れんじょう)
  独立配色 + 合并逻辑 + 音变规律说明
- 显式标注表:rendaku_marks(连用形连浊)、renjou_marks(连声)
- 每索引独立例词数配置(jlearn.toml + --config)
- HTML 单页应用 + 静态 HTML + PDF(playwright)

清洗工具增强:
- 拼音校验器(pinyin_checker)集成到 jclean
- 多音字拼音校正、ます形サ変動詞转原型

数据:
- 权威库补充连声词(反応/天皇/陰陽/観音/因縁/三位/輪廻/安穏)
- KANJIDIC2 音训分类表、拼音校正字典

整理 .gitignore:忽略生成产物(output/study_materials)、词典数据库、
任务运行日志、备份文件
2026-09-09 15:44:50 +08:00

212 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
测试新的两段式待确认文件 + done 信号机制。
"""
import pytest
import tempfile
import shutil
from pathlib import Path
from pl_japanese.cleaner import TaskManager
from pl_japanese.cleaner.workflow import CleanerWorkflow
@pytest.fixture
def temp_dir():
"""每个测试独立的临时目录"""
tmp = Path(tempfile.mkdtemp())
yield tmp
shutil.rmtree(tmp, ignore_errors=True)
@pytest.fixture(autouse=True)
def reset_pinyin_dict():
"""每个测试前重置拼音字典单例"""
from pl_japanese.cleaner.pinyin_overrides import DEFAULT_DICT
original_path = DEFAULT_DICT.source_path
original_word = DEFAULT_DICT.word_override.copy()
original_kana = DEFAULT_DICT.kana_hint.copy()
yield
DEFAULT_DICT.source_path = original_path
DEFAULT_DICT.word_override = original_word
DEFAULT_DICT.kana_hint = original_kana
def test_two_section_file_generation(temp_dir):
"""测试两段式待确认文件生成"""
tmp = temp_dir
tm = TaskManager(tasks_root=str(tmp / 'tasks'))
src = tmp / 'src.txt'
src.write_text(
'議会:ぎかい:\n' # 多音字 → 待确认
'社会:しゃかい:\n' # 多音字 → 待确认
'飲料:いんりょう:\n' # 自动通过
'IT:アイティー:\n', # 无汉字 → 建议skip
encoding='utf-8')
task = tm.create_task('t1', source=str(src))
main_path = str(tmp/'voc.txt')
skipped_path = str(tmp/'skip.txt')
wf = CleanerWorkflow(task, main=main_path, skipped=skipped_path)
result = wf.run()
assert result['status'] == 'reviewing'
assert result['process']['auto_pass'] == 1
assert result['process']['need_review'] == 3 # 2多音字 + 1skip建议
# 检查文件结构
review_file = Path(task.review_file)
content = review_file.read_text(encoding='utf-8')
lines = content.splitlines()
# 第一段标记
assert '自动通过' in lines[0]
# 飲料在第一段
assert '飲|料:いん|りょう:yin|liao' in content
# 第二段标记
assert '待确认' in content
# 待确认条目
assert '議|会:ぎ|かい:yi|hui' in content
assert '# 多音字' in content
# skip建议无汉字条目拼音为空
assert 'skip IT:アイティー:' in content
def test_apply_review_with_modifications(temp_dir):
"""测试人工review后应用修改"""
tmp = temp_dir
tm = TaskManager(tasks_root=str(tmp / 'tasks'))
src = tmp / 'src.txt'
src.write_text('議会:ぎかい:\n社会:しゃかい:\n飲料:いんりょう:\n', encoding='utf-8')
task = tm.create_task('t1', source=str(src))
main_path = str(tmp/'voc.txt')
skipped_path = str(tmp/'skip.txt')
wf = CleanerWorkflow(task, main=main_path, skipped=skipped_path)
wf.run()
# 模拟人工review修改第一条拼音第二条加skip
review_file = Path(task.review_file)
content = review_file.read_text(encoding='utf-8')
# 修改拼音
content = content.replace('議|会:ぎ|かい:yi|hui', '議|会:ぎ|かい:yi|kuai')
# 加skip标记
content = content.replace('社|会:しゃ|かい:she|hui', 'skip 社|会:しゃ|かい:she|hui')
content += 'done\n'
review_file.write_text(content, encoding='utf-8')
# 应用
result = wf.run()
assert result['status'] == 'ready'
assert result['apply_result']['accepted'] == 2 # 飲料 + 議会
assert result['apply_result']['skipped'] == 1 # 社会
# 检查落地文件
proc = wf.processor
auto = proc._read_clean_lines(proc.auto_done)
assert len(auto) == 2
assert '飲|料:いん|りょう:yin|liao' in auto
assert '議|会:ぎ|かい:yi|kuai' in auto # 修正后的拼音
skip = proc._read_clean_lines(proc.skip)
assert len(skip) == 1
assert '社|会:しゃ|かい:she|hui' in skip
def test_format_error_rollback_to_third_section(temp_dir):
"""测试格式错误回退到第三段"""
tmp = temp_dir
tm = TaskManager(tasks_root=str(tmp / 'tasks'))
src = tmp / 'src.txt'
src.write_text('議会:ぎかい:\n社会:しゃかい:\n', encoding='utf-8')
task = tm.create_task('t1', source=str(src))
main_path = str(tmp/'voc.txt')
skipped_path = str(tmp/'skip.txt')
wf = CleanerWorkflow(task, main=main_path, skipped=skipped_path)
wf.run()
# 模拟人工错误修改(误删分隔符)
review_file = Path(task.review_file)
content = review_file.read_text(encoding='utf-8')
content = content.replace('議|会:ぎ|かい:yi|hui', '議|会ぎ|かいyi|hui')
content += 'done\n'
review_file.write_text(content, encoding='utf-8')
# 尝试应用 → 格式错误
result = wf.run()
assert result['action'] == 'need_human'
assert '格式错误' in result['message']
# 检查回退结果:三段结构
content_after = review_file.read_text(encoding='utf-8')
lines = content_after.splitlines()
# 第一段:自动通过(无)
assert '自动通过' in content_after
# 第二段:待确认(正确的)
assert '待确认' in content_after
assert '社|会:しゃ|かい:she|hui' in content_after
# 第三段:格式错误
assert '格式错误,请修正' in content_after
assert '議|会ぎ|かいyi|hui # 格式错误:' in content_after
# done 被删除
assert 'done' not in content_after
def test_skip_suggestion_can_be_accepted_or_modified(temp_dir):
"""测试skip建议可以接受或修改"""
tmp = temp_dir
tm = TaskManager(tasks_root=str(tmp / 'tasks'))
src = tmp / 'src.txt'
src.write_text('IT:アイティー:\nCS:シーエス:\n', encoding='utf-8')
task = tm.create_task('t1', source=str(src))
main_path = str(tmp/'voc.txt')
skipped_path = str(tmp/'skip.txt')
wf = CleanerWorkflow(task, main=main_path, skipped=skipped_path)
wf.run()
review_file = Path(task.review_file)
content = review_file.read_text(encoding='utf-8')
# IT保持skipCS删掉skip前缀改为保留
# 注意:无汉字条目的拼音段为空,格式是 CS:シーエス:
content = content.replace('skip CS:シーエス:', 'CS:シーエス:')
content += 'done\n'
review_file.write_text(content, encoding='utf-8')
result = wf.run()
assert result['status'] == 'ready'
proc = wf.processor
auto = proc._read_clean_lines(proc.auto_done)
skip = proc._read_clean_lines(proc.skip)
assert 'CS:シーエス:' in auto # 删skip后进auto
assert 'IT:アイティー:' in skip # 保持skip
def test_tilde_preserved_in_output(temp_dir):
"""测试~被保留在输出中(占一段,拼音留空)"""
tmp = temp_dir
tm = TaskManager(tasks_root=str(tmp / 'tasks'))
src = tmp / 'src.txt'
src.write_text('~人:アメリカじん:\n~員:かいいん:\n', encoding='utf-8')
task = tm.create_task('t1', source=str(src))
main_path = str(tmp/'voc.txt')
skipped_path = str(tmp/'skip.txt')
wf = CleanerWorkflow(task, main=main_path, skipped=skipped_path)
result = wf.run()
# 条目全部自动通过,直接进 auto_done不经过待确认
assert result['status'] == 'ready'
proc = wf.processor
auto = proc._read_clean_lines(proc.auto_done)
# 占一段,拼音留空
assert '|人:アメリカ|じん:|ren' in auto
assert '|員:かい|いん:|yuan' in auto