Files
João Henrique b9bf3b2863 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
2026-09-08 16:13:27 -04:00

112 lines
3.7 KiB
Python

"""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