import gradio as gr import requests import os import json import docx import io import time import tempfile from dotenv import load_dotenv from pdfplumber import open as pdfplumber_open from PIL import Image import pytesseract try: import cv2 import numpy as np CV2_AVAILABLE = True except ImportError: CV2_AVAILABLE = False print("Aviso: OpenCV (cv2) não encontrado. Pré-processamento de imagem para OCR será limitado.") from datetime import datetime from ratelimiter import RateLimiter import difflib from html import escape import random import logging import traceback import re logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s') logger = logging.getLogger(__name__) load_dotenv() GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or os.environ.get("GEMINI_API_KEY") if not GEMINI_API_KEY: raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a no arquivo .env ou como Secret no Hugging Face.") GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}" logger.info(f"Usando API URL: ...{GEMINI_API_URL[-50:]}") CONFIG = {"max_retry_attempts": 3, "retry_delay": 5, "timeout": 120, "temperatura": 0.3, "max_tokens": 8192} rate_limiter = RateLimiter(max_calls=20, period=60) TIPOS_DE_PETICAO = ["Petição Inicial", "Contestação", "Reconvenção", "Impugnação à Contestação", "Embargos de Declaração", "Recurso de Apelação", "Agravo de Instrumento", "Petição de Tutela Provisória"] DICAS_OAB_MELHORADO = { "Clareza e Concisão": "Seja direto e evite jargões excessivos.", "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes.", "Revisão Ortográfica": "Erros de português prejudicam a credibilidade.", "Endereçamento (Inicial)": "Verifique a competência (Art. 46 CPC / Art. 101 CDC).", "Pedidos (Inicial)": "Certos, determinados e líquidos (se possível).", "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes do mérito.", "Impugnação Específica (Contestação)": "Conteste cada fato alegado (Art. 341, CPC)." } HISTORICO = [] CORRECAO_CACHE = {} def preprocess_image_for_ocr(img_pil): if not CV2_AVAILABLE: logger.warning("OpenCV não disponível, usando conversão simples para OCR.") return img_pil.convert('L') try: img_cv = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2GRAY) _, thresh = cv2.threshold(img_cv, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) return Image.fromarray(thresh) except Exception as e: logger.error(f"Erro no pré-processamento da imagem com OpenCV: {str(e)}") return img_pil.convert('L') def extract_text_from_pdf(file_path_or_obj): text = "" ocr_text_parts = [] try: logger.info(f"Abrindo PDF: {getattr(file_path_or_obj, 'name', file_path_or_obj)}") pdf_reader = pdfplumber_open(file_path_or_obj) logger.info(f"PDF com {len(pdf_reader.pages)} páginas.") for i, page in enumerate(pdf_reader.pages): page_text = page.extract_text(x_tolerance=2, y_tolerance=2) if page_text and len(page_text.strip()) > 20: text += page_text + "\n" else: logger.warning(f"Página {i+1} sem texto significativo extraído, tentando OCR.") try: img = page.to_image(resolution=200) preprocessed_img = preprocess_image_for_ocr(img.original) page_ocr_text = pytesseract.image_to_string(preprocessed_img, lang='por', config='--psm 6') if page_ocr_text and page_ocr_text.strip(): ocr_text_parts.append(page_ocr_text) else: logger.warning(f"OCR retornou vazio para a página {i+1}.") ocr_text_parts.append(f"[OCR Página {i+1}: Vazio ou falhou]") except Exception as ocr_e: logger.error(f"Erro no OCR da página {i+1}: {str(ocr_e)}") ocr_text_parts.append(f"[Erro OCR Página {i+1}: {str(ocr_e)}]") pdf_reader.close() ocr_text = "\n".join(ocr_text_parts) if text.strip() and len(text) > len(ocr_text) * 0.7: logger.info(f"Usando texto extraído do PDF ({len(text)} chars).") return text elif ocr_text.strip(): logger.info(f"Usando texto do OCR do PDF ({len(ocr_text)} chars).") return ocr_text elif text.strip(): logger.info("Usando texto extraído curto como fallback.") return text else: logger.warning("Não foi possível extrair texto significativo do PDF.") return "⚠️ Não foi possível extrair texto do PDF (extração e OCR falharam)." except Exception as e: logger.error(f"Erro geral ao processar o PDF: {str(e)}", exc_info=True) return f"❌ Erro crítico ao processar o PDF: {str(e)}" def extract_text_from_docx(file_path_or_obj): try: doc = docx.Document(file_path_or_obj) text = "\n".join([para.text for para in doc.paragraphs if para.text]) return text if text.strip() else "⚠️ Documento DOCX vazio ou sem texto." except Exception as e: logger.error(f"Erro ao processar o DOCX: {str(e)}", exc_info=True) return f"❌ Erro ao processar o DOCX: {str(e)}" def extract_text_from_txt(file_path_or_obj): try: content = b'' if isinstance(file_path_or_obj, str): with open(file_path_or_obj, 'rb') as f: content = f.read() elif hasattr(file_path_or_obj, 'read'): if hasattr(file_path_or_obj, 'seek') and callable(file_path_or_obj.seek): try: file_path_or_obj.seek(0) except Exception as seek_e: logger.warning(f"Não foi possível resetar ponteiro do arquivo: {seek_e}") content = file_path_or_obj.read() else: return "❌ Tipo de entrada TXT inválido." encodings = ['utf-8', 'latin-1', 'cp1252'] for encoding in encodings: try: decoded_text = content.decode(encoding) logger.info(f"Arquivo TXT decodificado com {encoding}.") return decoded_text if decoded_text.strip() else "⚠️ Arquivo TXT vazio." except UnicodeDecodeError: continue logger.error("Não foi possível decodificar o arquivo TXT com encodings comuns.") return "❌ Não foi possível decodificar o arquivo TXT. Verifique o encoding." except Exception as e: logger.error(f"Erro ao processar o TXT: {str(e)}", exc_info=True) return f"❌ Erro ao processar o TXT: {str(e)}" def process_file(file_obj): if file_obj is None: logger.warning("process_file chamado com file_obj=None") return "⚠️ Nenhum arquivo enviado." try: file_path = getattr(file_obj, 'name', None) if not file_path: logger.error("Não foi possível obter o nome do arquivo (file_obj.name está ausente).") return "❌ Erro: Não foi possível identificar o arquivo enviado." filename = os.path.basename(file_path).lower() logger.info(f"Processando arquivo: {filename}") try: file_size = os.path.getsize(file_path) logger.info(f"Tamanho do arquivo: {file_size} bytes") if file_size > 15 * 1024 * 1024: logger.warning(f"Arquivo {filename} ({file_size} bytes) excede o limite de 15MB.") return "⚠️ Arquivo muito grande (Máx 15MB)." except OSError as size_e: logger.warning(f"Não foi possível verificar o tamanho do arquivo: {size_e}") if filename.endswith('.pdf'): return extract_text_from_pdf(file_path) elif filename.endswith('.docx'): return extract_text_from_docx(file_path) elif filename.endswith('.txt'): return extract_text_from_txt(file_path) else: logger.warning(f"Formato de arquivo não suportado: {filename}") return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT." except Exception as e: logger.error(f"Erro GERAL ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True) return f"❌ Erro inesperado ao processar o arquivo: {str(e)}" def get_instrucoes_gerais_avaliacao(modo_treinamento=False): base = """ **Estrutura da Avaliação:** 1. **Análise Geral:** Visão geral concisa (2-3 linhas) sobre a adequação da peça ao caso e tipo, consistência e clareza. 2. **Nota Final:** 0 a 10 (uma casa decimal), com justificativa BREVE dos principais fatores. 3. **Pontos Positivos:** Liste pelo menos 2-3 aspectos bem executados, com justificativa (legal/técnica). 4. **Pontos a Melhorar:** Liste **TODOS** os problemas em ordem de gravidade (erros crassos primeiro). Para cada ponto: * Descrição clara do erro/omissão. * Fundamentação legal/doutrinária do porquê é um erro. * **Impacto estimado na nota** (ex: -0.5, -1.0, -2.0). * Sugestão de correção ou como deveria ter sido feito. 5. **Análise Específica dos Requisitos:** Avalie os itens chave da peça (listados abaixo para cada tipo). **Critérios Fundamentais (Aplicáveis a Quase Todos):** * **Coerência:** A peça responde ao caso/situação processual? Os argumentos são lógicos? * **Fundamentação:** Usou a lei, doutrina e jurisprudência corretamente? Citou artigos relevantes? * **Linguagem:** Clareza, objetividade, correção gramatical e formalidade adequada? * **Pedidos:** São claros, certos, determinados e coerentes com a fundamentação? * **NÃO INVENTAR FATOS:** Penalidade severa (-3.0 a -5.0) se criar fatos não presentes no caso. * **NÃO SE IDENTIFICAR:** Nota ZERO se houver nome, OAB, CPF, etc., do aluno na peça (exceto nomes das partes do caso). Use "ADVOGADO(A)", "OAB/...". """ if modo_treinamento: base += """ **Modo de Treinamento:** * Para cada Ponto a Melhorar, detalhe AINDA MAIS: a) Explicação aprofundada do erro e sua consequência prática. b) Base legal/doutrinária específica. c) **Exemplo de Redação Corrigida** para aquele trecho específico. d) Dica prática relevante (use o banco de dicas se aplicável). e) Possível estratégia alternativa. * Destaque inconsistências entre a peça e o caso com citações diretas. * Sugira estratégias jurídicas adicionais ou alternativas viáveis. """ return base def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False): instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento) prompt_especifico = f""" ## Análise de Peça Processual: {tipo_peticao} (Critérios OAB Simulados) **Caso/Contexto Fornecido:** ``` {caso} ``` **Peça do Aluno ({tipo_peticao}):** ``` {peticao} ``` {instrucoes_gerais} **Requisitos Específicos para {tipo_peticao}:** """ if tipo_peticao == "Petição Inicial": prompt_especifico += """ - Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I). - Qualificação completa das partes (Art. 319, II, CPC). - Exposição clara e lógica dos fatos (causa de pedir remota). - Fundamentação jurídica pertinente (causa de pedir próxima - Art. 319, III, CPC). - Pedidos certos, determinados e líquidos (se possível) (Art. 319, IV, CPC). - Valor da causa correto e justificado (Art. 291-292, 319, V, CPC). - Indicação das provas pretendidas (Art. 319, VI, CPC). - Opção pela audiência de conciliação/mediação (Art. 319, VII, CPC). - Pedido de citação do réu (forma específica se necessário). - Requerimento de juntada de documentos essenciais (procuração, custas/JG). - Fechamento formal ("Nestes termos pede deferimento", Local, data, Advogado, OAB). **Critérios Adicionais (Inicial):** * Uso de "XXX" para dados ausentes. Penalizar invenção (-0.5 por ocorrência). * Falta de pedido de juntada de custas/JG: -1.0 ponto. * Erro em "Nestes termos, pede deferimento" (com vírgula): -0.2 ponto. * Analisar foro (Art. 46 CPC vs. Art. 101 CDC): Escolha do foro do consumidor é válida se aplicável; não penalizar, apenas comentar se gerar dificuldades práticas. """ elif tipo_peticao == "Contestação": prompt_especifico += """ - Endereçamento ao juízo correto onde tramita a ação. - Qualificação completa do réu (se já não constante nos autos, referenciar). - Indicação do processo (número). - Tempestividade (menção implícita ou explícita de que está no prazo). - **Preliminares (Art. 337, CPC):** Alegar TODAS as aplicáveis (inexistência/nulidade citação, incompetência, inépcia, perempção, litispendência, coisa julgada, conexão, incapacidade, convenção de arbitragem, ausência de legitimidade/interesse, falta de caução, indevida concessão de JG). - **Impugnação Específica dos Fatos:** Rebater PONTO A PONTO os fatos alegados na inicial (Art. 341, CPC). A falta de impugnação específica torna o fato incontroverso! - **Mérito:** Apresentar a versão do réu sobre os fatos; apresentar a defesa de mérito direta (negando direito do autor) e/ou indireta (fatos impeditivos, modificativos, extintivos do direito do autor - ex: pagamento, prescrição, compensação). - Fundamentação jurídica da defesa. - **Pedidos:** Requerer acolhimento das preliminares (com extinção ou remessa); NO MÉRITO, requerer a IMPROCEDÊNCIA TOTAL dos pedidos do autor; Requerer a condenação do autor nas custas e honorários advocatícios (Art. 85, CPC). - Indicação das provas que pretende produzir. - Fechamento formal. **Critérios Adicionais (Contestação):** * A não alegação de preliminar arguível (exceto incompetência absoluta, conexão, convenção de arbitragem) preclui? Avaliar impacto. * Falha grave na impugnação específica: -2.0 a -4.0 pontos. * Ausência de pedido de improcedência: -1.5 pontos. """ elif tipo_peticao == "Reconvenção": prompt_especifico += """ - Apresentada NA MESMA PEÇA da Contestação (Art. 343, CPC). - Endereçamento ao mesmo juízo. - Indicação do processo. - Qualificação completa do réu-reconvinte e do autor-reconvindo (podem ser referenciadas se já completas). - **Conexão com a Ação Principal ou Fundamento de Defesa:** O pedido da reconvenção deve ser conexo (Art. 343, caput). - **Estrutura Similar à Petição Inicial:** Fatos, Fundamentação, Pedidos específicos, Valor da causa, Provas. - Pedido de intimação do autor-reconvindo para contestar a reconvenção (na pessoa de seu advogado - Art. 343, §1º). - Fechamento formal (pode ser conjunto com a contestação). **Critérios Adicionais (Reconvenção):** * Ausência de conexão clara: -2.0 pontos. Falta de pedido/valor da causa/provas específicos: -1.0 por item. Apresentada em peça separada (erro formal grave): -3.0 pontos. """ elif tipo_peticao == "Impugnação à Contestação": prompt_especifico += """ - Endereçamento ao juízo correto. Indicação do processo. Qualificação do autor (impugnante). Tempestividade (15 dias - Art. 350/351 CPC). - **Resposta às Preliminares:** Rebater cada preliminar arguida. - **Resposta a Fatos Impeditivos/Modificativos/Extintivos:** Manifestar-se sobre fatos novos (Art. 350). - **Resposta a Documentos Novos:** Manifestar-se sobre documentos juntados (Art. 437 CPC). - **Ratificação da Inicial:** Reiterar os termos e pedidos da inicial. - **Especificação de Provas:** Indicar provas para contrapor defesa indireta/preliminares (Art. 350/351). - Pedidos: Rejeição das preliminares, refutação da defesa, procedência dos pedidos da inicial. Fechamento formal. **Critérios Adicionais (Impugnação):** * Não responder a preliminar relevante: -1.5 pontos. Não se manifestar sobre fato novo: -2.0 pontos. Não ratificar a inicial: -0.5 pontos. """ elif tipo_peticao == "Embargos de Declaração": prompt_especifico += """ - **Contra Decisão Específica:** Indicar claramente qual decisão está sendo embargada. Endereçamento ao PRÓPRIO juízo prolator. Indicação do processo. Qualificação das partes. Tempestividade (5 dias - Art. 1023 CPC). - **Cabimento (Art. 1022, CPC):** Apontar **PRECISAMENTE** e **FUNDAMENTADAMENTE** a ocorrência de: Omissão, Contradição, Obscuridade, Erro Material. - **NÃO PODE REJULGAR A CAUSA:** Objetivo é sanar vício, não modificar o mérito (salvo efeitos infringentes excepcionais). - **Pedido:** Requerer o conhecimento e ACOLHIMENTO para sanar o vício (com ou sem efeitos infringentes, se for o caso). Fechamento formal. **Critérios Adicionais (Embargos):** * Usar para rediscutir mérito (erro grave): -4.0 pontos. Não apontar vício claramente: -3.0 pontos. Endereçar ao tribunal (erro): -1.0 ponto. """ elif tipo_peticao == "Recurso de Apelação": prompt_especifico += """ - **Peça de Interposição:** Endereçamento ao juízo a quo. Indicação do processo. Qualificação das partes. Pressupostos (cabimento - Art. 1009; tempestividade - 15 dias Art. 1003, §5º; preparo/JG; regularidade). Pedido de recebimento e remessa ao Tribunal. - **Razões Recursais:** Endereçamento ao TRIBUNAL competente. Exposição dos fatos e da sentença. **Fundamentos para Reforma/Anulação (Art. 1010, II e III):** Error in procedendo (anulação) ou Error in judicando (reforma). **Princípio da Dialeticidade:** Impugnar ESPECIFICAMENTE os fundamentos da sentença. - **Pedidos:** Conhecimento; PROVIMENTO para ANULAR ou REFORMAR (especificar); inversão da sucumbência. Fechamento formal. **Critérios Adicionais (Apelação):** * Falta de interposição ou razões: Nota zero. Endereçamento incorreto das razões: -1.0. Ausência de dialeticidade: -3.0 a -5.0. Não pedir anulação/reforma: -1.5. Falta de preparo/JG: -2.0. """ elif tipo_peticao == "Agravo de Instrumento": prompt_especifico += """ - **Peça Única:** Endereçamento direto ao TRIBUNAL. Indicação processo origem. Qualificação partes e advogados (com endereços - Art. 1016, IV). Cópia da decisão agravada (OBRIGATÓRIA - Art. 1017, I). - **Cabimento (Art. 1015 ou taxatividade mitigada):** Demonstrar hipótese legal ou urgência (Tema 988 STJ). Tempestividade (15 dias). Preparo/JG. - **Peças Obrigatórias (Art. 1017, I):** Decisão agravada, certidão de intimação, procurações. Peças Facultativas (Art. 1017, III). - **Razões (Art. 1016, II e III):** Exposição do fato e direito; razões para reforma/invalidação da decisão interlocutória. - **Pedido de Efeito Suspensivo ou Tutela Antecipada Recursal (Art. 1019, I):** Se houver risco, pedir fundamentadamente. - **Pedidos Finais:** Conhecimento; (se aplicável) efeito suspensivo/tutela antecipada; PROVIMENTO para reformar/invalidar; comunicação ao juiz (Art. 1018 - opcional se eletrônico). Fechamento formal. **Critérios Adicionais (Agravo):** * Ausência de peça obrigatória: Inadmissibilidade (-4.0). Não demonstrar cabimento: -3.0. Endereçar ao juiz a quo: -1.0. Não indicar advogados: -0.5. """ elif tipo_peticao == "Petição de Tutela Provisória": prompt_especifico += """ - **Tipo (Urgência ou Evidência):** Deixar claro. Antecedente ou incidental. Endereçamento (competente ou da causa). Qualificação partes. Indicação processo (se incidental). - **Tutela de Urgência (Art. 300):** **Fumus Boni Iuris** (probabilidade do direito) E **Periculum in Mora** (perigo de dano/risco ao resultado útil). **Reversibilidade** (Art. 300, §3º - se possível). - **Tutela de Evidência (Art. 311):** Demonstrar uma das hipóteses (I a IV). **NÃO precisa periculum in mora.** - **Pedido Específico:** Indicar CLARAMENTE a medida liminar solicitada. - **Se Antecedente (Art. 303 ou 305):** Indicar pedido final ou aditar/apresentar inicial no prazo. Valor da Causa. - **Pedidos:** Concessão da tutela (liminar/justificação prévia); citação/intimação; (se antecedente) indicação da ação principal/aditamento. Fechamento formal. **Critérios Adicionais (Tutela):** * Não demonstrar Fumus/Periculum (Urgência): -3.0 a -5.0. Não enquadrar na Evidência: -3.0. Pedido genérico: -2.0. Confundir requisitos: -1.5. """ else: prompt_especifico += """ - **Adequação ao Tipo:** A peça corresponde à estrutura e finalidade do tipo? - **Clareza:** É possível entender a pretensão? - **Fundamentação Mínima:** Tentativa de basear em lei ou fatos? - **Pedido:** Existe um pedido? """ prompt_especifico += "\n\n**Instruções Finais:** Analise a peça do aluno estritamente com base no caso/contexto fornecido e nos critérios acima. Seja rigoroso como um examinador da OAB, mas forneça feedback construtivo e detalhado. Gere a resposta **exclusivamente** no formato solicitado." return prompt_especifico def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao): prompt = f""" **Caso/Contexto Fornecido:** ``` {caso} ``` **Peça Original do Aluno ({tipo_peticao}):** ``` {peticao_original} ``` **Tarefa:** Com base no caso e nos problemas da peça original, **gere uma versão corrigida e aprimorada** da peça ({tipo_peticao}). Siga **rigorosamente** as melhores práticas e requisitos formais/materiais para este tipo de peça no exame da OAB. **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):** """ if tipo_peticao == "Petição Inicial": prompt += """ 1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável). 2. **Qualificação:** Completa (Art. 319, II), usando "XXX" para dados ausentes. 3. **Fatos:** Narre de forma cronológica, clara e fiel ao caso. 4. **Fundamentação:** Cite artigos pertinentes (CC, CDC, CPC) e súmulas/jurisprudência, se aplicável. 5. **Pedidos:** Claros, certos, determinados (quantificar danos se possível). Cumulados se cabível. 6. **Valor da Causa:** Correto (soma dos benefícios - Art. 292). 7. **Provas:** Indicar ("protesta provar o alegado por todos os meios..."). 8. **Audiência:** Indicar opção (Art. 319, VII). 9. **Citação:** Pedido de citação (ex: "via postal, Art. 246, I, CPC"). 10. **Custas:** Requerimento expresso de juntada da guia OU pedido de gratuidade (com pedido de declaração). **NUNCA OMITIR.** 11. **Fechamento:** "Nestes termos pede deferimento." (SEM vírgula). Seguido de "Local, data.", "ADVOGADO(A)", "OAB/...". **NUNCA identificar.** """ elif tipo_peticao == "Contestação": prompt += """ 1. **Endereçamento:** Ao juízo da ação. 2. **Identificação:** Número do processo, qualificação do réu. 3. **Tempestividade:** Mencionar ("A presente contestação é tempestiva..."). 4. **Estrutura:** Síntese da Inicial; Preliminares (Art. 337, fundamentadas); Mérito (Impugnação Específica + Defesa de Mérito Direta/Indireta com fundamentos); Reconvenção (Se houver, ANTES dos pedidos da contestação). 5. **Pedidos da Contestação:** Acolhimento das preliminares (com consequência); **IMPROCEDÊNCIA TOTAL** dos pedidos do autor; Condenação em custas e honorários. 6. **Provas:** Indicar as provas do réu. 7. **Fechamento:** Padrão OAB. """ elif tipo_peticao == "Reconvenção": prompt += """ **IMPORTANTE:** Gere a Reconvenção como parte da Contestação. 1. **Após a defesa de mérito da Contestação e ANTES dos pedidos da Contestação**, insira: "DA RECONVENÇÃO". 2. **Qualificação:** Réu-Reconvinte e Autor-Reconvindo. 3. **Conexão:** Justifique brevemente. 4. **Fatos e Direito Reconvencionais:** Narre e fundamente o pedido do reconvinte. 5. **Pedidos da Reconvenção:** Claros e específicos contra o autor-reconvindo. 6. **Valor da Causa e Provas da Reconvenção:** Específicos. 7. **Intimação:** Peça a intimação do autor-reconvindo para contestar. 8. **Continue com os Pedidos da Contestação**. 9. **Fechamento:** Padrão OAB. """ elif tipo_peticao == "Impugnação à Contestação": prompt += """ 1. Endereçamento, processo, qualificação autor, tempestividade. 2. Tópico: **DA(S) PRELIMINAR(ES) ARGUIDA(S)** - Rebater cada uma. 3. Tópico: **DO MÉRITO E DOS FATOS NOVOS** - Manifestar-se sobre fatos impeditivos/modificativos/extintivos e documentos. 4. Tópico: **DA RATIFICAÇÃO DA INICIAL** - Reiterar termos e pedidos. 5. Tópico: **DAS PROVAS** - Especificar provas contrapostas. 6. **Pedidos:** Rejeição das preliminares, refutação da defesa, procedência dos pedidos da inicial. 7. Fechamento padrão OAB. """ elif tipo_peticao == "Embargos de Declaração": prompt += """ 1. Endereçamento ao juízo prolator. Processo, Embargante, Embargado. Decisão embargada identificada. Tempestividade (5 dias). 2. Tópico para cada vício (Art. 1022): **DA OMISSÃO / DA CONTRADIÇÃO / DA OBSCURIDADE / DO ERRO MATERIAL**. 3. Dentro de cada tópico: **Transcrever trecho relevante da decisão** e explicar **objetivamente** o vício. NÃO REJULGAR. 4. **Pedido:** Conhecimento e ACOLHIMENTO para sanar o(s) vício(s) apontado(s), especificando o que se espera. Pedir efeitos infringentes se cabível e fundamentado. 5. Fechamento padrão OAB. """ elif tipo_peticao == "Recurso de Apelação": prompt += """ 1. **Peça de Interposição:** Endereçamento ao juízo a quo. Processo, Apelante, Apelado. Pressupostos (cabimento, tempestividade, preparo/JG). Pedido de recebimento e remessa. 2. **Razões Recursais:** Endereçamento ao Tribunal. Breve relato. **DOS FUNDAMENTOS PARA REFORMA/ANULAÇÃO:** Error in procedendo ou Error in judicando. Impugnar especificamente a sentença (dialeticidade). 3. **Pedidos:** Conhecimento; PROVIMENTO para ANULAR ou REFORMAR (especificar); inversão sucumbência. 4. Fechamento padrão OAB. """ elif tipo_peticao == "Agravo de Instrumento": prompt += """ 1. Peça única ao Tribunal. Processo origem, Agravante, Agravado, Advogados (com endereços). Decisão agravada identificada (juntar cópia). 2. **DO CABIMENTO:** Demonstrar hipótese Art. 1015 ou mitigação. **DA TEMPESTIVIDADE.** **DO PREPARO/JG.** **DAS PEÇAS OBRIGATÓRIAS E FACULTATIVAS.** 3. **DAS RAZÕES RECURSAIS:** Exposição fato/direito, motivos para reforma/invalidação da interlocutória. 4. **DO PEDIDO DE EFEITO SUSPENSIVO / TUTELA ANTECIPADA RECURSAL** (se cabível e fundamentado). 5. **Pedidos:** Conhecimento; (se aplicável) efeito suspensivo/tutela; PROVIMENTO para reformar/invalidar; comunicação ao juiz. 6. Fechamento padrão OAB. """ elif tipo_peticao == "Petição de Tutela Provisória": prompt += """ 1. Indicar TIPO (Urgência/Evidência) e CARÁTER (Antecedente/Incidental). Endereçamento. Qualificação. Processo (se incidental). 2. **DOS REQUISITOS:** * **Urgência:** Tópico **DA PROBABILIDADE DO DIREITO (FUMUS BONI IURIS)** e tópico **DO PERIGO DE DANO OU RISCO AO RESULTADO ÚTIL (PERICULUM IN MORA)**. Se possível, **DA REVERSIBILIDADE**. * **Evidência:** Tópico **DA HIPÓTESE DE EVIDÊNCIA (ART. 311, INCISO __)** - Demonstrar o enquadramento. 3. **DO PEDIDO LIMINAR:** Descrever CLARAMENTE a medida solicitada. 4. **Se Antecedente:** Indicar pedido final ou aditamento/inicial completa. Valor da Causa. 5. **Pedidos:** Concessão da tutela; citação/intimação; (se antecedente) indicação ação principal/aditamento. 6. Fechamento padrão OAB. """ else: prompt += """ 1. Estrutura Lógica: Começo, meio, fim. 2. Argumentos Claros. 3. Pedido Claro. 4. Formalidades: Linguagem adequada, fechamento OAB. """ prompt += "\n\n**Instrução Final:** Gere **APENAS** o texto completo da peça corrigida abaixo, começando pelo endereçamento e terminando com 'OAB/...'. Não inclua NENHUM comentário, introdução ou explicação ANTES ou DEPOIS do texto da peça." return prompt def pos_corrigir_peticao(peticao_corrigida, tipo_peticao): if "Nestes termos, pede deferimento" in peticao_corrigida: peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento") if tipo_peticao == "Petição Inicial": peticao_lower = peticao_corrigida.lower() if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower: partes = peticao_corrigida.split("Nestes termos pede deferimento") if len(partes) == 2: peticao_corrigida = partes[0].strip() + "\nRequer, ainda, a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento" + partes[1] else: peticao_corrigida = peticao_corrigida.replace("Local, data.", "Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.") if "audiência de conciliação" not in peticao_lower and "audiência de mediação" not in peticao_lower: partes = peticao_corrigida.split("Nestes termos pede deferimento") if len(partes) == 2: peticao_corrigida = partes[0].strip() + "\nOpta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\nNestes termos pede deferimento" + partes[1] else: peticao_corrigida = peticao_corrigida.replace("Local, data.", "Opta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\n\nLocal, data.") linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()] fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."] while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]): linhas.pop() linhas.extend(fechamento_padrao) peticao_corrigida = "\n".join(linhas) return peticao_corrigida def chamar_api(prompt): payload = {"contents": [{"parts": [{"text": prompt}]}],"generationConfig": {"temperature": CONFIG["temperatura"],"maxOutputTokens": CONFIG["max_tokens"],"topP": 0.95,}} headers = {"Content-Type": "application/json"} logger.info(f"Chamando API Gemini. Temp={CONFIG['temperatura']}, MaxTokens={CONFIG['max_tokens']}") for tentativa in range(CONFIG["max_retry_attempts"]): try: with rate_limiter: logger.debug(f"API Call - Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}") response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"]) logger.debug(f"API Response - Status: {response.status_code}") if response.status_code == 429: retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1))) logger.warning(f"Erro 429 (Too Many Requests). Aguardando {retry_after}s...") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(retry_after); continue else: return "❌ Erro 429: Muitas requisições. Tente novamente mais tarde." if response.status_code != 200: error_msg = "Erro desconhecido na API" try: error_info = response.json() error_msg = error_info.get("error", {}).get("message", error_msg) except json.JSONDecodeError: error_msg = f"Erro {response.status_code}: {response.text[:200]}" # Mostra início do erro não-JSON logger.error(f"API Error (Status {response.status_code}): {error_msg}") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue return f"❌ Erro da API ({response.status_code}): {error_msg}" response_data = response.json() if not response_data.get("candidates"): prompt_feedback = response_data.get("promptFeedback", {}) block_reason = prompt_feedback.get("blockReason") logger.error(f"API Response sem 'candidates'. Block Reason: {block_reason}") if block_reason: return f"❌ A solicitação foi bloqueada pela API. Motivo: {block_reason}. Tente reformular." else: return "❌ Resposta vazia ou inválida da API (sem 'candidates')." candidate = response_data["candidates"][0] finish_reason = candidate.get("finishReason") if finish_reason and finish_reason not in ["STOP", "MAX_TOKENS", "FINISH_REASON_UNSPECIFIED"]: logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}") safety_ratings = candidate.get("safetyRatings", []) harm_categories = [r['category'] for r in safety_ratings if r['probability'] not in ['NEGLIGIBLE', 'LOW']] if harm_categories: return f"❌ Resposta bloqueada por segurança. Motivo: {finish_reason} ({', '.join(harm_categories)})." else: return f"❌ Geração interrompida. Motivo: {finish_reason}." if not candidate.get("content") or not candidate["content"].get("parts"): logger.error("Estrutura de resposta inesperada: Faltando content/parts.") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue return "❌ Resposta inválida da API (estrutura inesperada)." texto_gerado = candidate["content"]["parts"][0]["text"].strip() logger.info(f"API retornou com sucesso ({len(texto_gerado)} chars). Finish Reason: {finish_reason}") return texto_gerado except requests.Timeout: logger.warning(f"Timeout na API - Tentativa {tentativa + 1}.") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue return "⏱️ Tempo esgotado na comunicação com a API." except requests.RequestException as e: logger.error(f"Erro de Rede/Requisição API - Tentativa {tentativa + 1}: {e}") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue return f"❌ Erro de conexão com a API: {e}" except json.JSONDecodeError as e: logger.error(f"Erro ao decodificar JSON da API: {e}") logger.debug(f"Resposta não-JSON: {response.text[:500]}") return f"❌ Erro ao processar a resposta da API (JSON inválido)." except Exception as e: logger.exception(f"Erro inesperado na chamada da API - Tentativa {tentativa + 1}: {str(e)}") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue return f"❌ Erro inesperado no processamento da API: {str(e)}" logger.error("Falha ao chamar a API após todas as tentativas.") return "❌ Falha ao comunicar com a API após múltiplas tentativas." def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False): logger.info(f"Iniciando correção. Tipo={tipo_peticao}, Treinamento={modo_treinamento}") start_time = time.time() analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html = "", "", "", "", "" peticao_conteudo = "" try: if not caso or not caso.strip(): logger.warning("Caso não fornecido.") return "⚠️ Forneça o caso/contexto da peça.", "", "", "", "" if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: logger.warning(f"Tipo de petição inválido ou não fornecido: {tipo_peticao}") return "⚠️ Selecione um tipo de petição válido.", "", "", "", "" if peticao_arquivo is not None: logger.info("Processando arquivo de petição...") peticao_conteudo = process_file(peticao_arquivo) if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"): logger.error(f"Erro ao processar arquivo: {peticao_conteudo}") return peticao_conteudo, "", "", "", "" elif peticao_texto and peticao_texto.strip(): peticao_conteudo = peticao_texto else: logger.warning("Nenhuma petição fornecida (texto ou arquivo).") return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", "" logger.info(f"Petição recebida ({len(peticao_conteudo)} chars).") troll_triggers = ["escreva a petição", "faça a petição", "redija a petição", "elabore a petição", "crie a petição", "gere a petição", "faça para mim", "faça por mim"] if any(trigger in peticao_conteudo.lower() for trigger in troll_triggers): logger.info("Detectado pedido para escrever petição.") return random.choice([ "Hummm... Parece que você quer que *eu* faça o trabalho, né? 😉 Que tal você tentar escrever a peça e *eu* te ajudo a corrigir?", "🚫 Opa! Meu trabalho é *corrigir* e *ensinar*, não fazer a peça do zero. Mostre o que você fez e eu te dou o feedback!", ]), "", "", "", "" cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}" if cache_key in CORRECAO_CACHE: logger.info("Correção encontrada no cache.") cached_data = CORRECAO_CACHE[cache_key] return cached_data["analise_formatada"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"], cached_data["diff_html"] logger.info("Estruturando prompt de análise...") prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento) logger.info("Chamando API para análise...") analise_bruta = chamar_api(prompt_analise) if analise_bruta.startswith(("❌", "⏱️", "⚠️")): logger.error(f"Erro retornado pela API (Análise): {analise_bruta}") return analise_bruta, "", "", "", "" logger.info("Análise recebida. Formatando...") analise_formatada = formatar_correcao(analise_bruta) logger.info("Estruturando prompt para peça corrigida...") prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao) logger.info("Chamando API para gerar peça corrigida...") peticao_corrigida_bruta = chamar_api(prompt_peticao) if peticao_corrigida_bruta.startswith(("❌", "⏱️", "⚠️")): logger.error(f"Erro retornado pela API (Correção): {peticao_corrigida_bruta}") peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}" else: logger.info("Peça corrigida recebida. Aplicando pós-correção...") start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"] start_index = -1 peticao_lower_corr = peticao_corrigida_bruta.lower() for marker in start_markers: idx = peticao_lower_corr.find(marker) if idx != -1: start_index = idx; break peticao_extraida = peticao_corrigida_bruta[start_index:] if start_index != -1 else peticao_corrigida_bruta peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao) logger.info("Pós-correção aplicada.") logger.info("Calculando diferenças...") diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final) logger.info("Diferenças calculadas.") resultado_cache = {"analise_formatada": analise_formatada,"peticao_corrigida": peticao_corrigida_final,"resumo_diff": resumo_diff,"criticas_diff": criticas_diff,"diff_html": diff_html} CORRECAO_CACHE[cache_key] = resultado_cache HISTORICO.append({"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"tipo_peticao": tipo_peticao,"caso": caso,"peticao_original": peticao_conteudo,**resultado_cache}) if len(HISTORICO) > 10: HISTORICO.pop(0) end_time = time.time() logger.info(f"Correção concluída em {end_time - start_time:.2f} segundos.") return analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html except Exception as e: logger.exception("Erro GERAL inesperado dentro de corrigir_peticao:") error_message = f"❌ Erro inesperado na função de correção: {str(e)}\nConsulte os logs para detalhes." return error_message, "", "", "", "" def formatar_correcao(texto): texto = texto.replace("### Análise Geral", "## 📝 Análise Geral") texto = texto.replace("### Nota Final", "## ⭐ Nota Final") texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos") texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar") texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos") linhas = texto.split('\n') linhas_formatadas = [] in_list = False for linha in linhas: linha_strip = linha.strip() if linha_strip.startswith(("1.","2.","3.","4.","5.")): linhas_formatadas.append(f"\n**{linha_strip}**") in_list = False elif linha_strip.startswith(("- ","* ")): prefix = " * " if in_list else "* " linhas_formatadas.append(prefix + linha_strip[2:]) in_list = True elif linha_strip.startswith(("a)","b)","c)","d)","e)")): linhas_formatadas.append(f" * {linha_strip}") in_list = True else: linhas_formatadas.append(linha) if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")): in_list = False texto_final = "\n".join(linhas_formatadas) nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})" match = re.search(nota_pattern, texto_final, re.IGNORECASE) if match: nota = match.group(2).replace(",",".") texto_final = re.sub(nota_pattern, f"\\1**{nota}/10**", texto_final, count=1, flags=re.IGNORECASE) return texto_final def fornecer_dica_contextual(texto_peticao, tipo_peticao): if not texto_peticao or not texto_peticao.strip(): return "*Digite algo na peça para receber dicas...*" dicas_relevantes = [] texto_lower = texto_peticao.lower() for chave in ["Clareza e Concisão", "Fundamentação Legal", "Revisão Ortográfica"]: if chave in DICAS_OAB_MELHORADO: dicas_relevantes.append(f"💡 **{chave}:** {DICAS_OAB_MELHORADO[chave]}") keywords_por_tipo = { "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor", "provas", "audiência", "citação", "custas"], "Contestação": ["preliminar", "mérito", "impugnação", "improcedência", "honorários", "art. 337", "art. 341"], "Reconvenção": ["reconvinte", "reconvindo", "conexão", "art. 343"], "Impugnação à Contestação": ["réplica", "preliminar", "fatos novos", "documentos", "ratifica", "art. 350"], "Embargos de Declaração": ["omissão", "contradição", "obscuridade", "erro material", "art. 1022"], "Recurso de Apelação": ["sentença", "reforma", "anulação", "dialeticidade", "preparo", "tribunal", "art. 1009"], "Agravo de Instrumento": ["interlocutória", "cabimento", "urgência", "efeito suspensivo", "art. 1015", "art. 1017"], "Petição de Tutela Provisória": ["urgência", "evidência", "liminar", "periculum", "fumus boni", "art. 300", "art. 311"], } if tipo_peticao in keywords_por_tipo: for chave, dica in DICAS_OAB_MELHORADO.items(): chave_lower = chave.lower() if f"({tipo_peticao.split()[0].lower()})" in chave_lower or \ any(kw in chave_lower for kw in keywords_por_tipo[tipo_peticao] if kw in texto_lower): dica_formatada = f"💡 **{chave}:** {dica}" if dica_formatada not in dicas_relevantes: dicas_relevantes.append(dica_formatada) if not dicas_relevantes: return "*Continue escrevendo ou selecione o tipo de peça para dicas mais específicas.*" return "\n\n".join(dicas_relevantes[:4]) def calcular_diff_interno(texto_original, texto_corrigido): if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")): return "","","" orig_lines = texto_original.splitlines() corr_lines = texto_corrigido.splitlines() diff = difflib.ndiff(orig_lines, corr_lines) resultado_diff_html = [] criticas = [] linhas_removidas = 0 linhas_adicionadas = 0 keywords_criticas_removidas = ["invenção", "identificação", "erro crasso", "incorreto", "prazo"] keywords_criticas_adicionadas = ["custas", "art. 319", "art. 337", "art. 1022", "art. 1015", "art. 300", "fumus", "periculum", "essencial", "preliminar", "tempestividade", "cabimento"] for linha in diff: texto = linha[2:] texto_escaped = escape(texto) if linha.startswith(' '): resultado_diff_html.append(f"