#!/usr/bin/env python3 """Run the one-call direct protocol MVP from compact Whisper JSON.""" from __future__ import annotations import argparse import json import re import shutil import sys import time from datetime import datetime from pathlib import Path from typing import Any, Callable REPO_ROOT = Path(__file__).resolve().parents[1] if str(REPO_ROOT) not in sys.path: sys.path.insert(0, str(REPO_ROOT)) from src.meeting_lab.llm.ollama import DEFAULT_ENDPOINT # noqa: E402 from src.meeting_lab.protocol.generate_direct_protocol import ( # noqa: E402 DEFAULT_MODEL, DirectProtocolResult, generate_direct_protocol, ) DEFAULT_OUTPUT_ROOT = Path("meeting_data/runs") def parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser = argparse.ArgumentParser(description="Generate one direct protocol from compact Whisper JSON.") parser.add_argument("transcript", type=Path) parser.add_argument("--context", type=Path) parser.add_argument("--output-root", type=Path, default=DEFAULT_OUTPUT_ROOT) parser.add_argument("--model", default=DEFAULT_MODEL) parser.add_argument("--ollama-endpoint", default=DEFAULT_ENDPOINT) return parser.parse_args(argv) def create_unique_run_dir( output_root: Path, transcript_stem: str, now: Callable[[], datetime] = datetime.now, ) -> Path: safe_stem = re.sub(r"[^A-Za-z0-9_.-]+", "_", transcript_stem).strip("._-") or "meeting" base = output_root / f"{safe_stem}_{now().strftime('%Y%m%d_%H%M%S')}" candidate = base suffix = 1 while candidate.exists(): candidate = output_root / f"{base.name}_{suffix:02d}" suffix += 1 candidate.mkdir(parents=True) return candidate def write_json(path: Path, data: Any) -> None: path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def persist_result(run_dir: Path, result: DirectProtocolResult) -> Path: protocol_dir = run_dir / "protocol" protocol_dir.mkdir() (protocol_dir / "exact_prompt.txt").write_text(result.exact_prompt, encoding="utf-8") write_json(protocol_dir / "raw_response.json", result.raw_response) write_json(protocol_dir / "runtime_metadata.json", result.runtime_metadata) protocol_path = run_dir / "protocol.md" protocol_path.write_text(result.protocol_text, encoding="utf-8") return protocol_path def run(args: argparse.Namespace) -> tuple[int, Path, Path | None]: run_dir = create_unique_run_dir(args.output_root, args.transcript.stem) timestamp = datetime.now().astimezone().isoformat(timespec="seconds") started = time.perf_counter() protocol_path: Path | None = None metadata: dict[str, Any] = { "run_id": run_dir.name, "timestamp": timestamp, "transcript_path": str(args.transcript.resolve()), "context_path": str(args.context.resolve()) if args.context else None, "model": args.model, "ollama_endpoint": args.ollama_endpoint, "status": "running", "total_runtime_seconds": None, "final_protocol_path": None, } try: transcript_dir = run_dir / "transcript" transcript_dir.mkdir() if not args.transcript.is_file(): raise FileNotFoundError(f"Transcript file does not exist: {args.transcript}") preserved_transcript = transcript_dir / "transcript.json" shutil.copy2(args.transcript, preserved_transcript) preserved_context: Path | None = None if args.context is not None: if not args.context.is_file(): raise FileNotFoundError(f"Meeting Context file does not exist: {args.context}") context_dir = run_dir / "context" context_dir.mkdir() preserved_context = context_dir / "meeting_context.yaml" shutil.copy2(args.context, preserved_context) write_json( run_dir / "input_manifest.json", { "transcript_source": str(args.transcript.resolve()), "transcript_copy": str(preserved_transcript.resolve()), "context_source": str(args.context.resolve()) if args.context else None, "context_copy": str(preserved_context.resolve()) if preserved_context else None, }, ) result = generate_direct_protocol( preserved_transcript, preserved_context, model=args.model, endpoint=args.ollama_endpoint, ) protocol_path = persist_result(run_dir, result) metadata["status"] = "completed" metadata["final_protocol_path"] = str(protocol_path.resolve()) except Exception as exc: metadata["status"] = "failed" metadata["failure"] = f"{type(exc).__name__}: {exc}" print(f"Error: {metadata['failure']}", file=sys.stderr) finally: metadata["total_runtime_seconds"] = round(time.perf_counter() - started, 3) write_json(run_dir / "run_metadata.json", metadata) return (0 if metadata["status"] == "completed" else 2), run_dir, protocol_path def main(argv: list[str] | None = None) -> int: code, _run_dir, protocol_path = run(parse_args(argv)) if protocol_path is not None: print(protocol_path) return code if __name__ == "__main__": raise SystemExit(main())