"""Exportação estruturada de uma leitura visual de clipe.""" from collections import defaultdict from dataclasses import asdict from datetime import datetime, timezone import json from pathlib import Path from ..dominio import Clipe from .visual import ResultadoVisualDoClipe VERSAO_DO_RELATORIO_VISUAL = "1.0" def gerar_relatorio_visual(clipe: Clipe, resultado: ResultadoVisualDoClipe, destino: str | Path, intervalo_de_amostragem: float) -> Path: """Grava um JSON autocontido, com fatos por frame e fatos temporais separados.""" if intervalo_de_amostragem <= 0: raise ValueError("intervalo_de_amostragem deve ser positivo.") caminho = Path(destino) caminho.parent.mkdir(parents=True, exist_ok=True) caminho.write_text(json.dumps(_dados_do_relatorio(clipe, resultado, intervalo_de_amostragem), ensure_ascii=False, indent=2), encoding="utf-8") return caminho def gerar_resumo_visual_markdown(clipe: Clipe, resultado: ResultadoVisualDoClipe, destino: str | Path, intervalo_de_amostragem: float) -> Path: """Grava uma visão humana do mesmo resultado exportado em JSON.""" caminho = Path(destino) caminho.parent.mkdir(parents=True, exist_ok=True) dados = _dados_do_relatorio(clipe, resultado, intervalo_de_amostragem) linhas = [f"# Relatório visual — {clipe.nome}", "", f"- Clipe: `{clipe.identificador}`", f"- Origem: `{clipe.arquivo}`", f"- Timeline: {clipe.intervalo_na_timeline.inicio:.3f}s–{clipe.intervalo_na_timeline.fim:.3f}s", f"- Amostragem: a cada {intervalo_de_amostragem:g} segundo(s)", "", "## Observações por frame", ""] for frame in dados["observacoes_por_frame"]: tipos = ", ".join(item["tipo"] for item in frame["evidencias"]) linhas.extend([f"### Origem {frame['timestamp_na_origem']:.3f}s", "", tipos or "Sem evidências.", ""]) linhas.extend(["## Continuidade", ""]) for evidencia in dados["evidencias_temporais"]: linhas.append(f"- {evidencia['tipo']}: {evidencia['inicio']:.3f}s–{evidencia['fim']:.3f}s — " f"`{json.dumps(evidencia['valor'], ensure_ascii=False)}`") caminho.write_text("\n".join(linhas) + "\n", encoding="utf-8") return caminho def _dados_do_relatorio(clipe: Clipe, resultado: ResultadoVisualDoClipe, intervalo_de_amostragem: float) -> dict: por_frame = defaultdict(list) temporais = [] for evidencia in resultado.evidencias: item = asdict(evidencia) if evidencia.inicio == evidencia.fim: por_frame[evidencia.inicio].append(item) else: temporais.append(item) origem = clipe.intervalo_na_origem return { "versao": VERSAO_DO_RELATORIO_VISUAL, "gerado_em": datetime.now(timezone.utc).isoformat(), "clipe": { "identificador": clipe.identificador, "nome": clipe.nome, "arquivo_original": clipe.arquivo, "intervalo_na_timeline": asdict(clipe.intervalo_na_timeline), "intervalo_na_origem": asdict(origem) if origem else None, }, "amostragem": {"intervalo_em_segundos": intervalo_de_amostragem}, "observacoes_por_frame": [ {"timestamp_na_origem": timestamp, "evidencias": evidencias} for timestamp, evidencias in sorted(por_frame.items()) ], "evidencias_temporais": temporais, "cenas": [asdict(cena) for cena in resultado.cenas_visuais], }