import argparse import json import re import sys from html import unescape from pathlib import Path ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from src.mdx_text_atoms import compare_atom_sets, extract_text_atoms, normalize_text_atom def _compact(text: str) -> str: normalized = normalize_text_atom(text or "") return re.sub(r"[^0-9A-Za-z가-힣]+", "", normalized) def _strip_html_to_text(html: str) -> str: html = re.sub(r"(?is)", "\n", html) html = re.sub(r"(?is)", "\n", html) html = re.sub(r"(?is)<[^>]+>", "\n", html) return unescape(html) def _load_source_text(run_dir: Path) -> str: step02 = run_dir / "phase_z2" / "steps" / "step02_normalized.json" data = json.loads(step02.read_text(encoding="utf-8"))["data"] parts: list[str] = [] if data.get("slide_title"): parts.append(str(data["slide_title"])) for section in data.get("sections") or []: if section.get("title"): parts.append(str(section["title"])) if section.get("raw_content"): parts.append(str(section["raw_content"])) if data.get("slide_footer"): parts.append(str(data["slide_footer"])) return "\n\n".join(parts) def check_run(run_dir: Path) -> dict: final_html = run_dir / "phase_z2" / "final.html" source_text = _load_source_text(run_dir) rendered_text = _strip_html_to_text(final_html.read_text(encoding="utf-8")) source_atoms = extract_text_atoms(source_text) rendered_atoms = extract_text_atoms(rendered_text) diff = compare_atom_sets(source_atoms, rendered_atoms) rendered_compact = _compact(" ".join(a.normalized for a in rendered_atoms)) compact_missing = [ atom for atom in source_atoms if _compact(atom.normalized) and _compact(atom.normalized) not in rendered_compact ] return { "run_id": run_dir.name, "source_atoms": len(source_atoms), "rendered_atoms": len(rendered_atoms), "missing_count": len(compact_missing), "added_count": len(diff["added_in_standardized"]), "missing": compact_missing, "added": diff["added_in_standardized"], } def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("run_ids", nargs="+") parser.add_argument("--runs-root", default="data/runs") parser.add_argument("--max-items", type=int, default=8) args = parser.parse_args() ok = True for run_id in args.run_ids: result = check_run(Path(args.runs_root) / run_id) if result["missing_count"]: ok = False print(f"=== {result['run_id']} ===") print(f" source_atoms : {result['source_atoms']}") print(f" rendered_atoms : {result['rendered_atoms']}") print(f" missing : {result['missing_count']}") print(f" added : {result['added_count']}") for item in result["missing"][: args.max_items]: print(f" - missing: {item.text}") for item in result["added"][: args.max_items]: print(f" + added: {item.text}") print() return 0 if ok else 1 if __name__ == "__main__": raise SystemExit(main())