import sys import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from music_token_metrics import ( MatrizSemAcorde, avaliar_sem_acorde, distancia_edicao_tokens, eh_sem_acorde, normalizar_token, qualidade_completa_token, qualidade_token, raiz_token, simplificar_token_musical, ) class MusicTokenMetricsTests(unittest.TestCase): def test_normaliza_aliases_de_no_chord_sem_confundir_com_acorde(self): for token in ("N", "N.C.", "no_chord", "sem_acorde", "silencio"): with self.subTest(token=token): self.assertEqual(normalizar_token(token), "N") self.assertTrue(eh_sem_acorde(token)) self.assertEqual(raiz_token(token), "") self.assertEqual(qualidade_token(token), "no_chord") self.assertEqual(simplificar_token_musical(token), "N") self.assertFalse(eh_sem_acorde("C")) self.assertEqual(qualidade_token("token-invalido"), "unknown") def test_qualidade_completa_distingue_extensoes_antes_colapsadas(self): casos = { "C": "major", "Cm": "minor", "C7": "dominant7", "Cmaj7": "major7", "Cm7": "minor7", "CM7": "major7", "C7M": "major7", "C6": "major6", "Cm6": "minor6", "C9": "dominant9", "Cmaj9": "major9", "Cm9": "minor9", "Cadd9": "add9", "Csus2": "sus2", "Csus4": "sus4", "Cdim": "diminished", "Cdim7": "diminished7", "C°7": "diminished7", "Cm7b5": "half_diminished7", "Cø7": "half_diminished7", "Caug": "augmented", "C+": "augmented", "C-7": "minor7", "C5": "power5", "N": "no_chord", } for token, esperado in casos.items(): with self.subTest(token=token): self.assertEqual(qualidade_completa_token(token), esperado) self.assertEqual(qualidade_token("C"), qualidade_token("Cmaj7")) self.assertNotEqual(qualidade_completa_token("C"), qualidade_completa_token("Cmaj7")) def test_matriz_no_chord_expoe_falso_positivo_e_falso_negativo(self): matriz = avaliar_sem_acorde( ["C", "N", "G", "N"], ["C", "N.C.", "N", "A"], ) self.assertEqual( matriz, MatrizSemAcorde( verdadeiros_positivos=1, falsos_positivos=1, falsos_negativos=1, verdadeiros_negativos=1, ), ) self.assertEqual(matriz.suporte, 2) self.assertAlmostEqual(matriz.acuracia, 0.5) self.assertAlmostEqual(matriz.precisao, 0.5) self.assertAlmostEqual(matriz.revocacao, 0.5) self.assertAlmostEqual(matriz.f1, 0.5) def test_matriz_no_chord_conta_token_ausente_como_falso_negativo(self): matriz = avaliar_sem_acorde(["C", "N"], ["C"]) self.assertEqual(matriz.falsos_negativos, 1) self.assertEqual(matriz.verdadeiros_negativos, 1) def test_distancia_edicao_separa_insercao_remocao_e_substituicao(self): insercao = distancia_edicao_tokens( ["C", "G", "AM", "F"], ["C", "D", "G", "AM", "F"], ) remocao = distancia_edicao_tokens(["C", "G", "AM", "F"], ["C", "AM", "F"]) substituicao = distancia_edicao_tokens(["C", "G"], ["C", "D"]) self.assertEqual((insercao.distancia, insercao.insercoes), (1, 1)) self.assertEqual((remocao.distancia, remocao.remocoes), (1, 1)) self.assertEqual((substituicao.distancia, substituicao.substituicoes), (1, 1)) def test_distancia_edicao_aceita_transformacao_musical(self): exata = distancia_edicao_tokens( ["Cmaj7", "G7/B"], ["C", "G"], raiz_token, ) self.assertEqual(exata.distancia, 0) self.assertEqual(exata.substituicoes, 0) self.assertEqual(exata.insercoes, 0) self.assertEqual(exata.remocoes, 0) def test_soma_de_matrizes_preserva_contagens_para_agregacao(self): primeira = MatrizSemAcorde(verdadeiros_positivos=1, falsos_positivos=2) segunda = MatrizSemAcorde(falsos_negativos=3, verdadeiros_negativos=4) self.assertEqual( primeira.somar(segunda), MatrizSemAcorde( verdadeiros_positivos=1, falsos_positivos=2, falsos_negativos=3, verdadeiros_negativos=4, ), ) if __name__ == "__main__": unittest.main()