| from __future__ import annotations |
|
|
| from collections.abc import Iterable, Iterator |
|
|
| from time_machine.application.encounter_service import EncounterService |
| from time_machine.domain.events import ErrorEvent, TemporalEvent |
| from time_machine.ports.speech import STTAdapter |
|
|
|
|
| class SpeechOrchestrator: |
| def __init__(self, stt: STTAdapter, encounter_service: EncounterService) -> None: |
| self.stt = stt |
| self.encounter_service = encounter_service |
|
|
| def handle_audio_turn( |
| self, |
| encounter_id: str, |
| audio: object, |
| ) -> Iterator[TemporalEvent]: |
| transcript = self.stt.transcribe(audio) |
| if not transcript.text.strip(): |
| yield ErrorEvent( |
| message="No speech could be decoded from the signal.", |
| severity="recoverable", |
| metadata={"encounter_id": encounter_id}, |
| ) |
| return |
| yield from self.encounter_service.handle_text_turn( |
| encounter_id=encounter_id, |
| text=transcript.text.strip(), |
| ) |
|
|
| def handle_audio_stream( |
| self, |
| encounter_id: str, |
| audio_chunks: Iterable[bytes], |
| ) -> Iterator[TemporalEvent]: |
| final_text = "" |
| for event in self.stt.stream_transcript(audio_chunks): |
| yield event |
| if event.transcript.is_final: |
| final_text = event.transcript.text |
|
|
| final_text = final_text.strip() |
| if final_text: |
| yield from self.encounter_service.handle_text_turn( |
| encounter_id=encounter_id, |
| text=final_text, |
| ) |
| else: |
| yield ErrorEvent( |
| message="No complete speech turn arrived from the signal.", |
| severity="recoverable", |
| metadata={"encounter_id": encounter_id}, |
| ) |
|
|