184 lines
5.6 KiB
Python
184 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Phase 4 replacement pipeline orchestrator.
|
|
|
|
Default flow:
|
|
1) translate.py
|
|
2) optional glossary verification (low-confidence/full/off)
|
|
3) apply_glossary.py
|
|
4) polish.py
|
|
5) build_report.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
if str(REPO_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|
|
|
from scripts.lib.markdown_chunker import split_by_headers
|
|
|
|
|
|
def resolve_project(arg: str) -> Path:
|
|
p = Path(arg)
|
|
if p.is_dir():
|
|
return p.resolve()
|
|
cand = REPO_ROOT / "projects" / arg
|
|
if cand.is_dir():
|
|
return cand.resolve()
|
|
raise SystemExit(f"project not found: {arg}")
|
|
|
|
|
|
def run_step(cmd: list[str], *, dry_run: bool) -> int:
|
|
print("$ " + " ".join(cmd))
|
|
if dry_run:
|
|
return 0
|
|
return subprocess.run(cmd, cwd=REPO_ROOT, check=False).returncode
|
|
|
|
|
|
def infer_workers(source_file: Path, fallback: int, cap: int = 8) -> int:
|
|
if not source_file.exists():
|
|
return fallback
|
|
text = source_file.read_text(encoding="utf-8")
|
|
blocks = split_by_headers(text, max_level=2)
|
|
if not blocks:
|
|
return fallback
|
|
cpu_cap = max(2, min(cap, (os.cpu_count() or 4)))
|
|
suggested = max(2, min(cpu_cap, (len(blocks) + 5) // 6))
|
|
return max(1, suggested if fallback <= 0 else min(max(fallback, 1), cpu_cap))
|
|
|
|
|
|
def low_confidence_terms(glossary_path: Path) -> list[str]:
|
|
if not glossary_path.exists():
|
|
return []
|
|
try:
|
|
glossary = json.loads(glossary_path.read_text(encoding="utf-8"))
|
|
except Exception:
|
|
return []
|
|
out: list[str] = []
|
|
for term, entry in glossary.items():
|
|
if not isinstance(entry, dict):
|
|
out.append(term)
|
|
continue
|
|
conf = str(entry.get("confidence", "")).lower()
|
|
verified = bool(entry.get("verified_at"))
|
|
if conf != "high" or not verified:
|
|
out.append(term)
|
|
return sorted(set(out))
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Phase 4 replacement pipeline")
|
|
parser.add_argument("project", help="Project slug or full path")
|
|
parser.add_argument("--translate-model", default="anthropic/claude-sonnet-4.6")
|
|
parser.add_argument("--glossary-model", default="anthropic/claude-haiku-4.5")
|
|
parser.add_argument("--polish-model", default="anthropic/claude-sonnet-4.6")
|
|
parser.add_argument("--translate-workers", type=int, default=0, help="0 means auto")
|
|
parser.add_argument("--glossary-workers", type=int, default=4)
|
|
parser.add_argument("--polish-workers", type=int, default=0, help="0 means auto")
|
|
parser.add_argument(
|
|
"--glossary-mode",
|
|
choices=["off", "low-confidence", "full"],
|
|
default="low-confidence",
|
|
help="off: skip, low-confidence: verify only low-confidence terms, full: verify all",
|
|
)
|
|
parser.add_argument("--dry-run", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
project_root = resolve_project(args.project)
|
|
phase4 = project_root / "phase4"
|
|
src_en = phase4 / "final_en.md"
|
|
if not src_en.exists():
|
|
raise SystemExit(f"missing source: {src_en}")
|
|
|
|
tw = infer_workers(src_en, args.translate_workers)
|
|
zh = phase4 / "final_zh.md"
|
|
pw = infer_workers(zh if zh.exists() else src_en, args.polish_workers)
|
|
|
|
print(f"Project: {project_root.name}")
|
|
print(f"Translate workers: {tw} | Polish workers: {pw}")
|
|
print(f"Glossary mode: {args.glossary_mode}")
|
|
print()
|
|
|
|
t0 = time.time()
|
|
steps: list[list[str]] = [
|
|
[
|
|
sys.executable,
|
|
str(REPO_ROOT / "scripts" / "translate.py"),
|
|
str(project_root),
|
|
"--workers",
|
|
str(tw),
|
|
"--model",
|
|
args.translate_model,
|
|
]
|
|
]
|
|
|
|
if args.glossary_mode != "off":
|
|
gcmd = [
|
|
sys.executable,
|
|
str(REPO_ROOT / "scripts" / "build_glossary.py"),
|
|
str(project_root),
|
|
"--workers",
|
|
str(args.glossary_workers),
|
|
"--model",
|
|
args.glossary_model,
|
|
]
|
|
if args.glossary_mode == "low-confidence":
|
|
terms = low_confidence_terms(phase4 / "glossary.json")
|
|
if terms:
|
|
if len(terms) > 80:
|
|
print(f"[info] low-confidence terms={len(terms)} is large; fallback to full glossary verify")
|
|
else:
|
|
gcmd += ["--only", ",".join(terms)]
|
|
else:
|
|
print("[info] no low-confidence glossary terms found; skipping glossary step")
|
|
gcmd = []
|
|
if gcmd:
|
|
steps.append(gcmd)
|
|
|
|
steps.extend(
|
|
[
|
|
[
|
|
sys.executable,
|
|
str(REPO_ROOT / "scripts" / "apply_glossary.py"),
|
|
str(project_root),
|
|
"--input",
|
|
"phase4/final_zh.md",
|
|
],
|
|
[
|
|
sys.executable,
|
|
str(REPO_ROOT / "scripts" / "polish.py"),
|
|
str(project_root),
|
|
"--workers",
|
|
str(pw),
|
|
"--model",
|
|
args.polish_model,
|
|
],
|
|
[
|
|
sys.executable,
|
|
str(REPO_ROOT / "scripts" / "build_report.py"),
|
|
str(project_root),
|
|
],
|
|
]
|
|
)
|
|
|
|
for cmd in steps:
|
|
rc = run_step(cmd, dry_run=args.dry_run)
|
|
if rc != 0:
|
|
return rc
|
|
|
|
print(f"\nPhase 4 pipeline done in {time.time() - t0:.1f}s")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|