"""End-to-end orchestration: statement → IR → solve → TikZ → verify.""" from __future__ import annotations import json from pathlib import Path from typing import Any from statement_to_tikz.emit_tikz import emit_standalone_tex from statement_to_tikz.formalize import formalize from statement_to_tikz.ir import GeometryIR, SolveMode, SolvedScene from statement_to_tikz.schematic import layout_schematic from statement_to_tikz.solve import DEFAULT_TOL, solve_geometry from statement_to_tikz.verify import check_ir_consistency, verify_scene def solve_with_fallback( ir: GeometryIR, *, tol: float = DEFAULT_TOL, force_schematic: bool = False, ) -> SolvedScene: ir = ir.model_copy(deep=True) ir.ensure_labels() if force_schematic: return layout_schematic(ir) exact = solve_geometry(ir, tol=tol) if exact.mode == SolveMode.exact: return exact # Prefer schematic over failed; seed from failed attempt if available base = exact.coordinates if exact.coordinates else None schematic = layout_schematic(ir, base=base) schematic.message = ( f"fallback after exact fail ({exact.message}); {schematic.message}" ) return schematic def run_pipeline( statement: str | None = None, *, ir: GeometryIR | None = None, out_dir: Path | str | None = None, basename: str = "fig", tol: float = DEFAULT_TOL, compile_pdf: bool = True, allow_offline_formalizer: bool = True, force_schematic: bool = False, formalizer_kwargs: dict[str, Any] | None = None, ) -> dict[str, Any]: """Run full or partial pipeline. Returns paths + report.""" if ir is None: if not statement: raise ValueError("provide statement or ir") ir = formalize( statement, allow_offline=allow_offline_formalizer, **(formalizer_kwargs or {}), ) else: ir = ir.model_copy(deep=True) if statement and not ir.statement: ir.statement = statement ir.ensure_labels() warnings = check_ir_consistency(ir) scene = solve_with_fallback(ir, tol=tol, force_schematic=force_schematic) tex = emit_standalone_tex(scene) result: dict[str, Any] = { "mode": scene.mode.value, "max_residual": scene.max_residual, "message": scene.message, "warnings": warnings, "ir": ir.model_dump(), "coordinates": scene.coordinates, "tex": tex, } if out_dir is not None: out = Path(out_dir) out.mkdir(parents=True, exist_ok=True) tex_path = out / f"{basename}.tex" ir_path = out / f"{basename}.ir.json" report_path = out / f"{basename}.report.json" tex_path.write_text(tex, encoding="utf-8") ir_path.write_text(ir.model_dump_json(indent=2), encoding="utf-8") report = verify_scene(scene, tex_path, tol=tol, compile=compile_pdf) report["warnings"] = warnings report["mode"] = scene.mode.value report["message"] = scene.message report["coordinates"] = scene.coordinates report_path.write_text(json.dumps(report, indent=2), encoding="utf-8") result["paths"] = { "tex": str(tex_path), "ir": str(ir_path), "report": str(report_path), "pdf": (report.get("compile") or {}).get("pdf"), } result["report"] = report else: result["report"] = verify_scene(scene, None, tol=tol, compile=False) return result def load_ir(path: Path | str) -> GeometryIR: data = json.loads(Path(path).read_text(encoding="utf-8")) data.pop("expected_mode", None) ir = GeometryIR.model_validate(data) ir.ensure_labels() return ir