"""Classificação dos grupos candidatos em tipos de retake. Transforma as métricas (similaridade textual, semântica, proximidade temporal, sinal de reinício e repetição do início) em um tipo e um nível de confiança, usando regras configuráveis. Nenhum valor isolado decide; a classificação combina os sinais. """ from __future__ import annotations from dataclasses import dataclass from .modelos_de_retakes import AgrupamentoDeFalas, ClassificacaoDeRetake SEM_INDICIO = "sem_indicio_de_retake" RETAKE_CONFIRMADO = "retake_confirmado" POSSIVEL_RETAKE = "possivel_retake" REPETICAO_DE_FRASE = "repeticao_de_frase" REPETICAO_DE_INICIO = "repeticao_de_inicio" REPETICAO_PARCIAL = "repeticao_parcial" CONTINUIDADE_DE_FALA = "continuidade_de_fala" @dataclass(frozen=True) class RegrasDeClassificacao: """Limiares e pesos configuráveis para a classificação.""" similaridade_textual_minima: float = 0.60 similaridade_para_confirmar: float = 0.85 proximidade_minima: float = 0.50 semantica_para_confirmar: float = 0.85 peso_textual: float = 0.50 peso_semantica: float = 0.25 peso_proximidade: float = 0.15 peso_reinicio: float = 0.10 # Evidência fraca: reforça a confiança, nunca decide o tipo sozinha. peso_visual_fraca: float = 0.03 class ClassificadorDeRetakes: """Atribui tipo e confiança a um ``AgrupamentoDeFalas``.""" def __init__(self, regras: RegrasDeClassificacao | None = None) -> None: self.regras = regras or RegrasDeClassificacao() def classificar(self, agrupamento: AgrupamentoDeFalas) -> ClassificacaoDeRetake: if not agrupamento.pares: return ClassificacaoDeRetake(SEM_INDICIO, 0.0) par = max(agrupamento.pares, key=lambda item: item.comparacao.similaridade_final) texto = par.comparacao.similaridade_final semantica = par.similaridade_semantica proximidade = par.intervalo.proximidade_temporal reproducao_inicio = par.comparacao.similaridade_do_inicio >= 0.9 tem_reinicio = bool(agrupamento.sinais_de_reinicio) r = self.regras confianca = ( r.peso_textual * texto + r.peso_semantica * semantica + r.peso_proximidade * proximidade + r.peso_reinicio * (1.0 if tem_reinicio else 0.0) ) # A análise visual é reforço (evidência fraca): soma um pequeno peso à # confiança sem jamais alterar o tipo de retake. if par.mudanca_visual_proxima: confianca += r.peso_visual_fraca # Repetições completas e muito semelhantes de frase. if texto >= r.similaridade_para_confirmar: tipo = RETAKE_CONFIRMADO if reproducao_inicio: tipo = RETAKE_CONFIRMADO elif ( texto >= r.similaridade_textual_minima and semantica >= r.semantica_para_confirmar and tem_reinicio ): tipo = RETAKE_CONFIRMADO elif ( texto >= r.similaridade_textual_minima and proximidade >= r.proximidade_minima ): tipo = POSSIVEL_RETAKE elif reproducao_inicio and tem_reinicio: tipo = REPETICAO_DE_INICIO elif semantica >= r.semantica_para_confirmar: tipo = REPETICAO_PARCIAL else: tipo = POSSIVEL_RETAKE if tipo == RETAKE_CONFIRMADO: confianca = min(1.0, max(confianca, texto * 0.9 + 0.05 * (1 if tem_reinicio else 0))) else: confianca = min(1.0, confianca) return ClassificacaoDeRetake(tipo, round(confianca, 3))