import sys import types import unittest from pathlib import Path from unittest.mock import patch sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from report_audio_accuracy import CasoAudio, ResultadoAudio, analisar_caso, resumir_resultados def resultado_teste(**sobrescritas): valores = { "filename": "caso.wav", "instrument": "violao", "expected": "C N G", "predicted": "C N N", "exact_match": False, "slash_tolerated_match": False, "rotated_exact_match": False, "rotated_simplified_match": False, "rotated_root_match": False, "musical_match": False, "token_accuracy": 2 / 3, "simplified_token_accuracy": 2 / 3, "root_accuracy": 2 / 3, "quality_accuracy": 2 / 3, "full_quality_accuracy": 0.25, "edit_distance": 1, "substitutions": 1, "insertions": 0, "deletions": 0, "token_error_rate": 1 / 3, "no_chord_true_positives": 1, "no_chord_false_positives": 1, "no_chord_false_negatives": 0, "no_chord_true_negatives": 1, "best_rotation_shift": 0, "expected_tokens": 3, "predicted_tokens": 3, "matched_tokens": 2, "elapsed_seconds": 0.1, "diagnostics": {}, } valores.update(sobrescritas) return ResultadoAudio(**valores) class ReportAudioAccuracyMetricsTests(unittest.TestCase): def test_analisar_caso_converte_gate_no_chord_em_token_somente_no_benchmark(self): modulo_harmonico = types.ModuleType("harmonic_pipeline") modulo_harmonico.analyze_harmonic_audio = lambda *_args, **_kwargs: { "cifra_palco": "", "diagnostico_harmonico": { "no_chord_detected": True, "no_chord_probability": 0.96, "no_chord_threshold": 0.88, "no_chord_reasons": ["digital_silence"], "score_kind": "heuristic_evidence_v1", }, } caso = CasoAudio( nome_arquivo="silencio.wav", esperado="N", instrumento="violao", caminho=Path("silencio.wav"), ) with patch.dict(sys.modules, {"harmonic_pipeline": modulo_harmonico}): resultado = analisar_caso(caso) self.assertEqual(resultado.predicted, "N") self.assertEqual(resultado.no_chord_true_positives, 1) self.assertEqual(resultado.no_chord_false_negatives, 0) self.assertTrue(resultado.diagnostics["no_chord_detected"]) def test_agrega_ter_e_no_chord_por_contagens_globais(self): primeiro = resultado_teste() segundo = resultado_teste( filename="outro.wav", expected="N", predicted="C", full_quality_accuracy=0.75, edit_distance=1, substitutions=1, token_error_rate=1.0, no_chord_true_positives=0, no_chord_false_positives=0, no_chord_false_negatives=1, no_chord_true_negatives=0, expected_tokens=1, predicted_tokens=1, matched_tokens=0, ) resumo = resumir_resultados([primeiro, segundo]) self.assertEqual(resumo["metrics_schema_version"], "music-token-metrics-v2") self.assertAlmostEqual(resumo["mean_full_quality_accuracy"], 0.5) self.assertAlmostEqual(resumo["token_error_rate"], 0.5) self.assertEqual( resumo["edit_operations"], { "distance": 2, "substitutions": 2, "insertions": 0, "deletions": 0, "expected_tokens": 4, }, ) self.assertEqual(resumo["no_chord"]["support"], 2) self.assertEqual(resumo["no_chord"]["true_positives"], 1) self.assertEqual(resumo["no_chord"]["false_positives"], 1) self.assertEqual(resumo["no_chord"]["false_negatives"], 1) self.assertAlmostEqual(resumo["no_chord"]["precision"], 0.5) self.assertAlmostEqual(resumo["no_chord"]["recall"], 0.5) self.assertAlmostEqual(resumo["no_chord"]["f1"], 0.5) por_instrumento = resumo["by_instrument"]["violao"] self.assertAlmostEqual(por_instrumento["token_error_rate"], 0.5) self.assertEqual(por_instrumento["no_chord"]["support"], 2) def test_resumo_vazio_declara_convencao_de_divisao_por_zero(self): resumo = resumir_resultados([]) self.assertEqual(resumo["token_error_rate"], 0.0) self.assertEqual(resumo["no_chord"]["accuracy"], 1.0) self.assertEqual(resumo["no_chord"]["precision"], 0.0) self.assertEqual(resumo["no_chord"]["recall"], 0.0) self.assertEqual(resumo["no_chord"]["f1"], 0.0) self.assertEqual(resumo["no_chord"]["zero_division"], 0) if __name__ == "__main__": unittest.main()