Files
meeting-lab/scripts/run_direct_protocol.py

154 lines
5.7 KiB
Python

#!/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,
DEFAULT_SAFE_INPUT_TOKEN_BUDGET,
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)
parser.add_argument(
"--safe-input-token-budget",
type=int,
default=DEFAULT_SAFE_INPUT_TOKEN_BUDGET,
)
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)
transcript_input = getattr(result, "transcript_input", None)
if transcript_input is not None:
(protocol_dir / "transcript_input.txt").write_text(
transcript_input, encoding="utf-8"
)
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,
safe_input_token_budget=args.safe_input_token_budget,
)
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())