"""ASR client contract + offline fake. Real implementations land in M2 (faster-whisper tiny/small, int8, on the Space CPU) and M5 (remote large model on the local GPU box). The fake keeps the UI and tests fully wired in the meantime. """ from pathlib import Path from typing import Protocol, runtime_checkable from pydantic import BaseModel class Transcription(BaseModel): text: str language: str | None = None class ASRError(RuntimeError): """A transcription failed (model load, decode, backend error...).""" @runtime_checkable class ASRClient(Protocol): async def transcribe( self, audio_path: Path, *, language: str | None = None ) -> Transcription: ... class FakeASRClient: def __init__(self, text: str = "(fake transcription)") -> None: self._text = text self.calls: list[Path] = [] async def transcribe(self, audio_path: Path, *, language: str | None = None) -> Transcription: self.calls.append(audio_path) return Transcription(text=self._text, language=language)