- Refactor codebase into src/ (preprocessing, translation, assembly) - Add pipeline/ scripts for individual stages - Externalize configuration to config/config.yaml - Fix Cover Image preservation - Update documentation and manuals
213 lines
7.0 KiB
Python
213 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Placeholder Backfill Test Script
|
|
Tests placeholder restoration on translated entries.
|
|
|
|
Usage:
|
|
python scripts/test_backfill.py --chapter 38
|
|
python scripts/test_backfill.py --chapter 38 --limit 10
|
|
"""
|
|
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from src.manifest_manager import ManifestManager
|
|
from src.format_restorer import FormatRestorer
|
|
|
|
# Configuration - Use unified .work directory
|
|
BOOK_NAME = "Empire of AI Dreams and Nightmares in Sam Altmans OpenAI (Karen Hao)"
|
|
WORK_DIR = Path(f".work/{BOOK_NAME}")
|
|
MANIFEST_PATH = WORK_DIR / "manifest.json"
|
|
|
|
|
|
def build_toc_from_manifest(manager: ManifestManager) -> list:
|
|
"""Build TOC from manifest entries."""
|
|
files = {}
|
|
for entry in manager.entries:
|
|
fp = entry.file_path
|
|
if fp not in files:
|
|
files[fp] = {'count': 0, 'first_text': ''}
|
|
files[fp]['count'] += 1
|
|
if not files[fp]['first_text'] and entry.original_text:
|
|
files[fp]['first_text'] = entry.original_text[:40].replace('\n', ' ')
|
|
|
|
toc = []
|
|
for i, (fp, info) in enumerate(sorted(files.items()), 1):
|
|
toc.append({
|
|
'index': i,
|
|
'href': fp,
|
|
'title': info['first_text'] or f"File {i}",
|
|
'paragraphs': info['count']
|
|
})
|
|
return toc
|
|
|
|
|
|
def get_chapter_entries(manager: ManifestManager, chapter_index: int) -> tuple:
|
|
"""Get entries for a specific chapter by index."""
|
|
toc = build_toc_from_manifest(manager)
|
|
|
|
if chapter_index < 1 or chapter_index > len(toc):
|
|
print(f"错误: 章节编号 {chapter_index} 无效 (范围: 1-{len(toc)})")
|
|
return None, None
|
|
|
|
chapter = toc[chapter_index - 1]
|
|
href = chapter['href']
|
|
|
|
entries = [e for e in manager.entries if e.file_path == href]
|
|
return chapter, entries
|
|
|
|
|
|
def test_backfill(chapter: dict, entries: list, limit: int = None):
|
|
"""Test placeholder restoration for a chapter."""
|
|
|
|
print(f"\n" + "=" * 70)
|
|
print(f"占位符回填测试 - 章节 #{chapter['index']}: {chapter['title'][:40]}...")
|
|
print("=" * 70)
|
|
|
|
# Filter entries with translation and placeholders
|
|
translated = [e for e in entries if e.translated_text]
|
|
with_placeholders = [e for e in translated if e.placeholders and len(e.placeholders) > 0]
|
|
|
|
print(f"\n统计:")
|
|
print(f" 总段落: {len(entries)}")
|
|
print(f" 已翻译: {len(translated)}")
|
|
print(f" 有占位符: {len(with_placeholders)}")
|
|
|
|
if not translated:
|
|
print("\n⚠️ 该章节没有已翻译的内容!")
|
|
return
|
|
|
|
# Test restoration
|
|
restorer = FormatRestorer()
|
|
|
|
success_count = 0
|
|
fail_count = 0
|
|
results = []
|
|
|
|
test_entries = with_placeholders[:limit] if limit else with_placeholders
|
|
|
|
print(f"\n测试 {len(test_entries)} 个带占位符的段落:")
|
|
print("-" * 70)
|
|
|
|
for i, entry in enumerate(test_entries, 1):
|
|
original = entry.original_text
|
|
translated = entry.translated_text
|
|
placeholders = entry.placeholders
|
|
|
|
# Get non-internal placeholders
|
|
visible_ph = {k: v for k, v in placeholders.items() if not k.startswith('_')}
|
|
|
|
# Perform restoration
|
|
restored, success = restorer.restore(translated, placeholders)
|
|
|
|
if success:
|
|
success_count += 1
|
|
status = "✅"
|
|
else:
|
|
fail_count += 1
|
|
status = "❌"
|
|
|
|
results.append({
|
|
'index': i,
|
|
'entry_id': entry.entry_id,
|
|
'original': original,
|
|
'translated': translated,
|
|
'restored': restored,
|
|
'placeholders': visible_ph,
|
|
'success': success
|
|
})
|
|
|
|
# Print summary
|
|
print(f"\n[{i}] {status} {entry.entry_id[-40:]}")
|
|
print(f" 占位符: {list(visible_ph.keys())}")
|
|
print(f" 原文: {original[:50]}...")
|
|
print(f" 译文: {translated[:50]}...")
|
|
|
|
if not success:
|
|
print(f" 还原: {restored[:50]}...")
|
|
# Show what placeholders are missing
|
|
missing = []
|
|
for k in visible_ph.keys():
|
|
if k.isdigit():
|
|
if f"φ{k}φ" not in translated and f"φ/{k}φ" not in translated:
|
|
missing.append(k)
|
|
if missing:
|
|
print(f" 缺失: {missing}")
|
|
|
|
# Summary
|
|
print("\n" + "=" * 70)
|
|
print(f"测试结果汇总")
|
|
print("=" * 70)
|
|
print(f" 成功: {success_count}/{len(test_entries)}")
|
|
print(f" 失败: {fail_count}/{len(test_entries)}")
|
|
|
|
if fail_count > 0:
|
|
print(f"\n失败案例详情:")
|
|
for r in results:
|
|
if not r['success']:
|
|
print(f"\n [{r['index']}] {r['entry_id'][-50:]}")
|
|
print(f" 原文: {r['original'][:60]}...")
|
|
print(f" 译文: {r['translated'][:60]}...")
|
|
print(f" 还原: {r['restored'][:60]}...")
|
|
print(f" 占位符: {r['placeholders']}")
|
|
|
|
# Also test entries without visible placeholders (only _prefix/_suffix)
|
|
prefix_suffix_only = [e for e in translated
|
|
if e.placeholders
|
|
and all(k.startswith('_') for k in e.placeholders.keys())]
|
|
|
|
if prefix_suffix_only:
|
|
print(f"\n\n额外测试: 只有 _prefix/_suffix 的段落 ({len(prefix_suffix_only)} 个)")
|
|
print("-" * 70)
|
|
|
|
ps_success = 0
|
|
ps_fail = 0
|
|
|
|
for entry in prefix_suffix_only[:5]: # Test first 5
|
|
restored, success = restorer.restore(entry.translated_text, entry.placeholders)
|
|
if success:
|
|
ps_success += 1
|
|
status = "✅"
|
|
else:
|
|
ps_fail += 1
|
|
status = "❌"
|
|
|
|
print(f" {status} {entry.entry_id[-40:]}")
|
|
if '_prefix' in entry.placeholders:
|
|
print(f" _prefix: {entry.placeholders['_prefix'][:30]}...")
|
|
if '_suffix' in entry.placeholders:
|
|
print(f" _suffix: {entry.placeholders['_suffix'][:30]}...")
|
|
|
|
print(f"\n 结果: {ps_success}/{min(5, len(prefix_suffix_only))} 成功")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="测试占位符回填")
|
|
parser.add_argument("--chapter", "-c", type=int, required=True, help="章节编号")
|
|
parser.add_argument("--limit", "-l", type=int, default=20, help="测试数量限制 (默认20)")
|
|
args = parser.parse_args()
|
|
|
|
if not MANIFEST_PATH.exists():
|
|
print(f"错误: Manifest 不存在: {MANIFEST_PATH}")
|
|
return
|
|
|
|
# Load manifest
|
|
manager = ManifestManager(MANIFEST_PATH)
|
|
manager.load()
|
|
print(f"已加载 manifest: {len(manager.entries)} 条目")
|
|
|
|
# Get chapter
|
|
chapter, entries = get_chapter_entries(manager, args.chapter)
|
|
if not chapter:
|
|
return
|
|
|
|
# Test backfill
|
|
test_backfill(chapter, entries, args.limit)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|