progress?
This commit is contained in:
289
analyzer/models/enhanced_analysis.py
Normal file
289
analyzer/models/enhanced_analysis.py
Normal file
@@ -0,0 +1,289 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user