caarleexx commited on
Commit
596d742
·
verified ·
1 Parent(s): a14f8ea

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +690 -0
  2. data.md +0 -0
  3. protocolo.json +59 -0
app.py ADDED
@@ -0,0 +1,690 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import time
4
+ import hashlib
5
+ from datetime import datetime
6
+ from concurrent.futures import ThreadPoolExecutor, as_completed
7
+
8
+ import gradio as gr
9
+ import google.generativeai as genai
10
+
11
+ # =================================================================================
12
+ # METADADOS DO PROJETO - HACKATHON OAB 2025
13
+ # --------------------------------------------------------------------------------
14
+ # Desenvolvedor: Carlos Rodrigues dos Santos
15
+ # Contato: [email protected]
16
+ # GitHub: github.com/carlex22
17
+ # Licença: GPLv3
18
+ # --------------------------------------------------------------------------------
19
+ # FUNÇÃO DE ORQUESTRAÇÃO, CADEIA COGNITIVA E GOVERNANÇA (Transparência Causal)
20
+ # --------------------------------------------------------------------------------
21
+ # O orquestrador gerencia a 'Cadeia Cognitiva' através de 'Agentes Causais',
22
+ # onde a saída de uma Fase (JSON de contexto) alimenta a entrada da próxima.
23
+ # Isso garante 'Transparência' e 'Governanca' por meio da:
24
+ #
25
+ # 1. Pipeline Estruturada: Execução sequencial das missões definidas em
26
+ # 'protocolo.json' (Fase 0 a 7), garantindo que cada agente cumpra um papel.
27
+ # 2. Saída Auditorável (JSON): As fases retornam um JSON padronizado para
28
+ # o painel 'Auditoria', permitindo rastrear o raciocínio de cada agente.
29
+ # 3. Ponto de Controle (STOP): A Fase 0 (e a lógica da pipeline) pode solicitar
30
+ # input do usuário (gatilho STOP), pausando a cadeia para validar fatos
31
+ # críticos ou inserir contexto faltante, garantindo governança humana
32
+ # em momentos de alta incerteza.
33
+ # 4. Formato Envolvente (Fase 7): A fase final (Relatório) recebe tratamento
34
+ # especial para apresentar a valoração de forma dinâmica e clara, conforme
35
+ # o propósito de valorar a dignidade humana.
36
+ # 5. Contexto Antecipatório: Opcionalmente inclui um passo inicial com dados
37
+ # de 'data.txt' para estabelecer regras e 'verdades factuais' para os agentes.
38
+ # =================================================================================
39
+
40
+
41
+ # Dependências para PDF
42
+ try:
43
+ import PyPDF2
44
+ PDF_SUPPORT = True
45
+ except ImportError:
46
+ PDF_SUPPORT = False
47
+ print("⚠️ PyPDF2 não instalado. PDFs serão lidos como texto simples.")
48
+
49
+ # ==================== 1. CONFIGURAÇÃO ====================
50
+
51
+ api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
52
+ if api_key and api_key != "SUA_API_KEY_AQUI":
53
+ genai.configure(api_key=api_key)
54
+
55
+ # Modelos do Gemini
56
+ model_flash = genai.GenerativeModel("gemini-flash-latest")
57
+ model_pro = genai.GenerativeModel("gemini-pro-latest")
58
+
59
+ ARQUIVO_CONFIG = "protocolo.json"
60
+ ARQUIVO_CONTEXTO_ANTECIPATORIO = "data.txt" # Arquivo com regras/verdade factual
61
+ ARQUIVO_DOCUMENTACAO = "help.md" # Arquivo com documentação (pode ser .txt)
62
+ PASTA_TRANSCRICOES = "transcricoes"
63
+ PAGES_PER_CHUNK = 10
64
+ MAX_WORKERS = 5 # Limite de chamadas paralelas
65
+
66
+ os.makedirs(PASTA_TRANSCRICOES, exist_ok=True)
67
+
68
+ # ==================== 2. UTILIDADES ====================
69
+
70
+ # Define as constantes de autoria
71
+ DEVS_NAME = "Carlos Rodrigues dos Santos"
72
+ DEVS_EMAIL = "[email protected]"
73
+ LICENSE_INFO = "GPLv3"
74
+
75
+ def ler_arquivo_texto(arquivo_path):
76
+ # Função auxiliar que lê um arquivo de texto pelo caminho
77
+ if not os.path.exists(arquivo_path):
78
+ return None
79
+ try:
80
+ with open(arquivo_path, "r", encoding="utf-8") as f:
81
+ conteudo = f.read()
82
+ return conteudo
83
+ except: return None
84
+
85
+ def carregar_protocolo():
86
+ """ Carrega o protocolo. Se não existir, cria um com um esqueleto limpo. """
87
+ return ler_arquivo_texto(ARQUIVO_CONFIG) or json.dumps(
88
+ [{"fase": 0, "nome": "INICIAR_ANALISE", "modelo": "flash", "tipo_saida": "json", "missao": "Leia o input do usuário e os documentos anexados. Identifique quem é a vítima, qual foi o dano (ex: Morte, Lesão Grave) e quem é o réu. Se faltarem informações críticas para uma análise de valoração (ex: os fatos do caso), defina DUVIDA_DETECTADA como true e use TESTE_REFLEXAO para perguntar ao usuário."}],
89
+ indent=2,
90
+ ensure_ascii=False
91
+ )
92
+
93
+ def carregar_documentacao():
94
+ """ Carrega o arquivo de documentação. """
95
+ return ler_arquivo_texto(ARQUIVO_DOCUMENTACAO) or "Documentação não encontrada. Crie um arquivo 'help.md' ou 'help.txt' na pasta principal para exibir aqui."
96
+
97
+
98
+ def limpar_nome_arquivo(nome):
99
+ # (Implementação existente)
100
+ nome_base = os.path.basename(nome)
101
+ nome_limpo = "".join([c for c in nome_base if c.isalnum() or c in (' ', '.', '_', '-')]).strip()
102
+ return nome_limpo + ".json"
103
+
104
+ def extrair_texto_pdf(caminho_pdf):
105
+ # (Implementação existente)
106
+ try:
107
+ with open(caminho_pdf, 'rb') as f:
108
+ reader = PyPDF2.PdfReader(f)
109
+ paginas = []
110
+ for i, page in enumerate(reader.pages):
111
+ texto = page.extract_text()
112
+ paginas.append({
113
+ "numero": i + 1,
114
+ "texto": texto,
115
+ "metadata": str(page)[:200]
116
+ })
117
+ return paginas, None
118
+ except Exception as e:
119
+ return None, str(e)
120
+
121
+
122
+ def fragmentar_pdf(paginas, tamanho_chunk=PAGES_PER_CHUNK):
123
+ # (Implementação existente)
124
+ chunks = []
125
+ for i in range(0, len(paginas), tamanho_chunk):
126
+ chunk = paginas[i:i + tamanho_chunk]
127
+ num_inicio = chunk[0]["numero"]
128
+ num_fim = chunk[-1]["numero"]
129
+
130
+ texto_consolidado = "\n---QUEBRA DE PÁGINA---\n".join(
131
+ [f"[PÁGINA {p['numero']}]\n{p['texto']}" for p in chunk]
132
+ )
133
+
134
+ chunks.append({
135
+ "id": f"chunk_{num_inicio}_{num_fim}",
136
+ "paginas": f"{num_inicio}-{num_fim}",
137
+ "num_paginas": len(chunk),
138
+ "texto": texto_consolidado,
139
+ "metadata": [p["metadata"] for p in chunk]
140
+ })
141
+ return chunks
142
+
143
+ def processar_pdf_completo(arquivo_pdf):
144
+ # (Implementação existente)
145
+ if not PDF_SUPPORT:
146
+ return None, "❌ PyPDF2 não disponível"
147
+
148
+ try:
149
+ paginas, erro = extrair_texto_pdf(arquivo_pdf.name if hasattr(arquivo_pdf, 'name') else arquivo_pdf)
150
+ if erro:
151
+ return None, f"❌ Erro ao ler PDF: {erro}"
152
+
153
+ chunks = fragmentar_pdf(paginas)
154
+ nome_arquivo = os.path.basename(arquivo_pdf.name if hasattr(arquivo_pdf, 'name') else arquivo_pdf)
155
+
156
+ return {
157
+ "arquivo": nome_arquivo,
158
+ "total_paginas": len(paginas),
159
+ "total_chunks": len(chunks),
160
+ "chunks": chunks,
161
+ "tipo": "pdf"
162
+ }, None
163
+ except Exception as e:
164
+ return None, f"❌ Erro no processamento: {str(e)}"
165
+
166
+ # ==================== 3. PIPELINE DE IA ====================
167
+
168
+ def transcrever_chunk(chunk_data, config_agentes):
169
+ # (Implementação existente)
170
+ modelo = model_flash
171
+ try:
172
+ if config_agentes and isinstance(config_agentes, list):
173
+ if config_agentes[0].get("modelo") == "pro":
174
+ modelo = model_pro
175
+ except:
176
+ pass
177
+
178
+ prompt = f"""
179
+ ANÁLISE DE DOCUMENTO (OCR/LEITURA):
180
+ Transcreva e estruture o conteúdo das páginas {chunk_data['paginas']}.
181
+ Texto extraído:
182
+ {chunk_data['texto']}
183
+
184
+ Retorne JSON: {{ "transcricao": "...", "objetos": ["..."], "resumo": "..." }}
185
+ """
186
+ try:
187
+ for tentativa in range(3):
188
+ try:
189
+ resposta = modelo.generate_content(prompt)
190
+ texto_resp = resposta.text.replace("```json", "").replace("```", "")
191
+ return json.loads(texto_resp.strip()), None
192
+ except Exception as inner_e:
193
+ if "429" in str(inner_e):
194
+ time.sleep(2 * (tentativa + 1))
195
+ continue
196
+ raise inner_e
197
+ except Exception as e:
198
+ return None, str(e)
199
+
200
+ # ==================== 4. GERENCIADOR DE ARQUIVOS ====================
201
+
202
+ class GerenciadorArquivos:
203
+ def __init__(self):
204
+ self.arquivos = {}
205
+
206
+ def adicionar(self, arquivo, arquivo_id):
207
+ # (Implementação existente)
208
+ self.arquivos[arquivo_id] = {
209
+ "arquivo": arquivo,
210
+ "nome": os.path.basename(arquivo.name),
211
+ "status": "adicionado",
212
+ "processado": None,
213
+ "transcricao": None
214
+ }
215
+
216
+ def gerar_prompt_com_transcricoes(self, texto_usuario):
217
+ # (Implementação existente)
218
+ prompt = texto_usuario + "\n\n--- CONTEXTO DOS ARQUIVOS ---\n"
219
+ count = 0
220
+ for _, item in self.arquivos.items():
221
+ if item["status"] == "processado" and item["transcricao"]:
222
+ count += 1
223
+ trans = item["transcricao"]
224
+ nome = item["nome"]
225
+ prompt += f"\n[ARQUIVO: {nome}]\n"
226
+
227
+ if isinstance(trans, dict) and "chunks_processados" in trans:
228
+ for chunk in trans["chunks_processados"]:
229
+ if chunk.get("status") == "OK":
230
+ resumo = chunk.get('resumo', '')
231
+ resumo = str(resumo) if resumo else ""
232
+ prompt += f"Páginas {chunk['paginas']}: {resumo}\n"
233
+
234
+ texto_full = chunk.get('transcricao', '')
235
+ if texto_full:
236
+ texto_seguro = str(texto_full)
237
+ prompt += f"Trecho: {texto_seguro[:400]}...\n"
238
+ else:
239
+ prompt += "Trecho: (vazio)\n"
240
+
241
+ elif isinstance(trans, dict) and "conteudo" in trans:
242
+ conteudo = str(trans['conteudo'])
243
+ prompt += f"Conteúdo: {conteudo[:1000]}...\n"
244
+
245
+ if count == 0:
246
+ prompt += "(Nenhum arquivo processado ainda)"
247
+ return prompt
248
+
249
+ # Instância Global
250
+ gerenciador = GerenciadorArquivos()
251
+
252
+ # ==================== 5. FUNÇÕES DE ORQUESTRAÇÃO ====================
253
+
254
+ def automacao_upload_processamento(files, history, config_json):
255
+ # (Função de processamento de arquivos)
256
+ if not files:
257
+ return history
258
+
259
+ try:
260
+ config_agentes = json.loads(config_json)
261
+ except:
262
+ config_agentes = []
263
+
264
+ if history is None:
265
+ history = []
266
+
267
+ history.append([None, f"📂 **SISTEMA:** Recebi {len(files)} arquivo(s). Verificando cache e processando..."])
268
+ yield history
269
+
270
+ ids_para_processar = []
271
+
272
+ for f in files:
273
+ # Usa um hash do conteúdo para ID de arquivo de texto simples para melhorar o cache
274
+ if f.name.lower().endswith(('.txt', '.json', '.md')):
275
+ with open(f.name, 'r', encoding='utf-8') as file:
276
+ file_hash = hashlib.sha256(file.read().encode('utf-8')).hexdigest()
277
+ arquivo_id = f"txt_{file_hash}"
278
+ else:
279
+ arquivo_id = f"arq_{int(time.time()*1000)}_{f.name}"
280
+
281
+ gerenciador.adicionar(f, arquivo_id)
282
+ ids_para_processar.append(arquivo_id)
283
+
284
+ for arq_id in ids_para_processar:
285
+ item = gerenciador.arquivos[arq_id]
286
+ nome = item["nome"]
287
+
288
+ # Cria um nome de cache consistente
289
+ nome_cache = limpar_nome_arquivo(nome)
290
+ caminho_cache = os.path.join(PASTA_TRANSCRICOES, nome_cache)
291
+
292
+ # Lógica de Cache e Reprocessamento
293
+ if os.path.exists(caminho_cache):
294
+ try:
295
+ with open(caminho_cache, "r", encoding="utf-8") as cache_file:
296
+ dados_cache = json.load(cache_file)
297
+ item["transcricao"] = dados_cache
298
+ item["status"] = "processado"
299
+ if nome.lower().endswith('.pdf') and "chunks_processados" in dados_cache:
300
+ item["processado"] = {"tipo": "pdf", "chunks": []}
301
+ history.append([None, f"♻️ **Cache Encontrado:** `{nome}` já foi processado. Carregando..."])
302
+ yield history
303
+ continue
304
+ except Exception as e:
305
+ history.append([None, f"⚠️ Erro cache `{nome}`: {e}. Reprocessando..."])
306
+
307
+ history.append([None, f"⚙️ **Processando:** `{nome}`..."])
308
+ yield history
309
+
310
+ if nome.lower().endswith('.pdf'):
311
+ if not PDF_SUPPORT:
312
+ history.append([None, f"❌ Erro em `{nome}`: Biblioteca PDF ausente."])
313
+ yield history
314
+ continue
315
+
316
+ pdf_proc, erro = processar_pdf_completo(item["arquivo"])
317
+ if erro:
318
+ history.append([None, f"❌ Erro em `{nome}`: {erro}"])
319
+ yield history
320
+ continue
321
+
322
+ item["processado"] = pdf_proc
323
+ chunks = pdf_proc["chunks"]
324
+ total_chunks = len(chunks)
325
+
326
+ chunks_ordenados = [None] * total_chunks
327
+
328
+ history.append([None, f"📄 `{nome}` fragmentado em {total_chunks} partes. Iniciando IA (Paralelo: {MAX_WORKERS} threads)..."])
329
+ yield history
330
+
331
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
332
+ futures_map = {}
333
+ for i, chunk in enumerate(chunks):
334
+ future = executor.submit(transcrever_chunk, chunk, config_agentes)
335
+ futures_map[future] = i
336
+
337
+ concluidos = 0
338
+ for future in as_completed(futures_map):
339
+ index_original = futures_map[future]
340
+ res, err = future.result()
341
+
342
+ if err:
343
+ chunks_ordenados[index_original] = {"status": "ERRO", "paginas": chunks[index_original]["paginas"]}
344
+ else:
345
+ chunks_ordenados[index_original] = {
346
+ "status": "OK",
347
+ "paginas": chunks[index_original]["paginas"],
348
+ "transcricao": res.get("transcricao"),
349
+ "resumo": res.get("resumo")
350
+ }
351
+
352
+ concluidos += 1
353
+ if concluidos % 2 == 0 or concluidos == total_chunks:
354
+ msg_base = f"📄 `{nome}`: Processando partes... ({concluidos}/{total_chunks})"
355
+ history[-1][1] = msg_base
356
+ yield history
357
+
358
+ dados_finais = {
359
+ "arquivo": nome,
360
+ "data_processamento": str(datetime.now()),
361
+ "chunks_processados": chunks_ordenados
362
+ }
363
+
364
+ item["transcricao"] = dados_finais
365
+ item["status"] = "processado"
366
+
367
+ try:
368
+ with open(caminho_cache, "w", encoding="utf-8") as f_out:
369
+ json.dump(dados_finais, f_out, indent=2, ensure_ascii=False)
370
+ history.append([None, f"💾 `{nome}` processado e salvo no cache."])
371
+ except Exception as e:
372
+ history.append([None, f"⚠️ Erro ao salvar cache: {e}"])
373
+
374
+ yield history
375
+
376
+ else:
377
+ res_content = ler_arquivo_texto(item["arquivo"].name)
378
+ if res_content:
379
+ item["processado"] = res_content
380
+ dados_finais = {"conteudo": res_content, "data_processamento": str(datetime.now())}
381
+ item["transcricao"] = dados_finais
382
+ item["status"] = "processado"
383
+
384
+ with open(caminho_cache, "w", encoding="utf-8") as f_out:
385
+ json.dump(dados_finais, f_out, indent=2, ensure_ascii=False)
386
+
387
+ history.append([None, f"✅ `{nome}` (Texto) lido e salvo."])
388
+ else:
389
+ history.append([None, f"❌ Falha ao ler `{nome}`."])
390
+ yield history
391
+
392
+ history.append([None, "🏁 **Processamento de lote finalizado.** Os arquivos estão prontos para análise."])
393
+ yield history
394
+
395
+
396
+ def chat_orquestrador(message, history, config_json, pipeline_state, incluir_passo_antecipatorio):
397
+ if pipeline_state.get("is_paused"):
398
+ # (Lógica de continuação após STOP)
399
+ history.append([message, None])
400
+
401
+ timeline_execucao = pipeline_state["timeline"]
402
+ agentes_restantes = pipeline_state["remaining_agents"]
403
+
404
+ timeline_execucao.append({
405
+ "passo": len(timeline_execucao) + 1,
406
+ "tipo": "resposta_usuario",
407
+ "conteudo": message
408
+ })
409
+
410
+ pipeline_state["is_paused"] = False
411
+
412
+ yield from executar_pipeline(history, timeline_execucao, agentes_restantes, pipeline_state)
413
+ return
414
+
415
+ # --- LÓGICA DE INÍCIO DE UMA NOVA CONVERSA ---
416
+
417
+ # 1. Monta o Prompt de Contexto dos Arquivos
418
+ try:
419
+ prompt_contexto = gerenciador.gerar_prompt_com_transcricoes(message)
420
+ except Exception as e:
421
+ history.append([message, f"❌ Erro ao gerar contexto: {str(e)}"])
422
+ yield history, [], pipeline_state
423
+ return
424
+
425
+ # 2. Carrega o Protocolo
426
+ try:
427
+ protocolo = json.loads(config_json)
428
+ except:
429
+ history.append([message, "❌ Erro no JSON de Configuração do Protocolo."])
430
+ yield history, [], pipeline_state
431
+ return
432
+
433
+ history.append([message, None])
434
+
435
+ # 3. Lógica do Passo Antecipatório
436
+ timeline_execucao = []
437
+ agentes_a_executar = protocolo
438
+
439
+ if incluir_passo_antecipatorio:
440
+ conteudo_antecipatorio = ler_arquivo_texto(ARQUIVO_CONTEXTO_ANTECIPATORIO)
441
+ if conteudo_antecipatorio:
442
+ # Injeta o contexto na timeline como o primeiro passo
443
+ timeline_execucao.append({
444
+ "passo": 1,
445
+ "tipo": "passo_antecipatorio_data",
446
+ "agente": "GOVERNANCA_INICIAL",
447
+ "conteudo": f"REGRAS E VERDADE FACTUAL INICIAL (data.txt):\n{conteudo_antecipatorio}"
448
+ })
449
+ history.append([None, f"ℹ️ **GOVERNANÇA INICIAL:** Contexto de `{ARQUIVO_CONTEXTO_ANTECIPATORIO}` injetado na cadeia cognitiva."])
450
+ history[-1][1] += f"\n\nInstrução do usuário: {message}"
451
+ else:
452
+ # Se o arquivo não existe, apenas adiciona a instrução do usuário
453
+ history[-1][1] = f"⚠️ Instrução do usuário: {message}"
454
+ history[-1][1] += f"\n\n**Aviso:** Passo Antecipatório ativado, mas arquivo `{ARQUIVO_CONTEXTO_ANTECIPATORIO}` não encontrado."
455
+
456
+ # Ajusta o número do passo inicial na timeline
457
+ passo_inicial_prompt = len(timeline_execucao) + 1
458
+ else:
459
+ passo_inicial_prompt = 1
460
+
461
+ # Adiciona a instrução do usuário (com contexto de arquivos) à trilha de auditoria
462
+ timeline_execucao.append({"passo": passo_inicial_prompt, "tipo": "prompt_usuario_base", "conteudo": prompt_contexto})
463
+
464
+ yield history, timeline_execucao, pipeline_state
465
+
466
+ # Inicia a execução dos agentes
467
+ yield from executar_pipeline(history, timeline_execucao, agentes_a_executar, pipeline_state)
468
+
469
+
470
+ def executar_pipeline(history, timeline_execucao, agentes_a_executar, pipeline_state):
471
+ # (Implementação de execução da pipeline e simulação de digitação)
472
+ passo_atual = len(timeline_execucao) + 1
473
+
474
+ for i, cfg in enumerate(agentes_a_executar):
475
+ nome_agente = cfg.get("nome", "Agente")
476
+ modelo_agente = model_pro if cfg.get("modelo") == "pro" else model_flash
477
+ tipo_saida = cfg.get("tipo_saida", "json")
478
+
479
+ msg_atual = history[-1][1] or ""
480
+ history[-1][1] = msg_atual + f"⏳ **{nome_agente}** está analisando...\n"
481
+ yield history, timeline_execucao, pipeline_state
482
+
483
+ prompt_agente = f"""
484
+ --- HISTÓRICO DA CONVERSA ATÉ AGORA ---
485
+ {json.dumps(timeline_execucao, ensure_ascii=False, indent=2)}
486
+ -----------------
487
+ Sua Identidade: {nome_agente}
488
+ Sua Missão Específica Agora: {cfg['missao']}
489
+ Se o tipo de saída exigido for 'json', sua resposta DEVE ser APENAS o JSON.
490
+ """
491
+
492
+ try:
493
+ inicio = time.time()
494
+ resp = modelo_agente.generate_content(prompt_agente)
495
+ texto_resp = resp.text
496
+ duracao = time.time() - inicio
497
+
498
+ # --- LÓGICA DE DETECÇÃO DE SAÍDA NÃO-JSON (STOP ou Relatório) ---
499
+ if tipo_saida == "json":
500
+ try:
501
+ # Tenta interpretar a resposta como JSON
502
+ texto_json_limpo = texto_resp.replace("```json", "").replace("```", "").strip()
503
+ resposta_json = json.loads(texto_json_limpo)
504
+
505
+ # 1. LÓGICA DE PAUSA (STOP)
506
+ if resposta_json.get("PROXIMA_ACAO") == "PERGUNTAR_USUARIO" and resposta_json.get("DUVIDA_DETECTADA") == True:
507
+
508
+ # VERIFICA REGRAS DE STOP - Fora de escopo?
509
+ if "STOP:" in texto_resp:
510
+ # Caso de STOP por fora do escopo
511
+ stop_message = texto_resp.split("STOP:")[1].strip() if "STOP:" in texto_resp else "Análise interrompida por dúvida crítica ou tema fora de escopo."
512
+ history[-1][1] = stop_message
513
+ yield history, timeline_execucao, pipeline_state
514
+ return
515
+
516
+ # Caso de STOP por dúvida crítica (regra do protocolo da Fase 0)
517
+ perguntas = resposta_json.get("TESTE_REFLEXAO", {}).get("perguntas", [])
518
+
519
+ # Formata a resposta STOP
520
+ stop_response = "STOP: preciso que você esclareça até 3 pontos antes de continuar:\n\n"
521
+ for idx, p in enumerate(perguntas):
522
+ if idx < 3:
523
+ stop_response += f"{idx + 1}) {p}\n"
524
+
525
+ # Salva o estado atual da pipeline
526
+ pipeline_state["is_paused"] = True
527
+ pipeline_state["timeline"] = timeline_execucao
528
+ pipeline_state["remaining_agents"] = agentes_a_executar[i+1:]
529
+
530
+ # Adiciona o passo à auditoria e ao chat
531
+ timeline_execucao.append({"passo": passo_atual, "tipo": "STOP_USUARIO_REQUERIDO", "agente": nome_agente, "detalhes": stop_response})
532
+
533
+ # Prepara a mensagem para o usuário no chat
534
+ msg_para_usuario = f"**{nome_agente}** precisa de mais informações. Por favor, responda aos pontos abaixo para seguirmos com a análise:\n\n"
535
+ for idx, p in enumerate(perguntas):
536
+ if idx < 3:
537
+ msg_para_usuario += f"**{idx + 1})** {p}\n"
538
+
539
+ msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
540
+ history[-1][1] = msg_atual + msg_para_usuario
541
+
542
+ yield history, timeline_execucao, pipeline_state
543
+ return # Sai da função para esperar o input do usuário
544
+
545
+ # 2. Resposta JSON normal
546
+ texto_para_auditoria = texto_json_limpo
547
+ timeline_execucao.append({"passo": passo_atual, "tipo": "resposta_agente", "agente": nome_agente, "resposta": texto_para_auditoria})
548
+
549
+ msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
550
+ novo_trecho = f"✅ **[{nome_agente}]** concluiu sua análise em ({duracao:.1f}s). (JSON para Auditoria)\n"
551
+ history[-1][1] = msg_atual + novo_trecho
552
+ yield history, timeline_execucao, pipeline_state
553
+
554
+ except json.JSONDecodeError:
555
+ # Se deveria ser JSON e não é, trata como erro na auditoria
556
+ timeline_execucao.append({"passo": passo_atual, "tipo": "erro_resposta_json", "agente": nome_agente, "resposta_raw": texto_resp, "erro": "Esperado JSON, mas recebeu texto não-JSON."})
557
+ msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
558
+ history[-1][1] = msg_atual + f"❌ **[{nome_agente}]** falhou: Resposta não era JSON válido. ({duracao:.1f}s).\n"
559
+ yield history, timeline_execucao, pipeline_state
560
+
561
+ elif tipo_saida == "texto":
562
+ # Lógica para a Fase 7 (RELATORIO_VALOR_VIDA)
563
+
564
+ # Adiciona o passo à auditoria
565
+ timeline_execucao.append({"passo": passo_atual, "tipo": "relatorio_final", "agente": nome_agente, "relatorio": texto_resp})
566
+
567
+ # Simula a digitação envolvente
568
+ msg_final = f"**[RELATÓRIO FINAL DO {nome_agente}]**\n\n"
569
+ history[-1][1] = msg_final
570
+ yield history, timeline_execucao, pipeline_state
571
+
572
+ # Divide o texto do relatório em blocos (parágrafos ou seções)
573
+ blocos = texto_resp.split('\n\n')
574
+
575
+ for bloco in blocos:
576
+ if bloco.strip():
577
+ # Simula a digitação bloco por bloco
578
+ msg_final += f"{bloco}\n\n"
579
+ history[-1][1] = msg_final
580
+ # Pequena pausa para o efeito dinâmico
581
+ time.sleep(0.5)
582
+ yield history, timeline_execucao, pipeline_state
583
+
584
+ # Finalização da digitação no chat
585
+ msg_final += "\n\n--- FIM DO RELATÓRIO DE VALORAÇÃO ---"
586
+ history[-1][1] = msg_final
587
+ yield history, timeline_execucao, pipeline_state
588
+
589
+ else:
590
+ # Caso de tipo_saida não definido (erro)
591
+ timeline_execucao.append({"passo": passo_atual, "tipo": "erro_config", "agente": nome_agente, "erro": "Tipo de saída não reconhecido ('json' ou 'texto')."})
592
+ msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
593
+ history[-1][1] = msg_atual + f"\n❌ Erro de Configuração em {nome_agente}: Tipo de saída inválido. ({duracao:.1f}s).\n"
594
+ yield history, timeline_execucao, pipeline_state
595
+
596
+ except Exception as e:
597
+ timeline_execucao.append({"passo": passo_atual, "tipo": "erro_agente", "agente": nome_agente, "erro": str(e)})
598
+ msg_atual = history[-1][1]
599
+ history[-1][1] = msg_atual.replace(f"⏳ **{nome_agente}** está analisando...\n", "") + f"\n❌ Erro em {nome_agente}: {str(e)}\n"
600
+ yield history, timeline_execucao, pipeline_state
601
+
602
+ passo_atual += 1
603
+
604
+ # ==================== 6. UI (Gradio) ====================
605
+
606
+ def ui_v29_stop_logic():
607
+ css = """
608
+ footer {display: none !important;}
609
+ .contain {border: none !important;}
610
+ """
611
+
612
+ config_inicial = carregar_protocolo()
613
+ documentacao = carregar_documentacao()
614
+
615
+ with gr.Blocks(title="AI Forensics Auto", css=css, theme=gr.themes.Soft()) as app:
616
+
617
+ # Estado da configuração dos agentes
618
+ state_config = gr.State(config_inicial)
619
+ # NOVO: Estado para controlar a pausa/continuação da pipeline
620
+ pipeline_state = gr.State({"is_paused": False, "timeline": [], "remaining_agents": []})
621
+
622
+ with gr.Tabs():
623
+ with gr.Tab("💬 Investigação"):
624
+
625
+ chatbot = gr.Chatbot(
626
+ height=400,
627
+ show_label=False,
628
+ show_copy_button=True,
629
+ render_markdown=True,
630
+ label="Chat de Investigação"
631
+ )
632
+
633
+ with gr.Row():
634
+ txt_input = gr.Textbox(
635
+ scale=8,
636
+ show_label=False,
637
+ placeholder="Digite sua instrução ou responda à pergunta do agente...",
638
+ lines=1
639
+ )
640
+ btn_enviar = gr.Button("Enviar 📨", variant="primary", scale=1)
641
+
642
+ # Caixa de Opções Avançadas
643
+ with gr.Accordion("⚙️ Opções Avançadas de Governança", open=False):
644
+ chk_antecipatorio = gr.Checkbox(
645
+ label=f"Incluir Contexto Antecipatório (`{ARQUIVO_CONTEXTO_ANTECIPATORIO}`) no início da análise (Recomendado).",
646
+ value=True, # Ativado por padrão
647
+ show_label=True
648
+ )
649
+
650
+ with gr.Accordion("📂 Adicionar Arquivos para Análise", open=False):
651
+ gr.Markdown("Selecione arquivos (PDF, TXT). A transcrição iniciará **automaticamente**.")
652
+ file_uploader = gr.File(
653
+ file_count="multiple",
654
+ file_types=[".pdf", ".txt", ".json", ".md"],
655
+ label="Arraste arquivos aqui ou clique para selecionar"
656
+ )
657
+
658
+ with gr.Tab("🕵️ Auditoria"):
659
+ gr.Markdown("### Trilha de Auditoria\nExibe o histórico completo de prompts e respostas de cada agente na última execução.")
660
+ json_audit = gr.JSON(label="Timeline da Execução da Última Mensagem")
661
+
662
+ with gr.Tab("⚙️ Protocolo & Autoria"):
663
+ gr.Markdown(f"### Protocolo Causal (Diretrizes de Agentes)\nDesenvolvedor: **{DEVS_NAME}** ({DEVS_EMAIL}) | Licença: **{LICENSE_INFO}**")
664
+ gr.Markdown("Visualize as missões dos agentes de IA. O número de fases é totalmente configurável na estrutura JSON abaixo.")
665
+ code_config = gr.Code(value=config_inicial, language="json", label="protocolo.json (Visualização de Diretrizes)", interactive=False)
666
+
667
+ with gr.Tab("📚 Documentação"):
668
+ gr.Markdown("### Documentação da Aplicação (help.md)")
669
+ gr.Markdown(documentacao)
670
+
671
+
672
+ # Ação de clique agora passa o novo checkbox para o orquestrador
673
+ btn_enviar.click(
674
+ chat_orquestrador,
675
+ inputs=[txt_input, chatbot, state_config, pipeline_state, chk_antecipatorio],
676
+ outputs=[chatbot, json_audit, pipeline_state]
677
+ ).then(
678
+ lambda: "", outputs=[txt_input]
679
+ )
680
+
681
+ file_uploader.upload(
682
+ automacao_upload_processamento,
683
+ inputs=[file_uploader, chatbot, state_config],
684
+ outputs=[chatbot]
685
+ )
686
+
687
+ return app
688
+
689
+ if __name__ == "__main__":
690
+ ui_v29_stop_logic().launch()
data.md ADDED
The diff for this file is too large to render. See raw diff
 
protocolo.json ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "fase": 0,
4
+ "nome": "CONTEXTO_INICIAL_VALOR_VIDA",
5
+ "modelo": "flash",
6
+ "tipo_saida": "json",
7
+ "missao": "INICIAR PELO RACIOCÍNIO DO USUÁRIO + CAPTURA BRUTA.\n\nObjetivos:\n- Ajudar a pessoa a organizar, com calma e clareza, o cenário de lesão, violência, acidente ou morte.\n- Capturar fatos básicos, histórico mínimo de vida, contexto do dano e nexo causal percebido.\n- Forçar o usuário a pensar se já tem alguma ideia de valor justo antes de acionar os outros agentes.\n- Detectar dúvida relevante e, se necessário, disparar STOP em vez de JSON.\n\nRegras STOP (fora de escopo):\n- Se a pergunta do usuário não tiver relação com: (a) casos de dano/indenização, (b) as missões/agentes, ou (c) o histórico mínimo do caso atual,\n ENTÃO o modelo NÃO responde ao conteúdo da pergunta.\n- Em vez disso, responde apenas:\n\n STOP: infelizmente não posso responder a essa pergunta,\n pois meu objetivo aqui é apenas ajudar a analisar fatos,\n nexo causal e valor de indenizações em casos de lesão,\n violência, acidente ou morte, seguindo as missões do protocolo.\n\n- Nessa situação, NUNCA retornar JSON; apenas o texto acima (ou uma versão equivalente, breve).\n\nRegras STOP (dúvida crítica):\n- Se a pergunta estiver dentro do contexto do caso, mas houver dúvida alta sobre ponto crítico (nexo causal, gravidade da lesão/morte, valor-base, identidade das partes),\n ENTÃO o agente pode responder diretamente com STOP + até 3 perguntas objetivas para esclarecimento, SEM JSON.\n- Exemplo de formato:\n\n STOP: preciso que você esclareça até 3 pontos antes de continuar:\n\n 1) ...\n 2) ...\n 3) ... (opcional)\n\nCampos de saída (quando NÃO for caso de STOP):\n1. PERGUNTA_NORMALIZADA\n2. CONTEXTO_IDENTIFICADO {\n tipo_caso: \"LESAO_LEVE|GRAVE|GRAVISSIMA|MORTE|OUTRO\",\n foro: \"civel|criminal|trabalhista|outro\"\n }\n3. RESUMO_FATOS_INICIAIS\n4. EXPECTATIVA_VALOR_INICIAL {\n sabe_valor: true/false,\n faixa_sugerida: { min: null, max: null },\n justificativa_intuitiva: \"...\"\n }\n5. NIVEL_CERTEZA_USUARIO (0-10)\n6. DADOS_MINIMOS_NEXO {\n houve_evento: true/false,\n houve_dano: true/false,\n sente_relacao_causa_efeito: true/false\n }\n7. AFETO_RAW { amor: 0.0, medo: 0.0, paixa: 0.0 }\n8. FELICIDADE_LATENTE\n9. SINAIS_DUVIDA { contradicoes: [], lacunas_obvias: [] }\n10. DUVIDA_DETECTADA { true|false }\n11. TESTE_REFLEXAO {\n perguntas: [\n \"O que exatamente aconteceu, em ordem temporal?\",\n \"Se você fosse juiz, que valor consideraria minimamente justo e por quê?\",\n \"Existe algum caso parecido que você conhece? O que aconteceu lá?\"\n ],\n instrucoes_ao_usuario: \"Responda com calma a cada pergunta. O sistema só seguirá quando houver clareza mínima sobre fatos e sua própria expectativa.\"\n }\n12. PROXIMA_ACAO { \"PERGUNTAR_USUARIO\" | \"AVANCAR_FASE_1\" }\n\nRegras gerais:\n- Se DUVIDA_DETECTADA == true em ponto crítico → considerar usar STOP em vez de JSON, conforme regras acima.\n- Se for possível responder com JSON estruturado sem comprometer segurança/claridade, retornar o JSON completo."
8
+ },
9
+ {
10
+ "fase": 1,
11
+ "nome": "HISTORICO_VIDA_E_REDE_AFETIVA",
12
+ "modelo": "flash",
13
+ "tipo_saida": "json",
14
+ "missao": "MAPEAR VIDA, PAPÉIS E REDE AFETIVA PARA VALOR_DA_VIDA/DIGNIDADE.\n\nObjetivos:\n- Clarificar quem é/era a vítima na sua biografia: trabalho, família, sonhos, vulnerabilidades.\n- Entender rede de dependência e afeto (cônjuge, filhos, pais, etc.).\n- Preparar terreno para valorar a perda/lesão na dimensão existencial.\n\nJSON:\n1. PERFIL_VITIMA { idade, genero, profissao, renda_media, estado_civil }\n2. PAPEL_SOCIAL_CENTRAL { provedor_familiar, cuidador, estudante, aposentado, outro }\n3. DEPENDENTES_DIRETOS { quantidade, tipos: [filhos, pais, conjuges, outros] }\n4. PROJETOS_DE_VIDA { curto_prazo, longo_prazo }\n5. VULNERABILIDADE_PREVIA { pobreza, doenca_preexistente, deficiencia, nenhum }\n6. REDE_AFETIVA { lista_pessoas_chave, grau_dependencia_emocional: 0-10 }\n7. IMPACTO_POTENCIAL_PERDA { descricao_curta, intensidade: 0-10 }\n8. COERENCIA_COM_CONTEXTO_INICIAL { coerente|parcial|incoerente }\n9. LACUNAS_HISTORICO_VIDA [lista]\n10. SUGESTAO_PERGUNTAS_ADICIONAIS_USUARIO [lista]"
15
+ },
16
+ {
17
+ "fase": 2,
18
+ "nome": "FATO_DANO_E_NEXO_CAUSAL",
19
+ "modelo": "flash",
20
+ "tipo_saida": "json",
21
+ "missao": "CLAREAR FATO, DANO E NEXO CAUSAL DE FORMA ESTRUTURADA.\n\nObjetivos:\n- Organizar, de forma lógica, o que aconteceu, que dano houve e qual nexo alegado.\n- Separar fato objetivo de percepção subjetiva.\n\nJSON:\n1. FATO_GERADOR_LINEAR { linha_do_tempo: [eventos_em_ordem] }\n2. TIPO_EVENTO { acidente_transito, erro_medico, violencia_domestica, crime_intencional, outro }\n3. DANO_CORPORAL_CLASSIFICACAO { LESAO_LEVE|GRAVE|GRAVISSIMA|MORTE|SEM_INFORMACAO }\n4. DANO_CONCRETO_DESCRITO { lesoes, sequelas, morte, dano_estetico }\n5. PROVAS_DISPONIVEIS { laudos_medicos, boletim_ocorrencia, fotos, videos, testemunhas }\n6. NARRATIVA_NEXO_CAUSAL { em_ate_500_caracteres }\n7. GRAU_CONFIANCA_NEXO_DECLARADO (0-10)\n8. AMBIGUIDADES_IDENTIFICADAS { sim|nao, detalhes }\n9. ITENS_QUE_EXIGEM_PERICIA { lista }\n10. COERENCIA_FATO_NEXO { alta|media|baixa }"
22
+ },
23
+ {
24
+ "fase": 3,
25
+ "nome": "CONTEXTO_E_CONSEQUENCIAS_DO_DANO",
26
+ "modelo": "flash",
27
+ "tipo_saida": "json",
28
+ "missao": "MAPEAR CONTEXTO E CONSEQUÊNCIAS MATERIAIS, MORAIS E EXISTENCIAIS.\n\nJSON:\n1. CONSEQUENCIAS_SAUDE { dor_cronica, limitacao_fisica, dependencia_terceiros, tratamento_longo_prazo }\n2. CONSEQUENCIAS_TRABALHO { dias_afastamento, perda_emprego, rebaixamento_funcao, incapacidade_parcial, incapacidade_total }\n3. CONSEQUENCIAS_FAMILIA { rompimento_relacoes, sobrecarga_cuidador, impacto_filhos }\n4. CONSEQUENCIAS_PSICOLOGICAS { ansiedade, depressao, TEPT, medo_constante }\n5. PERDA_QUALIDADE_VIDA { nota: 0-10, justificativa }\n6. DESCRICAO_DANO_MORAL_SUBJETIVO { humilhacao, medo_de_morrer, perda_dignidade, luto }\n7. SINTONIA_COM_HISTORICO_VIDA { sim|parcial|nao }\n8. PONTOS_FORTES_DANO_MORAL { lista }\n9. PONTOS_FRACOS_DANO_MORAL { lista }"
29
+ },
30
+ {
31
+ "fase": 4,
32
+ "nome": "GRAVIDADE_DANO_E_FAIXA_STJ",
33
+ "modelo": "flash",
34
+ "tipo_saida": "json",
35
+ "missao": "CONECTAR GRAVIDADE DA LESÃO ÀS FAIXAS JURISPRUDENCIAIS DE DANO MORAL.\n\nJSON:\n1. CLASSIFICACAO_JURIDICA_LESAO { LEVE|GRAVE|GRAVISSIMA|MORTE }\n2. CRITERIOS_USADOS { dias_incapacidade, risco_vida, sequela_permanente, incapacidade_trabalho, deformidade }\n3. FAIXA_REFERENCIA_STJ_SM { min_sm, max_sm, mediana_sm }\n4. FAIXA_REFERENCIA_STJ_RS { min_rs, max_rs, mediana_rs }\n5. AJUSTES_POR_CASO_CONCRETO { fatores_agravantes, fatores_atenuantes }\n6. FAIXA_AJUSTADA_RS { min_rs, max_rs }\n7. NOTA_SOBRE_TETOS_E_PISOS { comentario_curto }"
36
+ },
37
+ {
38
+ "fase": 5,
39
+ "nome": "CENARIOS_VALOR_VIDA",
40
+ "modelo": "flash",
41
+ "tipo_saida": "json",
42
+ "missao": "GERAR CENÁRIOS DE VALOR (PRINCIPAL/ALTERNATIVO/IMPROVÁVEL) PARA O DANO À VIDA/DIGNIDADE.\n\nJSON (lista de 3 cenários):\n[\n {\n \"ID\": \"PRINCIPAL\",\n \"DESCRICAO\": \"cenário mais provável com base nas provas e na jurisprudência\",\n \"PRIOR\": 0.6,\n \"VALOR_SUGERIDO_RS\": 0,\n \"SUPOSICOES_CHAVE\": [\"nexo_causal_reconhecido\"],\n \"COMPATIBILIDADE_FATOS\": \"alta\",\n \"DANO_MORAL_RS\": { \"min\": 0, \"max\": 0, \"mediano\": 0 },\n \"DANO_MATERIAL_RS\": 0,\n \"VALOR_TOTAL_RS\": { \"min\": 0, \"max\": 0, \"mediano\": 0 }\n },\n {\n \"ID\": \"ALTERNATIVO\",\n \"PRIOR\": 0.3,\n \"...\": \"estrutura_análoga\"\n },\n {\n \"ID\": \"IMPROVAVEL\",\n \"PRIOR\": 0.1,\n \"...\": \"estrutura_análoga\"\n }\n]\n\nRegra: soma dos PRIORES = 1.0."
43
+ },
44
+ {
45
+ "fase": 6,
46
+ "nome": "TESTE_JUSTICA_DO_VALOR",
47
+ "modelo": "flash",
48
+ "tipo_saida": "json",
49
+ "missao": "EXPLICAR POR QUE O VALOR ESCOLHIDO É JUSTO E POR QUE OUTROS NÃO SÃO.\n\nJSON:\n1. VALOR_RECOMENDADO_RS\n2. CENARIO_BASE { PRINCIPAL|ALTERNATIVO|IMPROVAVEL }\n3. POR_QUE_ESTE_VALOR\n4. POR_QUE_NAO_VALORES_MENORES [lista]\n5. POR_QUE_NAO_VALORES_MAIORES [lista]\n6. TESTE_PROPORCIONALIDADE { passou|falhou, justificativa }\n7. TESTE_ENRIQUECIMENTO_SEM_CAUSA { passou|falhou, justificativa }\n8. TESTE_REPARACAO_MINIMA_DIGNA { passou|falhou, justificativa }\n9. MARGEM_DISCRICIONARIEDADE_RS { min, max }"
50
+ },
51
+ {
52
+ "fase": 7,
53
+ "nome": "RELATORIO_VALOR_VIDA",
54
+ "modelo": "pro",
55
+ "tipo_saida": "texto",
56
+ "missao": "GERAR TEXTO FINAL EXPLICANDO O VALOR DA INDENIZAÇÃO À LUZ DA VIDA, HISTÓRIA E GRAVIDADE DO DANO.\n\nEstrutura mínima:\n1. RESUMO_FATOS_E_NEXO\n2. QUEM_ERA_A_VITIMA_E_O_QUE_PERDEU\n3. GRAVIDADE_DA_LESÃO_E_FAIXA_JURISPRUDENCIAL\n4. CENARIO_ESCOLHIDO_E_VALOR_RECOMENDADO\n5. POR_QUE_ESTE_VALOR\n6. POR_QUE_NAO_MENOS\n7. POR_QUE_NAO_MAIS\n8. OBSERVACOES_SOBRE_DUVIDAS_E_LIMITES_DA_ANALISE"
57
+ }
58
+ ]
59
+