58 lines
1.6 KiB
Python
58 lines
1.6 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from .config import Settings
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class AuthorizationDecision:
|
|
level: str
|
|
read_only: bool
|
|
requires_confirmation: bool
|
|
reason: str
|
|
|
|
|
|
def classify_voice_authorization(
|
|
speaker_id: str | None,
|
|
confidence: float,
|
|
settings: Settings | None = None,
|
|
) -> AuthorizationDecision:
|
|
"""Map speaker identity confidence to a safety tier.
|
|
|
|
Voice identity is advisory. Dangerous changes still require explicit approval
|
|
elsewhere in the protocol.
|
|
"""
|
|
|
|
settings = settings or Settings()
|
|
if not speaker_id:
|
|
return AuthorizationDecision(
|
|
level="unknown",
|
|
read_only=True,
|
|
requires_confirmation=True,
|
|
reason="No enrolled speaker matched this utterance.",
|
|
)
|
|
|
|
if confidence >= settings.speaker_confirmed_threshold:
|
|
return AuthorizationDecision(
|
|
level="confirmed",
|
|
read_only=False,
|
|
requires_confirmation=False,
|
|
reason=f"Speaker {speaker_id} matched above confirmed threshold.",
|
|
)
|
|
|
|
if confidence >= settings.speaker_probable_threshold:
|
|
return AuthorizationDecision(
|
|
level="probable",
|
|
read_only=False,
|
|
requires_confirmation=True,
|
|
reason=f"Speaker {speaker_id} matched above probable threshold only.",
|
|
)
|
|
|
|
return AuthorizationDecision(
|
|
level="unknown",
|
|
read_only=True,
|
|
requires_confirmation=True,
|
|
reason=f"Speaker {speaker_id} confidence below probable threshold.",
|
|
)
|