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: - 32 arquivos alterados - 16 novos - 15 modificados - 0 removidos 16 files changed, 638 insertions(+), 61 deletions(-) Arquivos: - code/engine/skills/boas-praticas-oo.md -> .agents/skills/boas-praticas-oo/SKILL.md - AGENTS.md - code/cep-plugin/index.html - code/cep-plugin/main.js - code/cep-plugin/styles.css - code/engine/integracoes/visual/apple_vision.py - code/engine/integracoes/visual/apple_vision_runner.swift - code/engine/integracoes/visual/extrator_ffmpeg.py - code/engine/scanner/__init__.py - code/engine/scanner/configuracao_visual.py - code/engine/scanner/coordenacao/__init__.py - code/engine/scanner/retakes/adaptador_de_falas.py - code/engine/scanner/retakes/agrupador_de_takes.py - code/engine/scanner/retakes/detector_de_reinicios.py - code/engine/scanner/retakes/modelos_de_retakes.py - code/engine/scanner/visual.py - .jhonny/ - .retakes/ - CODING_STANDARDS.md - code/engine/executar_retakes.py - code/engine/persistencia/ - code/engine/scanner/retakes/__init__.py - code/engine/scanner/retakes/carregador_de_artefatos.py - code/engine/scanner/retakes/classificador_de_retakes.py - code/engine/scanner/retakes/coordenador_de_retakes.py - code/engine/scanner/retakes/deteccao_de_retakes.py - code/engine/scanner/retakes/gerador_de_evidencias.py - code/engine/scanner/retakes/repositorio_de_retakes.py - code/engine/testes/test_consultas_de_persistencia.py - code/engine/testes/test_deteccao_de_retakes.py - code/engine/testes/test_persistencia_sqlite.py - code/relatorios/audio/arquivos/92f97877259a86a8095b1eecafdefe357212a12247d04b78df60e0c0ae38b2ea/
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
"""Entrada local do painel CEP para a análise de retakes.
|
||||
|
||||
Espelha o ``executar_scanner.py``: recebe um pedido JSON montado pela tela,
|
||||
executa a análise com as classes existentes em ``scanner/retakes`` e emite
|
||||
eventos de progresso em JSON (uma linha por evento) na saída padrão.
|
||||
|
||||
Ações suportadas no pedido:
|
||||
|
||||
- ``analisar``: executa a análise completa usando as transcrições já
|
||||
geradas pelo scanner (nunca retranscreve).
|
||||
- ``revisar``: registra a decisão manual do usuário sobre um grupo.
|
||||
|
||||
Formato do pedido (exemplo)::
|
||||
|
||||
{
|
||||
"acao": "analisar",
|
||||
"video_id": "entrevista_01",
|
||||
"artefatos_de_audio": ["...-audio-faixa-1.json"],
|
||||
"configuracao": {"criterio": "equilibrado"}
|
||||
}
|
||||
|
||||
Para ``revisar``::
|
||||
|
||||
{
|
||||
"acao": "revisar",
|
||||
"video_id": "entrevista_01",
|
||||
"revisao": {"grupo_id": "retake_0001", "acao": "confirmar"}
|
||||
}
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import asdict
|
||||
from pathlib import Path
|
||||
|
||||
from engine.scanner.retakes import (
|
||||
STATUS_CONFIRMADO,
|
||||
STATUS_IGNORADO,
|
||||
STATUS_PENDENTE,
|
||||
STATUS_REJEITADO,
|
||||
CarregadorDeArtefatosDeAudio,
|
||||
ClassificadorDeRetakes,
|
||||
CoordenadorDeRetakes,
|
||||
RegrasDeClassificacao,
|
||||
RepositorioDeRetakes,
|
||||
)
|
||||
|
||||
# Confiança mínima exibida por critério de confirmação da tela (§9 do guia).
|
||||
LIMIARES_POR_CRITERIO = {
|
||||
"conservador": 0.80,
|
||||
"equilibrado": 0.65,
|
||||
"sensivel": 0.45,
|
||||
"personalizado": 0.0,
|
||||
}
|
||||
STATUS_PADRAO_DO_PEDIDO = {
|
||||
"confirmar": STATUS_CONFIRMADO,
|
||||
"rejeitar": STATUS_REJEITADO,
|
||||
"manter": STATUS_PENDENTE,
|
||||
"ignorar": STATUS_IGNORADO,
|
||||
}
|
||||
|
||||
|
||||
def _emitir(etapa: str, percentual: float, detalhe: str = "") -> None:
|
||||
"""Emite um evento de progresso no mesmo protocolo do scanner."""
|
||||
print(json.dumps({"evento": "progresso", "etapa": etapa,
|
||||
"percentual": round(percentual, 1), "clipe": detalhe},
|
||||
ensure_ascii=False), flush=True)
|
||||
|
||||
|
||||
def _criterio_do_pedido(configuracao: dict) -> str:
|
||||
"""Valida e devolve o critério de confirmação escolhido na tela."""
|
||||
criterio = str(configuracao.get("criterio", "equilibrado"))
|
||||
if criterio not in LIMIARES_POR_CRITERIO:
|
||||
validos = ", ".join(sorted(LIMIARES_POR_CRITERIO))
|
||||
raise ValueError(f"Critério desconhecido: {criterio!r}. Válidos: {validos}.")
|
||||
return criterio
|
||||
|
||||
|
||||
def _grupos_do_dict(grupos) -> list[dict]:
|
||||
"""Converte os grupos de retakes para o formato consumido pela tela."""
|
||||
return [asdict(grupo) for grupo in grupos]
|
||||
|
||||
|
||||
def _analisar(pedido: dict, pasta_do_caso: Path) -> dict:
|
||||
"""Executa a análise completa de retakes para o caso informado."""
|
||||
configuracao = pedido.get("configuracao", {})
|
||||
criterio = _criterio_do_pedido(configuracao)
|
||||
video_id = str(pedido.get("video_id") or "video")
|
||||
|
||||
_emitir("Preparando análise", 5)
|
||||
repositorio = RepositorioDeRetakes(diretoria_de_trabalho=pasta_do_caso / "retakes")
|
||||
if repositorio.ja_analisado(video_id):
|
||||
_emitir("Reutilizando análise existente", 100)
|
||||
return {"reutilizado": True,
|
||||
"grupos": _grupos_do_dict(repositorio.carregar(video_id))}
|
||||
|
||||
_emitir("Carregando transcrição", 20)
|
||||
carregador = CarregadorDeArtefatosDeAudio()
|
||||
falas = carregador.carregar(pedido.get("artefatos_de_audio", []), video_id)
|
||||
|
||||
_emitir("Comparando trechos", 45)
|
||||
coordenador = CoordenadorDeRetakes(
|
||||
classificador_de_retakes=ClassificadorDeRetakes(RegrasDeClassificacao()),
|
||||
repositorio_de_retakes=repositorio,
|
||||
)
|
||||
_emitir("Agrupando tentativas", 65)
|
||||
resultado = coordenador.analisar(video_id=video_id, falas=falas)
|
||||
|
||||
_emitir("Calculando confiança", 85)
|
||||
limiar = LIMIARES_POR_CRITERIO[criterio]
|
||||
visiveis = [grupo for grupo in resultado.grupos if grupo.confianca >= limiar]
|
||||
_emitir("Salvando resultados", 95)
|
||||
repositorio.registrar(video_id, resultado.grupos)
|
||||
return {"reutilizado": False, "grupos": _grupos_do_dict(visiveis)}
|
||||
|
||||
|
||||
def _revisar(pedido: dict, pasta_do_caso: Path) -> dict:
|
||||
"""Registra a decisão manual do usuário sobre um grupo de retakes."""
|
||||
video_id = str(pedido.get("video_id") or "video")
|
||||
revisao = pedido.get("revisao", {})
|
||||
grupo_id = str(revisao.get("grupo_id", ""))
|
||||
acao = str(revisao.get("acao", ""))
|
||||
if acao not in STATUS_PADRAO_DO_PEDIDO:
|
||||
validos = ", ".join(sorted(STATUS_PADRAO_DO_PEDIDO))
|
||||
raise ValueError(f"Ação de revisão desconhecida: {acao!r}. Válidos: {validos}.")
|
||||
repositorio = RepositorioDeRetakes(diretoria_de_trabalho=pasta_do_caso / "retakes")
|
||||
grupo = repositorio.atualizar_status_de_revisao(
|
||||
video_id, grupo_id, STATUS_PADRAO_DO_PEDIDO[acao])
|
||||
return {"grupos": _grupos_do_dict([grupo])}
|
||||
|
||||
|
||||
def executar(entrada: Path, pasta_do_caso: Path) -> dict:
|
||||
"""Lê o pedido, executa a ação e devolve o resultado para a tela."""
|
||||
pedido = json.loads(entrada.read_text(encoding="utf-8"))
|
||||
acao = str(pedido.get("acao", "analisar"))
|
||||
if acao == "revisar":
|
||||
return _revisar(pedido, pasta_do_caso)
|
||||
return _analisar(pedido, pasta_do_caso)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Ponto de entrada da linha de comando usada pelo painel."""
|
||||
parser = argparse.ArgumentParser(description="Análise de retakes para o painel.")
|
||||
parser.add_argument("entrada", type=Path, help="Arquivo de pedido JSON.")
|
||||
parser.add_argument("pasta_do_caso", type=Path,
|
||||
help="Pasta do caso gerada pelo scanner.")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
resultado = executar(args.entrada, args.pasta_do_caso)
|
||||
except Exception as erro: # repassa a causa para a tela decidir como exibir
|
||||
print(json.dumps({"evento": "erro", "mensagem": str(erro)},
|
||||
ensure_ascii=False), flush=True)
|
||||
sys.exit(1)
|
||||
_emitir("Análise concluída", 100)
|
||||
print(json.dumps({"evento": "concluido", "resultado": resultado},
|
||||
ensure_ascii=False), flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user