nifty-coder/stemsplit-backend
0
1"""2Core data models for the voice control optimization system.3 4This module defines all the data structures used throughout the voice control5system, including provider configurations, transcription results, usage statistics,6and monitoring data.7"""8 9from dataclasses import dataclass, field10from typing import Dict, List, Optional, Any, Union11from datetime import datetime, timezone12from enum import Enum13 14 15class ProviderType(Enum):16 """Enumeration of supported STT provider types."""17 WEB_SPEECH_API = "web_speech_api"18 GOOGLE_SPEECH = "google_speech"19 AZURE_SPEECH = "azure_speech"20 ASSEMBLY_AI = "assembly_ai"21 DEEPGRAM = "deepgram"22 23 24class QuotaType(Enum):25 """Types of quota limits that can be enforced."""26 REQUESTS_PER_MINUTE = "requests_per_minute"27 REQUESTS_PER_HOUR = "requests_per_hour"28 REQUESTS_PER_DAY = "requests_per_day"29 AUDIO_MINUTES_PER_DAY = "audio_minutes_per_day"30 AUDIO_MINUTES_PER_MONTH = "audio_minutes_per_month"31 32 33@dataclass34class ProviderConfig:35 """Configuration for a speech-to-text provider."""36 name: str37 provider_type: ProviderType38 enabled: bool39 priority: int # Lower number = higher priority40 free_tier_limits: Dict[str, Any]41 rate_limits: Dict[str, int]42 supported_formats: List[str]43 supported_languages: List[str]44 cost_per_minute: float45 api_credentials: Dict[str, str]46 endpoint_url: Optional[str] = None47 timeout_seconds: int = 3048 max_retries: int = 349 50 def __post_init__(self):51 """Validate configuration after initialization."""52 if self.priority < 0:53 raise ValueError("Priority must be non-negative")54 if self.cost_per_minute < 0:55 raise ValueError("Cost per minute must be non-negative")56 if not self.supported_formats:57 raise ValueError("At least one supported format must be specified")58 59 60@dataclass61class WordTimestamp:62 """Timestamp information for a transcribed word."""63 word: str64 start_time: float65 end_time: float66 confidence: float67 68 69@dataclass70class TranscriptionResult:71 """Result of a speech-to-text transcription operation."""72 text: str73 confidence: float74 provider: str75 processing_time: float76 audio_duration: float77 language: str78 alternatives: List[str] = field(default_factory=list)79 word_timestamps: Optional[List[WordTimestamp]] = None80 is_final: bool = True81 session_id: Optional[str] = None82 created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))83 84 def __post_init__(self):85 """Validate transcription result after initialization."""86 if self.confidence < 0 or self.confidence > 1:87 raise ValueError("Confidence must be between 0 and 1")88 if self.processing_time < 0:89 raise ValueError("Processing time must be non-negative")90 if self.audio_duration < 0:91 raise ValueError("Audio duration must be non-negative")92 93 94@dataclass95class UsageStats:96 """Usage statistics for a provider within a time window."""97 provider: str98 requests_count: int99 audio_minutes: float100 estimated_cost: float101 success_rate: float102 average_latency: float103 time_window: str104 window_start: datetime105 window_end: datetime106 107 def __post_init__(self):108 """Validate usage statistics after initialization."""109 if self.requests_count < 0:110 raise ValueError("Request count must be non-negative")111 if self.audio_minutes < 0:112 raise ValueError("Audio minutes must be non-negative")113 if self.estimated_cost < 0:114 raise ValueError("Estimated cost must be non-negative")115 if self.success_rate < 0 or self.success_rate > 1:116 raise ValueError("Success rate must be between 0 and 1")117 if self.average_latency < 0:118 raise ValueError("Average latency must be non-negative")119 120 121@dataclass122class ProviderStatus:123 """Current status and health information for a provider."""124 name: str125 available: bool126 current_load: float127 quota_remaining: Dict[str, float]128 last_error: Optional[str]129 response_time_avg: float130 last_health_check: datetime = field(default_factory=datetime.utcnow)131 circuit_breaker_state: str = "CLOSED" # CLOSED, OPEN, HALF_OPEN132 consecutive_failures: int = 0133 134 def __post_init__(self):135 """Validate provider status after initialization."""136 if self.current_load < 0:137 raise ValueError("Current load must be non-negative")138 if self.response_time_avg < 0:139 raise ValueError("Response time average must be non-negative")140 if self.consecutive_failures < 0:141 raise ValueError("Consecutive failures must be non-negative")142 143 144@dataclass145class AudioSegment:146 """Represents a segment of audio data."""147 data: bytes148 start_time: float149 end_time: float150 sample_rate: int151 channels: int152 format: str153 154 @property155 def duration(self) -> float:156 """Calculate the duration of the audio segment."""157 return self.end_time - self.start_time158 159 def __post_init__(self):160 """Validate audio segment after initialization."""161 if self.start_time < 0:162 raise ValueError("Start time must be non-negative")163 if self.end_time <= self.start_time:164 raise ValueError("End time must be greater than start time")165 if self.sample_rate <= 0:166 raise ValueError("Sample rate must be positive")167 if self.channels <= 0:168 raise ValueError("Channels must be positive")169 170 171@dataclass172class ProcessedAudio:173 """Audio data that has been processed and optimized for a provider."""174 data: bytes175 format: str176 sample_rate: int177 channels: int178 duration: float179 provider: str180 compression_ratio: float = 1.0181 noise_reduced: bool = False182 silence_removed: bool = False183 184 def __post_init__(self):185 """Validate processed audio after initialization."""186 if self.duration <= 0:187 raise ValueError("Duration must be positive")188 if self.sample_rate <= 0:189 raise ValueError("Sample rate must be positive")190 if self.channels <= 0:191 raise ValueError("Channels must be positive")192 if self.compression_ratio <= 0:193 raise ValueError("Compression ratio must be positive")194 195 196@dataclass197class QuotaStatus:198 """Status of quota usage for a provider."""199 provider: str200 quota_type: QuotaType201 current_usage: float202 limit: float203 remaining: float204 reset_time: datetime205 percentage_used: float206 207 @property208 def is_exceeded(self) -> bool:209 """Check if quota is exceeded."""210 return self.current_usage >= self.limit211 212 def is_near_limit(self, threshold: float = 0.8) -> bool:213 """Check if quota is near the limit."""214 return self.percentage_used >= threshold215 216 def __post_init__(self):217 """Calculate derived fields and validate."""218 if self.limit > 0:219 self.percentage_used = min(self.current_usage / self.limit, 1.0)220 self.remaining = max(self.limit - self.current_usage, 0)221 else:222 self.percentage_used = 0.0223 self.remaining = 0.0224 225 if self.current_usage < 0:226 raise ValueError("Current usage must be non-negative")227 if self.limit < 0:228 raise ValueError("Limit must be non-negative")229 230 231@dataclass232class CacheStats:233 """Statistics about cache performance."""234 total_requests: int235 cache_hits: int236 cache_misses: int237 hit_rate: float238 total_size_bytes: int239 entry_count: int240 evictions: int241 242 @property243 def miss_rate(self) -> float:244 """Calculate cache miss rate."""245 return 1.0 - self.hit_rate246 247 def __post_init__(self):248 """Calculate hit rate and validate."""249 if self.total_requests > 0:250 self.hit_rate = self.cache_hits / self.total_requests251 else:252 self.hit_rate = 0.0253 254 if self.cache_hits < 0 or self.cache_misses < 0:255 raise ValueError("Cache hits and misses must be non-negative")256 if self.total_requests != self.cache_hits + self.cache_misses:257 raise ValueError("Total requests must equal hits plus misses")258 259 260@dataclass261class CostSummary:262 """Summary of costs across providers for a time period."""263 time_period: str264 start_date: datetime265 end_date: datetime266 total_cost: float267 provider_costs: Dict[str, float]268 total_requests: int269 total_audio_minutes: float270 average_cost_per_minute: float271 272 def __post_init__(self):273 """Validate cost summary after initialization."""274 if self.total_cost < 0:275 raise ValueError("Total cost must be non-negative")276 if self.total_requests < 0:277 raise ValueError("Total requests must be non-negative")278 if self.total_audio_minutes < 0:279 raise ValueError("Total audio minutes must be non-negative")280 if self.average_cost_per_minute < 0:281 raise ValueError("Average cost per minute must be non-negative")282 283 284@dataclass285class BudgetAlert:286 """Alert when budget thresholds are exceeded."""287 alert_id: str288 provider: str289 alert_type: str # "threshold_warning", "threshold_exceeded", "quota_exceeded"290 message: str291 current_cost: float292 threshold: float293 time_period: str294 created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))295 acknowledged: bool = False296 297 def __post_init__(self):298 """Validate budget alert after initialization."""299 if self.current_cost < 0:300 raise ValueError("Current cost must be non-negative")301 if self.threshold < 0:302 raise ValueError("Threshold must be non-negative")303 304 305# Type aliases for common data structures306ProviderMetrics = Dict[str, Union[float, int, str]]307ConfigurationDict = Dict[str, Any]308AudioFingerprint = str309CacheKey = str