itssKarthiii's picture
Upload 70 files
6b408d7 verified
"""
Explainability service for VoiceAuth API.
Generates human-readable explanations for voice detection results.
"""
import random
from typing import Literal
from app.models.enums import Classification
from app.utils.constants import AI_INDICATORS
from app.utils.constants import CONFIDENCE_DESCRIPTORS
from app.utils.constants import CONFIDENCE_THRESHOLD_HIGH
from app.utils.constants import CONFIDENCE_THRESHOLD_LOW
from app.utils.constants import CONFIDENCE_THRESHOLD_MEDIUM
from app.utils.constants import HUMAN_INDICATORS
from app.utils.logger import get_logger
logger = get_logger(__name__)
class ExplainabilityService:
"""
Service for generating explanations for voice detection results.
Provides human-readable explanations based on classification
and confidence levels.
"""
def __init__(self) -> None:
"""Initialize ExplainabilityService."""
self.ai_indicators = AI_INDICATORS.copy()
self.human_indicators = HUMAN_INDICATORS.copy()
self.confidence_descriptors = CONFIDENCE_DESCRIPTORS.copy()
def get_confidence_level(
self, confidence: float
) -> Literal["very_high", "high", "medium", "low"]:
"""
Map confidence score to a descriptive level.
Args:
confidence: Confidence score between 0.0 and 1.0
Returns:
Confidence level string
"""
if confidence >= CONFIDENCE_THRESHOLD_HIGH:
return "very_high"
elif confidence >= CONFIDENCE_THRESHOLD_MEDIUM:
return "high"
elif confidence >= CONFIDENCE_THRESHOLD_LOW:
return "medium"
else:
return "low"
def select_indicators(
self,
classification: Classification,
count: int = 2,
) -> list[str]:
"""
Select random indicators based on classification.
Args:
classification: AI_GENERATED or HUMAN
count: Number of indicators to select
Returns:
List of selected indicators
"""
if classification == Classification.AI_GENERATED:
indicators = self.ai_indicators
else:
indicators = self.human_indicators
# Select random indicators (with shuffle for variety)
selected = random.sample(indicators, min(count, len(indicators)))
return selected
def format_explanation(
self,
classification: Classification,
confidence: float,
indicators: list[str] | None = None,
) -> str:
"""
Format a complete explanation string.
Args:
classification: Classification result
confidence: Confidence score
indicators: Optional list of indicators (will be generated if not provided)
Returns:
Formatted explanation string
"""
# Get confidence level and descriptor
confidence_level = self.get_confidence_level(confidence)
descriptor = self.confidence_descriptors.get(confidence_level, "Indicators of")
# Select indicators if not provided
if indicators is None:
indicators = self.select_indicators(classification, count=2)
# Join indicators naturally
if len(indicators) == 1:
indicator_text = indicators[0]
elif len(indicators) == 2:
indicator_text = f"{indicators[0]} and {indicators[1]}"
else:
indicator_text = ", ".join(indicators[:-1]) + f", and {indicators[-1]}"
# Determine classification-specific suffix
if classification == Classification.AI_GENERATED:
suffix = "detected"
else:
suffix = "observed"
# Build final explanation
explanation = f"{descriptor} {indicator_text} {suffix}"
# Ensure explanation fits within limits
if len(explanation) > 195:
explanation = explanation[:192] + "..."
return explanation
def generate_explanation(
self,
classification: Classification,
confidence: float,
audio_metadata: dict | None = None,
) -> str:
"""
Generate a complete explanation for the detection result.
Args:
classification: Classification result (AI_GENERATED or HUMAN)
confidence: Confidence score (0.0 to 1.0)
audio_metadata: Optional audio metadata for enhanced explanations
Returns:
Human-readable explanation string
"""
logger.debug(
"Generating explanation",
classification=classification.value,
confidence=confidence,
)
# Select number of indicators based on confidence
confidence_level = self.get_confidence_level(confidence)
if confidence_level in ("very_high", "high"):
num_indicators = 3
elif confidence_level == "medium":
num_indicators = 2
else:
num_indicators = 1
indicators = self.select_indicators(classification, count=num_indicators)
explanation = self.format_explanation(classification, confidence, indicators)
logger.debug(
"Generated explanation",
explanation=explanation,
num_indicators=len(indicators),
)
return explanation
def generate_detailed_explanation(
self,
classification: Classification,
confidence: float,
audio_metadata: dict,
) -> dict:
"""
Generate a detailed explanation with metrics.
Args:
classification: Classification result
confidence: Confidence score
audio_metadata: Audio metadata from processing
Returns:
Dictionary with explanation details
"""
explanation = self.generate_explanation(
classification=classification,
confidence=confidence,
audio_metadata=audio_metadata,
)
confidence_level = self.get_confidence_level(confidence)
indicators = self.select_indicators(classification, count=3)
return {
"summary": explanation,
"confidence_level": confidence_level,
"indicators": indicators,
"audio_metrics": {
"duration": audio_metadata.get("duration_seconds"),
"energy": audio_metadata.get("rms_energy"),
},
}