feat: criado repositório jhonny-editor no Gitea
criado repositório jhonny-editor no Gitea adicionado script admin/deploy.command com commit automático atualizado admin/DEV-NOTES.md com template limpo Resumo: - 48 arquivos alterados - 26 novos - 19 modificados - 3 removidos 22 files changed, 658 insertions(+), 568 deletions(-) Arquivos: - AGENTS.md - admin/DEV-NOTES.md - admin/OpenCut.command - admin/commit.command - admin/deploy.command - admin/update.command - code/cep-plugin/index.html - code/cep-plugin/main.js - code/cep-plugin/styles.css - code/engine/ARQUITETURA.md - code/engine/arquitetura/README.md - code/engine/gerar_relatorio_timeline.py - code/engine/integracoes/apple_speech/apple_speech_transcriber.swift - code/engine/integracoes/apple_speech/provider_de_transcricao_apple.py - code/engine/integracoes/midia/__init__.py - code/engine/integracoes/whisper/provider_de_transcricao_local.py - code/engine/scanner/__init__.py - code/engine/scanner/coordenacao/__init__.py - code/engine/scanner/descoberta/__init__.py - code/engine/scanner/modelos.py - code/engine/scanner/transcricao_da_timeline.py - code/src/tools/discovery.ts - :memory:.ses - admin/Jhonny.command - admin/inativos/OpenCut.command - admin/inativos/update.command - code/docs/glossario-analise-emocional.md - code/docs/levantamento-apple-vision-e-apple-intelligence.md - code/docs/levantamento-ferramentas-analise-visual-local.md - code/engine/arquitetura/biblioteca-inteligente-de-videos.md - code/engine/executar_scanner.py - code/engine/integracoes/huggingface/ - code/engine/integracoes/midia/extracao_de_metadados.py - code/engine/integracoes/visual/ - code/engine/requirements-visual.txt - code/engine/scanner/configuracao_visual.py - code/engine/scanner/descoberta/descoberta_de_arquivos.py - code/engine/scanner/metadados.py - code/engine/scanner/relatorio_visual.py - code/engine/scanner/retakes/ - code/engine/scanner/visual.py - code/engine/testes/test_analise_visual_local.py - code/engine/testes/test_arquivos_e_metadados.py - code/engine/testes/test_provider_de_transcricao_apple.py - code/relatorios/analise-brools/ - code/relatorios/analise-visual/ - code/relatorios/audio/arquivos/ - code/relatorios/transcricao-timeline.md
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
"""Comparação textual entre falas.
|
||||
|
||||
Responsável pelos algoritmos de similaridade: Jaccard (conjunto de
|
||||
palavras), sequência (ordem das palavras via maior subsequência comum) e
|
||||
similaridade do início/fim da frase. Não decide nada sozinho — apenas
|
||||
produz métricas para o classificador.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from .modelos_de_retakes import Fala, ResultadoDaComparacao
|
||||
|
||||
|
||||
def _normalizar(texto: str) -> str:
|
||||
"""Minúsculas, sem pontuação e sem espaços duplicados."""
|
||||
sem_pontuacao = re.sub(r"[^\w\s]", " ", texto)
|
||||
return " ".join(sem_pontuacao.lower().split())
|
||||
|
||||
|
||||
def _lcs(a: list[str], b: list[str]) -> int:
|
||||
"""Tamanho da maior subsequência comum preservando a ordem."""
|
||||
anterior = [0] * (len(b) + 1)
|
||||
for palavra_a in a:
|
||||
atual = [0] * (len(b) + 1)
|
||||
for j, palavra_b in enumerate(b):
|
||||
if palavra_a == palavra_b:
|
||||
atual[j + 1] = anterior[j] + 1
|
||||
else:
|
||||
atual[j + 1] = max(atual[j], anterior[j + 1])
|
||||
anterior = atual
|
||||
return anterior[-1]
|
||||
|
||||
|
||||
class ComparadorDeFalas:
|
||||
"""Compara duas falas e calcula as métricas de similaridade textual."""
|
||||
|
||||
@staticmethod
|
||||
def normalizar(texto: str) -> str:
|
||||
return _normalizar(texto)
|
||||
|
||||
@staticmethod
|
||||
def _palavras_de(fala: Fala) -> list[str]:
|
||||
return fala.texto_normalizado.split() or _normalizar(fala.texto).split()
|
||||
|
||||
def comparar(self, fala_a: Fala, fala_b: Fala) -> ResultadoDaComparacao:
|
||||
palavras_a = self._palavras_de(fala_a)
|
||||
palavras_b = self._palavras_de(fala_b)
|
||||
|
||||
if not palavras_a or not palavras_b:
|
||||
return ResultadoDaComparacao(fala_a.id, fala_b.id)
|
||||
|
||||
# 1. Jaccard — conjunto de palavras (rápido, ótimo candidato).
|
||||
conjunto_a = set(palavras_a)
|
||||
conjunto_b = set(palavras_b)
|
||||
intersecao = len(conjunto_a & conjunto_b)
|
||||
uniao = len(conjunto_a | conjunto_b)
|
||||
jaccard = intersecao / uniao if uniao else 0.0
|
||||
|
||||
# 2. Sequência — ordem das palavras via LCS normalizada.
|
||||
lcs = _lcs(palavras_a, palavras_b)
|
||||
sequencia = lcs / max(len(palavras_a), len(palavras_b))
|
||||
|
||||
# 3. Início e final da frase.
|
||||
inicio = self._similaridade_de_prefijo(palavras_a, palavras_b)
|
||||
final = self._similaridade_de_sufixo(palavras_a, palavras_b)
|
||||
|
||||
# 4. Similaridade final ponderada.
|
||||
fim = 0.45 * sequencia + 0.30 * jaccard + 0.15 * inicio + 0.10 * final
|
||||
return ResultadoDaComparacao(
|
||||
fala_a_id=fala_a.id,
|
||||
fala_b_id=fala_b.id,
|
||||
similaridade_jaccard=round(jaccard, 4),
|
||||
similaridade_de_sequencia=round(sequencia, 4),
|
||||
similaridade_do_inicio=round(inicio, 4),
|
||||
similaridade_do_final=round(final, 4),
|
||||
similaridade_final=round(min(1.0, fim), 4),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _similaridade_de_prefijo(a: list[str], b: list[str]) -> float:
|
||||
if not a or not b:
|
||||
return 0.0
|
||||
n = 0
|
||||
for x, y in zip(a, b):
|
||||
if x != y:
|
||||
break
|
||||
n += 1
|
||||
return n / max(len(a), len(b))
|
||||
|
||||
@staticmethod
|
||||
def _similaridade_de_sufixo(a: list[str], b: list[str]) -> float:
|
||||
if not a or not b:
|
||||
return 0.0
|
||||
n = 0
|
||||
for x, y in zip(reversed(a), reversed(b)):
|
||||
if x != y:
|
||||
break
|
||||
n += 1
|
||||
return n / max(len(a), len(b))
|
||||
|
||||
@staticmethod
|
||||
def prefixo_comum_em_palavras(texto_a: str, texto_b: str) -> int:
|
||||
a = _normalizar(texto_a).split()
|
||||
b = _normalizar(texto_b).split()
|
||||
n = 0
|
||||
for x, y in zip(a, b):
|
||||
if x != y:
|
||||
break
|
||||
n += 1
|
||||
return n
|
||||
Reference in New Issue
Block a user