from array import array from dataclasses import replace import json import numpy as np from autoace_audio.audio import ( AudioMeasurements, AudioMetadata, ChannelAnalysis, NormalizedAudio, SpeechRegion, measure_audio, refine_primary_speech_measurements, ) from autoace_audio.fusion import fuse_prediction from autoace_audio.models import ( AcousticEmotionResult, EnvironmentalSoundResult, RoleAnalysisResult, SemanticEmotionResult, TranscriptResult, TranscriptSegment, _excessive_repetition, grounded_semantic_emotion, resolve_speaker_roles, transcript_subset, ) from autoace_audio.pipeline import AnalysisResult def normalized(samples, sample_rate=16_000): metadata = AudioMetadata( name="synthetic.wav", container_format="wav", codec_name="pcm_s16le", sample_rate=sample_rate, channels=1, channel_layout=None, duration_seconds=len(samples) / sample_rate, size_bytes=max(len(samples) * 2, 1), bit_rate=None, ) return NormalizedAudio( metadata=metadata, sample_rate=sample_rate, samples=array("f", samples), original_channels=1, channel_analysis=ChannelAnalysis(False, None, None), peak_before_normalization=max((abs(float(value)) for value in samples), default=0.0), normalization_gain=1.0, ) def base_measurements(**overrides): values = { "speech_regions": (SpeechRegion(0.0, 2.0, -20.0),), "speech_fraction": 0.5, "longest_internal_silence_seconds": 0.0, "long_silence_present": False, "clipping_ratio": 0.0, "loudness_dbfs": -20.0, "low_volume": False, "snr_proxy_db": 25.0, "spectral_flatness": 0.05, "static_score": 0.02, "bandwidth_hz": 6_000.0, "high_frequency_ratio": 0.2, "muffled": False, "noise_present": False, "noise_severity": "none", "deterministic_noise_type": "", "audio_quality": "clear", "speech_energy_variability_db": 4.0, "overlap_score": 0.1, "speaker_overlap_present": False, "vad_threshold_dbfs": -40.0, } values.update(overrides) return AudioMeasurements(**values) def test_vad_and_long_internal_silence_detection(): sample_rate = 16_000 time = np.arange(sample_rate, dtype=np.float32) / sample_rate speech = 0.25 * np.sin(2 * np.pi * 220 * time) waveform = np.concatenate([speech, np.zeros(sample_rate * 11, dtype=np.float32), speech]) result = measure_audio(normalized(waveform)) assert len(result.speech_regions) == 2 assert result.long_silence_present is True assert result.longest_internal_silence_seconds >= 10.9 def test_clipping_and_low_volume_are_measured_not_defaulted(): clipped = np.tile(np.array([1.0, -1.0, 0.9, -0.9], dtype=np.float32), 8_000) quiet_time = np.arange(16_000, dtype=np.float32) / 16_000 quiet = 0.005 * np.sin(2 * np.pi * 440 * quiet_time) clipped_result = measure_audio(normalized(clipped)) quiet_result = measure_audio(normalized(quiet)) assert clipped_result.clipping_ratio > 0.4 assert clipped_result.audio_quality == "severely_impaired" assert quiet_result.low_volume is True assert quiet_result.audio_quality != "clear" def test_asr_repetition_guard(): assert _excessive_repetition("hello hello hello hello hello") is True assert _excessive_repetition("the vehicle is ready for pickup tomorrow") is False def test_semantic_evidence_is_literal_and_asr_gated(): accepted = TranscriptSegment(0.0, 2.0, "Thank you, that sounds good.", 0.8, True, None) transcript = TranscriptResult( text=accepted.text, segments=(accepted,), confidence=0.8, reliable=True, accepted_count=1, rejected_count=0, status="ok", ) result = grounded_semantic_emotion(transcript) assert result.tone == "satisfied" assert result.grounded is True assert all(piece.casefold() in transcript.text.casefold() for piece in result.evidence) unreliable = TranscriptResult(transcript.text, (accepted,), 0.1, False, 1, 0, "unreliable") rejected = grounded_semantic_emotion(unreliable) assert rejected.tone is None assert rejected.status == "asr_inadequate" def test_fusion_penalizes_disagreement_and_preserves_noise_invariant(): base = AudioMeasurements( speech_regions=(), speech_fraction=0.5, longest_internal_silence_seconds=0.0, long_silence_present=False, clipping_ratio=0.0, loudness_dbfs=-20.0, low_volume=False, snr_proxy_db=25.0, spectral_flatness=0.05, static_score=0.02, bandwidth_hz=6_000.0, high_frequency_ratio=0.2, muffled=False, noise_present=False, noise_severity="none", deterministic_noise_type="", audio_quality="clear", speech_energy_variability_db=4.0, overlap_score=0.1, speaker_overlap_present=False, vad_threshold_dbfs=-40.0, ) acoustic = AcousticEmotionResult("neutral", "low", 0.8, "neu", {"neu": 0.8}, 2) semantic = SemanticEmotionResult("upset", "medium", 0.7, ("unacceptable",), True, "ok") acoustic_only = fuse_prediction(base, acoustic) disagreement = fuse_prediction(base, acoustic, semantic, hybrid_requested=True) assert disagreement.prediction.confidence < acoustic_only.prediction.confidence assert disagreement.disagreement is True assert disagreement.prediction.background_noise_type == "" assert disagreement.prediction.background_noise_severity.value == "none" def transcript_from(*segments): confidence = float(np.mean([segment.confidence for segment in segments])) if segments else 0.0 return TranscriptResult( " ".join(segment.text for segment in segments), tuple(segments), confidence, bool(segments), len(segments), 0, "ok" if segments else "unreliable", ) def test_agent_only_phrases_do_not_determine_customer_emotion(): agent = TranscriptSegment(0.0, 2.0, "Thank you for calling. How can I help?", 0.9, True, None) transcript = transcript_from(agent) roles = resolve_speaker_roles(transcript) assert roles.agent_segments == (agent,) assert roles.customer_segments == () customer_semantic = grounded_semantic_emotion(transcript_subset(transcript, roles.customer_segments)) assert customer_semantic.grounded is False assert customer_semantic.tone is None def test_neutral_agent_greeting_cannot_override_upset_customer_segment(): agent = TranscriptSegment(0.0, 1.5, "How can I help?", 0.9, True, None) customer = TranscriptSegment(2.0, 3.5, "This is unacceptable.", 0.85, True, None) transcript = transcript_from(agent, customer) roles = resolve_speaker_roles(transcript) semantic = grounded_semantic_emotion(transcript_subset(transcript, roles.customer_segments)) acoustic = AcousticEmotionResult( "upset", "high", 0.72, "ang", {"ang": 0.8}, 1, persistence=1.0, escalation=0.0, evidence_scope="customer_segments", ) result = fuse_prediction( base_measurements(), acoustic, semantic, hybrid_requested=True, role_analysis=roles, transcript=transcript, ) assert semantic.tone == "upset" assert result.prediction.emotional_tone.value == "upset" def test_repeated_hello_alone_does_not_force_upset(): segments = ( TranscriptSegment(0.0, 1.5, "How can I help?", 0.9, True, None), TranscriptSegment(4.0, 4.5, "Hello?", 0.7, True, None), TranscriptSegment(7.0, 7.5, "Hello?", 0.75, True, None), TranscriptSegment(10.0, 10.5, "Hello?", 0.8, True, None), ) transcript = transcript_from(*segments) roles = resolve_speaker_roles(transcript) acoustic = AcousticEmotionResult( "neutral", "medium", 0.68, "neu", {"neu": 0.8}, 3, persistence=1.0, escalation=0.0, evidence_scope="customer_segments", ) result = fuse_prediction( base_measurements(), acoustic, SemanticEmotionResult(None, None, 0.0, (), False, "no_grounded_evidence"), hybrid_requested=True, role_analysis=roles, transcript=transcript, ) assert roles.repeated_attention_count == 3 assert result.prediction.emotional_tone.value == "neutral" def test_repeated_attention_with_persistent_agitated_acoustics_can_increase_severity(): segments = ( TranscriptSegment(0.0, 1.5, "How can I help?", 0.9, True, None), TranscriptSegment(4.0, 4.5, "Hello?", 0.7, True, None), TranscriptSegment(7.0, 7.5, "Hello?", 0.75, True, None), ) transcript = transcript_from(*segments) roles = resolve_speaker_roles(transcript) acoustic = AcousticEmotionResult( "frustrated", "high", 0.68, "ang", {"ang": 0.72}, 2, persistence=0.8, escalation=0.3, evidence_scope="customer_segments", ) result = fuse_prediction( base_measurements(), acoustic, SemanticEmotionResult(None, None, 0.0, (), False, "no_grounded_evidence"), hybrid_requested=True, role_analysis=roles, transcript=transcript, ) assert result.prediction.emotional_tone.value == "upset" def test_customer_and_agent_text_are_not_mixed_for_semantics(): agent = TranscriptSegment(0.0, 2.0, "Thank you for calling. How can I help?", 0.9, True, None) customer = TranscriptSegment(3.0, 4.0, "This is unacceptable.", 0.8, True, None) transcript = transcript_from(agent, customer) roles = resolve_speaker_roles(transcript) customer_only = transcript_subset(transcript, roles.customer_segments) semantic = grounded_semantic_emotion(customer_only) assert "thank you" not in customer_only.text.casefold() assert semantic.tone == "upset" assert all(evidence.casefold() in customer.text.casefold() for evidence in semantic.evidence) def test_low_role_confidence_reduces_final_confidence(): agent = TranscriptSegment(0.0, 1.0, "How can I help?", 0.9, True, None) customer = TranscriptSegment(2.0, 4.5, "I need service.", 0.8, True, None) transcript = transcript_from(agent, customer) resolved = resolve_speaker_roles(transcript) high = replace(resolved, role_confidence=0.85) low = replace(resolved, role_confidence=0.20) acoustic = AcousticEmotionResult( "neutral", "low", 0.72, "neu", {"neu": 0.85}, 1, persistence=1.0, evidence_scope="customer_segments", ) semantic = SemanticEmotionResult(None, None, 0.0, (), False, "no_grounded_evidence") high_result = fuse_prediction( base_measurements(), acoustic, semantic, hybrid_requested=True, role_analysis=high, transcript=transcript, ) low_result = fuse_prediction( base_measurements(), acoustic, semantic, hybrid_requested=True, role_analysis=low, transcript=transcript, ) assert low_result.prediction.confidence < high_result.prediction.confidence def test_background_speech_energy_is_not_automatically_primary_speech_or_overlap(): measurements = base_measurements( speech_regions=(SpeechRegion(0.0, 10.0, -25.0),), speech_fraction=1.0, overlap_score=0.9, speaker_overlap_present=True, ) refined = refine_primary_speech_measurements(measurements, 10.0, ((1.0, 2.0),)) assert refined.primary_speech_fraction == 0.1 assert refined.speaker_overlap_present is False assert refined.overlap_reliability == "indeterminate" def test_background_television_does_not_automatically_imply_overlap(): measurements = refine_primary_speech_measurements(base_measurements(), 5.0, ((0.5, 2.5),)) acoustic = AcousticEmotionResult( "neutral", "low", 0.7, "neu", {"neu": 0.8}, 1, persistence=1.0, evidence_scope="customer_segments", ) environment = EnvironmentalSoundResult("television", 0.4, ("Television",), "ok") result = fuse_prediction(measurements, acoustic, environment=environment) assert result.prediction.background_noise_type == "television" assert result.prediction.speaker_overlap_present is False def test_public_download_details_exclude_transcript_and_grounding_text(): customer = TranscriptSegment(1.0, 2.0, "This private phrase is unacceptable.", 0.8, True, None) transcript = transcript_from(customer) roles = resolve_speaker_roles(transcript) semantic = grounded_semantic_emotion(transcript_subset(transcript, roles.customer_segments)) acoustic = AcousticEmotionResult( "upset", "medium", 0.65, "ang", {"ang": 0.7}, 1, persistence=1.0, evidence_scope="customer_segments", ) fused = fuse_prediction( base_measurements(), acoustic, semantic, hybrid_requested=True, role_analysis=roles, transcript=transcript, ) analysis = AnalysisResult( prediction=fused.prediction, measurements=base_measurements(), acoustic=acoustic, semantic_status=semantic.status, environment=None, timings_seconds={"total": 1.0}, fallback_statuses=(), mode="hybrid", audio_metadata={}, role_analysis=roles, _transcript=transcript, _semantic=semantic, _fusion=fused, ) downloadable = json.dumps(analysis.public_details()).casefold() memory_only = json.dumps(analysis.session_details()).casefold() assert "this private phrase" not in downloadable assert "unacceptable" not in downloadable assert "this private phrase" in memory_only