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,113 @@
|
||||
"""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)))
|
||||
Reference in New Issue
Block a user