Files
StreamLens/analyzer/models/enhanced_analysis.py
2025-07-28 18:28:26 -04:00

289 lines
11 KiB
Python

"""
Enhanced Analysis Data Models
This module defines data structures for enhanced protocol analysis including
timing analysis, quality metrics, and decoded data representation.
"""
from dataclasses import dataclass, field
from typing import Dict, List, Set, Any, Optional
from enum import Enum
class TimingQuality(Enum):
"""Timing quality classifications"""
EXCELLENT = "excellent" # < 1ppm drift, stable
GOOD = "good" # 1-10ppm drift, mostly stable
MODERATE = "moderate" # 10-100ppm drift, variable
POOR = "poor" # > 100ppm drift, unstable
UNKNOWN = "unknown"
class TimingStability(Enum):
"""Timing stability classifications"""
STABLE = "stable" # Consistent timing behavior
VARIABLE = "variable" # Some timing variations
UNSTABLE = "unstable" # Highly variable timing
UNKNOWN = "unknown"
class DataType(Enum):
"""Primary data types in enhanced protocols"""
ANALOG = "analog"
PCM = "pcm"
DISCRETE = "discrete"
TIME = "time"
VIDEO = "video"
TMATS = "tmats"
UNKNOWN = "unknown"
@dataclass
class TimingAnalysis:
"""Timing analysis results for enhanced protocols"""
avg_clock_drift_ppm: float = 0.0
max_clock_drift_ppm: float = 0.0
min_clock_drift_ppm: float = 0.0
drift_variance: float = 0.0
quality: TimingQuality = TimingQuality.UNKNOWN
stability: TimingStability = TimingStability.UNKNOWN
# Timing accuracy metrics
timing_accuracy_percent: float = 0.0
sync_errors: int = 0
timing_anomalies: int = 0
anomaly_rate_percent: float = 0.0
# Internal timing capabilities
has_internal_timing: bool = False
rtc_sync_available: bool = False
def calculate_quality(self) -> TimingQuality:
"""Calculate timing quality based on drift measurements"""
max_drift = abs(max(self.max_clock_drift_ppm, self.min_clock_drift_ppm, key=abs))
if max_drift < 1.0:
return TimingQuality.EXCELLENT
elif max_drift < 10.0:
return TimingQuality.GOOD
elif max_drift < 100.0:
return TimingQuality.MODERATE
else:
return TimingQuality.POOR
def calculate_stability(self) -> TimingStability:
"""Calculate timing stability based on variance"""
if self.drift_variance < 1.0:
return TimingStability.STABLE
elif self.drift_variance < 25.0:
return TimingStability.VARIABLE
else:
return TimingStability.UNSTABLE
@dataclass
class QualityMetrics:
"""Quality metrics for enhanced protocol data"""
# Frame quality metrics
avg_frame_quality_percent: float = 0.0
frame_quality_samples: List[float] = field(default_factory=list)
# Signal quality metrics
avg_signal_quality_percent: float = 0.0
signal_quality_samples: List[float] = field(default_factory=list)
# Error counts
sequence_gaps: int = 0
format_errors: int = 0
overflow_errors: int = 0
checksum_errors: int = 0
# Confidence metrics
avg_confidence_score: float = 0.0
confidence_samples: List[float] = field(default_factory=list)
low_confidence_frames: int = 0
# Data integrity
corrupted_frames: int = 0
missing_frames: int = 0
duplicate_frames: int = 0
def calculate_overall_quality(self) -> float:
"""Calculate overall quality score (0-100)"""
if not self.frame_quality_samples and not self.signal_quality_samples:
return 0.0
frame_score = self.avg_frame_quality_percent if self.frame_quality_samples else 100.0
signal_score = self.avg_signal_quality_percent if self.signal_quality_samples else 100.0
confidence_score = self.avg_confidence_score * 100 if self.confidence_samples else 100.0
# Weight the scores
weighted_score = (frame_score * 0.4 + signal_score * 0.4 + confidence_score * 0.2)
# Apply error penalties
total_frames = len(self.frame_quality_samples) or 1
error_rate = (self.format_errors + self.overflow_errors + self.corrupted_frames) / total_frames
penalty = min(error_rate * 50, 50) # Max 50% penalty
return max(0.0, weighted_score - penalty)
@dataclass
class DecodedData:
"""Container for decoded protocol data"""
# Channel information
channel_count: int = 0
analog_channels: int = 0
pcm_channels: int = 0
discrete_channels: int = 0
# Data type classification
primary_data_type: DataType = DataType.UNKNOWN
secondary_data_types: Set[DataType] = field(default_factory=set)
# Decoded field information
sample_decoded_fields: Dict[str, Any] = field(default_factory=dict)
available_field_names: List[str] = field(default_factory=list)
field_count: int = 0
critical_fields: List[str] = field(default_factory=list)
# Frame type analysis
frame_types: Set[str] = field(default_factory=set)
frame_type_distribution: Dict[str, int] = field(default_factory=dict)
# Special frame counts
tmats_frames: int = 0
setup_frames: int = 0
data_frames: int = 0
# Decoder metadata
decoder_type: str = "Standard"
decoder_version: Optional[str] = None
decode_success_rate: float = 1.0
def add_frame_type(self, frame_type: str):
"""Add a frame type to the analysis"""
self.frame_types.add(frame_type)
self.frame_type_distribution[frame_type] = self.frame_type_distribution.get(frame_type, 0) + 1
def get_dominant_frame_type(self) -> Optional[str]:
"""Get the most common frame type"""
if not self.frame_type_distribution:
return None
return max(self.frame_type_distribution.items(), key=lambda x: x[1])[0]
def update_data_type_classification(self):
"""Update primary data type based on channel counts"""
if self.analog_channels > 0 and self.analog_channels >= self.pcm_channels:
self.primary_data_type = DataType.ANALOG
elif self.pcm_channels > 0:
self.primary_data_type = DataType.PCM
elif self.discrete_channels > 0:
self.primary_data_type = DataType.DISCRETE
elif self.tmats_frames > 0:
self.primary_data_type = DataType.TMATS
# Add secondary types
if self.analog_channels > 0:
self.secondary_data_types.add(DataType.ANALOG)
if self.pcm_channels > 0:
self.secondary_data_types.add(DataType.PCM)
if self.discrete_channels > 0:
self.secondary_data_types.add(DataType.DISCRETE)
if self.tmats_frames > 0:
self.secondary_data_types.add(DataType.TMATS)
@dataclass
class EnhancedAnalysisData:
"""Complete enhanced analysis data combining all analysis types"""
timing: TimingAnalysis = field(default_factory=TimingAnalysis)
quality: QualityMetrics = field(default_factory=QualityMetrics)
decoded: DecodedData = field(default_factory=DecodedData)
# Legacy compatibility fields (will be deprecated)
avg_clock_drift_ppm: float = field(init=False)
max_clock_drift_ppm: float = field(init=False)
timing_quality: str = field(init=False)
timing_stability: str = field(init=False)
anomaly_rate: float = field(init=False)
avg_confidence_score: float = field(init=False)
avg_frame_quality: float = field(init=False)
sequence_gaps: int = field(init=False)
rtc_sync_errors: int = field(init=False)
format_errors: int = field(init=False)
overflow_errors: int = field(init=False)
channel_count: int = field(init=False)
analog_channels: int = field(init=False)
pcm_channels: int = field(init=False)
tmats_frames: int = field(init=False)
has_internal_timing: bool = field(init=False)
primary_data_type: str = field(init=False)
decoder_type: str = field(init=False)
sample_decoded_fields: Dict[str, Any] = field(init=False)
available_field_names: List[str] = field(init=False)
field_count: int = field(init=False)
frame_types: Set[str] = field(init=False)
timing_accuracy: float = field(init=False)
signal_quality: float = field(init=False)
def __post_init__(self):
"""Initialize legacy compatibility properties"""
self._update_legacy_fields()
def _update_legacy_fields(self):
"""Update legacy fields from new structured data"""
# Timing fields
self.avg_clock_drift_ppm = self.timing.avg_clock_drift_ppm
self.max_clock_drift_ppm = self.timing.max_clock_drift_ppm
self.timing_quality = self.timing.quality.value
self.timing_stability = self.timing.stability.value
self.anomaly_rate = self.timing.anomaly_rate_percent
self.has_internal_timing = self.timing.has_internal_timing
self.timing_accuracy = self.timing.timing_accuracy_percent
# Quality fields
self.avg_confidence_score = self.quality.avg_confidence_score
self.avg_frame_quality = self.quality.avg_frame_quality_percent
self.sequence_gaps = self.quality.sequence_gaps
self.rtc_sync_errors = self.timing.sync_errors
self.format_errors = self.quality.format_errors
self.overflow_errors = self.quality.overflow_errors
self.signal_quality = self.quality.avg_signal_quality_percent
# Decoded data fields
self.channel_count = self.decoded.channel_count
self.analog_channels = self.decoded.analog_channels
self.pcm_channels = self.decoded.pcm_channels
self.tmats_frames = self.decoded.tmats_frames
self.primary_data_type = self.decoded.primary_data_type.value
self.decoder_type = self.decoded.decoder_type
self.sample_decoded_fields = self.decoded.sample_decoded_fields
self.available_field_names = self.decoded.available_field_names
self.field_count = self.decoded.field_count
self.frame_types = self.decoded.frame_types
def update_from_components(self):
"""Update legacy fields when component objects change"""
self._update_legacy_fields()
def get_overall_health_score(self) -> float:
"""Calculate overall health score for the enhanced analysis"""
quality_score = self.quality.calculate_overall_quality()
# Factor in timing quality
timing_score = 100.0
if self.timing.quality == TimingQuality.EXCELLENT:
timing_score = 100.0
elif self.timing.quality == TimingQuality.GOOD:
timing_score = 80.0
elif self.timing.quality == TimingQuality.MODERATE:
timing_score = 60.0
elif self.timing.quality == TimingQuality.POOR:
timing_score = 30.0
else:
timing_score = 50.0 # Unknown
# Weight quality more heavily than timing
return (quality_score * 0.7 + timing_score * 0.3)