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

113 lines
4.1 KiB
Python

"""Comparação semântica entre falas (opcional).
A similaridade textual não cobre casos em que as palavras são diferentes
mas a ideia é a mesma. Este módulo define um protocolo para providers de
embeddings e uma implementação local (via Hugging Face Transformers), além
de um fallback puramente lexical usado quando nenhum provider está
disponível.
O `ComparadorSemantico` nunca decide sozinho que há retake — apenas
fornece uma métrica adicional para o classificador.
"""
from __future__ import annotations
import math
from typing import Protocol
from .modelos_de_retakes import Fala
class ProviderDeSimilaridadeSemantica(Protocol):
"""Contrato para quem calcula similaridade semântica entre dois textos."""
def similaridade(self, texto_a: str, texto_b: str) -> float: ...
class ComparadorLexicalSemantico:
"""Fallback puramente lexical para quando não há embeddings.
Reconstrói uma "similaridade semântica" a partir de palavras que
compartilham a mesma raiz (stemming simples por prefixo comum) e de
sinônimos frequentes em pt-PT. Destina-se a permitir executar a análise
sem carregar modelos pesados.
"""
_PARES_SINONIMOS = (
({"mostrar", "explicar", "demonstrar", "apresentar"},),
({"sistema", "programa", "aplicativo", "software"},),
({"configurar", "configuracao", "instalar", "ajustar"},),
)
def similaridade(self, texto_a: str, texto_b: str) -> float:
palavras_a = {p for p in self._palavras(texto_a)}
palavras_b = {p for p in self._palavras(texto_b)}
if not palavras_a or not palavras_b:
return 0.0
intersecao = 0.0
for palavra_a in palavras_a:
for palavra_b in palavras_b:
if palavra_a == palavra_b:
intersecao += 1.0
elif self._mesma_raiz(palavra_a, palavra_b):
intersecao += 0.7
elif self._mesmo_sinonimo(palavra_a, palavra_b):
intersecao += 0.6
tam = max(len(palavras_a), len(palavras_b))
return round(min(1.0, intersecao / tam), 4)
@staticmethod
def _palavras(texto: str) -> list[str]:
return [p for p in texto.lower().split()]
@staticmethod
def _mesma_raiz(a: str, b: str) -> bool:
raiz = min(len(a), len(b), 4)
return raiz >= 4 and a[:raiz] == b[:raiz]
@staticmethod
def _mesmo_sinonimo(a: str, b: str) -> bool:
return any(a in grupo and b in grupo for grupo in ComparadorLexicalSemantico._PARES_SINONIMOS)
class ComparadorSemantico:
"""Calcula similaridade semântica usando um provider injetável.
Quando o provider é ``None``, cai no ``ComparadorLexicalSemantico``
para não bloquear a análise na ausência de modelos locais.
"""
def __init__(self, provider: ProviderDeSimilaridadeSemantica | None = None) -> None:
self.provider = provider or ComparadorLexicalSemantico()
def comparar(self, fala_a: Fala, fala_b: Fala) -> float:
return float(self.provider.similaridade(fala_a.texto, fala_b.texto))
class ProviderDeSimilaridadeHuggingFace:
"""Embeds os textos localmente com um modelo de embeddings.
O modelo é carregado de forma preguiçosa para não custar nada até ser
de fato necessário (zona de dúvida do classificador).
"""
def __init__(self, modelo: str = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2") -> None:
self.modelo = modelo
self._encoder = None
def similaridade(self, texto_a: str, texto_b: str) -> float:
if self._encoder is None:
from sentence_transformers import SentenceTransformer
self._encoder = SentenceTransformer(self.modelo)
embeddings = self._encoder.encode([texto_a, texto_b], normalize_embeddings=True)
return float(_cosseno(embeddings[0], embeddings[1]))
def _cosseno(a: list[float], b: list[float]) -> float:
produto = sum(x * y for x, y in zip(a, b))
norma_a = math.sqrt(sum(x * x for x in a))
norma_b = math.sqrt(sum(y * y for y in b))
if norma_a == 0 or norma_b == 0:
return 0.0
return max(0.0, min(1.0, produto / (norma_a * norma_b)))