Add diarization and reusable MVP meeting pipeline
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from scripts import run_mvp_meeting as cli
|
||||
from src.meeting_lab.models.meeting_context import load_meeting_context
|
||||
from src.meeting_lab.orchestration import mvp as mvp_api
|
||||
from src.meeting_lab.orchestration.mvp import MvpMeetingConfig, MvpRunResult
|
||||
from src.meeting_lab.protocol.generate_direct_protocol import DirectProtocolResult
|
||||
from src.meeting_lab.transcription.whisper import TranscriptionError, TranscriptionResult
|
||||
|
||||
|
||||
def context_data():
|
||||
return {
|
||||
"schema_version": "1",
|
||||
"meeting": {
|
||||
"meeting_id": "programmatic-test",
|
||||
"title": "Programmatic Test",
|
||||
"language": "de",
|
||||
"date": None,
|
||||
"objective": "API prüfen",
|
||||
"notes": "",
|
||||
},
|
||||
"participants": [
|
||||
{
|
||||
"participant_id": "person-1",
|
||||
"display_name": "Test Person",
|
||||
"aliases": [],
|
||||
"role": "Projektleitung",
|
||||
"department": None,
|
||||
"attendance_status": "present",
|
||||
"notes": None,
|
||||
}
|
||||
],
|
||||
"speaker_mappings": {"SPEAKER_00": "person-1"},
|
||||
"mentioned_people": [],
|
||||
"organization": {"name": "Example", "departments": []},
|
||||
"known_entities": {},
|
||||
"context_rules": {"do_not_infer_responsibilities": True},
|
||||
}
|
||||
|
||||
|
||||
def fake_transcribe(audio, model, output, language, **kwargs):
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
raw = output / "whisper_raw.json"
|
||||
transcript = output / "transcript.json"
|
||||
text = output / "transcript.txt"
|
||||
metadata = output / "runtime_metadata.json"
|
||||
raw.write_text('{"transcription": []}\n', encoding="utf-8")
|
||||
transcript.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"text": "Ein kurzer Besprechungstext.",
|
||||
"segments": [
|
||||
{
|
||||
"id": 0,
|
||||
"start": 0.0,
|
||||
"end": 1.0,
|
||||
"text": "Ein kurzer Besprechungstext.",
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
text.write_text("Ein kurzer Besprechungstext.\n", encoding="utf-8")
|
||||
metadata.write_text('{"runtime_seconds": 0.1}\n', encoding="utf-8")
|
||||
return TranscriptionResult(output, raw, transcript, text, metadata, 0.1)
|
||||
|
||||
|
||||
def fake_protocol(transcript, context, **kwargs):
|
||||
rendered_context = load_meeting_context(context)
|
||||
assert rendered_context.meeting_id == "programmatic-test"
|
||||
return DirectProtocolResult(
|
||||
protocol_text="# Meeting Protocol\n",
|
||||
exact_prompt="prompt",
|
||||
model_metadata={"model": kwargs["model"]},
|
||||
runtime_metadata={"request_count": 1},
|
||||
raw_response={"response": "# Meeting Protocol\n"},
|
||||
)
|
||||
|
||||
|
||||
class MvpApiTests(unittest.TestCase):
|
||||
def config(self, root: Path):
|
||||
audio = root / "meeting.wav"
|
||||
model = root / "model.bin"
|
||||
audio.write_bytes(b"audio")
|
||||
model.write_bytes(b"model")
|
||||
return MvpMeetingConfig(
|
||||
audio_file=audio,
|
||||
whisper_model=model,
|
||||
output_root=root / "runs",
|
||||
model="test:model",
|
||||
)
|
||||
|
||||
def test_programmatic_context_is_persisted_without_source_yaml(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
config = self.config(root)
|
||||
events = []
|
||||
with (
|
||||
patch.object(mvp_api, "transcribe_audio", side_effect=fake_transcribe),
|
||||
patch.object(
|
||||
mvp_api, "generate_direct_protocol", side_effect=fake_protocol
|
||||
),
|
||||
patch.object(subprocess, "run") as subprocess_run,
|
||||
):
|
||||
result = mvp_api.run_mvp_meeting(
|
||||
config, meeting_context=context_data(), progress_sink=events.append
|
||||
)
|
||||
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
context_path = result.run_dir / "context/meeting_context.yaml"
|
||||
self.assertTrue(context_path.is_file())
|
||||
persisted = load_meeting_context(context_path)
|
||||
self.assertEqual(persisted.meeting_id, "programmatic-test")
|
||||
self.assertEqual(persisted.speaker_mappings, {"SPEAKER_00": "person-1"})
|
||||
subprocess_run.assert_not_called()
|
||||
self.assertEqual(
|
||||
[(event.stage, event.status) for event in events],
|
||||
[
|
||||
("preparing", "started"),
|
||||
("preparing", "completed"),
|
||||
("transcription", "started"),
|
||||
("transcription", "completed"),
|
||||
("protocol_generation", "started"),
|
||||
("protocol_generation", "completed"),
|
||||
("completed", "completed"),
|
||||
],
|
||||
)
|
||||
self.assertTrue(all(event.progress is None for event in events))
|
||||
|
||||
def test_failure_emits_terminal_failure_event(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
config = self.config(root)
|
||||
events = []
|
||||
with patch.object(
|
||||
mvp_api,
|
||||
"transcribe_audio",
|
||||
side_effect=TranscriptionError("stopped"),
|
||||
):
|
||||
result = mvp_api.run_mvp_meeting(config, progress_sink=events.append)
|
||||
|
||||
self.assertEqual(result.exit_code, 2)
|
||||
self.assertEqual(events[-1].stage, "failed")
|
||||
self.assertEqual(events[-1].status, "failed")
|
||||
self.assertIn("transcription", events[-1].message)
|
||||
metadata = json.loads((result.run_dir / "run_metadata.json").read_text())
|
||||
self.assertEqual(metadata["status"], "failed")
|
||||
|
||||
def test_cli_defaults_and_wrapper_delegate_without_subprocess(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
config = self.config(root)
|
||||
args = cli.parse_args(
|
||||
[str(config.audio_file), "--whisper-model", str(config.whisper_model)]
|
||||
)
|
||||
expected = MvpRunResult(0, root / "run", root / "run/protocol.md")
|
||||
with patch.object(cli, "run_mvp_meeting", return_value=expected) as api:
|
||||
actual = cli.run(args, context_override=context_data())
|
||||
|
||||
self.assertEqual(actual, (0, expected.run_dir, expected.protocol_path))
|
||||
delegated = api.call_args.args[0]
|
||||
self.assertEqual(delegated.diarization, "off")
|
||||
self.assertEqual(delegated.language, "de")
|
||||
self.assertEqual(delegated.whisper_executable, "whisper-cli")
|
||||
self.assertEqual(api.call_args.kwargs["meeting_context"], context_data())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user