#!/usr/bin/env python3 """ FR-2.2: Audio Data Validation Validates audio metrics against expected ranges """ from typing import NamedTuple class ValidationResult(NamedTuple): """Validation result""" valid: bool error: str = "" class AudioValidator: """ Validates audio metrics against hardware constraints and realistic ranges. """ # Hardware constraints (from FR spec) RMS_MIN_DB = -40.0 # Noise floor RMS_MAX_DB = 80.0 # Clipping threshold FREQ_MIN_HZ = 100 # Below = unreliable (FFT bin size ~43Hz) FREQ_MAX_HZ = 8000 # Nyquist @ 22.05kHz with safety margin # Extended ranges for detection (not storage) FREQ_MIN_EXTENDED_HZ = 20 FREQ_MAX_EXTENDED_HZ = 11000 @staticmethod def validate_rms(rms_db: float) -> ValidationResult: """ Validate RMS value. Args: rms_db: RMS in dB Returns: ValidationResult with valid flag and error message """ if not isinstance(rms_db, (int, float)): return ValidationResult(False, "RMS must be numeric") if rms_db < AudioValidator.RMS_MIN_DB: return ValidationResult( False, f"RMS {rms_db:.1f}dB below minimum {AudioValidator.RMS_MIN_DB}dB" ) if rms_db > AudioValidator.RMS_MAX_DB: return ValidationResult( False, f"RMS {rms_db:.1f}dB exceeds maximum {AudioValidator.RMS_MAX_DB}dB", ) return ValidationResult(True) @staticmethod def validate_frequency(freq_hz: int, strict: bool = True) -> ValidationResult: """ Validate frequency value. Args: freq_hz: Frequency in Hz strict: If True, use tight range (100-8000Hz), else extended (20-11000Hz) Returns: ValidationResult with valid flag and error message """ if not isinstance(freq_hz, int): return ValidationResult(False, "Frequency must be integer") if strict: min_hz = AudioValidator.FREQ_MIN_HZ max_hz = AudioValidator.FREQ_MAX_HZ else: min_hz = AudioValidator.FREQ_MIN_EXTENDED_HZ max_hz = AudioValidator.FREQ_MAX_EXTENDED_HZ if freq_hz < min_hz: return ValidationResult( False, f"Frequency {freq_hz}Hz below minimum {min_hz}Hz" ) if freq_hz > max_hz: return ValidationResult( False, f"Frequency {freq_hz}Hz exceeds maximum {max_hz}Hz" ) return ValidationResult(True) @staticmethod def validate_packet(rms_db: float, freq_hz: int) -> ValidationResult: """ Validate complete audio packet. Args: rms_db: RMS in dB freq_hz: Frequency in Hz Returns: ValidationResult with valid flag and error message """ rms_result = AudioValidator.validate_rms(rms_db) if not rms_result.valid: return rms_result freq_result = AudioValidator.validate_frequency(freq_hz, strict=True) if not freq_result.valid: return freq_result return ValidationResult(True)